diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000000..1a588e83937 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,10 @@ +[alias] +xtask = "run --package xtask --" +xclean = "xtask clean" +xdoc = "run --package xtask --features=deploy-docs,preview-docs --" +xfmt = "xtask fmt-packages" +qa = "xtask run example qa-test" +xcheck = "run --quiet --package xtask --features=semver-checks --" +xrelease = "run --package xtask --features=release -- release" +xrel-check = "run --package xtask --features=rel-check -- rel-check" +xmcp = "run --package xtask --features=mcp -- mcp" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..a19ade077d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGELOG.md merge=union diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..f908245951c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: ["bug", "bug jury"] +assignees: '' + +--- + +## Bug description + + + +## To Reproduce + + +1. ... +2. ... + + + + + +## Expected behavior + + + +## Environment + +- Target device: [e.g. ESP32-S3] +- Crate name and version: [e.g. esp-hal 1.0.0] \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..6b2bee70b04 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Ask questions in Matrix channel (Recommended) + url: https://matrix.to/#/#esp-rs:matrix.org + about: Ask any questions directly in our Matrix channel. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..733697823d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: ["feature request", "bug jury"] +assignees: '' + +--- + +## Motivations + + + +## Solution + + + +## Alternatives + + + +## Additional context + + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..7bce9873856 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Thank you for your contribution! + +We appreciate the time and effort you've put into this pull request. +To help us review it efficiently, please ensure you've gone through the following checklist: + +### Submission Checklist 📝 +- [ ] I have updated existing examples or added new ones (if applicable). +- [ ] I have used `cargo xtask fmt-packages` command to ensure that all changed code is formatted correctly. +- [ ] My changes were added to the [`CHANGELOG.md`](https://github.com/esp-rs/esp-hal/blob/main/esp-hal/CHANGELOG.md) in the **_proper_** section. +- [ ] I have added necessary changes to user code to the latest [Migration Guide](https://github.com/esp-rs/esp-hal/blob/main/esp-hal). +- [ ] My changes are in accordance to the [esp-rs developer guidelines](https://github.com/esp-rs/esp-hal/blob/main/documentation/DEVELOPER-GUIDELINES.md) + +#### Extra: +- [ ] I have read the [CONTRIBUTING.md guide](https://github.com/esp-rs/esp-hal/blob/main/documentation/CONTRIBUTING.md) and followed its instructions. + +### Pull Request Details 📖 + +#### Description +Please provide a clear and concise description of your changes, including the motivation behind these changes. The context is crucial for the reviewers. + +#### Testing +Describe how you tested your changes. diff --git a/.github/actions/build-tests/action.yml b/.github/actions/build-tests/action.yml new file mode 100644 index 00000000000..efc0698798e --- /dev/null +++ b/.github/actions/build-tests/action.yml @@ -0,0 +1,78 @@ +name: Build HIL tests (composite) +description: Checkout, install toolchains, build tests for a SoC, upload artifact +inputs: + event_name: + description: github.event_name + required: true + + repository: + description: owner/repo for workflow_dispatch + required: true + + branch: + description: branch/tag to checkout if no SHA + required: true + + soc: + description: SoC (e.g. esp32c3) + required: true + + rust_target: + description: rust target triple + required: true + + tests: + description: Optional comma-separated test names + required: false + default: "" + +runs: + using: composite + steps: + - uses: actions/checkout@v6 + if: ${{ inputs.event_name == 'merge_group' }} + - uses: actions/checkout@v6 + if: ${{ inputs.event_name == 'workflow_dispatch' }} + with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.branch }} + + - if: ${{ !contains(fromJson('["esp32","esp32s2","esp32s3"]'), inputs.soc) }} + uses: dtolnay/rust-toolchain@v1 + with: + target: ${{ inputs.rust_target }} + toolchain: stable + components: rust-src + + - if: ${{ !contains(fromJson('["esp32","esp32s2","esp32s3"]'), inputs.soc) }} + name: Install nightly Rust toolchain + uses: dtolnay/rust-toolchain@v1 + with: + target: ${{ inputs.rust_target }} + toolchain: nightly + components: rust-src + + - if: ${{ contains(fromJson('["esp32","esp32s2","esp32s3"]'), inputs.soc) }} + uses: esp-rs/xtensa-toolchain@v1.6 + with: + buildtargets: ${{ inputs.soc }} + default: true + version: 1.93.0.0 + + - name: Build tests + shell: bash + run: | + if [ -n "${{ inputs.tests }}" ]; then + echo "Building selected tests for ${{ inputs.soc }}: ${{ inputs.tests }}" + cargo xtask build tests ${{ inputs.soc }} --tests "${{ inputs.tests }}" + else + echo "Building ALL tests for ${{ inputs.soc }}" + cargo xtask build tests ${{ inputs.soc }} + fi + + - uses: actions/upload-artifact@v6 + with: + name: tests-${{ inputs.soc }} + path: ${{ github.workspace }}/target/tests/${{ inputs.soc }} + if-no-files-found: error + overwrite: true diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..ae554b2515c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,117 @@ +# esp-hal agent instructions + +Bare-metal `no_std` Rust HAL for Espressif SoCs. MSRV: **1.88.0** (source: `MSRV` env in `.github/workflows/ci.yml`). + +## Chip reference + +| Chip | Arch | Target | Toolchain flag | +|------|------|--------|----------------| +| esp32 | Xtensa | `xtensa-esp32-none-elf` | `--toolchain esp` | +| esp32s2 | Xtensa | `xtensa-esp32s2-none-elf` | `--toolchain esp` | +| esp32s3 | Xtensa | `xtensa-esp32s3-none-elf` | `--toolchain esp` | +| esp32c2 | RISC-V | `riscv32imc-unknown-none-elf` | `--toolchain stable` | +| esp32c3 | RISC-V | `riscv32imc-unknown-none-elf` | `--toolchain stable` | +| esp32c5 | RISC-V | `riscv32imac-unknown-none-elf` | `--toolchain stable` | +| esp32c6 | RISC-V | `riscv32imac-unknown-none-elf` | `--toolchain stable` | +| esp32h2 | RISC-V | `riscv32imac-unknown-none-elf` | `--toolchain stable` | + +## Commands + +All automation goes through `cargo xtask`. Use `--packages` and `--chips` to scope. + +| Task | Command | Notes | +|------|---------|-------| +| Format (required before PR) | `cargo xtask fmt-packages` | Fast | +| Lint | `cargo xtask lint-packages [--chips X --packages Y]` | Always scope with `--chips`/`--packages` | +| Host-side unit tests | `cargo xtask host-tests` | Fast, runs on host | +| Validate metadata | `cargo xtask update-metadata --check` | Fast | +| Validate changelog | `cargo xtask check-changelog` | Fast | +| Build an example | `cargo xtask run example [name] --chip ` | | +| Build docs | `cargo xtask build documentation --chips ` | Slow — scope to affected chips | +| HIL tests (needs hardware) | `cargo xtask run tests [--test name]` | Requires connected device | +| Full CI check for one chip | `cargo xtask ci ` | **Very slow** — use only as final check before opening a PR | + +**Prefer targeted commands** (`lint-packages --chips X --packages Y`, `run example ... --chip X`) during development. Only run `ci` as a final validation pass. + +## Test & example metadata + +Tests and examples use `//% KEY: value` comments to control xtask builds (full docs: `xtask/README.md`). + +| Annotation | Purpose | Example | +|------------|---------|---------| +| `//% CHIPS:` | Restrict to specific chips | `//% CHIPS: esp32c6 esp32s3` | +| `//% FEATURES:` | Enable cargo features | `//% FEATURES: unstable embassy` | +| `//% ENV:` | Set environment variables | `//% ENV: ESP_HAL_CONFIG_STACK_GUARD_OFFSET=4` | +| `//% CARGO-CONFIG:` | Cargo `--config` passthrough | `//% CARGO-CONFIG: ...` | + +Named configs build the same file with different settings: +``` +//% CHIPS(wifi): esp32 esp32c3 esp32c6 esp32s3 +//% CHIPS(ble_only): esp32h2 +//% FEATURES(wifi): esp-radio/wifi +//% FEATURES(ble_only): esp-radio/ble +``` + +## The `unstable` feature + +- **Libraries** must never enable `unstable`. Use `requires-unstable` instead. +- **Applications** (examples, tests) may enable `unstable` freely. +- Mark new unstable APIs with `#[instability::unstable]`. +- `unstable_module!` macro: makes a module `pub` when `unstable` is enabled, `pub(crate)` otherwise. +- Features prefixed with `__` (e.g. `__bluetooth`) are private — never enable directly. + +## esp-config + +Build-time configuration defined in `esp_config.yml` per crate. Override via environment variables. + +```rust +esp_config::esp_config_bool!("ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM"); +esp_config::esp_config_int!(u32, "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"); +esp_config::esp_config_str!("ESP_HAL_CONFIG_PSRAM_MODE"); +``` + +Config options must not alter the public API surface. + +## Driver conventions + +From `documentation/DEVELOPER-GUIDELINES.md` (read it for full details): + +- **Pins**: `fn with_signal_name(self, pin: impl PeripheralInput) -> Self` +- **Constructor**: takes `impl Instance + 'd`, returns `Result` +- **Config**: `Config` struct with `procmacros::BuilderLite` derive + separate `ConfigError` enum +- **Modes**: `DriverMode` type param; default `Blocking`; provide `into_async`/`into_blocking` +- **Drop**: must reset peripheral to idle state +- **Derives**: drivers: `Debug`; configs: `Default, Debug, PartialEq, Eq, Clone, Copy, Hash` +- **Doc examples**: use `#[doc = crate::before_snippet!()]`, `no_run`, `?` operator (not unwrap) + +## Conditional compilation + +Chip properties come from `esp-metadata/devices/*.toml`, generated into `esp-metadata-generated/`. + +Key symbols: `riscv` / `xtensa`, `single_core` / `multi_core`, `soc_has_`, `_driver_supported`. + +Prefer these over `#[cfg(feature = "esp32c3")]` where possible. + +## Changelog & migration + +- Add entries to `CHANGELOG.md` under `## [Unreleased]` > `### Added/Changed/Fixed/Removed` +- Format: `- Description. (#PR_NUMBER)` — amend existing entries when modifying same feature: `(#789, #1234)` +- Breaking changes: add migration steps to `MIGRATING-*.md` in the affected crate root +- Breaking changes to stable API require a `breaking-change-` PR label + +## PR checklist + +1. `cargo xtask fmt-packages` +2. `cargo xtask lint-packages --chips ` — fix all warnings +3. `cargo xtask update-metadata --check` — if metadata changed +4. `cargo xtask check-changelog` — add changelog entry if API changed +5. Build affected examples/tests for relevant chips +6. `cargo xtask host-tests` — if host-side code changed + +## Key references + +- `documentation/DEVELOPER-GUIDELINES.md` — full API design rules +- `documentation/CONTRIBUTING.md` — contribution workflow +- `xtask/README.md` — metadata annotations and xtask usage +- `.github/workflows/ci.yml` — CI steps and MSRV +- `esp-metadata/devices/*.toml` — per-chip peripheral definitions diff --git a/.github/rust-matchers.json b/.github/rust-matchers.json new file mode 100644 index 00000000000..e3798b2506a --- /dev/null +++ b/.github/rust-matchers.json @@ -0,0 +1,44 @@ +{ + "problemMatcher": [ + { + "owner": "rust-compiler", + "pattern": [ + { + "regexp": "^(?:\\x1B\\[[0-9;]*[a-zA-Z])*(warning|warn|error)(\\[(\\S*)\\])?(?:\\x1B\\[[0-9;]*[a-zA-Z])*: (.*?)(?:\\x1B\\[[0-9;]*[a-zA-Z])*$", + "severity": 1, + "message": 4, + "code": 3 + }, + { + "regexp": "^(?:\\x1B\\[[0-9;]*[a-zA-Z])*\\s+(?:\\x1B\\[[0-9;]*[a-zA-Z])*-->\\s(?:\\x1B\\[[0-9;]*[a-zA-Z])*(\\S+):(\\d+):(\\d+)(?:\\x1B\\[[0-9;]*[a-zA-Z])*$", + "file": 1, + "line": 2, + "column": 3 + } + ] + }, + { + "owner": "rust-formatter", + "pattern": [ + { + "regexp": "^(Diff in (\\S+)) at line (\\d+):", + "message": 1, + "file": 2, + "line": 3 + } + ] + }, + { + "owner": "rust-panic", + "pattern": [ + { + "regexp": "^.*panicked\\s+at\\s+'(.*)',\\s+(.*):(\\d+):(\\d+)$", + "message": 1, + "file": 2, + "line": 3, + "column": 4 + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/scripts/hil-find-run.js b/.github/scripts/hil-find-run.js new file mode 100644 index 00000000000..0c4132dfaff --- /dev/null +++ b/.github/scripts/hil-find-run.js @@ -0,0 +1,58 @@ +async function findHilRun({ + github, + context, + pr, + selector, + tests = '', + attempts = 10, + delayMs = 3000, +}) { + const { owner, repo } = context.repo; + const tests_trimmed = String(tests || '').trim(); + + const expectedTitle = tests_trimmed + ? `HIL for PR #${pr} (${selector}; tests: ${tests_trimmed})` + : `HIL for PR #${pr} (${selector})`; + + const delay = (ms) => new Promise((r) => setTimeout(r, ms)); + let run = null; + + // Poll multiple times; keep the *last* matching run we see + for (let attempt = 1; attempt <= attempts; attempt++) { + const { data } = await github.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id: 'hil.yml', + event: 'workflow_dispatch', + per_page: 50, + }); + + if (data.workflow_runs && data.workflow_runs.length > 0) { + const candidate = data.workflow_runs.find(r => + (r.display_title && r.display_title.trim() === expectedTitle) || + (r.name && r.name.trim() === expectedTitle) + ); + // Keep the *last* matching run we see across all attempts + if (candidate) { + run = candidate; + } + } + await delay(delayMs); + } + + const isMatrix = selector === 'quick' || selector === 'full'; + + let body = isMatrix + ? `Triggered **${selector}** HIL run for #${pr}.` + : `Triggered **HIL** run for #${pr} (chips: ${selector}).`; + + if (run?.html_url) { + body += `\n\nRun: ${run.html_url}`; + } else { + body += `\n\nCould not determine run URL yet. Please check the **HIL** workflow runs manually.`; + } + + return { runId: run?.id ? String(run.id) : '', body }; +} + +module.exports = { findHilRun }; diff --git a/.github/scripts/hil-parse.js b/.github/scripts/hil-parse.js new file mode 100644 index 00000000000..0c61f2a6e1f --- /dev/null +++ b/.github/scripts/hil-parse.js @@ -0,0 +1,48 @@ +const DEFAULT_ALLOWED = [ + 'esp32c2','esp32c3','esp32c5','esp32c6','esp32h2','esp32','esp32s2','esp32s3' +]; + +function parseTests(body) { + const text = String(body || "").trim(); + const m = text.match(/--tests?\s+(.+)$/i); + if (!m) return ""; + return m[1] + .trim() + .split(/[,\s]+/) + .map((s) => s.trim()) + .filter(Boolean) + .join(","); +} + +function parseChips(body, allowed = DEFAULT_ALLOWED) { + const body_trimmed = String(body || "").trim(); + + // Remove the leading "/hil" + const withoutCmd = body_trimmed.replace(/^\/hil\s+/i, ""); + + // Split on commas and/or whitespace, normalize and dedupe + const parts = withoutCmd + .split(/[,\s]+/) + .map((s) => s.toLowerCase().replace(/[,]+$/, "")) + .filter(Boolean); + + const chips = Array.from(new Set( + parts.filter(s => allowed.includes(s)) + )); + + if (!chips.length) { + return { + chips: "", + chipsLabel: "", + error: "No valid chips specified.\n\nAllowed chips are: " + allowed.join(", "), + }; + } + + return { + chips: chips.join(" "), + chipsLabel: chips.join(", "), + error: "", + }; +} + +module.exports = { parseTests, parseChips }; diff --git a/.github/scripts/hil-status.js b/.github/scripts/hil-status.js new file mode 100644 index 00000000000..40df5f3e7ff --- /dev/null +++ b/.github/scripts/hil-status.js @@ -0,0 +1,62 @@ +function statusSuffix(kind, conclusion) { + if (!conclusion) { + return `\n\n**Status update:** ${kind} run is still in progress or status unknown.`; + } + if (conclusion === "success") { + return `\n\n**Status update:** ✅ ${kind} run **succeeded**.`; + } + if (conclusion === "cancelled") { + return `\n\n**Status update:** ⚠️ ${kind} run was **cancelled**.`; + } + return `\n\n**Status update:** ❌ ${kind} run **failed** (conclusion: ${conclusion}).`; +} + +async function pollRun({ + github, + context, + runId, + commentId, + kind, + maxPolls = 60, + pollIntervalMs = 15000, +}) { + const { owner, repo } = context.repo; + const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + let conclusion = null; + + // Poll up to ~15 minutes by default (60 * 15s) + for (let i = 0; i < maxPolls; i++) { + await delay(pollIntervalMs); + + const { data } = await github.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: runId, + }); + + if (data.status === "completed") { + conclusion = data.conclusion; + break; + } + } + + const suffix = statusSuffix(kind, conclusion); + + const comment = await github.rest.issues.getComment({ + owner, + repo, + comment_id: commentId, + }); + + const body = `${comment.data.body}\n${suffix}`; + + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body, + }); +} + +module.exports = { pollRun }; diff --git a/.github/scripts/hil-trust.js b/.github/scripts/hil-trust.js new file mode 100644 index 00000000000..a114bf9ae79 --- /dev/null +++ b/.github/scripts/hil-trust.js @@ -0,0 +1,63 @@ +// .github/scripts/hil-trust.js + +const TRUST_HEADER = "### [HIL trust list]"; +const JSON_BEGIN = ""; + +function parseTrustCommand(body, cmd) { + const text = String(body || "").trim(); + const re = new RegExp(`^\\/${cmd}\\s+@([A-Za-z0-9-]+)\\b$`, "i"); + const m = text.match(re); + if (!m) return { login: "", error: `Usage: /${cmd} @` }; + return { login: m[1].toLowerCase(), error: "" }; +} + +function extractTrustedList(existingBody) { + const existing = String(existingBody || ""); + const m = existing.match(//); + if (!m || !m[1]) return []; + try { + const data = JSON.parse(m[1]); + return Array.isArray(data.trusted) ? data.trusted.map(s => String(s).toLowerCase()) : []; + } catch { + return []; + } +} + +function renderTrustBody(list) { + const trusted = Array.from(new Set(list.map(s => String(s).toLowerCase()))).sort(); + const json = JSON.stringify({ trusted }, null, 2); + const pretty = trusted.length ? trusted.map(u => `- @${u}`).join("\n") : "_None yet_"; + + const body = +`${TRUST_HEADER} +${JSON_BEGIN} +${json} +${JSON_END} + +
+Trusted users for this PR (click to expand) + +${pretty} + +
`; + + return { body, trusted }; +} + +function upsertTrusted(existingBody, login) { + const list = extractTrustedList(existingBody); + if (login && !list.includes(login)) list.push(login); + return renderTrustBody(list); +} + +function revokeTrusted(existingBody, login) { + const list = extractTrustedList(existingBody).filter(u => u !== String(login || "").toLowerCase()); + return renderTrustBody(list); +} + +module.exports = { + parseTrustCommand, + upsertTrusted, + revokeTrusted, +}; \ No newline at end of file diff --git a/.github/workflows/api-baseline-check.yml b/.github/workflows/api-baseline-check.yml new file mode 100644 index 00000000000..b54c054cb29 --- /dev/null +++ b/.github/workflows/api-baseline-check.yml @@ -0,0 +1,58 @@ +name: API Baseline Check + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + merge_group: + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# Cancel any currently running workflows from the same PR, branch, or +# tag when a new workflow is triggered. +# +# https://stackoverflow.com/a/66336834 +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + +jobs: + get-labels: + uses: ./.github/workflows/get-labels.yml + baseline-check: + needs: get-labels + if: | + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') && + !contains(needs.get-labels.outputs.labels, 'breaking-change-esp-hal') + runs-on: ubuntu-latest + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + + steps: + - uses: actions/checkout@v6 + + # Install the Rust toolchain for Xtensa devices: + - uses: esp-rs/xtensa-toolchain@v1.6 + with: + version: 1.93.0.0 + + # Install the Rust stable toolchain for RISC-V devices: + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: stable + components: rust-src + + - name: Semver-Check + shell: bash + run: | + cargo xcheck semver-check download-baselines + cargo xcheck semver-check --chips esp32 check + cargo xcheck semver-check --chips esp32s2 check + cargo xcheck semver-check --chips esp32s3 check + cargo xcheck semver-check --chips esp32c2 check + cargo xcheck semver-check --chips esp32c3 check + # cargo xcheck semver-check --chips esp32c5 check # TODO: enable + cargo xcheck semver-check --chips esp32c6 check + cargo xcheck semver-check --chips esp32h2 check diff --git a/.github/workflows/api-baseline-generation.yml b/.github/workflows/api-baseline-generation.yml new file mode 100644 index 00000000000..9a0b2d44608 --- /dev/null +++ b/.github/workflows/api-baseline-generation.yml @@ -0,0 +1,220 @@ +name: API Baseline Generation + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + source_branch: + description: "Branch to generate baseline from" + required: true + default: "main" + type: string + package_name: + description: "The package name" + required: true + default: "esp-hal" + force_regeneration: + description: "Force baseline regeneration even without breaking change" + required: false + default: false + type: boolean + tag_name: + description: "The Git tag to generate the baseline from" + required: false + default: "" + target_repository: + description: "Target repository for baseline storage (owner/repo)" + required: false + default: "" + type: string + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + +jobs: + manage-baselines: + runs-on: ubuntu-latest + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag_name || github.event.inputs.source_branch || 'main' }} + fetch-depth: 10 + + - name: Set target repository + if: github.event_name == 'workflow_dispatch' + shell: bash + run: | + if [ -n "${{ github.event.inputs.target_repository }}" ]; then + TARGET_REPO="${{ github.event.inputs.target_repository }}" + echo "Using custom target repository: $TARGET_REPO" + else + TARGET_REPO="${{ github.repository }}" + echo "Using current repository: $TARGET_REPO" + fi + echo "GITHUB_REPOSITORY=$TARGET_REPO" >> $GITHUB_ENV + echo "Target repository for baselines: $TARGET_REPO" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + components: rust-src + + - name: Install xtensa toolchain + uses: esp-rs/xtensa-toolchain@v1.6 + with: + version: 1.93.0.0 + + - name: Check if baseline generation is needed + id: check-generation + shell: bash + run: | + # Always generate for manual dispatch + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ github.event.inputs.force_regeneration }}" = "true" ]; then + echo "Manual baseline regeneration requested (force_regeneration=true)" + echo "should_generate=true" >> $GITHUB_OUTPUT + echo "trigger_reason=manual_force" >> $GITHUB_OUTPUT + else + echo "Manual baseline generation requested" + echo "should_generate=true" >> $GITHUB_OUTPUT + echo "trigger_reason=manual" >> $GITHUB_OUTPUT + fi + echo "packages=${{ github.event.inputs.package_name }}" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # For push events, check for breaking changes + echo "Checking for breaking change in merged PR..." + + # Get the commit message of the last commit + COMMIT_MSG=$(git log -1 --pretty=%B) + echo "Last commit message: $COMMIT_MSG" + + # Extract PR number from commit message if it follows conventional format + PR_NUMBER="" + if echo "$COMMIT_MSG" | grep -q "#"; then + PR_NUMBER=$(echo "$COMMIT_MSG" | grep -o "#[0-9]*" | grep -o "[0-9]*") + fi + + if [ -n "$PR_NUMBER" ]; then + echo "Found PR number: $PR_NUMBER" + + LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' 2>/dev/null || true) + echo "PR labels:" + echo "$LABELS" + + # Extract packages from "breaking-change-" labels + PACKAGES="" + if [ -n "$LABELS" ]; then + # -o: only output matches, not rows + # -P: Perl-compatible regular expressions + # `(?<=)`: look-behind: match only if this is present, but do not make this part of the match + PACKAGES=$(echo "$LABELS" \ + | grep -oP '(?<=^breaking-change-)[a-z0-9-]+$' || true \ + | tr '\n' ',' \ + | sed 's/,$//') + fi + + if [ -n "$PACKAGES" ]; then + echo "Breaking change detected for package(s): $PACKAGES" + echo "should_generate=true" >> "$GITHUB_OUTPUT" + echo "trigger_reason=breaking_change" >> "$GITHUB_OUTPUT" + echo "packages=$PACKAGES" >> "$GITHUB_OUTPUT" + else + echo "No breaking-change-* labels found" + echo "should_generate=false" >> "$GITHUB_OUTPUT" + echo "trigger_reason=none" >> "$GITHUB_OUTPUT" + fi + else + echo "No PR number found in commit message" + echo "should_generate=false" >> "$GITHUB_OUTPUT" + echo "trigger_reason=no_pr" >> "$GITHUB_OUTPUT" + fi + + - name: Generate API baselines + if: steps.check-generation.outputs.should_generate == 'true' + shell: bash + run: | + echo "Starting API baseline generation..." + echo "Trigger reason: ${{ steps.check-generation.outputs.trigger_reason }}" + echo "Packages: ${{ steps.check-generation.outputs.packages }}" + + cargo xcheck semver-check --packages "${{ steps.check-generation.outputs.packages }}" generate-baseline + + echo "API baseline generation completed" + + - name: Upload API baselines as artifact + if: steps.check-generation.outputs.should_generate == 'true' + uses: actions/upload-artifact@v6 + with: + name: api-baselines-${{ steps.check-generation.outputs.packages }} + path: ${{ steps.check-generation.outputs.packages }}/api-baseline/ + retention-days: 90 # Maximum for public repos + + - name: Create baseline summary + if: steps.check-generation.outputs.should_generate == 'true' + shell: bash + run: | + PACKAGE_NAME="${{ steps.check-generation.outputs.packages }}" + echo "Successfully generated and uploaded API baselines" + echo "Artifact name: api-baselines-$PACKAGE_NAME" + echo "Retention: 90 days (expires on $(date -d '+90 days' '+%Y-%m-%d'))" + echo "Source branch: ${{ github.event.inputs.source_branch || 'main' }}" + echo "Commit: ${{ github.sha }}" + echo "Target repository: $GITHUB_REPOSITORY" + + # Create appropriate summary based on trigger reason + if [ "${{ steps.check-generation.outputs.trigger_reason }}" = "breaking_change" ]; then + echo "## Breaking Change Baseline Regeneration" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "This API baseline regeneration was automatically triggered due to a breaking change in the merged PR." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.check-generation.outputs.trigger_reason }}" = "manual_force" ]; then + echo "## Manual Baseline Regeneration (Forced)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "This API baseline regeneration was manually triggered with force regeneration enabled." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.check-generation.outputs.trigger_reason }}" = "manual" ]; then + echo "## Manual Baseline Generation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "This API baseline generation was manually triggered." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + else + echo "## Baseline Generation Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "- **Target Repository**: \`$GITHUB_REPOSITORY\`" >> $GITHUB_STEP_SUMMARY + echo "- **Source Branch**: \`${{ github.event.inputs.source_branch || 'main' }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Artifact Name**: \`api-baselines-$PACKAGE_NAME\`" >> $GITHUB_STEP_SUMMARY + echo "- **Retention**: 90 days (expires $(date -d '+90 days' '+%Y-%m-%d'))" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Baseline Files Generated" >> $GITHUB_STEP_SUMMARY + echo "| Chip | File Size |" >> $GITHUB_STEP_SUMMARY + echo "|------|-----------|" >> $GITHUB_STEP_SUMMARY + + cd "$PACKAGE_NAME/api-baseline" + for file in *.json.gz; do + if [ -f "$file" ]; then + chip=$(basename "$file" .json.gz) + size=$(du -h "$file" | cut -f1) + echo "| $chip | $size |" >> $GITHUB_STEP_SUMMARY + fi + done + + - name: Create no-action summary + if: steps.check-generation.outputs.should_generate == 'false' + shell: bash + run: | + echo "## No Baseline Generation Needed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No breaking change detected in the merged PR. API baselines will not be regenerated." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 00000000000..2b842e40165 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,80 @@ +name: Backport merged PR + +on: + pull_request_target: + types: + - closed # this workflow will act on closed PR - no sense in opening PR with unconfirmed changes + - labeled # will fire if the label was added on an already closed PR + +permissions: + contents: write + pull-requests: write + +jobs: + backport: + # only already merged PRs that have at least one "*-backport" label + if: > + github.event.pull_request.merged == true && + ( + (github.event.action == 'closed' && + contains(join(github.event.pull_request.labels.*.name, ' '), '-backport')) + || + (github.event.action == 'labeled' && + contains(github.event.label.name, '-backport')) + ) + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 # for the benefit of backport-action, which relies on full history. + + - name: Detect package and target backport branch + id: detect + run: | + labels=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + + # first label ending in "-backport" + pkg_label=$(printf '%s\n' "$labels" | grep -- '-backport$' | head -n1 || true) + + if [ -z "$pkg_label" ]; then + echo "No *-backport label found, nothing to do." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Strip the "-backport" suffix from the label: + pkg=${pkg_label%-backport} + echo "Detected package: $pkg" + + # find backport branches (-..x) + branches=$(git ls-remote --heads origin "${pkg}-*.x" | while read -r _ ref; do + # ref is like "refs/heads/esp-hal-1.2.x" -> strip "refs/heads/" + echo "${ref#refs/heads/}" + done) + + if [ -z "$branches" ]; then + echo "No backport branches found for $pkg; skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Found candidate backport branches" + + # sort by version and pick the last (highest) one + target=$(printf '%s\n' "$branches" | sort -V | tail -n1) + echo "Using target backport branch: $target" + echo "target_branch=$target" >> "$GITHUB_OUTPUT" + + - name: Skip if nothing to do + if: steps.detect.outputs.skip == 'true' + run: echo "Skipping backport job." + + - name: Create backport PR via backport-action + if: steps.detect.outputs.skip != 'true' + uses: korthout/backport-action@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + target_branches: ${{ steps.detect.outputs.target_branch }} + label_pattern: ".*-backport$" + experimental: '{"conflict_resolution": "draft_commit_conflicts"}' diff --git a/.github/workflows/binary-size.yml b/.github/workflows/binary-size.yml new file mode 100644 index 00000000000..a12a538eeda --- /dev/null +++ b/.github/workflows/binary-size.yml @@ -0,0 +1,222 @@ +name: Binary Size Analysis + +on: + workflow_dispatch: + inputs: + pr_number: + description: "Pull request number to analyze" + required: true + type: string + +jobs: + binary-size: + runs-on: ubuntu-latest + permissions: + actions: read + pull-requests: write + contents: read + + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + dir-qa: ./qa-test + dir-dhcp: ./examples/wifi/embassy_dhcp + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + dir-qa: ./qa-test + dir-dhcp: ./examples/wifi/embassy_dhcp + - soc: esp32 + rust-target: xtensa-esp32-none-elf + dir-qa: ./qa-test + dir-dhcp: ./examples/wifi/embassy_dhcp + + steps: + - name: Setup Rust + # Install the Rust toolchain for RISC-V devices: + if: ${{ !contains(fromJson('["esp32", "esp32s2", "esp32s3"]'), matrix.target.soc) }} + uses: dtolnay/rust-toolchain@v1 + with: + target: ${{ matrix.target.rust-target }} + toolchain: stable + components: rust-src, llvm-tools + + # Install the Rust toolchain for Xtensa devices: + - if: contains(fromJson('["esp32", "esp32s2", "esp32s3"]'), matrix.target.soc) + uses: esp-rs/xtensa-toolchain@v1.6 + with: + buildtargets: ${{ matrix.target.soc }} + default: true + version: 1.93.0.0 + + - name: Install Binutils and Hub + run: | + cargo install cargo-binutils + sudo apt-get update + sudo apt-get install -y hub + + # Checkout repo & PR + - name: Checkout Repo (Initial) + uses: actions/checkout@v6 + + - name: Checkout Pull Request (PR Code at Root) + # context https://github.com/actions/checkout/issues/331 + run: hub pr checkout ${{ github.event.inputs.pr_number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Cargo Dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/ + ~/.cargo/git/ + ./target/ + key: ${{ runner.os }}-${{ matrix.target.soc }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Dir for result files + run: | + cd $HOME + mkdir results + + - name: Build PR QA Binary + working-directory: ${{ matrix.target.dir-qa }} + run: | + cargo build --release --bin sleep_timer --features ${{ matrix.target.soc }} --target ${{ matrix.target.rust-target }} + cp target/${{ matrix.target.rust-target }}/release/sleep_timer $HOME/results/pr_qa_build_${{ matrix.target.soc }}.elf + + - name: Build PR DHCP Binary + working-directory: ${{ matrix.target.dir-dhcp }} + run: | + # Build + cargo build --release --features ${{ matrix.target.soc }} --target ${{ matrix.target.rust-target }} + # Copy PR binary to parent directory (outside workspace) + cp target/${{ matrix.target.rust-target }}/release/embassy-dhcp $HOME/results/pr_dhcp_build_${{ matrix.target.soc }}.elf + + # Checkout base (current default branch HEAD) for comparison + - name: Checkout Base Commit + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Build Base QA Binary + working-directory: ${{ matrix.target.dir-qa }} + run: | + cargo build --release --bin sleep_timer --features ${{ matrix.target.soc }} --target ${{ matrix.target.rust-target }} + cp target/${{ matrix.target.rust-target }}/release/sleep_timer $HOME/results/base_qa_build_${{ matrix.target.soc }}.elf + + - name: Build Base DHCP Binary + working-directory: ${{ matrix.target.dir-dhcp }} + run: | + cargo build --release --features ${{ matrix.target.soc }} --target ${{ matrix.target.rust-target }} + cp target/${{ matrix.target.rust-target }}/release/embassy-dhcp $HOME/results/base_dhcp_build_${{ matrix.target.soc }}.elf + + - name: Copy binaries to workspace for Bloaty + run: | + # Copy files from $HOME/results to current workspace + cp $HOME/results/base_qa_build_${{ matrix.target.soc }}.elf ./ + cp $HOME/results/pr_qa_build_${{ matrix.target.soc }}.elf ./ + cp $HOME/results/base_dhcp_build_${{ matrix.target.soc }}.elf ./ + cp $HOME/results/pr_dhcp_build_${{ matrix.target.soc }}.elf ./ + ls -la *.elf + + # Diffs and Artifacts + - name: Run Bloaty QA Diff + id: bloaty-qa + uses: carlosperate/bloaty-action@v1 + with: + bloaty-args: -d sections --domain vm --source-filter "^.text$|^.data|^.bss|^.rwdata|^.rwtext" pr_qa_build_${{ matrix.target.soc }}.elf -- base_qa_build_${{ matrix.target.soc }}.elf + output-to-summary: false + continue-on-error: true + + - name: Save Bloaty QA output + run: | + echo "QA_DIFF< result-pr-${{ matrix.target.soc }}.txt + echo "\`\`\`" >> result-pr-${{ matrix.target.soc }}.txt + echo "${{ steps.bloaty-qa.outputs.bloaty-output || 'N/A' }}" >> result-pr-${{ matrix.target.soc }}.txt + echo "\`\`\`" >> result-pr-${{ matrix.target.soc }}.txt + echo "EOF" >> result-pr-${{ matrix.target.soc }}.txt + + - name: Run Bloaty DHCP Diff + id: bloaty-dhcp + uses: carlosperate/bloaty-action@v1 + with: + bloaty-args: -d sections --domain vm --source-filter "^.text$|^.data|^.bss|^.rwdata|^.rwtext" base_dhcp_build_${{ matrix.target.soc }}.elf -- pr_dhcp_build_${{ matrix.target.soc }}.elf + output-to-summary: false + continue-on-error: true + + - name: Save Bloaty DHCP output + run: | + echo "DHCP_DIFF<> result-pr-${{ matrix.target.soc }}.txt + echo "\`\`\`" >> result-pr-${{ matrix.target.soc }}.txt + echo "${{ steps.bloaty-dhcp.outputs.bloaty-output || 'N/A' }}" >> result-pr-${{ matrix.target.soc }}.txt + echo "\`\`\`" >> result-pr-${{ matrix.target.soc }}.txt + echo "EOF" >> result-pr-${{ matrix.target.soc }}.txt + cat result-pr-${{ matrix.target.soc }}.txt + + - name: Upload Result Artifact + uses: actions/upload-artifact@v6 + with: + name: result-${{ matrix.target.soc }} + path: result-pr-${{ matrix.target.soc }}.txt + + report-results: + needs: binary-size + if: always() + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - uses: actions/download-artifact@v4 + with: + pattern: result-* + path: ./results + merge-multiple: true + + - name: Generate Combined Report + id: combine + working-directory: ${{ github.workspace }} + run: cargo run -p xtask --features report -- generate-report --input results + + - name: Find and Update Comment with GH CLI + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ISSUE_NUMBER=${{ github.event.inputs.pr_number }} + echo "Processing PR Number: $ISSUE_NUMBER" + + COMMENTS=$(gh api repos/${{ github.repository }}/issues/$ISSUE_NUMBER/comments) + + # 1) Prefer the trigger comment ("Triggered binary size analysis ...") + COMMENT_ID=$(echo "$COMMENTS" | jq -r ' + .[] + | select(.user.login == "github-actions[bot]") + | select(.body | contains("Triggered binary size analysis")) + | .id' | head -n1) + + # 2) Fallback to an existing Binary Size Report comment (for older runs) + if [ -z "$COMMENT_ID" ] || [ "$COMMENT_ID" = "null" ]; then + COMMENT_ID=$(echo "$COMMENTS" | jq -r ' + .[] + | select(.user.login == "github-actions[bot]") + | select(.body | test("^## Binary Size Report")) + | .id' | head -n1) + fi + + if [ -n "$COMMENT_ID" ] && [ "$COMMENT_ID" != "null" ]; then + echo "Updating existing comment: $COMMENT_ID" + gh api -X PATCH \ + repos/${{ github.repository }}/issues/comments/$COMMENT_ID \ + -f "body=$(cat report.txt)" + else + echo "Creating new comment" + gh api -X POST \ + repos/${{ github.repository }}/issues/$ISSUE_NUMBER/comments \ + -f "body=$(cat report.txt)" + fi diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000..26fc8127080 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,206 @@ +name: Changelog check + +on: + pull_request: + # We will not track changes for the following packages/directories. + paths-ignore: + - "/examples/" + - "/extras/" + - "/hil-tests/" + - "/resources/" + - "/xtask/" + # Run on labeled/unlabeled in addition to defaults to detect + # adding/removing skip-changelog labels. + types: [opened, reopened, labeled, unlabeled, synchronize, ready_for_review] + +jobs: + get-labels: + uses: ./.github/workflows/get-labels.yml + changelog: + needs: get-labels + if: ${{ github.event_name != 'pull_request' || + (!contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') && + !github.event.pull_request.draft) }} # don't bother checking the changelog for draft PRs + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v6 + + - name: Check which package is modified + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + esp-alloc: + - 'esp-alloc/**' + esp-backtrace: + - 'esp-backtrace/**' + esp-bootloader-esp-idf: + - 'esp-bootloader-esp-idf/**' + esp-config: + - 'esp-config/**' + esp-hal: + - 'esp-hal/**' + esp-hal-procmacros: + - 'esp-hal-procmacros/**' + esp-lp-hal: + - 'esp-lp-hal/**' + esp-phy: + - 'esp-phy/**' + esp-rtos: + - 'esp-rtos/**' + esp-println: + - 'esp-println/**' + esp-riscv-rt: + - 'esp-riscv-rt/**' + esp-storage: + - 'esp-storage/**' + esp-radio: + - 'esp-radio/**' + esp-radio-rtos-driver: + - 'esp-radio-rtos-driver/**' + esp-sync: + - 'esp-sync/**' + xtensa-lx: + - 'xtensa-lx/**' + xtensa-lx-rt: + - 'xtensa-lx-rt/**' + + - name: Check that changelog updated (esp-alloc) + if: steps.changes.outputs.esp-alloc == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-alloc/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-alloc/CHANGELOG.md file." + + - name: Check that changelog updated (esp-backtrace) + if: steps.changes.outputs.esp-backtrace == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-backtrace/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-backtrace/CHANGELOG.md file." + + - name: Check that changelog updated (esp-bootloader-esp-idf) + if: steps.changes.outputs.esp-bootloader-esp-idf == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-bootloader-esp-idf/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-bootloader-esp-idf/CHANGELOG.md file." + + - name: Check that changelog updated (esp-config) + if: steps.changes.outputs.esp-config == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-config/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-config/CHANGELOG.md file." + + - name: Check that changelog updated (esp-hal) + if: steps.changes.outputs.esp-hal == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-hal/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-hal/CHANGELOG.md file." + + - name: Check that changelog updated (esp-hal-procmacros) + if: steps.changes.outputs.esp-hal-procmacros == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-hal-procmacros/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-hal-procmacros/CHANGELOG.md file." + + - name: Check that changelog updated (esp-lp-hal) + if: steps.changes.outputs.esp-lp-hal == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-lp-hal/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-lp-hal/CHANGELOG.md file." + + - name: Check that changelog updated (esp-phy) + if: steps.changes.outputs.esp-phy == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-phy/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-phy/CHANGELOG.md file." + + - name: Check that changelog updated (esp-rtos) + if: steps.changes.outputs.esp-rtos == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-rtos/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-rtos/CHANGELOG.md file." + + - name: Check that changelog updated (esp-println) + if: steps.changes.outputs.esp-println == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-println/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-println/CHANGELOG.md file." + + - name: Check that changelog updated (esp-riscv-rt) + if: steps.changes.outputs.esp-riscv-rt == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-riscv-rt/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-riscv-rt/CHANGELOG.md file." + + - name: Check that changelog updated (esp-storage) + if: steps.changes.outputs.esp-storage == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-storage/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-storage/CHANGELOG.md file." + + - name: Check that changelog updated (esp-radio) + if: steps.changes.outputs.esp-radio == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-radio/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-radio/CHANGELOG.md file." + + - name: Check that changelog updated (esp-radio-rtos-driver) + if: steps.changes.outputs.esp-radio-rtos-driver == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-radio-rtos-driver/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-radio-rtos-driver/CHANGELOG.md file." + + - name: Check that changelog updated (esp-sync) + if: steps.changes.outputs.esp-sync == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: esp-sync/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the esp-sync/CHANGELOG.md file." + + - name: Check that changelog updated (xtensa-lx) + if: steps.changes.outputs.xtensa-lx == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: xtensa-lx/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the xtensa-lx/CHANGELOG.md file." + + - name: Check that changelog updated (xtensa-lx-rt) + if: steps.changes.outputs.xtensa-lx-rt == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: xtensa-lx-rt/CHANGELOG.md + skipLabels: "skip-changelog" + missingUpdateErrorMessage: "Please add a changelog entry in the xtensa-lx-rt/CHANGELOG.md file." + + - name: Changelog format check + run: cargo xtask check-changelog diff --git a/.github/workflows/check-runner.yml b/.github/workflows/check-runner.yml new file mode 100644 index 00000000000..297d201c45e --- /dev/null +++ b/.github/workflows/check-runner.yml @@ -0,0 +1,45 @@ +name: Check Runner + +on: + workflow_call: + inputs: + primary-runner: + required: true + type: string + fallback-runner: + required: true + type: string + + outputs: + selected-runner: + description: "The runner to use (primary if available, otherwise fallback)" + value: ${{ jobs.detect-runner.outputs.selected-runner }} + +jobs: + detect-runner: + runs-on: ubuntu-latest + outputs: + selected-runner: ${{ steps.runner-check.outputs.runner }} + steps: + - id: runner-check + name: Determine runner availability (org-level) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ORG_NAME: ${{ github.repository_owner }} + PRIMARY_RUNNER: ${{ inputs.primary-runner }} + FALLBACK_RUNNER: ${{ inputs.fallback-runner }} + run: | + response=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/orgs/$ORG_NAME/actions/runners") + + AVAILABLE=$(echo "$response" | jq --arg name "$PRIMARY_RUNNER" \ + '.runners // [] | map(select(.name == $name and .status == "online" and .busy == false)) | length') + + if [[ "$AVAILABLE" -gt 0 ]]; then + echo "$PRIMARY_RUNNER is available" + echo "runner=$PRIMARY_RUNNER" >> "$GITHUB_OUTPUT" + else + echo "Falling back to $FALLBACK_RUNNER" + echo "runner=$FALLBACK_RUNNER" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml new file mode 100644 index 00000000000..077074abc01 --- /dev/null +++ b/.github/workflows/ci-nightly.yml @@ -0,0 +1,84 @@ +name: CI - nightly + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUSTDOCFLAGS: -Dwarnings + DEFMT_LOG: trace + +jobs: + esp-hal-nightly: + name: esp-hal | nightly (${{ matrix.device }}) + runs-on: ubuntu-latest + env: + CI: 1 + SSID: SSID + PASSWORD: PASSWORD + STATIC_IP: 1.1.1.1 + GATEWAY_IP: 1.1.1.1 + HOST_IP: 1.1.1.1 + + strategy: + fail-fast: false + matrix: + device: ["esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32h2"] + steps: + - uses: actions/checkout@v6 + + # Install the Rust nightly toolchain for RISC-V devices: + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: nightly + components: rust-src, clippy, rustfmt + + - name: Setup cargo-batch + run: | + # Note this is linux-only, but the macOS runner has cargo batch installed + if ! command -v cargo-batch &> /dev/null; then + mkdir -p $HOME/.cargo/bin + curl -L https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.6.0/cargo-batch > $HOME/.cargo/bin/cargo-batch + chmod +x $HOME/.cargo/bin/cargo-batch + fi + + - name: Build and Check + shell: bash + run: cargo xtask ci ${{ matrix.device }} --toolchain nightly + + check-global-symbols: + name: Check Global Symbols in esp-hal + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # Install the Rust toolchain for Xtensa devices: + - uses: esp-rs/xtensa-toolchain@v1.6 + with: + version: 1.93.0.0 + + # Check all chips at once only for esp-hal + - name: Run check-global-symbols + run: cargo xtask check-global-symbols + + create-issue: + name: Create GitHub Issue if any job failed + runs-on: ubuntu-latest + needs: [esp-hal-nightly, check-global-symbols] + if: ${{ failure() }} + steps: + - uses: actions/checkout@v6 + - run: | + sudo apt-get install gh -y + ISSUE_NAME=$(gh issue list --state open --search "Nightly CI Failure in:title" --json number --jq '.[0].number') + if [[ -z "$ISSUE_NAME" ]]; + then + gh issue create \ + --title "Nightly CI Failure" \ + --body "One or more jobs failed! [View the failed workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})." \ + --assignee JurajSadel + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36faa940ff1..9de38771b4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,33 @@ +# NOTE: +# +# When adding support for a new chip to `esp-hal`, there are a number of +# updates which must be made to the CI workflow in order to reflect this; the +# changes are: +# +# 1.) In the 'esp-hal' job, add the appropriate build command. +# 1a.) If the device has a low-power core (which is supported in +# `esp-lp-hal`), then update the `if` condition to build prerequisites. +# 2.) In the 'msrv' job, add checks as needed for the new chip. + name: CI on: pull_request: - branches: - - main + types: [opened, synchronize, reopened] push: + branches-ignore: + - "gh-readonly-queue/**" + - "main" + - "backport-**" + - "release-branch-**" + merge_group: workflow_dispatch: env: CARGO_TERM_COLOR: always GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MSRV: "1.88.0" + DEFMT_LOG: trace # Cancel any currently running workflows from the same PR, branch, or # tag when a new workflow is triggered. @@ -20,344 +38,359 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} jobs: - # -------------------------------------------------------------------------- - # Check + get-labels: + uses: ./.github/workflows/get-labels.yml - esp-hal-smartled: - runs-on: ubuntu-latest + # -------------------------------------------------------------------------- + # Build Packages + esp-hal: + needs: get-labels + runs-on: macos-m1-self-hosted + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + CI: 1 + SSID: SSID + PASSWORD: PASSWORD + STATIC_IP: 1.1.1.1 + GATEWAY_IP: 1.1.1.1 + HOST_IP: 1.1.1.1 + SKIP_CI: ${{ contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + + strategy: + fail-fast: false + matrix: + # load-blance runners a bit + group: + - xtensa + - riscv steps: - - uses: actions/checkout@v3 + - name: Skip CI + if: env.SKIP_CI == 'true' + run: | + echo "skipping" + + - uses: actions/checkout@v6 + if: env.SKIP_CI != 'true' + + # Install the Rust toolchain for Xtensa devices: + - uses: esp-rs/xtensa-toolchain@v1.6 + if: env.SKIP_CI != 'true' && matrix.group == 'xtensa' + with: + version: 1.93.0.0 + + # Install the Rust stable toolchain for RISC-V devices: - uses: dtolnay/rust-toolchain@v1 + if: env.SKIP_CI != 'true' && matrix.group == 'riscv' with: - target: riscv32imc-unknown-none-elf - toolchain: nightly-2023-03-09 + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: stable components: rust-src - - uses: esp-rs/xtensa-toolchain@v1.5 - with: - ldproxy: false - override: false - - uses: Swatinem/rust-cache@v2 - - # Check all RISC-V targets: - - name: check (esp32c3) - run: cd esp-hal-smartled/ && cargo +nightly-2023-03-09 check --features=esp32c3 - - name: check (esp32c6) - run: cd esp-hal-smartled/ && cargo +nightly-2023-03-09 check --features=esp32c6 - # Check all Xtensa targets: - - name: check (esp32) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32,esp32_40mhz - - name: check (esp32s2) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s2 - - name: check (esp32s3) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s3 - - esp32-hal: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: esp-rs/xtensa-toolchain@v1.5 - with: - default: true - buildtargets: esp32 - ldproxy: false - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - - name: build esp32-hal (no features) - run: cd esp32-hal/ && cargo build --examples - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32-hal (common features) - run: cd esp32-hal/ && cargo check --examples --features=eh1,ufmt - - name: check esp32-hal (async) - run: cd esp32-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32-hal (async, gpio) - run: cd esp32-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async - - name: check esp32-hal (async, spi) - run: cd esp32-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async - - esp32c2-hal: - runs-on: ubuntu-latest + - name: Setup cargo-batch + if: env.SKIP_CI != 'true' + run: | + if ! command -v cargo-batch &> /dev/null; then + cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked --force + fi + + - name: Build and Check Xtensa + if: env.SKIP_CI != 'true' && matrix.group == 'xtensa' + shell: bash + run: | + # lints and docs are checked separately + cargo xcheck ci esp32 --toolchain esp --no-lint --no-docs + cargo xcheck ci esp32s2 --toolchain esp --no-lint --no-docs + cargo xcheck ci esp32s3 --toolchain esp --no-lint --no-docs + + - name: Build and Check RISC-V + if: env.SKIP_CI != 'true' && matrix.group == 'riscv' + shell: bash + run: | + # lints and docs are checked separately + cargo xcheck ci esp32c2 --toolchain stable --no-lint --no-docs + cargo xcheck ci esp32c3 --toolchain stable --no-lint --no-docs + cargo xcheck ci esp32c5 --toolchain stable --no-lint --no-docs + cargo xcheck ci esp32c6 --toolchain stable --no-lint --no-docs + cargo xcheck ci esp32h2 --toolchain stable --no-lint --no-docs + + detect-extras-runner: + needs: get-labels + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + uses: ./.github/workflows/check-runner.yml + with: + primary-runner: macos-m1-self-hosted + fallback-runner: ubuntu-latest + + extras: + needs: [get-labels, detect-extras-runner] + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + runs-on: ${{ needs.detect-extras-runner.outputs.selected-runner }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@v1 with: - target: riscv32imc-unknown-none-elf - toolchain: nightly-2023-03-09 - components: rust-src - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - # We also use this as an opportunity to verify that the examples link - # for each supported image format. - - name: build esp32c2-hal (no features) - run: cd esp32c2-hal/ && cargo build --examples - - name: build esp32c2-hal (direct-boot) - run: cd esp32c2-hal/ && cargo build --examples --features=direct-boot - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32c2-hal (common features) - run: cd esp32c2-hal/ && cargo +nightly-2023-03-09 check --examples --features=eh1,ufmt - - name: check esp32c2-hal (async, systick) - run: cd esp32c2-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-systick - - name: check esp32c2-hal (async, timg0) - run: cd esp32c2-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32c2-hal (async, gpio) - run: cd esp32c2-hal/ && cargo +nightly-2023-03-09 check --example=embassy_wait --features=embassy,embassy-time-systick,async - - name: check esp32c2-hal (async, spi) - run: cd esp32c2-hal/ && cargo +nightly-2023-03-09 check --example=embassy_spi --features=embassy,embassy-time-systick,async - - esp32c3-hal: + toolchain: stable + + # Install dependencies for building the extra crates on ubuntu + - name: Install dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get -y install musl-tools libudev-dev pkg-config + # Build the extra crates + - name: Build the bench-server + run: cd extras/bench-server && cargo build + - name: Build esp-wifishark + run: cd extras/esp-wifishark && cargo build + - name: Build ieee802154-sniffer + run: cd extras/ieee802154-sniffer && cargo build + + docs: + needs: get-labels + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + group: + - xtensa + - riscv + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + + # Install the Rust toolchain for Xtensa devices: + - uses: esp-rs/xtensa-toolchain@v1.6 + with: + version: 1.88.0.0 + + # Install the Rust stable toolchain for RISC-V devices: - uses: dtolnay/rust-toolchain@v1 with: - target: riscv32imc-unknown-none-elf - toolchain: nightly-2023-03-09 + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: stable components: rust-src - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - # We also use this as an opportunity to verify that the examples link - # for each supported image format. - - name: build esp32c3-hal (no features) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 build --examples - - name: build esp32c3-hal (direct-boot) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 build --examples --features=direct-boot - - name: build esp32c3-hal (mcu-boot) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 build --examples --features=mcu-boot - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32c3-hal (common features) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 check --examples --features=eh1,ufmt - - name: check esp32c3-hal (async, systick) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-systick - - name: check esp32c3-hal (async, timg0) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32c3-hal (async, gpio) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 check --example=embassy_wait --features=embassy,embassy-time-systick,async - - name: check esp32c3-hal (async, spi) - run: cd esp32c3-hal/ && cargo +nightly-2023-03-09 check --example=embassy_spi --features=embassy,embassy-time-systick,async - - esp32c6-hal: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 + # Install the Rust nightly toolchain for RISC-V devices: - uses: dtolnay/rust-toolchain@v1 with: - target: riscv32imac-unknown-none-elf - toolchain: nightly-2023-03-09 + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: nightly components: rust-src - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - # We also use this as an opportunity to verify that the examples link - # for each supported image format. - - name: build esp32c6-hal (no features) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 build --examples - - name: build esp32c6-hal (direct-boot) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 build --examples --features=direct-boot - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32c6-hal (common features) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 check --examples --features=eh1,ufmt - - name: check esp32c6-hal (async, systick) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-systick - - name: check esp32c6-hal (async, timg0) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32c6-hal (async, gpio) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 check --example=embassy_wait --features=embassy,embassy-time-systick,async - - name: check esp32c6-hal (async, spi) - run: cd esp32c6-hal/ && cargo +nightly-2023-03-09 check --example=embassy_spi --features=embassy,embassy-time-systick,async - - esp32s2-hal: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: esp-rs/xtensa-toolchain@v1.5 - with: - default: true - buildtargets: esp32s2 - ldproxy: false - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - - name: check esp32s2-hal (no features) - run: cd esp32s2-hal/ && cargo build --examples - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32s2-hal (common features) - run: cd esp32s2-hal/ && cargo check --examples --features=eh1,ufmt - # FIXME: `time-systick` feature disabled for now, see 'esp32s2-hal/Cargo.toml'. - # - name: check esp32s2-hal (async, systick) - # run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick - - name: check esp32s2-hal (async, timg0) - run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32s2-hal (async, gpio) - run: cd esp32s2-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async - - name: check esp32s2-hal (async, spi) - run: cd esp32s2-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async - - esp32s3-hal: - runs-on: ubuntu-latest + - name: Build Xtensa docs + if: matrix.group == 'xtensa' + shell: bash + run: cargo xtask build documentation --chips esp32,esp32s2,esp32s3 - steps: - - uses: actions/checkout@v3 - - uses: esp-rs/xtensa-toolchain@v1.5 - with: - default: true - buildtargets: esp32s3 - ldproxy: false - - uses: Swatinem/rust-cache@v2 - - # Perform a full build initially to verify that the examples not only - # build, but also link successfully. - # We also use this as an opportunity to verify that the examples link - # for each supported image format. - - name: build esp32s3-hal (no features) - run: cd esp32s3-hal/ && cargo build --examples - - name: build esp32s3-hal (direct-boot) - run: cd esp32s3-hal/ && cargo build --examples --features=direct-boot - # Subsequent steps can just check the examples instead, as we're already - # confident that they link. - - name: check esp32s3-hal (common features) - run: cd esp32s3-hal/ && cargo check --examples --features=eh1,ufmt - - name: check esp32s3-hal (async, systick) - run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick - - name: check esp32s3-hal (async, timg0) - run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0 - - name: check esp32s3-hal (async, gpio) - run: cd esp32s3-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async - - name: check esp32s3-hal (async, spi) - run: cd esp32s3-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async + - name: Build RISC-V docs + if: matrix.group == 'riscv' + shell: bash + run: cargo xtask build documentation --chips esp32c2,esp32c3,esp32c5,esp32c6,esp32h2 # -------------------------------------------------------------------------- # MSRV - msrv-riscv: + msrv: + needs: get-labels runs-on: ubuntu-latest + env: + SKIP_CI: ${{ contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + + strategy: + fail-fast: false + matrix: + group: + - xtensa + - riscv steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@v1 + - name: Skip CI + if: env.SKIP_CI == 'true' + run: | + echo "skipping" + + - uses: actions/checkout@v6 + if: env.SKIP_CI != 'true' + + - uses: esp-rs/xtensa-toolchain@v1.6 + if: env.SKIP_CI != 'true' && matrix.group == 'xtensa' with: - target: riscv32imc-unknown-none-elf, riscv32imac-unknown-none-elf - toolchain: "1.65.0" - - uses: Swatinem/rust-cache@v2 - - # Verify the MSRV for all RISC-V chips. - - name: msrv (esp32c2-hal) - run: cd esp32c2-hal/ && cargo check --features=eh1,ufmt - - name: msrv (esp32c3-hal) - run: cd esp32c3-hal/ && cargo check --features=eh1,ufmt - - name: msrv (esp32c6-hal) - run: cd esp32c6-hal/ && cargo check --features=eh1,ufmt - - msrv-xtensa: - runs-on: ubuntu-latest + version: ${{ env.MSRV }} - steps: - - uses: actions/checkout@v3 - - uses: esp-rs/xtensa-toolchain@v1.5 + - name: esp toolchain checks + if: env.SKIP_CI != 'true' && matrix.group == 'xtensa' + run: rustc +esp --version --verbose + + - uses: dtolnay/rust-toolchain@v1 + if: env.SKIP_CI != 'true' && matrix.group == 'riscv' with: - ldproxy: false - version: "1.65.0" - - uses: Swatinem/rust-cache@v2 - - # Verify the MSRV for all Xtensa chips. - - name: msrv (esp32-hal) - run: cd esp32-hal/ && cargo check --features=eh1,ufmt - - name: msrv (esp32s2-hal) - run: cd esp32s2-hal/ && cargo check --features=eh1,ufmt - - name: msrv (esp32s3-hal) - run: cd esp32s3-hal/ && cargo check --features=eh1,ufmt + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf,x86_64-unknown-linux-gnu + toolchain: ${{ env.MSRV }} + components: rust-src,clippy - # -------------------------------------------------------------------------- - # Lint + - name: Stable toolchain checks + if: env.SKIP_CI != 'true' && matrix.group == 'riscv' + run: rustc +${{ env.MSRV }} --version --verbose - clippy-riscv: - runs-on: ubuntu-latest + # Verify the MSRV for all chips by running a lint session + - name: msrv RISC-V (esp-hal) + if: env.SKIP_CI != 'true' && matrix.group == 'riscv' + run: | + cargo xtask lint-packages --chips esp32c2,esp32c3,esp32c5,esp32c6,esp32h2 --toolchain ${{ env.MSRV }} + + - name: msrv Xtensa (esp-hal) + if: env.SKIP_CI != 'true' && matrix.group == 'xtensa' + run: | + cargo xtask lint-packages --chips esp32,esp32s2,esp32s3 --toolchain esp + # -------------------------------------------------------------------------- + # host tests + + detect-host-tests-runner: + needs: get-labels + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + uses: ./.github/workflows/check-runner.yml + with: + primary-runner: macos-m1-self-hosted + fallback-runner: ubuntu-latest + + host-tests: + needs: detect-host-tests-runner + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + runs-on: ${{ needs.detect-host-tests-runner.outputs.selected-runner }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + # Some of the configuration items in 'rustfmt.toml' require the 'nightly' + # release channel, MIRI is only available in nightly - uses: dtolnay/rust-toolchain@v1 with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@v2 - - # Run clippy on all packages targeting RISC-V. - - name: clippy (esp32c2-hal) - run: cargo +stable clippy --manifest-path=esp32c2-hal/Cargo.toml -- --no-deps - - name: clippy (esp32c3-hal) - run: cargo +stable clippy --manifest-path=esp32c3-hal/Cargo.toml -- --no-deps - - name: clippy (esp32c6-hal) - run: cargo +stable clippy --manifest-path=esp32c6-hal/Cargo.toml -- --no-deps - - clippy-xtensa: - runs-on: ubuntu-latest + toolchain: nightly + components: rustfmt,miri + + # Run xtask tests + - name: Run xtask tests + run: cd xtask && cargo test --features release + + # Check the formatting of all packages: + - run: cargo xtask fmt-packages --check + + # Check metadata generation for all packages: + - run: cargo xtask update-metadata --check + + # Run host tests for all applicable packages: + - run: cargo xtask host-tests + + # Check for unused dependencies + - name: Machete + uses: bnjbvr/cargo-machete@main + with: + args: > + esp-alloc + esp-backtrace + esp-bootloader-esp-idf + esp-config + esp-hal + esp-hal-procmacros + esp-lp-hal + esp-metadata + esp-phy + esp-println + esp-radio + esp-radio-rtos-driver + esp-riscv-rt + esp-rom-sys + esp-rtos + esp-storage + esp-sync + examples + extras + xtask + # -------------------------------------------------------------------------- + # Check links in .rs, .md, and .toml files + + link-check: + needs: get-labels + if: ${{ github.event_name != 'pull_request' || + !contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: esp-rs/xtensa-toolchain@v1.5 + - uses: actions/checkout@v6 + - uses: lycheeverse/lychee-action@v2 with: - ldproxy: false - - uses: Swatinem/rust-cache@v2 - - # Run clippy on all packages targeting Xtensa. - # - # The ESP32-S2 requires some additional information in order for the - # atomic emulation crate to build. - - name: clippy (esp32-hal) - run: cargo +esp clippy --manifest-path=esp32-hal/Cargo.toml -- --no-deps - - name: clippy (esp32s2-hal) - run: cargo +esp clippy --manifest-path=esp32s2-hal/Cargo.toml --target=xtensa-esp32s2-none-elf -Zbuild-std=core -- --no-deps - - name: clippy (esp32s3-hal) - run: cargo +esp clippy --manifest-path=esp32s3-hal/Cargo.toml -- --no-deps - - rustfmt: + args: > + --verbose + --no-progress + --format detailed + --timeout 7 + --max-concurrency 6 + --retry-wait-time 3 + --max-retries 6 + --accept "200,301,302" + --exclude-path ".*/target/.*|.*/static\.files/.*|.*/docs/.*" + --remap "https://github.com/${{ github.repository }}/(?:blob|tree)/[^/]+/(.+) file://$GITHUB_WORKSPACE/\$1" + './**/*.rs' + './**/*.md' + './**/*.toml' + + # Dummy HIL test-build check for PRs + # + # This job only exists so that the required "build-tests-full (...)" checks + # are green on normal PR CI. It does NOT actually build tests. + # + # The real test builds run in hil.yml on merge_group (and when dispatched + # via `/hil full`) using a job with the same id + matrix, so the merge + # queue reuses these check names. + # + # For a reference, check out this GH discussion: https://github.com/orgs/community/discussions/102764 + build-tests-full: + if: github.event_name != 'merge_group' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c2 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c2-jtag + host: aarch64 + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c3-usb + host: armv7 + - soc: esp32c5 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c5-usb + host: aarch64 + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32h2 + rust-target: riscv32imac-unknown-none-elf + runner: esp32h2-usb + host: armv7 + - soc: esp32 + rust-target: xtensa-esp32-none-elf + runner: esp32-jtag + host: aarch64 + - soc: esp32s2 + rust-target: xtensa-esp32s2-none-elf + runner: esp32s2-jtag + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 steps: - - uses: actions/checkout@v3 - - # Some of the items in 'rustfmt.toml' require the nightly release - # channel, so we must use this channel for the formatting checks - # to succeed. - - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: nightly - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - # Check the formatting of all packages. - - name: rustfmt (esp-hal-common) - run: cargo fmt --all --manifest-path=esp-hal-common/Cargo.toml -- --check - - name: rustfmt (esp-hal-procmacros) - run: cargo fmt --all --manifest-path=esp-hal-procmacros/Cargo.toml -- --check - - name: rustfmt (esp-hal-smartled) - run: cargo fmt --all --manifest-path=esp-hal-smartled/Cargo.toml -- --check - - name: rustfmt (esp32-hal) - run: cargo fmt --all --manifest-path=esp32-hal/Cargo.toml -- --check - - name: rustfmt (esp32c2-hal) - run: cargo fmt --all --manifest-path=esp32c2-hal/Cargo.toml -- --check - - name: rustfmt (esp32c3-hal) - run: cargo fmt --all --manifest-path=esp32c3-hal/Cargo.toml -- --check - - name: rustfmt (esp32c6-hal) - run: cargo fmt --all --manifest-path=esp32c6-hal/Cargo.toml -- --check - - name: rustfmt (esp32s2-hal) - run: cargo fmt --all --manifest-path=esp32s2-hal/Cargo.toml -- --check - - name: rustfmt (esp32s3-hal) - run: cargo fmt --all --manifest-path=esp32s3-hal/Cargo.toml -- --check + - name: Dummy placeholder for HIL test builds + run: | + echo "NOOP: Dummy build-tests-full for ${{ matrix.target.soc }}." + echo "Real HIL test builds run only in hil.yml (merge_group or /hil full)." diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml new file mode 100644 index 00000000000..2613c1fb142 --- /dev/null +++ b/.github/workflows/dispatch.yml @@ -0,0 +1,597 @@ +name: Dispatch + +on: + # labels on PRs + pull_request_target: + types: [labeled] + # slash commands & trust commands + issue_comment: + types: [created, edited] + +permissions: + actions: write + pull-requests: write + contents: read + +jobs: + # --------------------------------------------------------------------------- + # LABEL: TRUSTED AUTHOR + # --------------------------------------------------------------------------- + label-trusted-author: + if: github.event_name == 'pull_request_target' && + github.event.label.name == 'trusted-author' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Get PR author login + id: pr + uses: actions/github-script@v7 + with: + script: | + const login = (context.payload.pull_request.user.login || '').toLowerCase(); + if (!login) { + core.setFailed('Could not determine PR author login'); + return; + } + core.setOutput('login', login); + + - id: find + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: github-actions[bot] + body-includes: "[HIL trust list]" + + - name: Upsert trust list for author + id: upsert + uses: actions/github-script@v7 + with: + script: | + const { upsertTrusted } = require('./.github/scripts/hil-trust.js'); + const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; + const login = '${{ steps.pr.outputs.login }}'.toLowerCase(); + + const { body } = upsertTrusted(existing, login); + core.setOutput('body', body); + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find.outputs.comment-id }} + body: ${{ steps.upsert.outputs.body }} + edit-mode: replace + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + Author **@${{ steps.pr.outputs.login }}** was trusted for this PR via the `trusted-author` label. + They can now use `/hil quick`, `/hil full` and `/hil ` and their features. + + # --------------------------------------------------------------------------- + # TRUST MANAGEMENT (/trust, /revoke) + # --------------------------------------------------------------------------- + + trust: + if: > + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + (github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'OWNER') && + startsWith(github.event.comment.body, '/trust ') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Parse login + id: parse + uses: actions/github-script@v7 + with: + script: | + const { parseTrustCommand } = require('./.github/scripts/hil-trust.js'); + const res = parseTrustCommand(context.payload.comment.body, 'trust'); + if (res.error) core.setFailed(res.error); + core.setOutput('login', res.login); + + - id: find + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.issue.number }} + comment-author: github-actions[bot] + body-includes: "[HIL trust list]" + + - name: Upsert trust list + id: upsert + uses: actions/github-script@v7 + with: + script: | + const { upsertTrusted } = require('./.github/scripts/hil-trust.js'); + const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; + const login = '${{ steps.parse.outputs.login }}'.toLowerCase(); + + const { body } = upsertTrusted(existing, login); + core.setOutput('body', body); + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + comment-id: ${{ steps.find.outputs.comment-id }} + body: ${{ steps.upsert.outputs.body }} + edit-mode: replace + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + # separate short confirmation comment + body: "Trusted **@${{ steps.parse.outputs.login }}** for this PR. They can now use `/hil quick`, `/hil full` and `/hil ` and their features." + + revoke: + if: > + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + (github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'OWNER') && + startsWith(github.event.comment.body, '/revoke ') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Parse login + id: parse + uses: actions/github-script@v7 + with: + script: | + const { parseTrustCommand } = require('./.github/scripts/hil-trust.js'); + const res = parseTrustCommand(context.payload.comment.body, 'revoke'); + if (res.error) core.setFailed(res.error); + core.setOutput('login', res.login); + + - id: find + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.issue.number }} + comment-author: github-actions[bot] + body-includes: "[HIL trust list]" + + - name: Remove from trust list + id: update + uses: actions/github-script@v7 + with: + script: | + const { revokeTrusted } = require('./.github/scripts/hil-trust.js'); + const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; + const login = '${{ steps.parse.outputs.login }}'.toLowerCase(); + + const { body } = revokeTrusted(existing, login); + core.setOutput('body', body); + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + comment-id: ${{ steps.find.outputs.comment-id }} + body: ${{ steps.update.outputs.body }} + edit-mode: replace + + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: "Revoked **@${{ steps.parse.outputs.login }}** for this PR." + + # --------------------------------------------------------------------------- + # SLASH-BASED DISPATCHES (/hil quick, /hil full, /hil ) + # --------------------------------------------------------------------------- + + auth: + # Only PR comments need auth + if: github.event_name == 'issue_comment' && github.event.issue.pull_request + runs-on: ubuntu-latest + outputs: + allowed: ${{ steps.check.outputs.allowed }} + steps: + - id: check + uses: actions/github-script@v7 + with: + script: | + const assoc = context.payload.comment.author_association; + const commenter = context.payload.comment.user.login.toLowerCase(); + const pr = context.payload.issue.number; + + if (assoc === 'MEMBER' || assoc === 'OWNER') { + core.setOutput('allowed', 'true'); + return; + } + + const { owner, repo } = context.repo; + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: pr, per_page: 100 } + ); + const trust = comments.find(c => + c.user?.login === 'github-actions[bot]' && + typeof c.body === 'string' && + c.body.includes('HIL_TRUST_JSON') + ); + + let allowed = false; + if (trust) { + const m = trust.body.match(//); + if (m && m[1]) { + try { + const data = JSON.parse(m[1]); + const list = (data.trusted || []).map(s => String(s).toLowerCase()); + allowed = list.includes(commenter); + } catch {} + } + } + core.setOutput('allowed', allowed ? 'true' : 'false'); + + hil-quick: + needs: auth + if: github.event_name == 'issue_comment' && + needs.auth.outputs.allowed == 'true' && + startsWith(github.event.comment.body, '/hil quick') + runs-on: ubuntu-latest + outputs: + run_id: ${{ steps.find-run.outputs.run_id }} + comment_id: ${{ steps.comment.outputs.comment-id }} + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Parse tests + id: parse-tests + uses: actions/github-script@v7 + with: + script: | + const { parseTests } = require('./.github/scripts/hil-parse.js'); + core.setOutput('tests', parseTests(context.payload.comment.body)); + + - name: Dispatch HIL (quick) + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: hil.yml + ref: ${{ github.event.repository.default_branch }} + inputs: | + { + "repository": "${{ github.repository }}", + "branch": "refs/pull/${{ github.event.issue.number }}/head", + "matrix": "quick", + "pr_number": "${{ github.event.issue.number }}", + "chips": "", + "tests": "${{ steps.parse-tests.outputs.tests }}" + } + + - name: Find HIL run URL (quick) + id: find-run + uses: actions/github-script@v7 + with: + script: | + const { findHilRun } = require('./.github/scripts/hil-find-run.js'); + + const { runId, body } = await findHilRun({ + github, + context, + pr: context.payload.issue.number, + selector: 'quick', + tests: '${{ steps.parse-tests.outputs.tests }}', + }); + + core.setOutput('run_id', runId); + core.setOutput('body', body); + + - name: Confirm in PR + id: comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: ${{ steps.find-run.outputs.body }} + + hil-quick-status: + needs: hil-quick + if: needs.hil-quick.outputs.run_id != '' && needs.hil-quick.outputs.comment_id != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Wait for quick HIL run and update comment + uses: actions/github-script@v7 + with: + script: | + const { pollRun } = require('./.github/scripts/hil-status.js'); + + await pollRun({ + github, + context, + runId: Number('${{ needs.hil-quick.outputs.run_id }}'), + commentId: Number('${{ needs.hil-quick.outputs.comment_id }}'), + kind: 'HIL (quick)', + // Quick runs can legitimately take >15min, especially with queueing. + maxPolls: 180, + }); + + hil-full: + needs: auth + if: github.event_name == 'issue_comment' && + needs.auth.outputs.allowed == 'true' && + startsWith(github.event.comment.body, '/hil full') + runs-on: ubuntu-latest + outputs: + run_id: ${{ steps.find-run.outputs.run_id }} + comment_id: ${{ steps.comment.outputs.comment-id }} + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Parse tests + id: parse-tests + uses: actions/github-script@v7 + with: + script: | + const { parseTests } = require('./.github/scripts/hil-parse.js'); + core.setOutput('tests', parseTests(context.payload.comment.body)); + + - name: Dispatch HIL (full) + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: hil.yml + ref: ${{ github.event.repository.default_branch }} + inputs: | + { + "repository": "${{ github.repository }}", + "branch": "refs/pull/${{ github.event.issue.number }}/head", + "matrix": "full", + "pr_number": "${{ github.event.issue.number }}", + "chips": "", + "tests": "${{ steps.parse-tests.outputs.tests }}" + } + + - name: Find HIL run URL (full) + id: find-run + uses: actions/github-script@v7 + with: + script: | + const { findHilRun } = require('./.github/scripts/hil-find-run.js'); + + const { runId, body } = await findHilRun({ + github, + context, + pr: context.payload.issue.number, + selector: 'full', + tests: '${{ steps.parse-tests.outputs.tests }}', + }); + + core.setOutput('run_id', runId); + core.setOutput('body', body); + + - name: Confirm in PR + id: comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: ${{ steps.find-run.outputs.body }} + + hil-full-status: + needs: hil-full + if: needs.hil-full.outputs.run_id != '' && needs.hil-full.outputs.comment_id != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Wait for full HIL run and update comment + uses: actions/github-script@v7 + with: + script: | + const { pollRun } = require('./.github/scripts/hil-status.js'); + + await pollRun({ + github, + context, + runId: Number('${{ needs.hil-full.outputs.run_id }}'), + commentId: Number('${{ needs.hil-full.outputs.comment_id }}'), + kind: 'HIL (full)', + }); + + # --------------------------------------------------------------------------- + # PER-CHIP HIL: /hil esp32c3 [esp32s3 ...] [--tests ...] + # --------------------------------------------------------------------------- + + hil-chips: + needs: auth + if: github.event_name == 'issue_comment' && + needs.auth.outputs.allowed == 'true' && + startsWith(github.event.comment.body, '/hil ') && + !startsWith(github.event.comment.body, '/hil quick') && + !startsWith(github.event.comment.body, '/hil full') + runs-on: ubuntu-latest + outputs: + run_id: ${{ steps.find-run.outputs.run_id }} + comment_id: ${{ steps.comment.outputs.comment-id }} + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Parse chips + id: parse + uses: actions/github-script@v7 + with: + script: | + const { parseChips } = require('./.github/scripts/hil-parse.js'); + + const res = parseChips(context.payload.comment.body); + + core.setOutput('chips', res.chips); + core.setOutput('chips_label', res.chipsLabel); + core.setOutput('error', res.error); + + - name: Parse tests + id: parse-tests + uses: actions/github-script@v7 + with: + script: | + const { parseTests } = require('./.github/scripts/hil-parse.js'); + core.setOutput('tests', parseTests(context.payload.comment.body)); + + - name: Report invalid chips + if: steps.parse.outputs.chips == '' && steps.parse.outputs.error != '' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + @${{ github.event.comment.user.login }}, HIL **per-chip** request failed: ${{ steps.parse.outputs.error }} + + - name: Dispatch HIL (per-chip) + if: steps.parse.outputs.chips != '' + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: hil.yml + ref: ${{ github.event.repository.default_branch }} + inputs: | + { + "repository": "${{ github.repository }}", + "branch": "refs/pull/${{ github.event.issue.number }}/head", + "matrix": "chips", + "pr_number": "${{ github.event.issue.number }}", + "chips": "${{ steps.parse.outputs.chips }}", + "tests": "${{ steps.parse-tests.outputs.tests }}" + } + + - name: Find HIL run URL (per-chip) + if: steps.parse.outputs.chips != '' + id: find-run + uses: actions/github-script@v7 + with: + script: | + const { findHilRun } = require('./.github/scripts/hil-find-run.js'); + + const { runId, body } = await findHilRun({ + github, + context, + pr: context.payload.issue.number, + selector: '${{ steps.parse.outputs.chips }}', + tests: '${{ steps.parse-tests.outputs.tests }}', + }); + + core.setOutput('run_id', runId); + core.setOutput('body', body); + + - name: Confirm in PR + if: steps.parse.outputs.chips != '' + id: comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: ${{ steps.find-run.outputs.body }} + + hil-chips-status: + needs: hil-chips + if: needs.hil-chips.outputs.run_id != '' && needs.hil-chips.outputs.comment_id != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # for `require` + with: + fetch-depth: 1 + + - name: Wait for per-chip HIL run and update comment + uses: actions/github-script@v7 + with: + script: | + const { pollRun } = require('./.github/scripts/hil-status.js'); + + await pollRun({ + github, + context, + runId: Number('${{ needs.hil-chips.outputs.run_id }}'), + commentId: Number('${{ needs.hil-chips.outputs.comment_id }}'), + kind: 'HIL (per-chip)', + }); + + hil-deny: + needs: auth + if: + github.event_name == 'issue_comment' && needs.auth.outputs.allowed != 'true' && + (startsWith(github.event.comment.body, '/hil quick') || + startsWith(github.event.comment.body, '/hil full') || + startsWith(github.event.comment.body, '/hil ') || + startsWith(github.event.comment.body, '/test-size')) + runs-on: ubuntu-latest + steps: + - name: Inform not allowed + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + @${{ github.event.comment.user.login }}, sorry — you're not allowed to execute HIL runs or binary size analysis for this PR. + Please ask an `esp-rs` member/owner to grant access with: + ``` + /trust @${{ github.event.comment.user.login }} + ``` + After that, you can use `/hil quick`, `/hil full`, `/hil ` or `/test-size`. + + hil-help: + if: github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/hil') && + !startsWith(github.event.comment.body, '/hil quick') && + !startsWith(github.event.comment.body, '/hil full') && + !contains(github.event.comment.body, 'esp32') + runs-on: ubuntu-latest + steps: + - name: Explain usage + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + Usage: + - `/hil quick` — run a quick HIL matrix (only `ESP32-S3` (Xtensa) and `ESP32-C6` (RISC-V) tests) + - `/hil full` — run the full HIL matrix for all supported chips + - `/hil [ ...]` — run the full HIL tests **only** for the listed chips + - `/test-size` — run binary size analysis for this PR + - You can optionally append `--test [,...]` to any `/hil` command to only run selected tests. + If you aren't a repository **member/owner**, you must be **trusted for this PR**. + Maintainers can grant access with a `trusted-author` label or with: + ``` + /trust @ + ``` + and revoke with: + ``` + /revoke @ + ``` + + binary-size: + needs: auth + if: github.event_name == 'issue_comment' && + needs.auth.outputs.allowed == 'true' && + startsWith(github.event.comment.body, '/test-size') + runs-on: ubuntu-latest + steps: + - name: Dispatch Binary Size Analysis + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: binary-size.yml + ref: ${{ github.event.repository.default_branch }} + inputs: | + { + "pr_number": "${{ github.event.issue.number }}" + } + + - name: Confirm in PR + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number }} + body: > + Triggered binary size analysis for + #${{ github.event.issue.number }}. Results will be posted as a comment when ready. diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000000..f5361511031 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,194 @@ +name: Documentation + +on: + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + packages: + description: > + A JSON structure describing the packages to build. + E.g: [{"name":"esp-hal","tag":"v0.23.1"},{"name":"esp-radio","tag":"esp-radio-v0.12"}] + + NOTE: You can run `cargo xtask release tag-releases` to get the json output generated for this workflow. + + If you want to build all packages, leave this field empty. + required: false + server: + type: choice + description: Which server to deploy to + options: + - preview + - production + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + setup: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-pr')) }} + # required for this step, as stdout is captured by `hal-xtask release tag-releases` + env: + RUST_LOG: off + steps: + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + + # Build the `xtask` package using the latest commit, and copy the + # resulting binary to the `~/.cargo/bin/` directory. We do this to + # avoid having to rebuild different versions of the package for + # different tags if they do not fall on the same commit, and to + # make sure that we take advantage of any subsequent updates to + # the xtask which may have happened after a release was tagged. + - name: Checkout repository + uses: actions/checkout@v6 + with: + repository: esp-rs/esp-hal + - name: Build xtask + run: | + cargo build --package=xtask --features=deploy-docs + cp target/debug/xtask ~/.cargo/bin/hal-xtask + + - name: Cache xtask for all jobs + uses: actions/upload-artifact@v6 + with: + name: xtask + path: target/debug/xtask + + - id: setup_manual + name: Set up [manual run with specific packages] + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.packages != '' }} + run: | + input='${{ github.event.inputs.packages }}' + echo "packages=${input}" >> "$GITHUB_OUTPUT" + + - id: setup_pr + name: Set up [pull request or all packages] + if: ${{ github.event_name == 'pull_request' || github.event.inputs.packages == '' }} + # Release PRs will ignore the tag values and just check out the latest commit + run: | + output=$(hal-xtask release tag-releases) + echo "packages=${output}" >> "$GITHUB_OUTPUT" + + outputs: + packages: "${{ steps.setup_manual.outputs.packages || steps.setup_pr.outputs.packages }}" + + build: + needs: setup + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-pr')) }} + strategy: + fail-fast: true + matrix: + packages: ${{ fromJson(needs.setup.outputs.packages) }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: esp-rs/xtensa-toolchain@v1.6 + with: + default: true + version: 1.93.0.0 + + # xtensa-toolchain installs rustup and a basic toolchain, but doesn't install rust-src + - name: rust-src + run: rustup component add rust-src --toolchain nightly + + # Checkout the tag we need to start building the docs + - name: Checkout repository + uses: actions/checkout@v6 + if: ${{ github.event_name == 'workflow_dispatch' }} + with: + repository: esp-rs/esp-hal + ref: ${{ matrix.packages.tag }} + + # If running a release PR, we want to build docs for the latest commit on the released branch. + - name: Checkout repository + uses: actions/checkout@v6 + if: ${{ github.event_name != 'workflow_dispatch' }} + with: + repository: esp-rs/esp-hal + + - name: Download xtask + uses: actions/download-artifact@v4 + with: + name: xtask + path: ~/.cargo/bin/ + + - name: Build documentation + run: | + chmod +x ~/.cargo/bin/xtask + ~/.cargo/bin/xtask build documentation --packages=${{ matrix.packages.name }} --base-url ${{ fromJSON('["https://preview-docs.espressif.com/projects/rust/", "https://docs.espressif.com/projects/rust/"]')[github.event.inputs.server == 'production'] }} + + # https://github.com/actions/deploy-pages/issues/303#issuecomment-1951207879 + - name: Remove problematic '.lock' files + run: find docs -name ".lock" -exec rm -f {} \; + + - name: Upload docs for ${{ matrix.packages.name }} + uses: actions/upload-artifact@v6 + with: + name: ${{ matrix.packages.name }} + path: "docs/${{ matrix.packages.name }}" + + assemble: + needs: [setup, build] + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-pr')) }} + + steps: + # Check out the sources, the xtask reads package versions from the Cargo.toml files + - uses: actions/checkout@v6 + + - name: Download all docs and xtask + uses: actions/download-artifact@v4 + with: + path: "docs/" + + # Create an index for _all_ packages. Per-package workflows don't upload the landing page. + - name: Create index.html + run: | + chmod +x docs/xtask/xtask + docs/xtask/xtask build documentation-index + rm -rf docs/xtask + + - if: ${{ github.event.inputs.server == 'preview' || github.event_name == 'pull_request' }} + name: Deploy to preview server + uses: appleboy/scp-action@v0.1.7 + with: + host: preview-docs.espressif.com + username: ${{ secrets.PREVIEW_USERNAME }} + key: ${{ secrets.PREVIEW_KEY }} + target: ${{ secrets.PREVIEW_TARGET }} + source: "docs/" + strip_components: 1 # remove the docs prefix + overwrite: true + + # Deploying to production server is only allowed for manual runs + - if: ${{ github.event.inputs.server == 'production' && github.event_name == 'workflow_dispatch' }} + name: Deploy to production server + uses: appleboy/scp-action@v0.1.7 + with: + host: docs.espressif.com + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_KEY }} + target: ${{ secrets.PRODUCTION_TARGET }} + source: "docs/" + strip_components: 1 # remove the docs prefix + overwrite: true + docsrs: + runs-on: ubuntu-latest + if: ${{ (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-pr')) }} + steps: + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: nightly + targets: riscv32imac-unknown-none-elf + - uses: actions/checkout@v6 + with: + repository: esp-rs/esp-hal + + - name: Install cargo-docs-rs + run: cargo install cargo-docs-rs --locked + - name: Run cargo-docs-rs + run: cd esp-hal && cargo docs-rs diff --git a/.github/workflows/get-labels.yml b/.github/workflows/get-labels.yml new file mode 100644 index 00000000000..0c2ae90f7c3 --- /dev/null +++ b/.github/workflows/get-labels.yml @@ -0,0 +1,57 @@ +name: Get Labels + +on: + workflow_call: + outputs: + labels: + description: "Space-separated list of all labels" + value: ${{ jobs.get-labels.outputs.labels }} + packages: + description: "Space-separated list of packages with breaking changes" + value: ${{ jobs.get-labels.outputs.packages }} + +jobs: + get-labels: + runs-on: ubuntu-latest + outputs: + labels: ${{ steps.list.outputs.labels }} + packages: ${{ steps.list.outputs.packages }} + steps: + - uses: actions/checkout@v6 + + - name: List labels + id: list + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + LABELS=$(printf "%b" "${{ join(github.event.pull_request.labels.*.name, '\n') }}") + elif [[ "${{ github.event_name }}" == "merge_group" ]]; then + COMMIT_MSG=$(git log -1 --pretty=%B) + PR_NUMBER=$(echo "$COMMIT_MSG" | grep -o "#[0-9]*" | grep -o "[0-9]*" || true) + if [ -n "$PR_NUMBER" ]; then + LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' 2>/dev/null || true) + fi + fi + + PACKAGES="" + if [ -n "$LABELS" ]; then + # -o: only output matches, not rows + # -P: Perl-compatible regular expressions + # `(?<=)`: look-behind: match only if this is present, but do not make this part of the match + PACKAGES=$(echo "$LABELS" \ + | grep -oP '(?<=^breaking-change-)[a-z0-9-]+$' || true \ + | tr '\n' ',' \ + | sed 's/,$//') + fi + + # Convert labels to space-separated for the 'labels' output + LABELS_SPACE=$(echo "$LABELS" | tr '\n' ' ' | xargs) + # Convert comma-separated PACKAGES to space-separated + PACKAGES_SPACE=$(echo "$PACKAGES" | tr ',' ' ') + + echo "labels=$LABELS_SPACE" >> "$GITHUB_OUTPUT" + echo "packages=$PACKAGES_SPACE" >> "$GITHUB_OUTPUT" + + echo "Detected labels: $LABELS_SPACE" + echo "Detected packages: $PACKAGES_SPACE" diff --git a/.github/workflows/hil.yml b/.github/workflows/hil.yml new file mode 100644 index 00000000000..0af40a3eb9f --- /dev/null +++ b/.github/workflows/hil.yml @@ -0,0 +1,528 @@ +name: HIL + +run-name: > + ${{ github.event_name == 'merge_group' && 'HIL (merge queue)' || + + github.event.inputs.pr_number != '' && + format( + 'HIL for PR #{0} ({1}{2})', + github.event.inputs.pr_number, + github.event.inputs.chips != '' && github.event.inputs.chips || github.event.inputs.matrix, + github.event.inputs.tests != '' && format('; tests: {0}', github.event.inputs.tests) || '' + ) || + + format( + 'HIL (manual dispatch, {0}{1})', + github.event.inputs.chips != '' && github.event.inputs.chips || github.event.inputs.matrix, + github.event.inputs.tests != '' && format('; tests: {0}', github.event.inputs.tests) || '' + ) + }} + +on: + # No automatic PR runs; we'll trigger via slash/label + keep merge queue + merge_group: + workflow_dispatch: + inputs: + repository: + description: "Owner and repository to test" + required: true + default: "esp-rs/esp-hal" + branch: + description: "Branch, tag or SHA to checkout." + required: true + default: "main" + # choose quick or full matrix + matrix: + description: "Test matrix size: quick|full|chips" + required: false + default: "quick" + pr_number: + description: "Pull request number (for slash-triggered runs)" + required: false + default: "" + chips: + description: "Space-separated list of SoCs to test (for per-chip runs)" + required: false + default: "" + tests: + description: "Optional list of test names to run (ELF basenames)" + required: false + default: "" + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + get-labels: + uses: ./.github/workflows/get-labels.yml + build-xtasks: + needs: get-labels + if: > + !contains(needs.get-labels.outputs.labels, 'skip-ci') + name: Build xtasks + runs-on: ubuntu-latest + + steps: + # merge_group checks out the merge commit implicitly + - uses: actions/checkout@v6 + if: github.event_name == 'merge_group' + # workflow_dispatch: checkout requested repo+ref (prefer SHA when given) + - uses: actions/checkout@v6 + if: github.event_name == 'workflow_dispatch' + with: + repository: ${{ github.event.inputs.repository }} + ref: ${{ github.event.inputs.branch }} + + - name: Install target for cross-compilation + run: | + rustup target add aarch64-unknown-linux-gnu + rustup target add armv7-unknown-linux-gnueabihf + - name: Install cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Build xtasks + run: | + cross build --release --target armv7-unknown-linux-gnueabihf -p xtask + cross build --release --target aarch64-unknown-linux-gnu -p xtask + + - name: Upload artifact | armv7-unknown-linux-gnueabihf + uses: actions/upload-artifact@v6 + with: + name: xtask-armv7 + path: target/armv7-unknown-linux-gnueabihf/release/xtask + + - name: Upload artifact | aarch64-unknown-linux-gnu + uses: actions/upload-artifact@v6 + with: + name: xtask-aarch64 + path: target/aarch64-unknown-linux-gnu/release/xtask + + # NOTE: + # There is also a dummy job named 'build-tests-full' in `ci.yml` which runs + # only on non-merge-group events to satisfy branch protection required + # checks on PRs. + # + # This job is the *real* HIL test-build job, used on merge_group and when + # triggered via `/hil full`. + build-tests-full: + needs: get-labels + if: > + github.event_name == 'merge_group' || + (github.event_name == 'workflow_dispatch' && + github.event.inputs.matrix == 'full' && + github.event.inputs.chips == '') + runs-on: ubuntu-latest + env: + SKIP_CI: ${{ contains(needs.get-labels.outputs.labels, 'skip-ci-non-code-change') }} + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c2 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c2-jtag + host: aarch64 + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c3-usb + host: armv7 + - soc: esp32c5 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c5-usb + host: aarch64 + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32h2 + rust-target: riscv32imac-unknown-none-elf + runner: esp32h2-usb + host: armv7 + - soc: esp32 + rust-target: xtensa-esp32-none-elf + runner: esp32-jtag + host: aarch64 + - soc: esp32s2 + rust-target: xtensa-esp32s2-none-elf + runner: esp32s2-jtag + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + steps: + - name: Skip CI + if: env.SKIP_CI == 'true' + run: | + echo "skipping" + + - uses: actions/checkout@v6 + if: env.SKIP_CI != 'true' + + - uses: ./.github/actions/build-tests + if: env.SKIP_CI != 'true' + with: + event_name: ${{ github.event_name }} + repository: ${{ github.event.inputs.repository || github.repository }} + branch: ${{ github.event.inputs.branch || 'main' }} + soc: ${{ matrix.target.soc }} + rust_target: ${{ matrix.target.rust-target }} + tests: ${{ github.event.inputs.tests || '' }} + + build-tests-quick: + if: > + github.event_name == 'workflow_dispatch' && + github.event.inputs.chips == '' && + github.event.inputs.matrix != 'full' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/build-tests + with: + event_name: ${{ github.event_name }} + repository: ${{ github.event.inputs.repository || github.repository }} + branch: ${{ github.event.inputs.branch || 'main' }} + soc: ${{ matrix.target.soc }} + rust_target: ${{ matrix.target.rust-target }} + tests: ${{ github.event.inputs.tests || '' }} + + build-tests-chips: + if: github.event_name == 'workflow_dispatch' && github.event.inputs.chips != '' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c2 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c2-jtag + host: aarch64 + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c3-usb + host: armv7 + - soc: esp32c5 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c5-usb + host: aarch64 + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32h2 + rust-target: riscv32imac-unknown-none-elf + runner: esp32h2-usb + host: armv7 + - soc: esp32 + rust-target: xtensa-esp32-none-elf + runner: esp32-jtag + host: aarch64 + - soc: esp32s2 + rust-target: xtensa-esp32s2-none-elf + runner: esp32s2-jtag + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + + steps: + - uses: actions/checkout@v6 + with: + repository: ${{ github.event.inputs.repository || github.repository }} + ref: ${{ github.event.inputs.branch || 'main' }} + + - name: Decide if this SoC is requested + id: filter + run: | + CHIPS="${{ github.event.inputs.chips }}" + SOC="${{ matrix.target.soc }}" + if echo " $CHIPS " | grep -qw "$SOC"; then + echo "run=true" >> $GITHUB_OUTPUT + echo "Including $SOC in this per-chip run." + else + echo "run=false" >> $GITHUB_OUTPUT + echo "Skipping $SOC (not requested)." + fi + + - uses: ./.github/actions/build-tests + if: steps.filter.outputs.run == 'true' + with: + event_name: ${{ github.event_name }} + repository: ${{ github.event.inputs.repository || github.repository }} + branch: ${{ github.event.inputs.branch || 'main' }} + soc: ${{ matrix.target.soc }} + rust_target: ${{ matrix.target.rust-target }} + tests: ${{ github.event.inputs.tests || '' }} + + hil-quick: + if: > + github.event_name == 'workflow_dispatch' && + github.event.inputs.chips == '' && + github.event.inputs.matrix != 'full' + needs: [build-tests-quick, build-xtasks] + runs-on: + labels: [self-hosted, "${{ matrix.target.runner }}"] + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + steps: + - uses: actions/download-artifact@v4 + with: + name: tests-${{ matrix.target.soc }} + path: tests-${{ matrix.target.soc }} + # In a quick run, some artifacts won't exist — job wouldn't fail because of that. + continue-on-error: true + + - uses: actions/download-artifact@v4 + with: + name: xtask-${{ matrix.target.host }} + + - name: Skip if this target wasn't built + id: guard + run: | + if [ ! -d "tests-${{ matrix.target.soc }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "No tests for ${{ matrix.target.soc }} in this run; skipping." + fi + + - name: Compute tests arg + if: steps.guard.outputs.skip != 'true' + id: tests + run: | + if [ -n "${{ github.event.inputs.tests || '' }}" ]; then + echo "value=--elfs ${{ github.event.inputs.tests }}" >> $GITHUB_OUTPUT + else + echo "value=" >> $GITHUB_OUTPUT + fi + + - name: Run Tests + if: steps.guard.outputs.skip != 'true' + id: run-tests + run: | + [ -f ~/setup.sh ] && source ~/setup.sh + + export PATH=$PATH:/home/espressif/.cargo/bin + chmod +x xtask + ./xtask run elfs ${{ matrix.target.soc }} tests-${{ matrix.target.soc }} ${{ steps.tests.outputs.value }} + + - name: Clean up + if: always() + run: | + rm -rf tests-${{ matrix.target.soc }} || true + rm -f xtask || true + + hil-full: + if: > + !contains(needs.get-labels.outputs.labels, 'skip-ci') && + ( + github.event_name == 'merge_group' || + (github.event_name == 'workflow_dispatch' && + github.event.inputs.matrix == 'full' && + github.event.inputs.chips == '') + ) + needs: [build-tests-full, build-xtasks, get-labels] + runs-on: + labels: [self-hosted, "${{ matrix.target.runner }}"] + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c2 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c2-jtag + host: aarch64 + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c3-usb + host: armv7 + - soc: esp32c5 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c5-usb + host: aarch64 + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32h2 + rust-target: riscv32imac-unknown-none-elf + runner: esp32h2-usb + host: armv7 + - soc: esp32 + rust-target: xtensa-esp32-none-elf + runner: esp32-jtag + host: aarch64 + - soc: esp32s2 + rust-target: xtensa-esp32s2-none-elf + runner: esp32s2-jtag + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + steps: + - uses: actions/download-artifact@v4 + with: + name: tests-${{ matrix.target.soc }} + path: tests-${{ matrix.target.soc }} + continue-on-error: true + + - uses: actions/download-artifact@v4 + with: + name: xtask-${{ matrix.target.host }} + + - name: Skip if this target wasn't built + id: guard + run: | + if [ ! -d "tests-${{ matrix.target.soc }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "No tests for ${{ matrix.target.soc }} in this run; skipping." + fi + + - name: Compute tests arg + if: steps.guard.outputs.skip != 'true' + id: tests + run: | + if [ -n "${{ github.event.inputs.tests || '' }}" ]; then + echo "value=--elfs ${{ github.event.inputs.tests }}" >> $GITHUB_OUTPUT + else + echo "value=" >> $GITHUB_OUTPUT + fi + + - name: Run Tests + if: steps.guard.outputs.skip != 'true' + id: run-tests + run: | + [ -f ~/setup.sh ] && source ~/setup.sh + + export PATH=$PATH:/home/espressif/.cargo/bin + chmod +x xtask + ./xtask run elfs ${{ matrix.target.soc }} tests-${{ matrix.target.soc }} ${{ steps.tests.outputs.value }} + + - name: Clean up + if: always() + run: | + rm -rf tests-${{ matrix.target.soc }} || true + rm -f xtask || true + + hil-chips: + if: github.event_name == 'workflow_dispatch' && github.event.inputs.chips != '' + needs: [build-tests-chips, build-xtasks] + runs-on: + labels: [self-hosted, "${{ matrix.target.runner }}"] + strategy: + fail-fast: false + matrix: + target: + - soc: esp32c2 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c2-jtag + host: aarch64 + - soc: esp32c3 + rust-target: riscv32imc-unknown-none-elf + runner: esp32c3-usb + host: armv7 + - soc: esp32c5 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c5-usb + host: aarch64 + - soc: esp32c6 + rust-target: riscv32imac-unknown-none-elf + runner: esp32c6-usb + host: armv7 + - soc: esp32h2 + rust-target: riscv32imac-unknown-none-elf + runner: esp32h2-usb + host: armv7 + - soc: esp32 + rust-target: xtensa-esp32-none-elf + runner: esp32-jtag + host: aarch64 + - soc: esp32s2 + rust-target: xtensa-esp32s2-none-elf + runner: esp32s2-jtag + host: armv7 + - soc: esp32s3 + rust-target: xtensa-esp32s3-none-elf + runner: esp32s3-usb + host: armv7 + steps: + - uses: actions/download-artifact@v4 + with: + name: tests-${{ matrix.target.soc }} + path: tests-${{ matrix.target.soc }} + continue-on-error: true + + - uses: actions/download-artifact@v4 + with: + name: xtask-${{ matrix.target.host }} + + - name: Skip if this target wasn't built + id: guard + run: | + if [ ! -d "tests-${{ matrix.target.soc }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "No tests for ${{ matrix.target.soc }} in this per-chip run; skipping." + fi + + - name: Compute tests arg + if: steps.guard.outputs.skip != 'true' + id: tests + run: | + if [ -n "${{ github.event.inputs.tests || '' }}" ]; then + echo "value=--elfs ${{ github.event.inputs.tests }}" >> $GITHUB_OUTPUT + else + echo "value=" >> $GITHUB_OUTPUT + fi + + - name: Run Tests + if: steps.guard.outputs.skip != 'true' + id: run-tests + run: | + [ -f ~/setup.sh ] && source ~/setup.sh + + export PATH=$PATH:/home/espressif/.cargo/bin + chmod +x xtask + ./xtask run elfs ${{ matrix.target.soc }} tests-${{ matrix.target.soc }} ${{ steps.tests.outputs.value }} + + - name: Clean up + if: always() + run: | + rm -rf tests-${{ matrix.target.soc }} || true + rm -f xtask || true + + required: + needs: get-labels + if: > + github.event_name == 'merge_group' && + !contains(needs.get-labels.outputs.labels, 'skip-ci') + runs-on: ubuntu-latest + steps: + - run: echo "All HIL matrix jobs passed on merge_group." diff --git a/.github/workflows/issue-handler.yml b/.github/workflows/issue-handler.yml new file mode 100644 index 00000000000..978e80428ce --- /dev/null +++ b/.github/workflows/issue-handler.yml @@ -0,0 +1,16 @@ +name: Add new issues to project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/esp-rs/projects/2 + github-token: ${{ secrets.PAT }} diff --git a/.github/workflows/merge-conflict.yml b/.github/workflows/merge-conflict.yml new file mode 100644 index 00000000000..c77e06d757a --- /dev/null +++ b/.github/workflows/merge-conflict.yml @@ -0,0 +1,18 @@ +name: "Merge Conflict Monitor" +on: + push: + branches: [main] + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + conflict-monitor: + runs-on: ubuntu-latest + steps: + - name: Label & Notify Conflicts + uses: eps1lon/actions-label-merge-conflict@v3 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + dirtyLabel: "merge-conflict" + removeOnDirtyLabel: true + commentOnDirty: "New commits in main has made this PR unmergable. Please resolve the conflicts." diff --git a/.github/workflows/pre-rel-check.yml b/.github/workflows/pre-rel-check.yml new file mode 100644 index 00000000000..4f8bffdb84c --- /dev/null +++ b/.github/workflows/pre-rel-check.yml @@ -0,0 +1,73 @@ +# Check with a local registry. +# +# We want to check if we can compile examples/tests after bumping the deps by using a local registry. +# (i.e. if we will publish the right set of crates) + +name: pre-release-checks + +on: + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + check: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-pr')) }} + + steps: + - uses: actions/checkout@v6 + + # Install the Rust toolchain for Xtensa devices: + - uses: esp-rs/xtensa-toolchain@v1.6 + with: + version: 1.92.0.0 + + # Install the Rust stable toolchain for RISC-V devices: + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: stable + components: rust-src + + # Install the Rust nightly toolchain for RISC-V devices: + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: nightly + components: rust-src + + - name: init-local-registry + run: | + cargo xrel-check init + cargo xrel-check update + cargo xrel-check replace-path-deps + + - name: try-build + shell: bash + run: | + # lints and docs are checked a couple of times already by other workflows + cargo xcheck ci esp32 --toolchain esp --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32s2 --toolchain esp --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32s3 --toolchain esp --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32c2 --toolchain stable --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32c3 --toolchain stable --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32c5 --toolchain stable --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32c6 --toolchain stable --no-lint --no-docs --no-check-crates + cargo xcheck ci esp32h2 --toolchain stable --no-lint --no-docs --no-check-crates + + - name: try-build-tests + shell: bash + run: | + cargo xtask build tests esp32 + cargo xtask build tests esp32s2 + cargo xtask build tests esp32s3 + cargo xtask build tests esp32c2 + cargo xtask build tests esp32c3 + cargo xtask build tests esp32c5 + cargo xtask build tests esp32c6 + cargo xtask build tests esp32h2 diff --git a/.gitignore b/.gitignore index f6809d40365..e340f0ca191 100644 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,21 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# Other -**/settings.json - -# wokwi related files +# Wokwi related files diagram.json wokwi.toml -# vscode -.vscode \ No newline at end of file +# We'll ignore VS Code settings (at least for now...) +**/.vscode/ +**/.zed/ + +# Ignore generated documentation +docs/ + +# Ignore the generated release plan +release_plan.jsonc + +# Ignore the generated semver baselines +api-baseline/ + +.mcp.json diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 00000000000..8e047da135c --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,29 @@ +# Local/private network addresses +# Local/private network addresses (protocol-aware) +^https?://localhost +^https?://127\.0\.0\.1 +^https?://0\.0\.0\.0 +.*\.local +^https?://192\.168\..* +^https?://10\..* +^https?://172\.(1[6-9]|2[0-9]|3[0-1])\..* + +# Template placeholders +%7B[^%]+%7D +\{[^}]+\} + +# GitHub compare links (ignore only these) +^https://github\.com/.*/compare/.*\.\.\..* + +# GitHub patterns +releases/tag/$ + +# Shield.io badges +img\.shields\.io/badge/MSRV- + +# TODO: remove once released +docs\.espressif\.com/projects/rust/esp-phy/latest/ +docs\.espressif\.com/projects/rust/esp-rtos/latest/ +docs\.espressif\.com/projects/rust/esp-radio-rtos-driver/latest/ +docs\.espressif\.com/projects/rust/esp-radio/latest/ +docs\.espressif\.com/projects/rust/esp-sync/latest/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 0c991755311..00000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,71 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -Please note that only changes to the `esp-hal-common` package are tracked in this CHANGELOG. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.9.0] - 2023-05-02 - -### Added - -- Add bare-bones PSRAM support for ESP32-S2 (#493) -- Add `DEBUG_ASSIST` functionality (#484) -- Add RSA peripheral support (#467) -- Add PeripheralClockControl argument to `timg`, `wdt`, `sha`, `usb-serial-jtag` and `uart` constructors (#463) -- Added API to raise and reset software interrupts (#426) -- Implement `embedded_hal_nb::serial::*` traits for `UsbSerialJtag` (#498) - -### Fixed - -- Fix `get_wakeup_cause` comparison error (#472) -- Use 192 as mclk_multiple for 24-bit I2S (#471) -- Fix `CpuControl::start_app_core` signature (#466) -- Move `rwtext` after other RAM data sections (#464) -- ESP32-C3: Disable `usb_pad_enable` when setting GPIO18/19 to input/output (#461) -- Fix 802.15.4 clock enabling (ESP32-C6) (#458) - -### Changed - -- Update `embedded-hal-async` and `embassy-*` dependencies (#488) -- Update to `embedded-hal@1.0.0-alpha.10` and `embedded-hal-nb@1.0.0-alpha.2` (#487) -- Let users configure the LEDC output pin as open-drain (#474) -- Use bitflags to decode wakeup cause (#473) -- Minor linker script additions (#470) -- Minor documentation improvements (#460) - -### Removed - -- Remove unnecessary generic from `UsbSerialJtag` driver (#492) -- Remove `#[doc(inline)]` from esp-hal-common re-exports (#490) - -## [0.8.0] - 2023-03-27 - -## [0.7.1] - 2023-02-22 - -## [0.7.0] - 2023-02-21 - -## [0.5.0] - 2023-01-26 - -## [0.4.0] - 2022-12-12 - -## [0.3.0] - 2022-11-17 - -## [0.2.0] - 2022-09-13 - -## [0.1.0] - 2022-08-05 - -[unreleased]: https://github.com/esp-rs/esp-hal/compare/v0.9.0...HEAD -[0.9.0]: https://github.com/esp-rs/esp-hal/compare/v0.8.0...v0.9.0 -[0.8.0]: https://github.com/esp-rs/esp-hal/compare/v0.7.1...v0.8.0 -[0.7.1]: https://github.com/esp-rs/esp-hal/compare/v0.7.0...v0.7.1 -[0.7.0]: https://github.com/esp-rs/esp-hal/compare/v0.5.0...v0.7.0 -[0.5.0]: https://github.com/esp-rs/esp-hal/compare/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/esp-rs/esp-hal/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/esp-rs/esp-hal/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/esp-rs/esp-hal/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..1def66943f7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[workspace] +resolver = "2" +members = ["xtask", "xtask-mcp-macros"] +exclude = [ + "esp-alloc", + "esp-backtrace", + "esp-bootloader-esp-idf", + "esp-config", + "esp-hal", + "esp-hal-procmacros", + "esp-rom-sys", + "esp-lp-hal", + "esp-metadata", + "esp-metadata-generated", + "esp-phy", + "esp-println", + "esp-rtos", + "esp-radio-rtos-driver", + "esp-riscv-rt", + "esp-radio", + "esp-storage", + "esp-sync", + "examples", + "extras/bench-server", + "extras/esp-wifishark", + "extras/ieee802154-sniffer", + "hil-test", + "qa-test", + "xtensa-lx", + "xtensa-lx-rt", + "xtensa-lx-rt-proc-macros", + + "init-local-registry", +] diff --git a/README.md b/README.md index d0a961d8d69..5da6667ede7 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,95 @@ -# esp-hal +

+ esp-rs logo +

-![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/esp-rs/esp-hal/ci.yml?label=CI&logo=github&style=flat-square) -![MIT/Apache-2.0 licensed](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue?style=flat-square) -[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) +

esp-hal

-**H**ardware **A**bstraction **L**ayer crates for the **ESP32**, **ESP32-C2/C3/C6**, and **ESP32-S2/S3** from Espressif. +

+ GitHub Actions Workflow Status + GitHub Actions Workflow Status + MIT/Apache-2.0 licensed + + Matrix + +

-These HALs are `no_std`; if you are looking for `std` support, please use [esp-idf-hal] instead. +Bare-metal (`no_std`) hardware abstraction layer for Espressif devices. Currently supports the following devices: -This project is still in the early stages of development, and as such there should be no expectation of API stability. A significant number of peripherals currently have drivers implemented (you can see a full list [here]) but have varying levels of functionality. For most basic tasks, this should be usable already. +- ESP32 Series: _ESP32_ +- ESP32-C Series: _ESP32-C2, ESP32-C3, ESP32-C6_ +- ESP32-H Series: _ESP32-H2_ +- ESP32-S Series: _ESP32-S2, ESP32-S3_ -If you have any questions, comments, or concerns, please [open an issue], [start a new discussion], or join us on [Matrix]. For additional information regarding any of the crates in this repository, please refer to the crate's README. +Additionally provides support for programming the low-power RISC-V cores found on the _ESP32-C6_, _ESP32-S2_, and _ESP32-S3_ via the [esp-lp-hal] package. -| Crate | Target | Technical Reference Manual | -| :-----------: | :----------------------------: | :------------------------: | -| [esp32-hal] | `xtensa-esp32-none-elf` | [ESP32] | -| [esp32c2-hal] | `riscv32imc-unknown-none-elf` | [ESP32-C2] | -| [esp32c3-hal] | `riscv32imc-unknown-none-elf` | [ESP32-C3] | -| [esp32c6-hal] | `riscv32imac-unknown-none-elf` | [ESP32-C6] | -| [esp32s2-hal] | `xtensa-esp32s2-none-elf` | [ESP32-S2] | -| [esp32s3-hal] | `xtensa-esp32s3-none-elf` | [ESP32-S3] | +For additional information regarding any of the crates in this repository, please refer to the relevant crate's `README.md` file. If you have any questions, comments, or concerns, please [open an issue], or join us on [Matrix]. -[here]: https://github.com/esp-rs/esp-hal/issues/19 -[esp-idf-hal]: https://github.com/esp-rs/esp-idf-hal +If you are currently using (or considering using) `esp-hal` in a production environment and have any feedback or require support, please feel free to contact us at . + +> [!NOTE] +> +> This repository includes crates that are at various stages of maturity and stability. While many functionalities have already been implemented and are usable for most tasks, certain advanced or less common features may still be under development. Each crate may offer different levels of functionality and guarantees. + +[esp-lp-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp-lp-hal +[esp-idf-svc]: https://github.com/esp-rs/esp-idf-svc [open an issue]: https://github.com/esp-rs/esp-hal/issues/new -[start a new discussion]: https://github.com/esp-rs/esp-hal/discussions/new [matrix]: https://matrix.to/#/#esp-rs:matrix.org -[esp32-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32-hal -[esp32c2-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c2-hal -[esp32c3-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c3-hal -[esp32c6-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c6-hal -[esp32s2-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32s2-hal -[esp32s3-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32s3-hal -[esp32]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf -[esp32-c2]: https://www.espressif.com/sites/default/files/documentation/esp8684_technical_reference_manual_en.pdf -[esp32-c3]: https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf -[esp32-c6]: https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf -[esp32-s2]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf -[esp32-s3]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf -[atomic emulation]: https://github.com/esp-rs/riscv-atomic-emulation-trap - -## Quickstart - -We recommend using [cargo-generate] and [esp-template] in order to generate a new project with all the required dependencies and configuration: - -```bash -$ cargo install cargo-generate -$ cargo generate -a esp-rs/esp-template -``` -For more information on using this template, please refer to [its README]. +## Getting Started -[cargo-generate]: https://github.com/cargo-generate/cargo-generate -[esp-template]: https://github.com/esp-rs/esp-template -[its readme]: https://github.com/esp-rs/esp-template/blob/main/README.md +For information relating to the development of Rust applications on ESP devices, please first read [The Rust on ESP Book]. -## Ancillary Crates +For information about the HAL and how to use it in your own projects, please refer to the [documentation]. -There are a number of other crates within the [esp-rs organization] which can be used in conjunction with `esp-hal`: +When browsing the examples, we recommend viewing the tag for the `esp-hal` release you are using to ensure compatibility, e.g. [esp-hal-v1.0.0], as the `main` branch is used for development and APIs may have changed in the meantime. -| Crate | Description | -| :-------------: | :----------------------------------------------------------------------------: | -| [esp-alloc] | A simple `no_std` heap allocator | -| [esp-backtrace] | Backtrace support for bare-metal applications | -| [esp-println] | Provides `print!` and `println!` implementations | -| [esp-storage] | Implementation of [embedded-storage] traits to access unencrypted flash memory | -| [esp-wifi] | `no_std` Wi-Fi/Bluetooth LE support | +[The Rust on ESP Book]: https://docs.espressif.com/projects/rust/book/ +[documentation]: https://docs.espressif.com/projects/rust/ +[esp-hal-v1.0.0]: https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples -[esp-rs organization]: https://github.com/esp-rs -[esp-alloc]: https://github.com/esp-rs/esp-alloc -[esp-backtrace]: https://github.com/esp-rs/esp-backtrace -[esp-println]: https://github.com/esp-rs/esp-println -[esp-storage]: https://github.com/esp-rs/esp-storage -[embedded-storage]: https://github.com/rust-embedded-community/embedded-storage -[esp-wifi]: https://github.com/esp-rs/esp-wifi +## Resources -## MSRV +- [The Rust Programming Language](https://doc.rust-lang.org/book/) +- [The Embedded Rust Book](https://docs.rust-embedded.org/book/index.html) +- [The Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) +- [The Rust on ESP Book](https://docs.espressif.com/projects/rust/esp-hal/latest/) +- [Embedded Rust (no_std) on Espressif](https://docs.espressif.com/projects/rust/no_std-training/) -The **M**inimum **S**upported **R**ust **V**ersions are: +## Support policy -- `1.65.0` for RISC-V devices (**ESP32-C2**, **ESP32-C3**, **ESP32-C6**) -- `1.65.0` for Xtensa devices (**ESP32**, **ESP32-S2**, **ESP32-S3**) -- `1.67.0` for all `async` examples (`embassy_hello_world`, `embassy_wait`, etc.) +All active development will occur on `main`. -Note that targeting the Xtensa ISA currently requires the use of the [esp-rs/rust] compiler fork. The [esp-rs/rust-build] repository has pre-compiled release artifacts for most common platforms, and provides installation scripts to aid you in the process. +We will only backport fixes to the _latest_ minor release in a major version. For example, this means we will apply patches (bug fixes) to `1.1.x` until `1.2.0` is released, at which point all patches are only backported to the `1.2.x` series of releases. -RISC-V is officially supported by the official Rust compiler. +If you are a user of `unstable` APIs, we will never push breaking changes in a patch release. However, `unstable` changes _will_ make their way into minor releases. This means that as an `unstable` user updating from `1.1.x` to `1.2.x` _may_ introduce breaking changes. If you depend on `unstable`, we recommend defining your esp-hal dependency as follows: -[esp-rs/rust]: https://github.com/esp-rs/rust -[esp-rs/rust-build]: https://github.com/esp-rs/rust-build +```toml +esp-hal = { version = "~1.1" } +``` -## Git Hooks +Using the [`~` operator](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) will prevent cargo auto updating to minor versions, allowing you to use `cargo update` without the possibility of breaking your project. -We provide a simple `pre-commit` hook to verify the formatting of each package prior to committing changes. This can be enabled by placing it in the `.git/hooks/` directory: +## AI Contribution Policy -```bash -$ cp pre-commit .git/hooks/pre-commit -``` +We follow the same policy as the official Rust Embedded working group, please review [the policy](https://github.com/rust-embedded/wg/blob/HEAD/CODE_OF_CONDUCT.md#ai-tool-use-policy) before contributing with AI tools. + +## Contributing + +We have a number of living documents to aid contributing to the project, please give these a read before modifying code: -When using this hook, you can choose to ignore its failure on a per-commit basis by committing with the `--no-verify` flag; however, you will need to be sure that all packages are formatted when submitting a pull request. +- [DEVELOPER-GUIDELINES](https://github.com/esp-rs/esp-hal/blob/main/documentation/DEVELOPER-GUIDELINES.md) +- [CONTRIBUTING-GUIDE](https://github.com/esp-rs/esp-hal/blob/main/documentation/CONTRIBUTING.md) ## License -Licensed under either of: +All packages within this repository are licensed under either of: - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. -### Contribution +### Contribution notice Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without diff --git a/documentation/CHIP_BRING_UP.md b/documentation/CHIP_BRING_UP.md new file mode 100644 index 00000000000..b34e7beea96 --- /dev/null +++ b/documentation/CHIP_BRING_UP.md @@ -0,0 +1,294 @@ +# Chip Support Guidelines + +This guide aims to reach a chip bring-up state where basic examples build and flash successfully (even if they do nothing) and watchdogs reboot the chip. + +In general, comparison and existing knowledge are key to obtaining all required data. As a first step, determine which already supported chip is most similar to the one being added. In the case of the `ESP32-C5`, the closest references were `ESP32-C6` and/or `ESP32-H2`. Even if no clearly similar chip exists — simply choose one as a reference and work based on that. + +We prefer to trust `ESP-IDF` first and `TRM` second. The reason is simple: `TRM`, unfortunately, might sometimes contain inaccuracies, while `ESP-IDF` code is proven to work — operability is our main priority. As a side task, you may look for inconsistencies in the TRM and report them via GitHub issues (if you are an external contributor) or in the `Documentation` channel (if you are a team member). + +## `espflash` And Test Tooling + +For bring-up, you will typically need to add support in tooling, otherwise testing will be blocked. + +### espflash Support + +Do **not** guess which ESP-IDF revision the bootloaders/stubs come from. Instead, refer to the corresponding [`README`](https://github.com/esp-rs/espflash/blob/main/espflash/resources/README.md) in `espflash`, which documents the exact source commit and related build assumptions: + +In particular, this page specifies: +- which ESP-IDF branch/commit the bootloaders were built from +- the current MMU page size assumptions (and any resulting flash size configuration requirements) +- where flasher stubs are sourced from + +Also, you will need to implement and define a couple of chip-specific functions in `espflash/src/targets/.rs`. It is sufficient to use the implementation of already supported chips as a reference. + +### probe-rs Support (optional) + +If CI testing or debugger are in scope, the new chip must also be supported in [`probe-rs`](https://github.com/probe-rs/probe-rs). + +As a reference, see the [PR](https://github.com/probe-rs/probe-rs/pull/3635), which added `ESP32-C5` support. + +Adding probe-rs support requires a flash loader. You will need to use `esp-flash-loader` to add one. This project has a [guide](https://github.com/esp-rs/esp-flash-loader?tab=readme-ov-file#adding-new-chips) on how to add new chips and build a flash loader. + +Once completed, probe-rs can be used for flashing, debugging, and automated tests. + +## Linker Scripts +The process usually begins with linker scripts. + +### `memory.x` + +First, determine the source of truth for memory mappings. For example, using `esp32c6` as a reference, you will see the following definition: +``` +RAM : ORIGIN = 0x40800000 , LENGTH = 0x6E610 +``` + +In `ESP-IDF` (preferably `master` or the `latest` release), search for `6E610` — it is unique enough to find relevant matches. The search will lead to: + +- `components/bootloader/subproject/main/ld/esp32c6/bootloader.ld.in` +- `components/esp_system/ld/esp32c6/memory.ld.in` + +Inside `memory.ld.in`, you will find: + +```c +#if !CONFIG_SECURE_ENABLE_TEE +#define SRAM_SEG_START (0x40800000) +#else +#define SRAM_SEG_START (0x40800000 + CONFIG_SECURE_TEE_IRAM_SIZE + CONFIG_SECURE_TEE_DRAM_SIZE) +#define FLASH_SEG_OFFSET (CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE) +#endif // CONFIG_SECURE_ENABLE_TEE + +#define SRAM_SEG_START 0x4086E610 /* 2nd stage bootloader iram_loader_seg start address */ +#define SRAM_SEG_SIZE SRAM_SEG_END - SRAM_SEG_START +``` + +Here, `SRAM_SEG_START` corresponds to our `ORIGIN`, and `LENGTH` is `SRAM_SEG_END` - `SRAM_SEG_START`. +Now look up the corresponding definitions for your new chip. + +As an example, in `memory.ld.in` for C5: + +```c +#if !CONFIG_SECURE_ENABLE_TEE +#define SRAM_SEG_START (0x40800000) +#else +#define SRAM_SEG_START (0x40800000 + CONFIG_SECURE_TEE_IRAM_SIZE + CONFIG_SECURE_TEE_DRAM_SIZE) +#define FLASH_SEG_OFFSET (CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE) +#endif // CONFIG_SECURE_ENABLE_TEE + +#define SRAM_SEG_END 0x4084E5A0 /* 2nd stage bootloader iram_loader_seg start address */ +#define SRAM_SEG_SIZE SRAM_SEG_END - SRAM_SEG_START +``` + +This means: +- `ORIGIN = 0x40800000` +- `LENGTH = 0x4084E5A0 - 0x40800000 = 0x4E5A0` + +To verify correctness, search for the `bootloader_iram_loader_seg_start == assertion`. It should match ORIGIN + LENGTH. + +Next, update the `dram2_seg` length formula. You need the value of `SOC_ROM_STACK_START` from ESP-IDF. For C5, this value is `0x4085e5a0`. The final definition becomes: +``` + dram2_seg ( RW ) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), len = 0x4085e5a0 - (ORIGIN(RAM) + LENGTH(RAM)) +``` + +--- + +For `ROM` definitions, inspect `drom_seg (R)` and `irom_seg (R)` in `memory.ld.in`. The segment definitions and comments there are sufficient to determine the correct values for `ORIGIN` and `LENGTH`, as well as to understand why an offset of `0x20` is required. The comments in `memory.ld.in` typically explain how flash segments are mapped and why the offset is applied. + +For `RTC_FAST` memory mappings, either: + +- Look for the `LP RAM (RTC Memory) Layout` comment in `memory.ld.in`, or +- Inspect the `lp_ram_seg(RW)` definition. + +These provide both the origin address and length of the segment. + +### `.x` and `linkall.x` + +These linker files are slightly more involved. + +It is recommended to copy an existing script (e.g., `esp32c6.x`) and adjust it as needed. + +In `linkall.x`, include the required linker scripts as done for other chips, and `PROVIDE_ALIAS` for: + +- `ROTEXT` / `RODATA` +- `RWTEXT` / `RWDATA` + +Ensure these map correctly to the memory segments defined in `memory.x`. + +For C5 specifically: + +- `RTC_FAST` exists +- `IRAM` / `DRAM` share the same address range +- `IROM` / `DROM` also share the same address range + +For `.x`, refer to similar chips (`c6`, `h2`, `c2`) and copy as needed, adjusting addresses where required. In recent Espressif chips, linker structures are typically similar. + +--- + +If you want to see the fully expanded linker scripts, build an example `ESP-IDF` project (`hello-world` is enough) for the same chip, then search inside the project’s build/ directory for *.ld. + +Those *.ld files are the complete, final linker scripts produced by the build system and passed to the linker. Use them as the “ground truth”, and compare them to the corresponding *.ld.in template inputs to verify your #defines, memory regions, and section placement match what ESP-IDF ends up generating. + +### esp-rom-sys + +To complete the linker-related bring-up, handle the `esp-rom-sys` crate. + +1. Copy all `.ld` files from `esp-idf/components/esp-rom//ld` except files containing `.libc*` or `.newlib*`. It would be ideal to document which `ESP-IDF` commit you are specifically taking these linker scripts from. The future wireless drivers implementation for [esp-wireless-drivers-3rdparty](https://github.com/esp-rs/esp-wireless-drivers-3rdparty/tree/master) should be taken from the same commit. + +2. Place them into: + `esp-rom-sys/ld//rom` + +3. Copy `additional.ld` from some existing implementation for other chip. + +4. In `ESP-IDF`, code-search for all `ROM` functions defined in `additional.ld` and update their addresses for your chip. + +## esp-metadata + +The next milestone is adding the new device to metadata. Create a new file (`esp-metadata/devices/.toml`) and paste the template below, filling in the placeholders: + +``` +# Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "" +arch = "riscv" +target = "riscv32imc-unknown-none-elf" +cores = +trm = "https://www.espressif.com/sites/default/files/documentation/_technical_reference_manual_en.pdf" + +peripherals = [ + +] + +symbols = [ +# Additional peripherals defined by us (the developers): + +] + +# [device.soc] + +memory_map = { ranges = [ +{ name = "dram", start = , end = }, +{ name = "dram2_uninit", start = 0, end = 1 }, # TODO +] } + + + +clocks = { system_clocks = { clock_tree = [ +# FIXME: these fake-definitions only exist for code to compile + { name = "", type = "source", output = "1", always_on = true }, +] }, peripheral_clocks = { templates = [ + # templates + + { name = "default_clk_en_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "clk_en_template", value = "{{default_clk_en_template}}" }, + { name = "rst_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "conf_register", value = "{{peripheral}}_conf" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_field", value = "{{peripheral}}_rst_en" }, +], peripheral_clocks = [ + +] } } + +``` + +Then, declare new chip in `esp-metadata-generated`: +- Define the feature in it's Cargo.toml: + ``` + = ["_device-selected"] + ``` + +- Add it to the other chips in `lib.rs` of this crate. + +And ***that's it***. The rest will be generated after you run `cargo xtask update-metadata`. + +#### Adding first peripherals + +To determine which peripherals to include, try building a simple example and observe compilation failures. For initial bring-up, enable only essential components: + +- `MODEM_SYSCON` / `LPCON` +- `SYSTEM` +- `SYSTIMER` +- Interrupt core + +Avoid enabling advanced peripherals (e.g., `i2c`, `spi`, LP peripherals) at this stage. + +***Important note***: During initial support, it is acceptable to `cfg`-gate or temporarily disable some functionality. For example, many parts depend on DMA, and proper isolation is not yet fully implemented. This will be addressed once issue [#4901](https://github.com/esp-rs/esp-hal/issues/4901) is resolved. + + +## HAL +### clocks_ll + +You don't need to **implement** clocks at this phase, however fake-defining them and at least using some blinding placeholders is required. Refer to the previous [C5 support PR ](https://github.com/esp-rs/esp-hal/pull/4859/changes#diff-75a5e847ec368b58a227b16ea788a0007a569040ed5793bf8cdda6f37676f0f5) as guidance. + +### esp-hal/src/efuse + +Open `espflash` in a workspace and run: +```bash +cargo xgenerate-efuse-fields /PATH/TO/LOCAL/esptool/ +``` + +This generates Rust definitions for eFuse fields based on `esptool`. + +Copy the generated file (without modification) to `efuse//fields.rs`. Then implement required eFuse functions in `efuse//mod.rs` + +Use other chips as reference and verify field names and operations against ESP-IDF and `esptool`. + + +### Auxiliary crates + +For initial bring-up, you may also need to add support in: + +- `esp-backtrace` +- `esp-bootloader-esp-idf` +- `esp-sync` +- `esp-println` (optional, for a basic "Hello World") + +These crates are mostly platform-independent. In many cases, adding a feature flag in `Cargo.toml` is enough. Where chip-specific adjustments are required (e.g., `esp-println`, `esp-bootloader-esp-idf`), verify values against ESP-IDF. + +### Interrupts + +Specify the correct RISC-V interrupt controller flavour in `[device.interrupt]` within metadata. Add necessary `cfg` gates for the new chip (refer to existing implementations). + +### RTC_CNTL + +Identify `SocResetReasons` for the new chip and implement the corresponding enumeration. These are typically defined in ESP-IDF under [`reset_reasons.h`](https://github.com/espressif/esp-idf/blob/release/v6.0/components/soc/esp32c5/include/soc/reset_reasons.h). + +In `rtc_cntl/mod.rs`, ensure watchdog-related functionality (`feed()`, `wakeup_cause()`) is enabled for the new chip. Other parts may be `cfg`-gated if necessary (will be simplified after [#4901](https://github.com/esp-rs/esp-hal/issues/4901) is resolved). + +### SOC + +Add a minimal clocks implementation (placeholder + default clock value) in `soc//clocks.rs`. + +Also implement `regi2c` functions using `ESP-IDF` as reference. + +## Subsequent Addition Of Peripherals +Implementing further peripheral support boils down to a relatively simple loop of actions: +- Define the driver in `metadata`: + - Describe the peripheral via [device.] following the example of other chips + - Add the corresponding entry to the clock tree (check if needed clocks are defined). +- Check the driver in `esp-hal` for the presence of `cfg`-gates that restrict your chip, or implement the missing parts of the functionality for it. +- Enable and run the relevant tests and examples. +- If it didn't work and errors were returned, fix them. + +## Troubleshooting + +- **Clocks are not enabled** + Make sure the required peripheral clocks are enabled and the module is taken out of reset. Ensure the clock source itself is defined correctly in `metadata` + +- **Registers do not match hardware (old ECO / TRM issue)** + If register layout or behavior does not match expectations, the `TRM` may be outdated or based on an older ECO. Cross-check against `ESP-IDF` and fix the PAC definitions if necessary. + +- **Chip does not boot** + Verify basic system configuration. Ensure `UART_SCLK` is enabled. + +- **DMA does not work** + Confirm that DMA clocks are enabled and that the memory region used is accessible. Check whether `APM` prevents `DMA` from reading or writing memory. + +- **A huge number of errors** + Sometimes this process does indeed cause dozens or even hundreds of errors, but it is worth checking again to see if most of them can be corrected with a couple of `cfg`-gates. diff --git a/documentation/CONTRIBUTING.md b/documentation/CONTRIBUTING.md new file mode 100644 index 00000000000..b0f684199b8 --- /dev/null +++ b/documentation/CONTRIBUTING.md @@ -0,0 +1,145 @@ + +# Welcome to the `esp-hal` Contributing Guide + +Thank you for considering contributing to our project! Your efforts help make `esp-hal` a better ecosystem for everyone. + +This guide outlines the contribution workflow, from reporting issues and submitting pull requests, to the review process and eventual merger of contributions. + +## Quick Navigation +* [New Contributor Guide] +* [Getting Started] + * [Issues: Reporting and Resolving] + * [Making Changes: Fork, Edit, and Pull Request] + * [Testing Your Contributions] + * [Commit Your Updates] +* [Pull Request: From Submission to Merge] +* [Your PR is merged!] + +[New Contributor Guide]: #new-contributor-guide +[Getting Started]: #getting-started +[Issues: Reporting and Resolving]: #issues-reporting-and-resolving +[Making Changes: Fork, Edit, and Pull Request]: #making-changes-fork-edit-and-pull-request +[Testing Your Contributions]: #testing-your-contributions +[Commit your updates]: #commit-your-updates +[Pull Request: From Submission to Merge]: #pull-request-from-submission-to-merge +[Your PR is merged!]: #your-pr-is-merged + +## New Contributor Guide + +Welcome aboard! If you're new to `esp-hal` or open-source contribution, here are some resources to get you started: + +* Intro to Open Source Contribution: [GitHub's Guide] +* [Setting Up Git] +* Workflow Insights: [GitHub Flow] +* Collaborating via [Pull Requests] + +Before adding or changing code, review the [esp-rs developer guidelines]. + +[GitHub's Guide]: https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github +[Setting Up Git]: https://docs.github.com/en/get-started/quickstart/set-up-git +[GitHub Flow]: https://docs.github.com/en/get-started/quickstart/github-flow +[Pull Requests]: https://docs.github.com/en/github/collaborating-with-pull-requests +[esp-rs developer guidelines]: ./DEVELOPER-GUIDELINES.md +[HIL guide]: https://github.com/esp-rs/esp-hal/blob/main/documentation/HIL-GUIDE.md + +## Getting Started + +### Issues: Reporting and Resolving + +#### Reporting a New Issue + +Encountered a problem or have an idea? First, [check existing issues] to avoid duplicates. If your concern is new, use our [issue form] to submit it. + +[check existing issues]: https://github.com/esp-rs/esp-hal/issues +[issue form]: https://github.com/esp-rs/esp-hal/issues/new/ + +#### Working on an Issue + +Browse [existing issues] to find one that resonates with you. Use labels for easier filtering. If you decide to tackle an issue, it's courteous (but not mandatory) to let others know by commenting. + +[existing issues]: https://github.com/esp-rs/esp-hal/issues + +#### Making Changes: Fork, Edit, and Pull Request + +1. **Fork**: Start by [forking the repository]. This keeps the main project safe while you make your changes. +2. **Setup**: Ensure you have the latest Rust toolchain via [rustup.rs]. +3. **Branch**: Create a branch in your fork for your changes. Keep your changes focused and limited to a single issue or feature. + +[forking the repository]: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo +[rustup.rs]: https://rustup.rs/ + +#### What You Should Do: + +* **API changes**: If your contribution changes the API, please adapt the driver (including module level documentation) and examples accordingly and update the [HIL] (Hardware-in-the-Loop) tests. +* **Run Related Examples**: After making changes, run any affected examples to ensure they build successfully and perform as expected. +* **Manual Testing**: For hardware-related changes, manually test your changes on the actual devices when possible. If not, please note it in the corresponding issue, and someone from our team will assist with testing. This is crucial because hardware behavior can sometimes differ from what's simulated or expected. +* **HIL Tests**: Ensure that any changes to the API or hardware interaction logic are reflected in the HIL tests located in the `hil-test` directory. This helps verify the real-world applicability of your changes. + +By taking these extra steps to test your contributions, you help maintain the high quality and reliability of `esp-hal`, ensuring it remains a robust platform for everyone. + +[HIL]: https://github.com/esp-rs/esp-hal/tree/main/hil-test + +### Testing Your Contributions + +Ensuring the quality and reliability of `esp-hal` is a shared responsibility, and testing plays a critical role in this process. Our GitHub CI automatically checks the buildability of all examples and drivers within the project. However, automated tests can't catch everything, especially when it comes to the nuanced behavior of hardware interactions. So make sure that the example affected by your change works as expected. + +Further steps that can (or should) be taken in testing: + +* Using [xtask], build examples for the specified chip. +* When documentation or doctests change, run `cargo xtask build documentation` and `cargo xtask run doc-tests ` to build the documentation and run the doctests. To reduce build/test time, use `--packages` to specify the package(s) and `--chips` (for documentation builds) to specify the target chip(s). +* Run the [HIL] tests locally if changes have been made to them. + +For detailed instructions on how to use our HIL tests with comment commands, see the [HIL guide]. + +[xtask]: https://github.com/esp-rs/esp-hal/tree/main/xtask + +### Commit Your Updates + +Commit your changes once you're satisfied. Review your own work to streamline the review process later. Use `rustfmt` and `cargo clippy` to ensure your code adheres to Rust's conventions. + +```shell +rustup component add rustfmt +rustup component add clippy +``` + +We _strongly_ recommend that you format your code before committing to ensure consistency throughout the project. +To format all packages in the workspace, run the following command in a terminal from the root of the repository: + +```shell +cargo xtask fmt-packages +``` + +We also recommend using the `lint-packages` subcommand, which uses `cargo clippy` and will lint the entire driver in order to catch common mistakes in the code. + +```shell +cargo xtask lint-packages +``` + +This will use `rustfmt` to ensure that all source code is formatted correctly prior to committing. + +## Pull Request: From Submission to Merge + +* Fill the pull request template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request. +* [Link your PR] to any relevant issues it addresses. +* [Allow edits from maintainers] so the branch can be updated for a merge. Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request additional information. +* Make sure you add an entry with your changes to the relevant crate's changelog. + * Place your entry in the appropriate section of the upcoming version + * Make sure you reference the right pull request at the end of the changelog entry. If you made a change in PR 1234, end your changelog entry with `(#1234)`. + * You can amend existing changelog entries, when appropriate. In this case, just append your PR number to that entry's, like `(#789, #1234)`. +* If your change requires user code to be changed, make sure you add your changes to the next version's migration guide. +* We may ask for changes to be made before a PR can be merged, either using [suggested changes] or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. +* As you update your PR and apply changes, mark each conversation as [resolved]. +* Resolve merge conflicts if they arise, using resources like [this git tutorial] for help. + +[Link your PR]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue +[Allow edits from maintainers]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork#enabling-repository-maintainer-permissions-on-existing-pull-requests +[suggested changes]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request +[resolved]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#resolving-conversations +[this git tutorial]: https://github.com/skills/resolve-merge-conflicts + + +## Your PR is Merged! + +Congratulations! The `esp-rs` team thanks you for your contributions! + +Contributing to open source extends beyond just code! Each contribution, regardless of size, plays a significant role. We appreciate your involvement in this collective endeavor. diff --git a/documentation/DEVELOPER-GUIDELINES.md b/documentation/DEVELOPER-GUIDELINES.md new file mode 100644 index 00000000000..76ad62df9b4 --- /dev/null +++ b/documentation/DEVELOPER-GUIDELINES.md @@ -0,0 +1,276 @@ +# `esp-hal` developers' guidelines + +## About + +This is a living document - make sure to check the latest version of this document. + +> [!NOTE] +> Not all of the currently existing code follows this guideline, yet. + +In general, the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines) apply to all projects in the ESP-RS GitHub organization where possible. + - Especially for public API but if possible also for internal APIs. + +## Working with the repository + +### rust-analyzer + +The repository contains multiple crates, with support for multiple different devices. `rust-analyzer` is not able to handle the complete repository at once, so you will need to change configuration depending on what you work on. +You will need to point `rust-analyzer` to one of the crates you want to work on, using `linkedProjects`. While this option can be used to add multiple crates to the workspace, we often need to enable features +on them which may only exist in one of the crates (e.g. `esp-radio`'s `wifi` feature). The recommendation is, therefore, to only enable a single crate at a time - ideally one that pulls in other crates as a dependency. + +Additionally, you will need to specify the target triple of your particular chip, enable the chip's Cargo feature, and select the `esp` toolchain if you are working with an Xtensa MCU. + +It is also recommended to directly configure `rustfmt` to use the config file in the repository root. This will +prevent your editor from reformatting code in a way that CI would reject. + +An example configuration for the Zed editor may look like this: + +```json +{ + "lsp": { + "rust-analyzer": { + "initialization_options": { + "rustfmt": { + // note this needs to be an absolute path + "extraArgs": ["--config-path=/rustfmt.toml"] + }, + // Select esp-rtos, which also pulls in esp-hal + "linkedProjects": ["./esp-rtos/Cargo.toml"], + "cargo": { + // This must match the target MCU's target + "target": "xtensa-esp32s3-none-elf", + // Prevents rust-analyzer from blocking cargo + "targetDir": "target/rust-analyzer", + "extraEnv": { + // ESP32-S3 is an Xtensa MCU, we need to work with the esp toolchain + "RUSTUP_TOOLCHAIN": "esp" + }, + // Enable device support and a wide set of features on the esp-rtos crate. + "features": ["esp32s3", "embassy", "esp-radio", "rtos-trace"] + } + } + } + } +} +``` + +The same configuration in VSCode is: +```json +{ + "rust-analyzer.rustfmt.extraArgs": [ + "--config-path=/rustfmt.toml" + ], + "rust-analyzer.cargo.target": "xtensa-esp32s3-none-elf", + "rust-analyzer.cargo.targetDir": "target/rust-analyzer", + "rust-analyzer.cargo.extraEnv": { + "RUSTUP_TOOLCHAIN": "esp" + }, + "rust-analyzer.linkedProjects": [ + "./esp-rtos/Cargo.toml" + ], + "rust-analyzer.cargo.features": [ + "esp32s3", + "embassy", + "esp-radio", + "rtos-trace" + ] +} +``` + +If you switch between crates and/or devices often, you may want to keep multiple sets of configurations commented in your config file, or as separate files as templates. + +## Metadata, conditional compilation + +A good chunk of the code is generated from metadata. The metadata lives in `esp-metadata`. The code in `esp-metadata-generated` is generated by the `xtask`, using the `cargo xtask update-metadata` command, and using the `esp-metadata` crate as a library. + +The structure of the metadata TOML files is defined in two parts: +- `esp-metadata/src/cfg.rs` contains the driver configuration options in the `driver_configs!` macro call. This macro also generates the `PeriConfig` type necessary for serde. +- `esp-metadata/src/lib.rs` defines `Device` which is the structure that is deserialized directly from the TOML files. + +`esp-metadata-generated` can be used two ways: +- As a build dependency using the `build-script` feature, it provides the list of chips, and their properties suitable for "host" code. It also provides functions to define symbols for conditional compilation. +- As a firmware dependency using chip-specific Cargo features, it provides macros. + +### Conditional compilation, symbols + +> **Note:** The following list is incomplete. The full list of symbols can be found in the `esp-metadata/src/lib.rs` file, the generator is implemented in `Config::all`. + +The metadata provides different kinds of symbols: +- `single_core` and `multi_core` depending on the chip's core count. +- `xtensa` and `riscv` depending on the chip's architecture. +- `[device.symbols]`: Legacy symbols which are defined verbatim. These are being slowly phased out. +- `[device.peripherals]`: For each peripheral, it defines a `soc_has_` symbol. This can be used to conditionally compile code based on the presence of a peripheral. +- `[device.]`: If a driver is defined and it is not set as `support_status = "not_supported"`, it defines a `_driver_supported` symbol. +- Each driver generates symbols based on some common rules: + - Each `bool` in `properties` defines a `_` symbol. + - Each `Option` defines a `__is_set` symbol, if it has a value. + - Strings and numeric options define `_ = ""` symbols. + - `Vec` options define one `__` symbol per element. +- In addition, drivers that set `has_computed_properties`, or properties that implement `GeneralProperty` may define its own custom set of symbols. There are no rules here. + +You can find the complete list of available symbols in `esp-metadata-generated/src/_build_script_utils.rs`, in the `emit_check_cfg_directives` function. + +## Amendments to the Rust API Guidelines + +- `C-RW-VALUE` and `C-SERDE` do not apply. +- `C-COMMON-TRAITS`: + The set of traits to implement depend on the type and use case. In esp-hal, we can highlight a few such use cases and provide recommendations what should be implemented. If nothing here applies, use your best judgement. + - Driver structures: `Debug` + - Driver configuration: `Default`, `Debug`, `PartialEq/Eq`, `Clone/Copy`, `Hash` + - `Clone/Copy` depends on the size and contents of the structure. They should generally be implemented, unless there is a good reason not to. + - The `Default` configuration needs to make sense for a particular driver, and applying the default configuration must not fail. + - Error types: `Debug`, `PartialEq/Eq`, `Clone/Copy`, `Hash`, `Error`, `Display` + +## Construction and Destruction of Drivers + +- If a driver requires pins, those pins should be configured using `fn with_signal_name(self, pin: impl PeripheralInput<'d>) -> Self` or `fn with_signal_name(self, pin: PeripheralOutput<'d>) -> Self` + - The `with_signal_name` functions should be placed in an `impl` block that is generic over the driver mode (i.e. they shouldn't be constrained to just `Blocking` or just `Async`) unless there is a technical reason to do otherwise. +- If a driver supports multiple peripheral instances (for example, I2C0 is one such instance): + - The driver should not be generic over the peripheral instance. + - The author must use `crate::any_peripheral` to define the "any" peripheral instance type. + - The driver must implement a `new` constructor that automatically converts the peripheral instance into the any type. + - The `Instance` trait should have an `IntoAnyPeripheral` supertrait. `IntoAnyPeripheral` is generated by the `any_peripheral!` macro. The actual name of the trait depends on the name of the `AnyPeripheral`. + - The constructor should take `fn new(peri: impl Instance + 'd)` and call `degrade()` to type-erase the peripheral. +- If a driver is configurable, configuration options should be implemented as a `Config` struct in the same module where the driver is located. + - The driver's constructor should take the config struct by value, and it should return `Result`. + - The `ConfigError` enum should be separate from other `Error` enums used by the driver. + - The driver should implement `fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError>`. + - In case the driver's configuration is infallible (all possible combinations of options are supported by the hardware), the `ConfigError` should be implemented as an empty `enum`. + - Configuration structs should + - Use the [Builder Lite](https://matklad.github.io/2022/05/29/builder-lite.html) pattern. Prefer deriving `procmacros::BuilderLite` in order to automatically implement the pattern. + - Implement `Copy` if possible. + - The fields should be private. The `BuilderLite` macro generates setters and getters automatically. + - If the configuration is expected to be applied more than once (as, for example, different device configurations on a shared SPI bus may be) and calculating register values based on the configuration is costly, the configuration struct should precompute and store the result of those calculations. +- If a driver implements both blocking and async operations, or only implements blocking operations, but may support asynchronous ones in the future, the driver's type signature must include a `crate::DriverMode` type parameter. +- By default, constructors must configure the driver for blocking mode. The driver must implement `into_async` (and a matching `into_blocking`) function that reconfigures the driver. + - `into_async` must configure the driver and/or the associated DMA channels. This most often means enabling an interrupt handler. + - `into_blocking` must undo the configuration done by `into_async`. +- The asynchronous driver implementation must also expose the blocking methods (except for interrupt related functions). +- Drivers must have a `Drop` implementation resetting the peripheral to idle state. There are some exceptions to this: + - GPIO where common usage is to "set and drop" so they can't be changed + - Where we don't want to disable the peripheral as it's used internally, for example SYSTIMER is used by `time::now()` API. See `KEEP_ENABLED` in src/system.rs + - A driver doesn't need to do anything special for deinitialization and has a `PeripheralGuard` field which implements the disabling and resetting of the peripheral. +- Consider using a builder-like pattern for driver construction. + +## Interoperability + +- Don't use `log::XXX!` macros directly - use the wrappers in `fmt.rs` (e.g. just `info!` instead of `log::info!` or importing `log::*`)! +- Consider implementing common ecosystem traits, like the ones in `embedded-hal` or `embassy-embedded-hal`. + - Where the guidelines suggest implementing `Debug`, `defmt::Format` should also be implemented. + - The `defmt::Format` implementation needs to be gated behind the `defmt` feature. + - see [this example](https://github.com/esp-rs/esp-hal/blob/df2b7bd8472cc1d18db0d9441156575570f59bb3/esp-hal/src/spi/mod.rs#L15) + - e.g. `#[cfg_attr(feature = "defmt", derive(defmt::Format))]` + - Implementations of common, but unstable traits (e.g. `embassy_embedded_hal::SetConfig`) need to be gated with the `unstable` feature. +- Libraries depending on esp-hal, should disable the `rt` feature to avoid future compatibility concerns. + +## API Surface + +- API documentation must be provided for every new driver and API. +- Private details should not leak into the public API, and should be made private where technically possible. + - Implementation details that _need_ to be public should be marked with `#[doc(hidden)]` and a comment as to why it needs to be public. + - For the time being, this includes any `Instance` traits, and `State` or `Info` structs as well. + - Functions which technically need to be public but shouldn't be callable by the user need to be sealed. + - see [this example in Rust's core library](https://github.com/rust-lang/rust/blob/044a28a4091f2e1a5883f7fa990223f8b200a2cd/library/core/src/error.rs#L89-L100) +- Any public traits, that **must not** be implemented downstream need to be `Sealed`. +- Prefer compile-time checks over runtime checks where possible, prefer a fallible API over panics. +- Follow naming conventions in order to be consistent across drivers - take inspiration from existing drivers. +- Design APIs in a way that they are easy to use. +- Driver API decisions should be assessed individually, don't _not_ just follow embedded-hal or other ecosystem trait crates. Expose the capabilities of the hardware. (Ecosystem traits are implemented on top of the inherent API) +- Avoid type states and extraneous generics whenever possible + - These often lead to usability problems, and tend to just complicate things needlessly - sometimes it can be a good tradeoff to make a type not ZST + - Common cases of useless type info is storing pin information - this is usually not required after configuring the pins and will bloat the complexity of the type massively. +- Avoiding `&mut self` when `&self` is safe to use. `&self` is generally easier to use as an API. Typical applications of this are where the methods just do writes to registers which don't have side effects. +- Maintain order consistency in the API, such as in the case of pairs like RX/TX. +- If your driver provides a way to listen for interrupts, the interrupts should be listed in a `derive(EnumSetType)` enum as opposed to one function per interrupt flag. +- If a driver only implements a subset of a peripheral's capabilities, it should be placed in the `peripheral::subcategory` module. + - For example, if a driver implements the slave-mode I2C driver, it should be placed into `i2c::slave`. + - This helps us reducing the need of introducing breaking changes if we implement additional functionalities. +- Avoid abbreviations and contractions in the API, where possible. + - Saving a few characters may introduce ambiguity, e.g `SpiTransDone`, is it `Transmit` or `Transfer`? + - Common abbreviations, that are well understood such as `Dma` are perfectly fine. +- Config options should not affect the public API. + - If you add a new configuration option using `esp-config`, the added option should not alter the public API of the package being configured. + +## Maintainability + +- Avoid excessive use of macros unless there is no other option; modification of the PAC crates should be considered before resorting to macros. +- Every line of code is a liability. Take some time to see if your implementation can be simplified before opening a PR. +- If you are porting code from ESP-IDF (or anything else), please include a link WITH the commit hash in it, and please highlight the relevant line(s) of code +- If necessary provide further context as comments (consider linking to code, PRs, TRM - make sure to use permanent links, e.g. include the hash when linking to a Git repository, include the revision, page number etc. when linking to TRMs) +- Prefer line comments (//) to block comments (/* ... */) +- Generally, follow common "good practices" and idiomatic Rust style +- All `Future` objects (public or private) must be marked with ``#[must_use = "futures do nothing unless you `.await` or poll them"]``. +- Prefer `cfg_if!` (or, if the branches just pick between separate values of the same variable, `cfg!()`) over multiple exclusive `#[cfg]` attributes. `cfg_if!`/`cfg!()` visually divide the options, often results in simpler conditions and simplifies adding new branches in the future. +- When marking an API as `unstable`: + - Prefer to use `#[instability::unstable]`. + - Use the attribute on each public function instead of inherent impl blocks. +- The documentation should contain no more than three primary impl blocks (excluding trait implementations): + - Blocking: Should be listed first, as it serves as the entry point for creating most drivers. + - Async: Should appear second in the documentation. + - Both: A combined impl block for both Blocking and Async. +- Methods inside the impl blocks should be grouped by functionality. + +## Driver implementation + +- If a common `Instance` trait is used for multiple peripherals, those traits should not have any logic implemented in them. +- The `Instance` traits should only be used to access information about a peripheral instance. +- The internal implementation of the driver should be non-generic over the peripheral instance. This helps the compiler produce smaller code. +- The author is encouraged to return a static shared reference to an `Info` and a `State` structure from the `Instance` trait. + - The `Info` struct should describe the peripheral. Do not use any interior mutability. + - The `State` struct should contain counters, wakers and other, mutable state. As this is accessed via a shared reference, interior mutability and atomic variables are preferred. +- When applying configuration, consider using a drop guard to reset a consistent default state on error. + +## Modules Documentation + +Modules should have the following documentation format: +```rust +//! # Peripheral Name (Peripheral Acronym) +//! +//! ## Overview +//! Small description of the peripheral, see ESP-IDF docs or TRM +//! +//! ## Configuration +//! Explain how can the peripheral be configured, and which parameters can be configured +//! +//! ## Usage +//! Explain if we implement any external traits +//! +//! ## Examples +//! +//! ### Name of the Example +//! Small description of the example if needed +//! ```rust, no_run +#[doc = crate::before_snippet!()] +//! ... +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Implementation State +//! List unsupported features +``` +- If any of the headers is empty, remove it +- When possible, use ESP-IDF docs and TRM as references and include links if possible. + - In case of referencing an ESP-IDF link make it chip-specific, for example: + ``` + #![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", crate::soc::chip!(), "/api-reference/peripherals/etm.html)")] + ``` + - In case of referencing a TRM chapter, use the `crate::trm_markdown_link!()` macro. If you are referring to a particular chapter, you may use `crate::trm_markdown_link!("#chapter_anchor")`. +- Documentation examples must be short + - But must also provide value beyond what the rustdoc generated docs show + - Showing a snippet of a slightly more complex interaction, for example inverting the signals for a driver + - Showing construction if it is more complex, or requires some non-obvious precursor steps. Think about this for drivers that take a generic instance to construct, rustdoc doesn't do a good job of showing what concrete things can be passed into a constructor. + - they are `no_run` - we cannot run them on a target but we can make sure the contained code compiles + - avoid `unwrap` etc. - use the `?` operator where possible + - the `before_snippet!()` provides some necessary boiler plate for you + - For more complex scenarios, create an example. +- Use rustdoc syntax for linking to other documentation items instead of markdown links where possible + - https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html + +## Breaking changes + +We check our stable API surface using semver-checks. To facilitate these checks, we do a number of preprocessing steps to ensure we're only checking _our_ stable API. We may want to allow breaking changes, in the case of fixing soundness issues etc. In this case, all that is required is to add the `breaking-change-` label to the PR making the change. + +### Compiler updates + +Compiler updates force the regeneration of the API baseline due to the dependency on the unstable rustdoc format. Regeneration **must** occur on the same commit as the previous generation in this case, to ensure no breaking changes are silently introduced. Checkout the last commit of generation, generate the baseline and move them out of the repo. Checkout your PR branch and add them back there. diff --git a/documentation/HIL-GUIDE.md b/documentation/HIL-GUIDE.md new file mode 100644 index 00000000000..46c0eba5273 --- /dev/null +++ b/documentation/HIL-GUIDE.md @@ -0,0 +1,89 @@ +# HIL/CI Commands Guide + +This document describes all comments-based CI controls available on pull requests. + +By default, only `esp-rs` organization **members** and **owners** can trigger HIL or binary-size jobs. However, they may grant similar rights to other users, see [Trust Management Commands] section for details + +[Trust Management Commands]: #trust-management-commands + +## HIL Test Commands + +Behind the scenes, all comment-commands are first handled by `dispatch.yml`. +This workflow parses the comment, checks trust/permissions, and then triggers the appropriate workflow (such as `hil.yml` or `binary_size.yml`) via `workflow_dispatch`. + +### `/hil quick` + +Runs a **minimal** HIL matrix on a limited set of chips: + +- Currently: **ESP32-S3** (Xtensa) and **ESP32-C6** (RISC-V) + +The CI will: + +1. Trigger appropriate workflow. +2. Post a comment with a link to the triggered HIL run. +3. Edit that comment later with a status update (succeeded / failed / cancelled / still running). + +In further commands, the feedback from the bot will be identical. + +### `/hil full` + +Runs the **full** HIL matrix on **all supported chips**. + +The full matrix run will also be executed in the merge queue and is a prerequisite for a successful merge. + +### `/hil [ ...]` + +Runs HIL tests **only for the listed chips**. + +Examples: + +- `/hil esp32c3` +- `/hil esp32c3 esp32s3` +- `/hil esp32c3,esp32c6, esp32s3` + +### `/hil --test [, ...]` + +Runs **only chosen** HIL tests for selected chip(-s) or matrix. + +Examples: +- `/hil quick --test rmt` +- `/hil esp32 esp32c6 --tests rmt, i2c` + +Both `--test` and `--tests` will work. + +Please note that e.g. `/hil esp32s2 --test esp_radio::wifi_controller::tests::test_scan_doesnt_leak` will not work the same as running a specific test via `xtask`, because we use the `xtask` subcommand “run elfs” to run HIL tests in CI. Consequently, the command will be accepted by the bot, but the entire binary file will be run (in the case above, the entire `wifi_controller` test). + +### `/test-size` + +Triggers the binary size analysis workflow, which: + +- Builds selected binaries for the PR and for the base branch. +- Runs `bloaty` to compare section sizes. +- Generates a report. + +## Trust Management Commands + +These commands are for maintainers (MEMBER/OWNER) and operate per PR. + +### `/trust @username` + +- Adds `@username` to the PR’s HIL trust list. +- Posts a short confirmation comment and updates/creates the “HIL trust list” comment. + +Trusted users can then run all the aforementioned commands. + +### `trusted-author` label + +When this label is assigned, the author of this PR will receive the rights to run HIL and binary size tests, just as when using the `/trust` command. + +### `/revoke @username` + +- Removes `@username` from the PR’s HIL trust list. +- Updates the “HIL trust list” bot comment. +- Posts a short confirmation comment. + +After revocation, the user loses access to the commands above for that PR. + +## Help / Usage Hints + +If you request `/hil help` or `/hil` without a valid variant or chips, the bot will respond with a short usage explanation, and a reminder that the requester must be a maintainer or trusted author. diff --git a/documentation/REPRODUCERS.md b/documentation/REPRODUCERS.md new file mode 100644 index 00000000000..2a0b4467e9a --- /dev/null +++ b/documentation/REPRODUCERS.md @@ -0,0 +1,24 @@ +# Guide on minimal reproducers + +A Minimal Reproducer is a small, self-contained, and buildable code example that demonstrates a specific bug or issue you've encountered while using esp-hal (and related crates in this repository). + +Providing a good reproducer is essential for the maintainers to quickly understand, verify, and fix your issue. + +## Key Requirements for a Minimal Reproducer + +To be effective, your reproducer should adhere to the following principles: + +- Self-Contained and Buildable: The code must be a complete project (use `esp-generate` to create the skeleton) that can be built and run without any modifications by the person trying to reproduce the issue. +- Minimal: It should contain only the code strictly necessary to demonstrate the issue. Remove any unrelated features, dependencies, or verbose logging that doesn't contribute to the problem. +- No Unrelated Dependencies: Avoid including external crates or libraries unless they are directly necessary for the specific feature you are reporting a bug on. +- Avoid referencing a crate modified by you - even if it's just added logging. Ideally reference the latest released version of a crate _or_ use a git-reference pointing to a specific commit (ideally the latest at the time of creating the reproducer) +- External Hardware and Wiring: If the issue requires external components (e.g., an LED, an I2C sensor), explicitly state the required hardware and provide a clear, simple description or diagram of the wiring. + +## Special Considerations for Connectivity Issues + +If your issue involves Wi-Fi, Bluetooth, or other networking components: + +- Avoid API Keys/Registration: The code must not access web APIs that require an API key, registration, or a paid subscription. Use public, open, and readily available endpoints (e.g., public time servers, simple HTTP test sites) or focus on local network interaction if possible. This ensures anyone can test the code immediately. +- Provide Environment Details: Since connectivity issues are often environment-specific, you must include: + - The model of the access point (AP) or router you are using (e.g., "TP-Link AX1500", "FritzBox 7590"). + - Any relevant configuration details (e.g., "WPA2 Personal", "using 2.4GHz band"). diff --git a/esp-alloc/.clippy.toml b/esp-alloc/.clippy.toml new file mode 100644 index 00000000000..cda8d17eed4 --- /dev/null +++ b/esp-alloc/.clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/esp-alloc/CHANGELOG.md b/esp-alloc/CHANGELOG.md new file mode 100644 index 00000000000..9ee590436f7 --- /dev/null +++ b/esp-alloc/CHANGELOG.md @@ -0,0 +1,95 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `global-allocator` Cargo feature to opt in to the `#[global_allocator]`, this feature is enabled by default. (#4703) +- Support for ESP32-C5 (#4884) + +### Changed + + +### Fixed + +- Fixed arithmetic overflow in tracking total amount of allocated/freed memory (#4783) + +### Removed + + +## [v0.9.0] - 2025-10-13 + +### Added + +- Added chip-selection features (#4023) +- New default feature (`compat`) enables implementations for `malloc`, `free`, `calloc`, `realloc` and others (#3890, #4043) +- `ESP_ALLOC_CONFIG_HEAP_ALGORITHM` to select the global heap algorithm (LLFF, TLSF) (#4130, #4316) +- Added option to use TLSF as the heap allocator, implemented by the `rlsf` crate (#4130, #4316) + +### Changed + +- Make stats structs fields public (#3828) +- Fixed documentation to use `ram(reclaimed)` instead of a no longer valid linker_section syntax. (#4245) + +### Fixed + +- Fix problem of not de-allocating memory in some situations (#3949) + +## [v0.8.0] - 2025-06-03 + +### Added + +- `allocator_api2` to support allocator APIs on stable Rust. (#3318, #3487) +- `AnyMemory`, `InternalMemory`, `ExternalMemory` allocators. (#3318) +- Removed the `Unused` section for `stats()` to make the output cleaner (#3486) + +### Changed + +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) +- Update `defmt` to 1.0 (#3416) + +## [0.7.0] - 2025-02-24 + +### Added + +- `esp_alloc::heap_allocator!` now accepts attributes, e.g., `esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000)` (#3133) + +### Changed + +- `esp_alloc::heap_allocator!` syntax has been changed to `esp_alloc::heap_allocator!(size: 64000)` (#3135) + +## 0.6.0 - 2025-01-15 + +### Added + +- `esp_alloc::HEAP.stats()` can now be used to get heap usage information (#2137) + +### Changed + +- Bump MSRV to 1.84 (#2951) + +## 0.5.0 - 2024-10-10 + +### Changed + +- a global allocator is created in esp-alloc, now you need to add individual memory regions (up to 3) to the allocator (#2099) + +## 0.4.0 - 2024-06-04 + +## 0.3.0 - 2023-04-25 + +## 0.2.1 - 2023-04-21 + +## 0.2.0 - 2023-02-22 + +## 0.1.0 - 2022-07-25 + +[0.7.0]: https://github.com/esp-rs/esp-hal/releases/tag/esp-alloc-v0.7.0 +[v0.8.0]: https://github.com/esp-rs/esp-hal/compare/esp-alloc-v0.7.0...esp-alloc-v0.8.0 +[v0.9.0]: https://github.com/esp-rs/esp-hal/compare/esp-alloc-v0.8.0...esp-alloc-v0.9.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-alloc-v0.9.0...HEAD diff --git a/esp-alloc/Cargo.toml b/esp-alloc/Cargo.toml new file mode 100644 index 00000000000..1ffe6008b16 --- /dev/null +++ b/esp-alloc/Cargo.toml @@ -0,0 +1,99 @@ +[package] +name = "esp-alloc" +version = "0.9.0" +edition = "2024" +rust-version = "1.86.0" +description = "A heap allocator for Espressif devices" +documentation = "https://docs.espressif.com/projects/rust/esp-alloc/latest/" +keywords = ["allocator", "embedded", "esp32", "espressif", "memory"] +categories = ["embedded", "memory-management", "no-std"] +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +doc-config = { features = ["defmt"] } +check-configs = [ + { features = [] }, + { features = ["defmt"] }, + { features = ["internal-heap-stats"] }, + { features = ["defmt", "internal-heap-stats"] }, +] +clippy-configs = [ + { features = ["defmt", "internal-heap-stats"] }, +] + +[package.metadata.docs.rs] +default-target = "riscv32imc-unknown-none-elf" +features = ["nightly"] + +[lib] +bench = false +test = false + +[dependencies] +allocator-api2 = { version = "0.3.0", default-features = false } +defmt = { version = "1.0.1", optional = true } +cfg-if = "1" +enumset = "1" +esp-sync = { version = "0.1.1", path = "../esp-sync" } +document-features = "0.2" + +linked_list_allocator = { version = "0.10.5", default-features = false, features = ["const_mut_refs"] } +rlsf = { version = "0.2", features = ["unstable"] } + +[build-dependencies] +esp-config = { version = "0.6.1", path = "../esp-config", features = ["build"] } + +[features] +default = ["compat", "global-allocator"] + +## Enable nightly rustc-only features, like `feature(allocator_api)`. +nightly = [] + +## Implement `defmt::Format` on certain types. +defmt = ["dep:defmt", "enumset/defmt"] + +## Enable this feature if you want to keep stats about the internal heap usage such as: +## - Max memory usage since initialization of the heap +## - Total allocated memory since initialization of the heap +## - Total freed memory since initialization of the heap +## +## ⚠️ Note: Enabling this feature will require extra computation every time alloc/dealloc is called. +internal-heap-stats = [] + +## Provide C-compatibility functions (malloc, free, ...) +## +## Note that allocated memory returned by all these functions is 4-byte aligned. +## This is in-line with e.g. ESP-IDF and the supported CPU architectures and usually +## not a problem. +## +## Strictly speaking it violates `malloc`s contract saying +## > "... functions return a pointer to the allocated memory, which is suitably +## > aligned for any type that fits into the requested size or less." +## +## However this _can_ cause problems when using the allocated memory from Rust +## where alignment checks are in place. So special caution might be needed in this case. +compat = [] + +## Enable the `#[global_allocator]` attribute for `EspHeap`. +global-allocator = [] + +#! ### Chip selection +#! One of the following features must be enabled to select the target chip: + +## +esp32c2 = ["esp-sync/esp32c2"] +## +esp32c3 = ["esp-sync/esp32c3"] +## +esp32c5 = ["esp-sync/esp32c5"] +## +esp32c6 = ["esp-sync/esp32c6"] +## +esp32h2 = ["esp-sync/esp32h2"] +## +esp32 = ["esp-sync/esp32"] +## +esp32s2 = ["esp-sync/esp32s2"] +## +esp32s3 = ["esp-sync/esp32s3"] diff --git a/esp-alloc/README.md b/esp-alloc/README.md new file mode 100644 index 00000000000..feb393f463e --- /dev/null +++ b/esp-alloc/README.md @@ -0,0 +1,30 @@ +# esp-alloc + +[![Crates.io](https://img.shields.io/crates/v/esp-alloc?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-alloc) +[![docs.rs](https://img.shields.io/docsrs/esp-alloc?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-alloc/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-alloc?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +A simple `no_std` heap allocator for RISC-V and Xtensa processors from Espressif. Supports all currently available ESP32 devices. + +**NOTE:** using this as your global allocator requires using Rust 1.68 or greater, or the `nightly` release channel. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-alloc/build.rs b/esp-alloc/build.rs new file mode 100644 index 00000000000..84dc1209ac4 --- /dev/null +++ b/esp-alloc/build.rs @@ -0,0 +1,9 @@ +use esp_config::generate_config_from_yaml_definition; + +fn main() { + // emit config + println!("cargo:rerun-if-changed=./esp_config.yml"); + let cfg_yaml = std::fs::read_to_string("./esp_config.yml") + .expect("Failed to read esp_config.yml for esp-alloc"); + generate_config_from_yaml_definition(&cfg_yaml, true, true, None).unwrap(); +} diff --git a/esp-alloc/esp_config.yml b/esp-alloc/esp_config.yml new file mode 100644 index 00000000000..ce2d608e8db --- /dev/null +++ b/esp-alloc/esp_config.yml @@ -0,0 +1,14 @@ +crate: esp-alloc + +options: + - name: heap_algorithm + description: "The heap algorithm to use. TLSF offers higher performance + and bounded allocation time, but uses more memory." + default: + - value: '"LLFF"' + constraints: + - type: + validator: enumeration + value: + - "LLFF" + - "TLSF" diff --git a/esp-alloc/src/allocators.rs b/esp-alloc/src/allocators.rs new file mode 100644 index 00000000000..0441733b325 --- /dev/null +++ b/esp-alloc/src/allocators.rs @@ -0,0 +1,135 @@ +#[cfg(feature = "nightly")] +use core::alloc::{AllocError as CoreAllocError, Allocator as CoreAllocator}; +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::NonNull, +}; + +use allocator_api2::alloc::{AllocError, Allocator}; +use enumset::EnumSet; + +use crate::MemoryCapability; + +fn allocate_caps( + capabilities: EnumSet, + layout: Layout, +) -> Result, AllocError> { + let raw_ptr = unsafe { crate::HEAP.alloc_caps(capabilities, layout) }; + let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) +} + +use crate::EspHeap; + +unsafe impl Allocator for EspHeap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let raw_ptr = unsafe { self.alloc(layout) }; + let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + self.dealloc(ptr.as_ptr(), layout); + } + } +} + +#[cfg(feature = "nightly")] +unsafe impl CoreAllocator for EspHeap { + fn allocate(&self, layout: Layout) -> Result, CoreAllocError> { + let raw_ptr = unsafe { self.alloc(layout) }; + let ptr = NonNull::new(raw_ptr).ok_or(CoreAllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + self.dealloc(ptr.as_ptr(), layout); + } + } +} + +/// An allocator that uses all configured, available memory. +pub struct AnyMemory; + +unsafe impl Allocator for AnyMemory { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + allocate_caps(EnumSet::empty(), layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} + +#[cfg(feature = "nightly")] +unsafe impl CoreAllocator for AnyMemory { + fn allocate(&self, layout: Layout) -> Result, CoreAllocError> { + allocate_caps(EnumSet::empty(), layout).map_err(|_| CoreAllocError) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} + +/// An allocator that uses internal memory only. +pub struct InternalMemory; + +unsafe impl Allocator for InternalMemory { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + allocate_caps(EnumSet::from(MemoryCapability::Internal), layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} + +#[cfg(feature = "nightly")] +unsafe impl CoreAllocator for InternalMemory { + fn allocate(&self, layout: Layout) -> Result, CoreAllocError> { + allocate_caps(EnumSet::from(MemoryCapability::Internal), layout).map_err(|_| CoreAllocError) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} + +/// An allocator that uses external (PSRAM) memory only. +pub struct ExternalMemory; + +unsafe impl Allocator for ExternalMemory { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + allocate_caps(EnumSet::from(MemoryCapability::External), layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} + +#[cfg(feature = "nightly")] +unsafe impl CoreAllocator for ExternalMemory { + fn allocate(&self, layout: Layout) -> Result, CoreAllocError> { + allocate_caps(EnumSet::from(MemoryCapability::External), layout).map_err(|_| CoreAllocError) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { + crate::HEAP.dealloc(ptr.as_ptr(), layout); + } + } +} diff --git a/esp-alloc/src/heap/llff.rs b/esp-alloc/src/heap/llff.rs new file mode 100644 index 00000000000..b6c5fa39be0 --- /dev/null +++ b/esp-alloc/src/heap/llff.rs @@ -0,0 +1,40 @@ +use core::{alloc::Layout, ptr::NonNull}; + +use linked_list_allocator::Heap; + +pub(crate) struct LlffHeap { + heap: Heap, +} + +impl LlffHeap { + pub unsafe fn new(heap_bottom: *mut u8, size: usize) -> Self { + let mut heap = Heap::empty(); + unsafe { heap.init(heap_bottom, size) }; + Self { heap } + } + + pub fn size(&self) -> usize { + self.heap.size() + } + + pub fn used(&self) -> usize { + self.heap.used() + } + + pub fn free(&self) -> usize { + self.heap.free() + } + + pub fn allocate(&mut self, layout: Layout) -> Option> { + self.heap.allocate_first_fit(layout).ok() + } + + pub(crate) unsafe fn try_deallocate(&mut self, ptr: NonNull, layout: Layout) -> bool { + if self.heap.bottom() <= ptr.as_ptr() && self.heap.top() >= ptr.as_ptr() { + unsafe { self.heap.deallocate(ptr, layout) }; + true + } else { + false + } + } +} diff --git a/esp-alloc/src/heap/mod.rs b/esp-alloc/src/heap/mod.rs new file mode 100644 index 00000000000..9bce65f0b62 --- /dev/null +++ b/esp-alloc/src/heap/mod.rs @@ -0,0 +1,9 @@ +#[cfg(heap_algorithm_llff)] +mod llff; +#[cfg(heap_algorithm_tlsf)] +mod tlsf; + +#[cfg(heap_algorithm_llff)] +pub(crate) use llff::LlffHeap as Heap; +#[cfg(heap_algorithm_tlsf)] +pub(crate) use tlsf::TlsfHeap as Heap; diff --git a/esp-alloc/src/heap/tlsf.rs b/esp-alloc/src/heap/tlsf.rs new file mode 100644 index 00000000000..fae38bcab03 --- /dev/null +++ b/esp-alloc/src/heap/tlsf.rs @@ -0,0 +1,69 @@ +use core::{alloc::Layout, ptr::NonNull}; + +use rlsf::Tlsf; + +// TODO: make this configurable +type Heap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::BITS as usize }>; + +pub(crate) struct TlsfHeap { + heap: Heap, + pool_start: usize, + pool_end: usize, +} + +impl TlsfHeap { + pub unsafe fn new(heap_bottom: *mut u8, size: usize) -> Self { + let mut heap = Heap::new(); + + let block = unsafe { core::slice::from_raw_parts(heap_bottom, size) }; + let actual_size = unsafe { heap.insert_free_block_ptr(block.into()).unwrap() }; + + Self { + heap, + pool_start: heap_bottom as usize, + pool_end: heap_bottom as usize + actual_size.get(), + } + } + + pub fn size(&self) -> usize { + self.pool_end - self.pool_start + } + + pub fn used(&self) -> usize { + let mut used = 0; + let pool = + unsafe { core::slice::from_raw_parts(self.pool_start as *const u8, self.size()) }; + for block in unsafe { self.heap.iter_blocks(NonNull::from(pool)) } { + if block.is_occupied() { + used += block.size(); + } + } + used + } + + pub fn free(&self) -> usize { + let mut free = 0; + let pool = + unsafe { core::slice::from_raw_parts(self.pool_start as *const u8, self.size()) }; + for block in unsafe { self.heap.iter_blocks(NonNull::from(pool)) } { + if !block.is_occupied() { + free += block.max_payload_size(); + } + } + free + } + + pub fn allocate(&mut self, layout: Layout) -> Option> { + self.heap.allocate(layout) + } + + pub(crate) unsafe fn try_deallocate(&mut self, ptr: NonNull, layout: Layout) -> bool { + let addr = ptr.addr().get(); + if self.pool_start <= addr && self.pool_end > addr { + unsafe { self.heap.deallocate(ptr, layout.align()) }; + true + } else { + false + } + } +} diff --git a/esp-alloc/src/lib.rs b/esp-alloc/src/lib.rs new file mode 100644 index 00000000000..475f1443268 --- /dev/null +++ b/esp-alloc/src/lib.rs @@ -0,0 +1,681 @@ +//! A `no_std` heap allocator for RISC-V and Xtensa processors from +//! Espressif. Supports all currently available ESP32 devices. +//! +//! **NOTE:** using this as your global allocator requires using Rust 1.68 or +//! greater, or the `nightly` release channel. +//! +//! # Using this as your Global Allocator +//! +//! ```rust,no_run +//! use esp_alloc as _; +//! +//! fn init_heap() { +//! const HEAP_SIZE: usize = 32 * 1024; +//! static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); +//! +//! unsafe { +//! esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new( +//! HEAP.as_mut_ptr() as *mut u8, +//! HEAP_SIZE, +//! esp_alloc::MemoryCapability::Internal.into(), +//! )); +//! } +//! } +//! ``` +//! +//! Alternatively, you can use the `heap_allocator!` macro to configure the +//! global allocator with a given size: +//! +//! ```rust,no_run +//! esp_alloc::heap_allocator!(size: 32 * 1024); +//! ``` +//! +//! # Using this with the nightly `allocator_api`-feature +//! +//! Sometimes you want to have more control over allocations. +//! +//! For that, it's convenient to use the nightly `allocator_api`-feature, +//! which allows you to specify an allocator for single allocations. +//! +//! **NOTE:** To use this, you have to enable the crate's `nightly` feature +//! flag. +//! +//! Create and initialize an allocator to use in single allocations: +//! +//! ```rust,no_run +//! static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +//! +//! fn init_psram_heap() { +//! unsafe { +//! PSRAM_ALLOCATOR.add_region(esp_alloc::HeapRegion::new( +//! psram::psram_vaddr_start() as *mut u8, +//! psram::PSRAM_BYTES, +//! esp_alloc::MemoryCapability::External.into(), +//! )); +//! } +//! } +//! ``` +//! +//! And then use it in an allocation: +//! +//! ```rust,no_run +//! let large_buffer: Vec = Vec::with_capacity_in(1048576, &PSRAM_ALLOCATOR); +//! ``` +//! +//! Alternatively, you can use the `psram_allocator!` macro to configure the +//! global allocator to use PSRAM: +//! +//! ```rust,no_run +//! let p = esp_hal::init(esp_hal::Config::default()); +//! esp_alloc::psram_allocator!(p.PSRAM, esp_hal::psram); +//! ``` +//! +//! You can also use the `ExternalMemory` allocator to allocate PSRAM memory +//! with the global allocator: +//! +//! ```rust,no_run +//! let p = esp_hal::init(esp_hal::Config::default()); +//! esp_alloc::psram_allocator!(p.PSRAM, esp_hal::psram); +//! +//! let mut vec = Vec::::new_in(esp_alloc::ExternalMemory); +//! ``` +//! +//! ## `allocator_api` feature on stable Rust +//! +//! `esp-alloc` implements the allocator trait from [`allocator_api2`], which +//! provides the nightly-only `allocator_api` features in stable Rust. The crate +//! contains implementations for `Box` and `Vec`. +//! +//! To use the `allocator_api2` features, you need to add the crate to your +//! `Cargo.toml`. Note that we do not enable the `alloc` feature by default, but +//! you will need it for the `Box` and `Vec` types. +//! +//! ```toml +//! allocator-api2 = { version = "0.3", default-features = false, features = ["alloc"] } +//! ``` +//! +//! With this, you can use the `Box` and `Vec` types from `allocator_api2`, with +//! `esp-alloc` allocators: +//! +//! ```rust,no_run +//! let p = esp_hal::init(esp_hal::Config::default()); +//! esp_alloc::heap_allocator!(size: 64000); +//! esp_alloc::psram_allocator!(p.PSRAM, esp_hal::psram); +//! +//! let mut vec: Vec = Vec::new_in(esp_alloc::InternalMemory); +//! +//! vec.push(0xabcd1234); +//! assert_eq!(vec[0], 0xabcd1234); +//! ``` +//! +//! Note that if you use the nightly `allocator_api` feature, you can use the +//! `Box` and `Vec` types from `alloc`. `allocator_api2` is still available as +//! an option, but types from `allocator_api2` are not compatible with the +//! standard library types. +//! +//! # Heap stats +//! +//! You can also get stats about the heap usage at anytime with: +//! +//! ```rust,no_run +//! let stats: HeapStats = esp_alloc::HEAP.stats(); +//! // HeapStats implements the Display and defmt::Format traits, so you can +//! // pretty-print the heap stats. +//! println!("{}", stats); +//! ``` +//! +//! Example output: +//! +//! ```txt +//! HEAP INFO +//! Size: 131068 +//! Current usage: 46148 +//! Max usage: 46148 +//! Total freed: 0 +//! Total allocated: 46148 +//! Memory Layout: +//! Internal | ████████████░░░░░░░░░░░░░░░░░░░░░░░ | Used: 35% (Used 46148 of 131068, free: 84920) +//! ``` +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![no_std] +#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] + +mod allocators; +mod heap; +mod macros; +#[cfg(feature = "compat")] +mod malloc; + +use core::{ + alloc::{GlobalAlloc, Layout}, + fmt::Display, + ptr::{self, NonNull}, +}; + +pub use allocators::*; +use enumset::{EnumSet, EnumSetType}; +use esp_sync::NonReentrantMutex; + +use crate::heap::Heap; + +/// The global allocator instance +#[cfg_attr(feature = "global-allocator", global_allocator)] +pub static HEAP: EspHeap = EspHeap::empty(); + +const BAR_WIDTH: usize = 35; + +fn write_bar(f: &mut core::fmt::Formatter<'_>, usage_percent: usize) -> core::fmt::Result { + let used_blocks = BAR_WIDTH * usage_percent / 100; + (0..used_blocks).try_for_each(|_| write!(f, "█"))?; + (used_blocks..BAR_WIDTH).try_for_each(|_| write!(f, "░")) +} + +#[cfg(feature = "defmt")] +fn write_bar_defmt(fmt: defmt::Formatter, usage_percent: usize) { + let used_blocks = BAR_WIDTH * usage_percent / 100; + (0..used_blocks).for_each(|_| defmt::write!(fmt, "█")); + (used_blocks..BAR_WIDTH).for_each(|_| defmt::write!(fmt, "░")); +} + +#[derive(EnumSetType, Debug)] +/// Describes the properties of a memory region +pub enum MemoryCapability { + /// Memory must be internal; specifically it should not disappear when + /// flash/spiram cache is switched off + Internal, + /// Memory must be in SPI RAM + External, +} + +/// Stats for a heap region +#[derive(Debug)] +pub struct RegionStats { + /// Total usable size of the heap region in bytes. + pub size: usize, + + /// Currently used size of the heap region in bytes. + pub used: usize, + + /// Free size of the heap region in bytes. + pub free: usize, + + /// Capabilities of the memory region. + pub capabilities: EnumSet, +} + +impl Display for RegionStats { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let usage_percent = self.used * 100 / self.size; + + // Display Memory type + if self.capabilities.contains(MemoryCapability::Internal) { + write!(f, "Internal")?; + } else if self.capabilities.contains(MemoryCapability::External) { + write!(f, "External")?; + } else { + write!(f, "Unknown")?; + } + + write!(f, " | ")?; + + write_bar(f, usage_percent)?; + + write!( + f, + " | Used: {}% (Used {} of {}, free: {})", + usage_percent, self.used, self.size, self.free + ) + } +} + +#[cfg(feature = "defmt")] +#[allow(clippy::if_same_then_else)] +impl defmt::Format for RegionStats { + fn format(&self, fmt: defmt::Formatter<'_>) { + let usage_percent = self.used * 100 / self.size; + + if self.capabilities.contains(MemoryCapability::Internal) { + defmt::write!(fmt, "Internal"); + } else if self.capabilities.contains(MemoryCapability::External) { + defmt::write!(fmt, "External"); + } else { + defmt::write!(fmt, "Unknown"); + } + + defmt::write!(fmt, " | "); + + write_bar_defmt(fmt, usage_percent); + + defmt::write!( + fmt, + " | Used: {}% (Used {} of {}, free: {})", + usage_percent, + self.used, + self.size, + self.free + ); + } +} + +/// A memory region to be used as heap memory +pub struct HeapRegion { + heap: Heap, + capabilities: EnumSet, +} + +impl HeapRegion { + /// Create a new [HeapRegion] with the given capabilities + /// + /// # Safety + /// + /// - The supplied memory region must be available for the entire program (`'static`). + /// - The supplied memory region must be exclusively available to the heap only, no aliasing. + /// - `size > 0`. + pub unsafe fn new( + heap_bottom: *mut u8, + size: usize, + capabilities: EnumSet, + ) -> Self { + Self { + heap: unsafe { Heap::new(heap_bottom, size) }, + capabilities, + } + } + + /// Return stats for the current memory region + pub fn stats(&self) -> RegionStats { + RegionStats { + size: self.size(), + used: self.used(), + free: self.free(), + capabilities: self.capabilities, + } + } + + fn size(&self) -> usize { + self.heap.size() + } + + fn used(&self) -> usize { + self.heap.used() + } + + fn free(&self) -> usize { + self.heap.free() + } + + fn allocate(&mut self, layout: Layout) -> Option> { + self.heap.allocate(layout) + } + + unsafe fn try_deallocate(&mut self, ptr: NonNull, layout: Layout) -> bool { + unsafe { self.heap.try_deallocate(ptr, layout) } + } +} + +/// Stats for a heap allocator +/// +/// Enable the "internal-heap-stats" feature if you want collect additional heap +/// informations at the cost of extra cpu time during every alloc/dealloc. +#[derive(Debug)] +pub struct HeapStats { + /// Granular stats for all the configured memory regions. + pub region_stats: [Option; 3], + + /// Total size of all combined heap regions in bytes. + pub size: usize, + + /// Current usage of the heap across all configured regions in bytes. + pub current_usage: usize, + + /// Estimation of the max used heap in bytes. + #[cfg(feature = "internal-heap-stats")] + pub max_usage: usize, + + /// Estimation of the total allocated bytes since initialization. + #[cfg(feature = "internal-heap-stats")] + pub total_allocated: u64, + + /// Estimation of the total freed bytes since initialization. + #[cfg(feature = "internal-heap-stats")] + pub total_freed: u64, +} + +impl Display for HeapStats { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "HEAP INFO")?; + writeln!(f, "Size: {}", self.size)?; + writeln!(f, "Current usage: {}", self.current_usage)?; + #[cfg(feature = "internal-heap-stats")] + { + writeln!(f, "Max usage: {}", self.max_usage)?; + writeln!(f, "Total freed: {}", self.total_freed)?; + writeln!(f, "Total allocated: {}", self.total_allocated)?; + } + writeln!(f, "Memory Layout: ")?; + for region in self.region_stats.iter() { + if let Some(region) = region.as_ref() { + region.fmt(f)?; + writeln!(f)?; + } + } + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for HeapStats { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!(fmt, "HEAP INFO\n"); + defmt::write!(fmt, "Size: {}\n", self.size); + defmt::write!(fmt, "Current usage: {}\n", self.current_usage); + #[cfg(feature = "internal-heap-stats")] + { + defmt::write!(fmt, "Max usage: {}\n", self.max_usage); + defmt::write!(fmt, "Total freed: {}\n", self.total_freed); + defmt::write!(fmt, "Total allocated: {}\n", self.total_allocated); + } + defmt::write!(fmt, "Memory Layout:\n"); + for region in self.region_stats.iter() { + if let Some(region) = region.as_ref() { + defmt::write!(fmt, "{}\n", region); + } + } + } +} + +/// Internal stats to keep track across multiple regions. +#[cfg(feature = "internal-heap-stats")] +struct InternalHeapStats { + max_usage: usize, + total_allocated: u64, + total_freed: u64, +} + +struct EspHeapInner { + heap: [Option; 3], + #[cfg(feature = "internal-heap-stats")] + internal_heap_stats: InternalHeapStats, +} + +impl EspHeapInner { + /// Crate a new UNINITIALIZED heap allocator + pub const fn empty() -> Self { + EspHeapInner { + heap: [const { None }; 3], + #[cfg(feature = "internal-heap-stats")] + internal_heap_stats: InternalHeapStats { + max_usage: 0, + total_allocated: 0, + total_freed: 0, + }, + } + } + + pub unsafe fn add_region(&mut self, region: HeapRegion) { + let free = self + .heap + .iter() + .enumerate() + .find(|v| v.1.is_none()) + .map(|v| v.0); + + if let Some(free) = free { + self.heap[free] = Some(region); + } else { + panic!( + "Exceeded the maximum of {} heap memory regions", + self.heap.len() + ); + } + } + + /// Returns an estimate of the amount of bytes in use in all memory regions. + pub fn used(&self) -> usize { + let mut used = 0; + for region in self.heap.iter() { + if let Some(region) = region.as_ref() { + used += region.heap.used(); + } + } + used + } + + /// Return usage stats for the [EspHeap]. + /// + /// Note: + /// [HeapStats] directly implements [Display], so this function can be + /// called from within `println!()` to pretty-print the usage of the + /// heap. + pub fn stats(&self) -> HeapStats { + let mut region_stats: [Option; 3] = [const { None }; 3]; + + let mut used = 0; + let mut free = 0; + for (id, region) in self.heap.iter().enumerate() { + if let Some(region) = region.as_ref() { + let stats = region.stats(); + free += stats.free; + used += stats.used; + region_stats[id] = Some(region.stats()); + } + } + + cfg_if::cfg_if! { + if #[cfg(feature = "internal-heap-stats")] { + HeapStats { + region_stats, + size: free + used, + current_usage: used, + max_usage: self.internal_heap_stats.max_usage, + total_allocated: self.internal_heap_stats.total_allocated, + total_freed: self.internal_heap_stats.total_freed, + } + } else { + HeapStats { + region_stats, + size: free + used, + current_usage: used, + } + } + } + } + + /// Returns an estimate of the amount of bytes available. + pub fn free(&self) -> usize { + self.free_caps(EnumSet::empty()) + } + + /// The free heap satisfying the given requirements + pub fn free_caps(&self, capabilities: EnumSet) -> usize { + let mut free = 0; + for region in self.heap.iter().filter(|region| { + if region.is_some() { + region + .as_ref() + .unwrap() + .capabilities + .is_superset(capabilities) + } else { + false + } + }) { + if let Some(region) = region.as_ref() { + free += region.heap.free(); + } + } + free + } + + /// Allocate memory in a region satisfying the given requirements. + /// + /// # Safety + /// + /// This function is unsafe because undefined behavior can result + /// if the caller does not ensure that `layout` has non-zero size. + /// + /// The allocated block of memory may or may not be initialized. + unsafe fn alloc_caps( + &mut self, + capabilities: EnumSet, + layout: Layout, + ) -> *mut u8 { + #[cfg(feature = "internal-heap-stats")] + let before = self.used(); + let mut iter = self + .heap + .iter_mut() + .filter_map(|region| region.as_mut()) + .filter(|region| region.capabilities.is_superset(capabilities)); + + let allocation = loop { + let Some(region) = iter.next() else { + return ptr::null_mut(); + }; + + if let Some(res) = region.allocate(layout) { + break res; + } + }; + + #[cfg(feature = "internal-heap-stats")] + { + // We need to call used because the heap impls have some internal overhead + // so we cannot use the size provided by the layout. + let used = self.used(); + + self.internal_heap_stats.total_allocated = self + .internal_heap_stats + .total_allocated + .saturating_add((used - before) as u64); + self.internal_heap_stats.max_usage = + core::cmp::max(self.internal_heap_stats.max_usage, used); + } + + allocation.as_ptr() + } +} + +/// A memory allocator +/// +/// In addition to what Rust's memory allocator can do it allows to allocate +/// memory in regions satisfying specific needs. +pub struct EspHeap { + inner: NonReentrantMutex, +} + +impl EspHeap { + /// Crate a new UNINITIALIZED heap allocator + pub const fn empty() -> Self { + EspHeap { + inner: NonReentrantMutex::new(EspHeapInner::empty()), + } + } + + /// Add a memory region to the heap + /// + /// `heap_bottom` is a pointer to the location of the bottom of the heap. + /// + /// `size` is the size of the heap in bytes. + /// + /// You can add up to three regions per allocator. + /// + /// Note that: + /// + /// - Memory is allocated from the first suitable memory region first + /// + /// - The heap grows "upwards", towards larger addresses. Thus `end_addr` must be larger than + /// `start_addr` + /// + /// - The size of the heap is `(end_addr as usize) - (start_addr as usize)`. The allocator won't + /// use the byte at `end_addr`. + /// + /// # Safety + /// + /// - The supplied memory region must be available for the entire program (a `'static` + /// lifetime). + /// - The supplied memory region must be exclusively available to the heap only, no aliasing. + /// - `size > 0`. + pub unsafe fn add_region(&self, region: HeapRegion) { + self.inner.with(|heap| unsafe { heap.add_region(region) }) + } + + /// Returns an estimate of the amount of bytes in use in all memory regions. + pub fn used(&self) -> usize { + self.inner.with(|heap| heap.used()) + } + + /// Return usage stats for the [EspHeap]. + /// + /// Note: + /// [HeapStats] directly implements [Display], so this function can be + /// called from within `println!()` to pretty-print the usage of the + /// heap. + pub fn stats(&self) -> HeapStats { + self.inner.with(|heap| heap.stats()) + } + + /// Returns an estimate of the amount of bytes available. + pub fn free(&self) -> usize { + self.inner.with(|heap| heap.free()) + } + + /// The free heap satisfying the given requirements + pub fn free_caps(&self, capabilities: EnumSet) -> usize { + self.inner.with(|heap| heap.free_caps(capabilities)) + } + + /// Allocate memory in a region satisfying the given requirements. + /// + /// # Safety + /// + /// This function is unsafe because undefined behavior can result + /// if the caller does not ensure that `layout` has non-zero size. + /// + /// The allocated block of memory may or may not be initialized. + pub unsafe fn alloc_caps( + &self, + capabilities: EnumSet, + layout: Layout, + ) -> *mut u8 { + self.inner + .with(|heap| unsafe { heap.alloc_caps(capabilities, layout) }) + } +} + +unsafe impl GlobalAlloc for EspHeap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { self.alloc_caps(EnumSet::empty(), layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let Some(ptr) = NonNull::new(ptr) else { + return; + }; + + self.inner.with(|this| { + #[cfg(feature = "internal-heap-stats")] + let before = this.used(); + let mut iter = this.heap.iter_mut(); + + while let Some(Some(region)) = iter.next() { + if unsafe { region.try_deallocate(ptr, layout) } { + break; + } + } + + #[cfg(feature = "internal-heap-stats")] + { + // We need to call `used()` because [linked_list_allocator::Heap] does internal + // size alignment so we cannot use the size provided by the + // layout. + this.internal_heap_stats.total_freed = this + .internal_heap_stats + .total_freed + .saturating_add((before - this.used()) as u64); + } + }) + } +} diff --git a/esp-alloc/src/macros.rs b/esp-alloc/src/macros.rs new file mode 100644 index 00000000000..6d2cabd4466 --- /dev/null +++ b/esp-alloc/src/macros.rs @@ -0,0 +1,59 @@ +//! Macros provided for convenience + +/// Initialize a global heap allocator providing a heap of the given size in +/// bytes. This supports attributes. +/// +/// # Usage +/// ```rust, no_run +/// // Use 64kB in the same region stack uses (dram_seg), for the heap. +/// heap_allocator!(size: 64000); +/// // Use 64kB for the heap in the memory region reclaimed from the bootloader, which is otherwise unused. +/// heap_allocator!(#[ram(reclaimed)] size: 64000); +/// ``` +#[macro_export] +macro_rules! heap_allocator { + ($(#[$m:meta])* size: $size:expr) => {{ + $(#[$m])* + static mut HEAP: core::mem::MaybeUninit<[u8; $size]> = core::mem::MaybeUninit::uninit(); + + unsafe { + $crate::HEAP.add_region($crate::HeapRegion::new( + HEAP.as_mut_ptr() as *mut u8, + $size, + $crate::MemoryCapability::Internal.into(), + )); + } + }}; +} + +/// Initialize a global heap allocator backed by PSRAM +/// +/// You need a SoC which supports PSRAM and activate the feature to enable +/// it. You need to pass the PSRAM peripheral and the psram module path. +/// +/// # Usage +/// +/// ```rust, no_run +/// esp_alloc::psram_allocator!(peripherals.PSRAM, hal::psram); +/// ``` +/// +/// # ⚠️ Limitations +/// +/// On ESP32, ESP32-S2 and ESP32-S3 the atomic instructions do not work +/// correctly when the memory they access is located in PSRAM. This means that +/// the allocator must not be used to allocate `Atomic*` types - either directly +/// or indirectly. Be very careful when using PSRAM in your global allocator. +#[macro_export] +macro_rules! psram_allocator { + ($peripheral:expr, $psram_module:path) => {{ + use $psram_module as _psram; + let (start, size) = _psram::psram_raw_parts(&$peripheral); + unsafe { + $crate::HEAP.add_region($crate::HeapRegion::new( + start, + size, + $crate::MemoryCapability::External.into(), + )); + } + }}; +} diff --git a/esp-alloc/src/malloc.rs b/esp-alloc/src/malloc.rs new file mode 100644 index 00000000000..49d002daae5 --- /dev/null +++ b/esp-alloc/src/malloc.rs @@ -0,0 +1,124 @@ +#[unsafe(no_mangle)] +pub unsafe extern "C" fn malloc(size: usize) -> *mut u8 { + unsafe { malloc_with_caps(size, enumset::EnumSet::empty()) } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn malloc_internal(size: usize) -> *mut u8 { + unsafe { malloc_with_caps(size, crate::MemoryCapability::Internal.into()) } +} + +unsafe fn malloc_with_caps( + size: usize, + caps: enumset::EnumSet, +) -> *mut u8 { + let total_size = size + 4; + + unsafe { + let ptr = crate::HEAP.alloc_caps( + caps, + core::alloc::Layout::from_size_align_unchecked(total_size, 4), + ); + + if ptr.is_null() { + return ptr; + } + + *(ptr as *mut usize) = total_size; + ptr.offset(4) + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free(ptr: *mut u8) { + if ptr.is_null() { + return; + } + + use core::alloc::GlobalAlloc; + + unsafe { + let ptr = ptr.offset(-4); + let total_size = *(ptr as *const usize); + + crate::HEAP.dealloc( + ptr, + core::alloc::Layout::from_size_align_unchecked(total_size, 4), + ) + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_internal(ptr: *mut u8) { + unsafe { free(ptr) } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn calloc(number: u32, size: usize) -> *mut u8 { + let total_size = number as usize * size; + unsafe { + let ptr = malloc(total_size); + + if !ptr.is_null() { + for i in 0..total_size as isize { + ptr.offset(i).write_volatile(0); + } + } + + ptr + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn calloc_internal(number: u32, size: usize) -> *mut u8 { + let total_size = number as usize * size; + unsafe { + let ptr = malloc_internal(total_size); + + if !ptr.is_null() { + for i in 0..total_size as isize { + ptr.offset(i).write_volatile(0); + } + } + + ptr + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn realloc(ptr: *mut u8, new_size: usize) -> *mut u8 { + unsafe { realloc_with_caps(ptr, new_size, enumset::EnumSet::empty()) } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn realloc_internal(ptr: *mut u8, new_size: usize) -> *mut u8 { + unsafe { realloc_with_caps(ptr, new_size, crate::MemoryCapability::Internal.into()) } +} + +unsafe fn realloc_with_caps( + ptr: *mut u8, + new_size: usize, + caps: enumset::EnumSet, +) -> *mut u8 { + unsafe extern "C" { + fn memcpy(d: *mut u8, s: *const u8, l: usize); + } + + unsafe { + let p = malloc_with_caps(new_size, caps); + if !p.is_null() && !ptr.is_null() { + let len = usize::min( + (ptr as *const u32).sub(1).read_volatile() as usize, + new_size, + ); + memcpy(p, ptr, len); + free(ptr); + } + p + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn get_free_internal_heap_size() -> usize { + crate::HEAP.free_caps(crate::MemoryCapability::Internal.into()) +} diff --git a/esp-backtrace/CHANGELOG.md b/esp-backtrace/CHANGELOG.md new file mode 100644 index 00000000000..7696de03e36 --- /dev/null +++ b/esp-backtrace/CHANGELOG.md @@ -0,0 +1,117 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial ESP32-C5 support (#4859, #4866) +- Made `Backtrace` and `BacktraceFrame` implement `Clone` (#5089) + +### Changed + + +### Fixed + + +### Removed + + +## [v0.18.1] - 2025-10-30 + +## [v0.18.0] - 2025-10-13 + +### Changed + +- `exception-handler` now panics. (#3838) +- Only halt cores in panics when `halt-cores` feature is enabled. (#4010) +- It is no longer possible to select multiple halt method features (`halt-cores`, `custom-halt`, `semihosting`) (#4012) +- RISC-V: If stack-frames are not enabled the panic-handler will now emit a stack dump (#4189) + +### Removed + +- the `exception-handler` feature got removed (#3887) + +## [v0.17.0] - 2025-07-16 + +### Removed + +- Removed support for ESP32-P4 (#3754) + +## [v0.16.0] - 2025-06-03 + +### Added + +- The length of the stack trace can now be configured using `ESP_BACKTRACE_CONFIG_BACKTRACE_FRAMES` (#3271) +- `Backtrace` and `BacktraceFrame` types. (#3280) + +### Changed + +- The `arch::backtrace` function now returns a `Backtrace` struct (#3280) +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) +- Update `defmt` to 1.0 (#3416) + +### Fixed + +- Stack traces no longer stop at recursive functions (#3270) +- ESP32/S2/S3: Fixed an issue where the backtrace wasn't correctly captured in some cases (#3272) + +## [0.15.1] - 2025-02-24 + +### Fixed + +- `PanicInfo` is now printed natively by `defmt` (#3112) + +## 0.15.0 - 2025-01-15 + +### Changed + +- Bump MSRV to 1.84 (#2951) + +## 0.14.2 - 2024-10-10 + +### Fixed + +- Fix build when not using `panic-handler` (#2257) + +## 0.14.1 - 2024-09-06 + +### Changed + +- Print a more helpful message in case of a `Cp0Disabled` exception (#2061) + +## 0.14.0 - 2024-08-29 + +### Added + +- Add custom-pre-backtrace feature (#1822) + +### Changed + +- Improve panic message printing (#1823) + +## 0.13.0 - 2024-07-16 + +## 0.12.2 - 2024-07-15 + +### Changed + +- Remove build script check for `nightly-2024-06-12` (#1788) + +## 0.12.1 - 2024-06-19 + +### Fixed + +- Fix compilation for nightly after 2024-06-12. (#1681) +- Only prints float registers on targets which have them. (#1690) + +[0.15.1]: https://github.com/esp-rs/esp-hal/releases/tag/esp-backtrace-v0.15.1 +[v0.16.0]: https://github.com/esp-rs/esp-hal/compare/esp-backtrace-v0.15.1...esp-backtrace-v0.16.0 +[v0.17.0]: https://github.com/esp-rs/esp-hal/compare/esp-backtrace-v0.16.0...esp-backtrace-v0.17.0 +[v0.18.0]: https://github.com/esp-rs/esp-hal/compare/esp-backtrace-v0.17.0...esp-backtrace-v0.18.0 +[v0.18.1]: https://github.com/esp-rs/esp-hal/compare/esp-backtrace-v0.18.0...esp-backtrace-v0.18.1 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-backtrace-v0.18.1...HEAD diff --git a/esp-backtrace/Cargo.toml b/esp-backtrace/Cargo.toml new file mode 100644 index 00000000000..2571b0b2c71 --- /dev/null +++ b/esp-backtrace/Cargo.toml @@ -0,0 +1,95 @@ +[package] +name = "esp-backtrace" +version = "0.18.1" +edition = "2024" +rust-version = "1.86.0" +description = "Bare-metal backtrace support for Espressif devices" +documentation = "https://docs.espressif.com/projects/rust/esp-backtrace/latest/" +keywords = ["backtrace", "embedded", "esp32", "espressif"] +categories = ["embedded", "hardware-support", "no-std"] +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +doc-config = { features = ["defmt"] } +check-configs = [ + { features = ["println", "esp-println/auto"] }, + { features = ["defmt"] }, + { features = ["defmt", "panic-handler", "custom-halt"] }, + { features = ["defmt", "panic-handler", "halt-cores"] }, + { features = ["defmt", "panic-handler", "semihosting"] } +] +clippy-configs = [ + { features = ["panic-handler", "halt-cores", "defmt"] }, +] + +[package.metadata.docs.rs] +default-target = "riscv32imc-unknown-none-elf" +features = ["esp32c3", "panic-handler", "println", "esp-println/uart"] + +[lib] +bench = false +test = false + +[dependencies] +cfg-if = "1" +defmt = { version = "1", optional = true } +esp-config = { version = "0.6.1", path = "../esp-config" } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } +esp-println = { version = "0.16.1", optional = true, default-features = false, path = "../esp-println" } +heapless = "0.9" +semihosting = { version = "0.1.20", optional = true } +document-features = "0.2" + +[target.'cfg(target_arch = "riscv32")'.dependencies] +riscv = { version = "0.15.0" } + +[target.'cfg(target_arch = "xtensa")'.dependencies] +xtensa-lx = { version = "0.13.0", path = "../xtensa-lx" } + +[build-dependencies] +esp-config = { version = "0.6.1", path = "../esp-config", features = ["build"] } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"] } + +[features] +default = ["colors"] + +esp32 = ["esp-println?/esp32", "esp-metadata-generated/esp32", "semihosting?/openocd-semihosting", "print-float-registers"] +esp32c2 = ["esp-println?/esp32c2", "esp-metadata-generated/esp32c2"] +esp32c3 = ["esp-println?/esp32c3", "esp-metadata-generated/esp32c3"] +esp32c5 = ["esp-println?/esp32c5", "esp-metadata-generated/esp32c5"] +esp32c6 = ["esp-println?/esp32c6", "esp-metadata-generated/esp32c6"] +esp32h2 = ["esp-println?/esp32h2", "esp-metadata-generated/esp32h2"] +esp32s2 = ["esp-println?/esp32s2", "esp-metadata-generated/esp32s2", "semihosting?/openocd-semihosting"] +esp32s3 = ["esp-println?/esp32s3", "esp-metadata-generated/esp32s3", "semihosting?/openocd-semihosting", "print-float-registers"] + +## Use `esp-println` +println = ["dep:esp-println"] + +## Use `defmt` +## +## Please note that `defmt` does _not_ provide MSRV guarantees with releases, and as such we are +## not able to make any MSRV guarantees when this feature is enabled. For more information refer to +## the MSRV section of `defmt`'s README: https://github.com/knurling-rs/defmt?tab=readme-ov-file#msrv +defmt = ["dep:defmt"] + +print-float-registers = [] # TODO support esp32p4 + +# You may optionally enable one or more of the below features to provide +# additional functionality: +## Print messages in red +colors = [] +# TODO: these features assume panic-handler is enabled but they don't enforce it. +## Invoke the extern function `custom_halt()` instead of doing a loop {} in case of a panic. This feature does not imply the `halt-cores` feature. +custom-halt = [] +## Invoke the extern function `custom_pre_backtrace()` before handling a panic +custom-pre-backtrace = [] +## Halt both CPUs on ESP32 / ESP32-S3 instead of doing a `loop {}` in case of a panic +halt-cores = [] +## Exit with a semihosting call in case of a panic +semihosting = ["dep:semihosting"] +## Include a panic handler +panic-handler = [] + +[lints.rust] +unexpected_cfgs = "allow" diff --git a/esp-backtrace/README.md b/esp-backtrace/README.md new file mode 100644 index 00000000000..da0d88448bd --- /dev/null +++ b/esp-backtrace/README.md @@ -0,0 +1,35 @@ +# esp-backtrace - backtrace for ESP32 bare-metal + +[![Crates.io](https://img.shields.io/crates/v/esp-backtrace?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-backtrace) +[![docs.rs](https://img.shields.io/docsrs/esp-backtrace?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-backtrace/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-backtrace?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +Supports the ESP32, ESP32-C2/C3/C5/C6, ESP32-H2, and ESP32-S2/S3. Optional panic handler is included, which can be enabled via its respective feature. + +Please note that when targeting a RISC-V device, you **need** to force frame pointers (i.e. `"-C", "force-frame-pointers",` in your `.cargo/config.toml`); this is **not** required for Xtensa. + +You can get an array of backtrace addresses (currently limited to 10) via `arch::backtrace()` if +you want to create a backtrace yourself (i.e. not using the panic handler). + +When using the panic handler make sure to include `use esp_backtrace as _;`. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-backtrace/build.rs b/esp-backtrace/build.rs new file mode 100644 index 00000000000..a75ebb69fae --- /dev/null +++ b/esp-backtrace/build.rs @@ -0,0 +1,46 @@ +use std::error::Error; + +use esp_config::generate_config_from_yaml_definition; +use esp_metadata_generated::{assert_unique_features, assert_unique_used_features}; + +fn main() -> Result<(), Box> { + // Ensure that exactly one chip has been specified: + let chip = esp_metadata_generated::Chip::from_cargo_feature()?; + + // Ensure that exactly a backend is selected: + assert_unique_used_features!("defmt", "println"); + + // Ensure that there aren't multiple halt methods selected: + assert_unique_features!("custom-halt", "halt-cores", "semihosting"); + + if !cfg!(feature = "panic-handler") + && cfg!(any( + feature = "custom-halt", + feature = "halt-cores", + feature = "semihosting" + )) + { + print_warning("A halt method is selected, but esp-backtrace is not the panic handler.") + } + + // emit config + println!("cargo:rerun-if-changed=./esp_config.yml"); + let cfg_yaml = std::fs::read_to_string("./esp_config.yml") + .expect("Failed to read esp_config.yml for esp-backtrace"); + generate_config_from_yaml_definition(&cfg_yaml, true, true, Some(chip))?; + + println!("cargo::rustc-check-cfg=cfg(stack_dump)"); + if !chip.is_xtensa() + && !std::env::var("CARGO_ENCODED_RUSTFLAGS") + .unwrap_or_default() + .contains("force-frame-pointers") + { + println!("cargo::rustc-cfg=stack_dump"); + } + + Ok(()) +} + +fn print_warning(message: impl core::fmt::Display) { + println!("cargo:warning={message}"); +} diff --git a/esp-backtrace/esp_config.yml b/esp-backtrace/esp_config.yml new file mode 100644 index 00000000000..90d31766ba2 --- /dev/null +++ b/esp-backtrace/esp_config.yml @@ -0,0 +1,21 @@ +crate: esp-backtrace + +options: + - name: backtrace_frames + description: The maximum number of frames that will be printed in a backtrace. + default: + - value: 10 + + - name: stack-dump-max-size + description: Max amount of stack to dump. + default: + - value: '"8K"' + constraints: + - type: + validator: enumeration + value: + - "4K" + - "8K" + - "16K" + - "32K" + active: 'chip == "esp32c2" || chip == "esp32c3" || chip == "esp32c6" || chip == "esp32h2"' diff --git a/esp-backtrace/src/lib.rs b/esp-backtrace/src/lib.rs new file mode 100644 index 00000000000..80261790054 --- /dev/null +++ b/esp-backtrace/src/lib.rs @@ -0,0 +1,225 @@ +//! This is a lightweight crate for obtaining backtraces on Espressif devices. +//! +//! It provides an optional panic handler and supports a range of output options, all configurable +//! through feature flags. +#![cfg_attr(target_arch = "riscv32", doc = "\n")] +#![cfg_attr( + target_arch = "riscv32", + doc = "\nPlease note that you **need** to force frame pointers (i.e. `\"-C\", \"force-frame-pointers\",` in your `.cargo/config.toml`).\n" +)] +#![cfg_attr( + target_arch = "riscv32", + doc = "Otherwise the panic handler will emit a stack dump which needs tooling to decode it.\n\n" +)] +#![cfg_attr(target_arch = "riscv32", doc = "\n")] +//! You can get an array of backtrace addresses (limited to 10 entries by default) via +//! `arch::backtrace()` if you want to create a backtrace yourself (i.e. not using the panic +//! handler). +//! +//! ## Features +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +//! ## Additional configuration +//! +//! We've exposed some configuration options that don't fit into cargo +//! features. These can be set via environment variables, or via cargo's `[env]` +//! section inside `.cargo/config.toml`. Below is a table of tunable parameters +//! for this crate: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_backtrace_config_table.md"))] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))] +#![no_std] + +#[macro_use] +extern crate esp_metadata_generated; + +#[cfg(feature = "defmt")] +use defmt as _; +#[cfg(feature = "println")] +use esp_println as _; + +const MAX_BACKTRACE_ADDRESSES: usize = + esp_config::esp_config_int!(usize, "ESP_BACKTRACE_CONFIG_BACKTRACE_FRAMES"); + +#[derive(Clone)] +pub struct Backtrace(pub(crate) heapless::Vec); + +impl Backtrace { + /// Captures a stack backtrace. + #[inline] + pub fn capture() -> Self { + arch::backtrace() + } + + /// Returns the backtrace frames as a slice. + #[inline] + pub fn frames(&self) -> &[BacktraceFrame] { + &self.0 + } +} + +#[derive(Clone)] +pub struct BacktraceFrame { + pub(crate) pc: usize, +} + +impl BacktraceFrame { + pub fn program_counter(&self) -> usize { + self.pc - crate::arch::RA_OFFSET + } +} + +#[cfg(feature = "panic-handler")] +const RESET: &str = "\u{001B}[0m"; +#[cfg(feature = "panic-handler")] +const RED: &str = "\u{001B}[31m"; + +#[cfg(all(feature = "panic-handler", feature = "defmt"))] +macro_rules! println { + ($($arg:tt)*) => { + defmt::error!($($arg)*); + }; +} + +#[cfg(all(feature = "panic-handler", feature = "defmt", stack_dump))] +pub(crate) use println; + +#[cfg(all(feature = "panic-handler", feature = "println"))] +macro_rules! println { + ($($arg:tt)*) => { + esp_println::println!($($arg)*); + }; +} + +#[cfg(feature = "panic-handler")] +fn set_color_code(_code: &str) { + #[cfg(all(feature = "colors", feature = "println"))] + { + println!("{}", _code); + } +} + +#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")] +#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")] +pub(crate) mod arch; + +#[cfg(feature = "panic-handler")] +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + pre_backtrace(); + + set_color_code(RED); + println!(""); + println!("====================== PANIC ======================"); + + println!("{}", info); + set_color_code(RESET); + + cfg_if::cfg_if! { + if #[cfg(not(stack_dump))] + { + println!(""); + println!("Backtrace:"); + println!(""); + + let backtrace = Backtrace::capture(); + #[cfg(target_arch = "riscv32")] + if backtrace.frames().is_empty() { + println!( + "No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)" + ); + } + for frame in backtrace.frames() { + println!("0x{:x}", frame.program_counter()); + } + } else { + arch::dump_stack(); + } + } + + abort() +} + +// Ensure that the address is in DRAM. +// +// Address ranges can be found in `esp-metadata/devices/$CHIP.toml` in the `device` table. +fn is_valid_ram_address(address: u32) -> bool { + memory_range!("DRAM").contains(&address) +} + +#[cfg(feature = "halt-cores")] +fn halt() { + #[cfg(any(feature = "esp32", feature = "esp32s3"))] + { + #[cfg(feature = "esp32")] + mod registers { + pub(crate) const OPTIONS0: u32 = 0x3ff48000; + pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac; + } + + #[cfg(feature = "esp32s3")] + mod registers { + pub(crate) const OPTIONS0: u32 = 0x60008000; + pub(crate) const SW_CPU_STALL: u32 = 0x600080bc; + } + + let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32; + + unsafe { + // We need to write the value "0x86" to stall a particular core. The write + // location is split into two separate bit fields named "c0" and "c1", and the + // two fields are located in different registers. Each core has its own pair of + // "c0" and "c1" bit fields. + + let options0 = registers::OPTIONS0 as *mut u32; + + options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010); + + sw_cpu_stall.write_volatile( + sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26) + | (0x21 << 20) + | (0x21 << 26), + ); + } + } +} + +#[cfg(feature = "panic-handler")] +fn pre_backtrace() { + #[cfg(feature = "custom-pre-backtrace")] + { + unsafe extern "Rust" { + fn custom_pre_backtrace(); + } + unsafe { custom_pre_backtrace() } + } +} + +#[cfg(feature = "panic-handler")] +fn abort() -> ! { + println!(""); + println!(""); + println!(""); + + cfg_if::cfg_if! { + if #[cfg(feature = "semihosting")] { + arch::interrupt_free(|| { + semihosting::process::abort(); + }); + } else if #[cfg(feature = "halt-cores")] { + halt(); + } else if #[cfg(feature = "custom-halt")] { + // call custom code + unsafe extern "Rust" { + fn custom_halt() -> !; + } + unsafe { custom_halt() } + } + } + + #[allow(unreachable_code)] + arch::interrupt_free(|| { + #[allow(clippy::empty_loop)] + loop {} + }) +} diff --git a/esp-backtrace/src/riscv.rs b/esp-backtrace/src/riscv.rs new file mode 100644 index 00000000000..c914de3073c --- /dev/null +++ b/esp-backtrace/src/riscv.rs @@ -0,0 +1,117 @@ +use core::arch::asm; + +#[cfg(feature = "panic-handler")] +pub(crate) use riscv::interrupt::free as interrupt_free; + +use crate::{Backtrace, BacktraceFrame}; + +// subtract 4 from the return address +// the return address is the address following the JALR +// we get better results (especially if the caller was the last instruction in +// the calling function) if we report the address of the JALR itself +// even if it was a C.JALR we should get good results using RA - 4 +pub(super) const RA_OFFSET: usize = 4; + +/// Get an array of backtrace addresses. +/// +/// This needs `force-frame-pointers` enabled. +#[inline(never)] +#[cold] +pub fn backtrace() -> Backtrace { + let fp = unsafe { + let mut _tmp: u32; + asm!("mv {0}, x8", out(reg) _tmp); + _tmp + }; + + backtrace_internal(fp, 2) +} + +pub(crate) fn backtrace_internal(fp: u32, suppress: u32) -> Backtrace { + let mut result = Backtrace(heapless::Vec::new()); + + let mut fp = fp; + let mut suppress = suppress; + + if !crate::is_valid_ram_address(fp) { + return result; + } + + while !result.0.is_full() { + // RA/PC + let address = unsafe { (fp as *const u32).offset(-1).read_volatile() }; + // next FP + fp = unsafe { (fp as *const u32).offset(-2).read_volatile() }; + + if address == 0 { + break; + } + + if !crate::is_valid_ram_address(fp) { + break; + } + + if suppress == 0 { + _ = result.0.push(BacktraceFrame { + pc: address as usize, + }); + } else { + suppress -= 1; + } + } + + result +} + +#[cfg(all(stack_dump, feature = "panic-handler"))] +pub(super) fn dump_stack() { + cfg_if::cfg_if! { + if #[cfg(feature = "println")] { + const MAX_STACK_DUMP_SIZE: u32 = match () { + _ if cfg!(stack_dump_max_size_4k) => 4 * 1024, + _ if cfg!(stack_dump_max_size_8k) => 8 * 1024, + _ if cfg!(stack_dump_max_size_16k) => 16 * 1024, + _ if cfg!(stack_dump_max_size_32k) => 32 * 1024, + _ => core::unreachable!(), + }; + + let (pc, sp) = unsafe { + unsafe extern "C" { + #[allow(improper_ctypes)] + fn __get_pc_sp() -> (u32, u32); + } + + __get_pc_sp() + }; + + unsafe extern "C" { + static _stack_start: u32; + } + + let end = u32::min( + core::ptr::addr_of!(_stack_start) as u32, + sp + MAX_STACK_DUMP_SIZE, + ); + + esp_println::print!("STACKDUMP: {:08x} ", pc); + for address in sp..end { + let byte = unsafe { (address as *const u8).read_volatile() }; + esp_println::print!("{:02x}", byte); + } + esp_println::println!(); + } else { + super::println!("Stack dumps are not supported with DEFMT."); + } + } +} + +#[cfg(all(stack_dump, feature = "panic-handler"))] +core::arch::global_asm!( + " +.globl __get_pc_sp +__get_pc_sp: + mv a0, ra + mv a1, sp + ret +" +); diff --git a/esp-backtrace/src/xtensa.rs b/esp-backtrace/src/xtensa.rs new file mode 100644 index 00000000000..802fbc761e6 --- /dev/null +++ b/esp-backtrace/src/xtensa.rs @@ -0,0 +1,91 @@ +use core::arch::asm; + +#[cfg(feature = "panic-handler")] +pub(crate) use xtensa_lx::interrupt::free as interrupt_free; + +use crate::{Backtrace, BacktraceFrame}; + +// subtract 3 from the return address +// the return address is the address following the callxN +// we get better results (especially if the caller was the last function in the +// calling function) if we report the address of callxN itself +pub(super) const RA_OFFSET: usize = 3; + +/// This function returns the caller's frame pointer. +#[inline(never)] +#[cold] +fn sp() -> u32 { + let mut sp: u32; + unsafe { + asm!( + "mov {0}, a1", // current stack pointer + // Spill registers, otherwise `sp - 12` will not contain the previous stack pointer + "add a12,a12,a12", + "rotw 3", + "add a12,a12,a12", + "rotw 3", + "add a12,a12,a12", + "rotw 3", + "add a12,a12,a12", + "rotw 3", + "add a12,a12,a12", + "rotw 4", + out(reg) sp + ); + } + + // current frame pointer, caller's stack pointer + unsafe { ((sp - 12) as *const u32).read_volatile() } +} + +/// Get an array of backtrace addresses. +#[inline(never)] +#[cold] +pub fn backtrace() -> Backtrace { + let sp = sp(); + + backtrace_internal(sp, 0) +} + +pub(crate) fn remove_window_increment(address: u32) -> u32 { + (address & 0x3fff_ffff) | 0x4000_0000 +} + +pub(crate) fn backtrace_internal(sp: u32, suppress: u32) -> Backtrace { + let mut result = Backtrace(heapless::Vec::new()); + + let mut fp = sp; + let mut suppress = suppress; + + if !crate::is_valid_ram_address(fp) { + return result; + } + + while !result.0.is_full() { + // RA/PC + let address = unsafe { (fp as *const u32).offset(-4).read_volatile() }; + let address = remove_window_increment(address); + // next FP + fp = unsafe { (fp as *const u32).offset(-3).read_volatile() }; + + // the return address is 0 but we sanitized the address - then 0 becomes + // 0x40000000 + if address == 0x40000000 { + break; + } + + if !crate::is_valid_ram_address(fp) { + break; + } + + if suppress == 0 { + _ = result.0.push(BacktraceFrame { + pc: address as usize, + }); + } else { + suppress -= 1; + } + } + + result +} diff --git a/esp-bootloader-esp-idf/CHANGELOG.md b/esp-bootloader-esp-idf/CHANGELOG.md new file mode 100644 index 00000000000..b10336fd0af --- /dev/null +++ b/esp-bootloader-esp-idf/CHANGELOG.md @@ -0,0 +1,90 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `OtaUpdater::ota_data()` (#4413) +- Initial ESP32-C5 support (#4859) + +### Changed + +- `Ota::new()` now takes `FlashRegion` by value (#4413) +- The section of `ESP_APP_DESC` has been changed to `.flash.appdesc` to align with the behavior of esptool (#4745) + +### Fixed + + +### Removed + + +## [v0.4.0] - 2025-10-30 + +### Added + +- `OtaUpdater::next_partition` (#4339) + +### Removed + +- `OtaUpdater::with_next_partition` (#4339) + +## [v0.3.0] - 2025-10-13 + +### Added + +- `FlashRegion::partition_size` (#3902) +- `PartitionTable::booted_partition` (#3979) +- A new high-level OTA update helper, `OtaUpdater`, has been introduced. This simplifies the process of performing an OTA update by validating the partition table, finding the next available update slot, and handling the activation of the new image. (#4150) +- Support for partition tables with more than two OTA application partitions (up to 16). The OTA logic now correctly cycles through all available `ota_X` slots. (#4150) +- `PartitionTable::booted_partition()` function to determine which application partition is currently running by inspecting MMU registers. (#4150) +- `PartitionTable::iter()` to iterate over available partitions. (#4150) + +### Changed + +- The `ota` module's API has been updated to support a variable number of OTA partitions. `Ota::new` now requires the number of available OTA partitions. (#4150) +- `ota::Ota::current_slot()` and `set_current_slot()` have been replaced by the more explicit `current_app_partition()` and `set_current_app_partition()`, which now operate on `AppPartitionSubType`. (#4150) +- The `ota/update` example has been updated to use the new high-level `OtaUpdater`, demonstrating a simpler and safer update workflow. (#4150) + +### Fixed + +- FlashRegion: The `capacity` methods implemented for `embedded_storage::ReadStorage` and `embedded_storage::nor_flash::ReadNorFlash` now return the same value (#3902) +- Don't fail the build on long project names (#3905) +- FlashRegion: Fix off-by-one bug when bounds checking (#3977) +- Correctly set the `ESP_IDF_COMPATIBLE_VERSION` in the application descriptor. It was previously using the `MMU_PAGE_SIZE` configuration value by mistake. (#4150) + +## [v0.2.0] - 2025-07-16 + +### Changed + +- The `log` feature has been renamed to `log-04` (#3675) +- `defmt` and `log-04` can no longer be selected at the same time (#3675) +- A chip feature (e.g. `esp32`) is now required (#3688) +- Use ROM functions for MD5 (#3758) + +### Fixed + +- Fixed a problem with calculating the otadata checksum (#3629) + +## [v0.1.0] - 2025-06-03 + +### Added + +- Support ESP-IDF app descriptor (#3281) +- Support reading partition tables and conveniently read/write partition content (#3316) +- OTA-DATA partition support (#3354) + +### Changed + +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) +- Update `defmt` to 1.0 (#3416) + +[v0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/esp-bootloader-esp-idf-v0.1.0 +[v0.2.0]: https://github.com/esp-rs/esp-hal/compare/esp-bootloader-esp-idf-v0.1.0...esp-bootloader-esp-idf-v0.2.0 +[v0.3.0]: https://github.com/esp-rs/esp-hal/compare/esp-bootloader-esp-idf-v0.2.0...esp-bootloader-esp-idf-v0.3.0 +[v0.4.0]: https://github.com/esp-rs/esp-hal/compare/esp-bootloader-esp-idf-v0.3.0...esp-bootloader-esp-idf-v0.4.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-bootloader-esp-idf-v0.4.0...HEAD diff --git a/esp-bootloader-esp-idf/Cargo.toml b/esp-bootloader-esp-idf/Cargo.toml new file mode 100644 index 00000000000..415ad0e1717 --- /dev/null +++ b/esp-bootloader-esp-idf/Cargo.toml @@ -0,0 +1,97 @@ +[package] +name = "esp-bootloader-esp-idf" +version = "0.4.0" +edition = "2024" +rust-version = "1.88.0" +description = "Functionality related to the esp-idf bootloader" +documentation = "https://docs.espressif.com/projects/rust/esp-bootloader-esp-idf/latest/" +keywords = ["esp32", "espressif", "no-std"] +categories = ["embedded", "hardware-support", "no-std"] +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +doc-config = { features = ["defmt", "validation"] } +check-configs = [ + { features = [] }, + { features = ["log-04"] }, + { features = ["defmt", "validation"] }, +] +clippy-configs = [ + { features = ["defmt", "validation"] }, +] + +[package.metadata.docs.rs] +default-target = "riscv32imac-unknown-none-elf" + +[lib] +bench = false +test = true + +[dependencies] +cfg-if = "1" +defmt = { version = "1.0.1", optional = true } +document-features = "0.2" +esp-config = { version = "0.6.1", path = "../esp-config" } +esp-hal-procmacros = { version = "0.21.0", path = "../esp-hal-procmacros", features = ["__esp_idf_bootloader"] } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } +esp-rom-sys = { version = "0.1.3", path = "../esp-rom-sys", optional = true } +embedded-storage = "0.3.1" +log-04 = { package = "log", version = "0.4", optional = true } +strum = { version = "0.27", default-features = false, features = ["derive"] } + +crc = { version = "3.3.0", optional = true } +md-5 = { version = "0.10.6", default-features = false, optional = true } + +[build-dependencies] +jiff = { version = "0.2.13", default-features = false, features = ["std"] } +esp-config = { version = "0.6.1", path = "../esp-config", features = ["build"] } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"] } + +# Make doctests & host-tests work together: +[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))'.dev-dependencies] +esp-hal = { version = "~1.0", path = "../esp-hal" } + +[features] +default = ["validation"] + +## Enable MD5 validation of the partition table. +validation = [] + +## Enable support for version 0.4 of the `log` crate +log-04 = ["dep:log-04"] + +## Enable support for `defmt` +defmt = ["dep:defmt"] + +# Replace ROM functions with pure Rust implementations, needed for tests. +std = ["dep:crc", "dep:md-5"] + +#! ### Chip selection +#! One of the following features must be enabled to select the target chip: + +## +esp32c2 = ["esp-rom-sys/esp32c2", "esp-metadata-generated/esp32c2", "esp-hal/esp32c2"] +## +esp32c3 = ["esp-rom-sys/esp32c3", "esp-metadata-generated/esp32c3", "esp-hal/esp32c3"] +## +esp32c5 = ["esp-rom-sys/esp32c5", "esp-metadata-generated/esp32c5", "esp-hal/esp32c5"] +## +esp32c6 = ["esp-rom-sys/esp32c6", "esp-metadata-generated/esp32c6", "esp-hal/esp32c6"] +## +esp32h2 = ["esp-rom-sys/esp32h2", "esp-metadata-generated/esp32h2", "esp-hal/esp32h2"] +## +esp32 = ["esp-rom-sys/esp32", "esp-metadata-generated/esp32", "esp-hal/esp32"] +## +esp32s2 = ["esp-rom-sys/esp32s2", "esp-metadata-generated/esp32s2", "esp-hal/esp32s2"] +## +esp32s3 = ["esp-rom-sys/esp32s3", "esp-metadata-generated/esp32s3", "esp-hal/esp32s3"] + +# "md-5" is hidden behind `std` feature and `cargo machete` incorrectly marks it as unused. +[package.metadata.cargo-machete] +ignored = ["md-5", "esp-hal-procmacros"] + +[lints.rust] +# Starting with 1.85.0, the test cfg is considered to be a "userspace" config despite being also set by rustc and should be managed by the build system itself. +# CI started to fail with rustc 1.92.0-nightly (f04e3dfc8 2025-10-19), https://github.com/esp-rs/esp-hal/pull/4386#issuecomment-3491320175 +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test)'] } diff --git a/esp-bootloader-esp-idf/README.md b/esp-bootloader-esp-idf/README.md new file mode 100644 index 00000000000..bfb34360946 --- /dev/null +++ b/esp-bootloader-esp-idf/README.md @@ -0,0 +1,29 @@ +# esp-bootloader-esp-idf + +[![Crates.io](https://img.shields.io/crates/v/esp-bootloader-esp-idf?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-bootloader-esp-idf) +[![docs.rs](https://img.shields.io/docsrs/esp-bootloader-esp-idf?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-bootloader-esp-idf/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.88.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-bootloader-esp-idf?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +This offers additional support for the ESP-IDF 2nd stage bootloader. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/esp-bootloader-esp-idf/build.rs b/esp-bootloader-esp-idf/build.rs new file mode 100644 index 00000000000..3cb965bfcd1 --- /dev/null +++ b/esp-bootloader-esp-idf/build.rs @@ -0,0 +1,40 @@ +use std::{env, error::Error}; + +use esp_config::generate_config_from_yaml_definition; +use esp_metadata_generated::assert_unique_features; +use jiff::Timestamp; + +fn main() -> Result<(), Box> { + println!("cargo::rustc-check-cfg=cfg(embedded_test)"); + + // Log and defmt are mutually exclusive features. The main technical reason is + // that allowing both would make the exact panicking behaviour a fragile + // implementation detail. + assert_unique_features!("log-04", "defmt"); + + let build_time = match env::var("SOURCE_DATE_EPOCH") { + Ok(val) => Timestamp::from_microsecond(val.parse::()?).unwrap(), + Err(_) => Timestamp::now(), + }; + + let build_time_formatted = build_time.strftime("%H:%M:%S"); + let build_date_formatted = build_time.strftime("%Y-%m-%d"); + + println!("cargo::rustc-env=ESP_BOOTLOADER_BUILD_TIME={build_time_formatted}"); + println!("cargo::rustc-env=ESP_BOOTLOADER_BUILD_DATE={build_date_formatted}"); + + // Ensure that exactly one chip has been specified (unless the "std" feature is enabled) + let chip = if !cfg!(feature = "std") { + Some(esp_metadata_generated::Chip::from_cargo_feature().unwrap()) + } else { + None + }; + + // emit config + println!("cargo:rerun-if-changed=./esp_config.yml"); + let cfg_yaml = std::fs::read_to_string("./esp_config.yml") + .expect("Failed to read esp_config.yml for esp-bootloader-esp-idf"); + generate_config_from_yaml_definition(&cfg_yaml, true, true, chip).unwrap(); + + Ok(()) +} diff --git a/esp-bootloader-esp-idf/esp_config.yml b/esp-bootloader-esp-idf/esp_config.yml new file mode 100644 index 00000000000..114097ec966 --- /dev/null +++ b/esp-bootloader-esp-idf/esp_config.yml @@ -0,0 +1,28 @@ +crate: esp-bootloader-esp-idf + +options: + - name: mmu_page_size + description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor. + default: + - value: '"64k"' + constraints: + - type: + validator: enumeration + value: + - 8k + - 16k + - 32k + - 64k + + - name: esp_idf_version + description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader. + default: + - value: '"0.0.0"' + + - name: partition-table-offset + description: "The address of partition table (by default 0x8000). Allows you to + move the partition table, it gives more space for the bootloader. Note that the + bootloader and app will both need to be compiled with the same + PARTITION_TABLE_OFFSET value." + default: + - value: 32768 diff --git a/esp-bootloader-esp-idf/src/fmt.rs b/esp-bootloader-esp-idf/src/fmt.rs new file mode 100644 index 00000000000..dbec78406d2 --- /dev/null +++ b/esp-bootloader-esp-idf/src/fmt.rs @@ -0,0 +1,332 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert!($($x)*); + } else { + ::core::assert!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert_eq!($($x)*); + } else { + ::core::assert_eq!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert_ne!($($x)*); + } else { + ::core::assert_ne!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert!($($x)*); + } else { + ::core::debug_assert!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert_eq!($($x)*); + } else { + ::core::debug_assert_eq!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert_ne!($($x)*); + } else { + ::core::debug_assert_ne!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::todo!($($x)*); + } else { + ::core::todo!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::unreachable!($($x)*); + } else { + ::core::unreachable!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::panic!($($x)*); + } else { + ::core::panic!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::trace!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::trace!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::debug!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::info!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::info!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::warn!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::warn!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::error!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::error!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cold] +#[inline(never)] +#[cfg(not(feature = "defmt"))] +pub(crate) fn __unwrap_failed(arg: &str, e: impl ::core::fmt::Debug) -> ! { + ::core::panic!("unwrap of `{}` failed: {:?}", arg, e); +} + +#[cold] +#[inline(never)] +#[cfg(not(feature = "defmt"))] +pub(crate) fn __unwrap_failed_with_message( + arg: &str, + e: impl core::fmt::Debug, + msg: impl core::fmt::Display, +) -> ! { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", arg, msg, e); +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed(::core::stringify!($arg), e) } + } + }; + ($arg:expr, $msg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed_with_message(::core::stringify!($arg), e, $msg) } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed_with_message(::core::stringify!($arg), e, ::core::format_args!($($msg,)*)) } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + #[allow(unused)] + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +/// A way to `{:x?}` format a byte slice which is compatible with `defmt` +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl Debug for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl Display for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl LowerHex for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Bytes<'_> { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/esp-bootloader-esp-idf/src/lib.rs b/esp-bootloader-esp-idf/src/lib.rs new file mode 100644 index 00000000000..dda73e2bc62 --- /dev/null +++ b/esp-bootloader-esp-idf/src/lib.rs @@ -0,0 +1,400 @@ +//! # Bootloader Support Library supplementing esp-hal +//! +//! ## Overview +//! +//! This crate contains functionality related to the ESP-IDF 2nd stage +//! bootloader. +//! +//! - populating the application-descriptor +//! - read the partition table +//! - conveniently use a partition to read and write flash contents +//! +//! ## Examples +//! +//! ### Populating the Application Descriptor +//! +//! To use the default values: +//! +//! ```rust, no_run +//! #![no_std] +//! #![no_main] +//! +//! #[panic_handler] +//! fn panic(_: &core::panic::PanicInfo) -> ! { +//! loop {} +//! } +//! +//! esp_bootloader_esp_idf::esp_app_desc!(); +//! +//! #[esp_hal::main] +//! fn main() -> ! { +//! let _peripherals = esp_hal::init(esp_hal::Config::default()); +//! +//! loop {} +//! } +//! ``` +//! +//! If you want to customize the application descriptor: +//! +//! ```rust, no_run +//! #![no_std] +//! #![no_main] +//! +//! #[panic_handler] +//! fn panic(_: &core::panic::PanicInfo) -> ! { +//! loop {} +//! } +//! +//! esp_bootloader_esp_idf::esp_app_desc!( +//! // Version +//! "1.0.0", +//! // Project name +//! "my_project", +//! // Build time +//! "12:00:00", +//! // Build date +//! "2021-01-01", +//! // ESP-IDF version +//! "4.4", +//! // MMU page size +//! 8 * 1024, +//! // Minimal eFuse block revision supported by image. Format: major * 100 + minor +//! 0, +//! // Maximum eFuse block revision supported by image. Format: major * 100 + minor +//! u16::MAX +//! ); +//! +//! #[esp_hal::main] +//! fn main() -> ! { +//! let _peripherals = esp_hal::init(esp_hal::Config::default()); +//! +//! loop {} +//! } +//! ``` +//! +//! ## Reclaimed memory +//! +//! After the bootloader has started the application, the `.dram2_uninit` region becomes available +//! for use. This region can be used for dynamic memory allocation or other purposes, but the data +//! placed there cannot be initialized (i.e. it must be `MaybeUninit`). For convenience, you can +//! use the `#[esp_hal::ram(reclaimed)]` attribute, which will also check that the variable can be +//! placed in the reclaimed memory. +#![doc = ""] +#![cfg_attr(not(feature = "std"), doc = concat!("For ", esp_metadata_generated::chip!(), " the size of the reclaimed memory is ", esp_metadata_generated::memory_range!(size as str, "DRAM2_UNINIT")," bytes."))] +#![doc = ""] +//! ## Additional configuration +//! +//! We've exposed some configuration options that don't fit into cargo +//! features. These can be set via environment variables, or via cargo's `[env]` +//! section inside `.cargo/config.toml`. Below is a table of tunable parameters +//! for this crate: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_bootloader_esp_idf_config_table.md"))] +#![doc = ""] +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![no_std] + +// MUST be the first module +mod fmt; + +#[cfg(not(feature = "std"))] +mod rom; +#[cfg(not(feature = "std"))] +pub(crate) use rom as crypto; + +#[cfg(feature = "std")] +mod non_rom; +#[cfg(embedded_test)] +pub use crypto::Crc32 as Crc32ForTesting; +#[cfg(feature = "std")] +pub(crate) use non_rom as crypto; + +pub mod partitions; + +pub mod ota; + +pub mod ota_updater; + +// We run tests on the host which happens to be MacOS machines and mach-o +// doesn't like `link-sections` this way +#[cfg(not(target_os = "macos"))] +#[unsafe(link_section = ".espressif.metadata")] +#[used] +#[unsafe(export_name = "bootloader.NAME")] +static OTA_FEATURE: [u8; 7] = *b"ESP-IDF"; + +/// ESP-IDF compatible application descriptor +/// +/// This gets populated by the [esp_app_desc] macro. +#[repr(C)] +pub struct EspAppDesc { + /// Magic word ESP_APP_DESC_MAGIC_WORD + magic_word: u32, + /// Secure version + secure_version: u32, + /// Reserved + reserv1: [u32; 2], + /// Application version + version: [core::ffi::c_char; 32], + /// Project name + project_name: [core::ffi::c_char; 32], + /// Compile time + time: [core::ffi::c_char; 16], + /// Compile date + date: [core::ffi::c_char; 16], + /// Version IDF + idf_ver: [core::ffi::c_char; 32], + /// sha256 of elf file + app_elf_sha256: [u8; 32], + /// Minimal eFuse block revision supported by image, in format: major * 100 + /// + minor + min_efuse_blk_rev_full: u16, + /// Maximal eFuse block revision supported by image, in format: major * 100 + /// + minor + max_efuse_blk_rev_full: u16, + /// MMU page size in log base 2 format + mmu_page_size: u8, + /// Reserved + reserv3: [u8; 3], + /// Reserved + reserv2: [u32; 18], +} + +impl EspAppDesc { + /// Needs to be public since it's used by the macro + #[doc(hidden)] + #[expect(clippy::too_many_arguments, reason = "For internal use only")] + pub const fn new_internal( + version: &str, + project_name: &str, + build_time: &str, + build_date: &str, + idf_ver: &str, + min_efuse_blk_rev_full: u16, + max_efuse_blk_rev_full: u16, + mmu_page_size: u32, + ) -> Self { + Self { + magic_word: ESP_APP_DESC_MAGIC_WORD, + secure_version: 0, + reserv1: [0; 2], + version: str_to_cstr_array(version), + project_name: str_to_cstr_array(project_name), + time: str_to_cstr_array(build_time), + date: str_to_cstr_array(build_date), + idf_ver: str_to_cstr_array(idf_ver), + app_elf_sha256: [0; 32], + min_efuse_blk_rev_full, + max_efuse_blk_rev_full, + mmu_page_size: (mmu_page_size.ilog2()) as u8, + reserv3: [0; 3], + reserv2: [0; 18], + } + } + + /// The magic word - should be `0xABCD5432` + pub fn magic_word(&self) -> u32 { + self.magic_word + } + + /// Secure version + pub fn secure_version(&self) -> u32 { + self.secure_version + } + + /// Application version + pub fn version(&self) -> &str { + array_to_str(&self.version) + } + + /// Application name + pub fn project_name(&self) -> &str { + array_to_str(&self.project_name) + } + + /// Compile time + pub fn time(&self) -> &str { + array_to_str(&self.time) + } + + /// Compile data + pub fn date(&self) -> &str { + array_to_str(&self.date) + } + + /// IDF version + pub fn idf_ver(&self) -> &str { + array_to_str(&self.idf_ver) + } + + /// SHA256 + /// + /// The default tooling won't populate this + pub fn app_elf_sha256(&self) -> &[u8; 32] { + &self.app_elf_sha256 + } + + /// Minimal eFuse block revision supported by image + /// + /// Format `major * 100 + minor` + pub fn min_efuse_blk_rev_full(&self) -> u16 { + self.min_efuse_blk_rev_full + } + + /// Maximal eFuse block revision supported by image + /// + /// Format `major * 100 + minor` + pub fn max_efuse_blk_rev_full(&self) -> u16 { + self.max_efuse_blk_rev_full + } + + /// MMU page size in bytes + pub fn mmu_page_size(&self) -> u32 { + 2_u32.pow(self.mmu_page_size as u32) + } +} + +impl core::fmt::Debug for EspAppDesc { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EspAppDesc") + .field("magic_word", &self.magic_word) + .field("secure_version", &self.secure_version) + .field("version", &self.version()) + .field("project_name", &self.project_name()) + .field("time", &self.time()) + .field("date", &self.date()) + .field("idf_ver", &self.idf_ver()) + .field("app_elf_sha256", &self.app_elf_sha256) + .field("min_efuse_blk_rev_full", &self.min_efuse_blk_rev_full) + .field("max_efuse_blk_rev_full", &self.max_efuse_blk_rev_full) + .field("mmu_page_size", &self.mmu_page_size) + .finish() + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for EspAppDesc { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "EspAppDesc (\ + magic_word = {}, \ + secure_version = {}, \ + version = {}, \ + project_name = {}, \ + time = {}, \ + date = {}, \ + idf_ver = {}, \ + app_elf_sha256 = {}, \ + min_efuse_blk_rev_full = {}, \ + max_efuse_blk_rev_full = {}, \ + mmu_page_size = {}\ + )", + self.magic_word, + self.secure_version, + self.version(), + self.project_name(), + self.time(), + self.date(), + self.idf_ver(), + self.app_elf_sha256, + self.min_efuse_blk_rev_full, + self.max_efuse_blk_rev_full, + self.mmu_page_size, + ) + } +} + +fn array_to_str(array: &[core::ffi::c_char]) -> &str { + let len = array.iter().position(|b| *b == 0).unwrap_or(array.len()); + unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len)) + } +} + +const ESP_APP_DESC_MAGIC_WORD: u32 = 0xABCD5432; + +const fn str_to_cstr_array(s: &str) -> [::core::ffi::c_char; C] { + let bytes = s.as_bytes(); + let mut ret: [::core::ffi::c_char; C] = [0; C]; + let mut i = 0; + loop { + ret[i] = bytes[i] as _; + i += 1; + if i >= bytes.len() || i >= C { + break; + } + } + ret +} + +/// Build time +pub const BUILD_TIME: &str = env!("ESP_BOOTLOADER_BUILD_TIME"); + +/// Build date +pub const BUILD_DATE: &str = env!("ESP_BOOTLOADER_BUILD_DATE"); + +/// MMU page size in bytes +pub const MMU_PAGE_SIZE: u32 = { + let mmu_page_size = + esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_MMU_PAGE_SIZE").as_bytes(); + match mmu_page_size { + b"8k" => 8 * 1024, + b"16k" => 16 * 1024, + b"32k" => 32 * 1024, + b"64k" => 64 * 1024, + _ => 64 * 1024, + } +}; + +/// The (pretended) ESP-IDF version +pub const ESP_IDF_COMPATIBLE_VERSION: &str = + esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_ESP_IDF_VERSION"); + +/// This macro populates the application descriptor (see [EspAppDesc]) which is +/// available as a static named `ESP_APP_DESC` +/// +/// In most cases you can just use the no-arguments version of this macro. +#[macro_export] +macro_rules! esp_app_desc { + () => { + $crate::esp_app_desc!( + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_NAME"), + $crate::BUILD_TIME, + $crate::BUILD_DATE, + $crate::ESP_IDF_COMPATIBLE_VERSION, + $crate::MMU_PAGE_SIZE, + 0, + u16::MAX + ); + }; + + ( + $version: expr, + $project_name: expr, + $build_time: expr, + $build_date: expr, + $idf_ver: expr, + $mmu_page_size: expr, + $min_efuse_blk_rev_full: expr, + $max_efuse_blk_rev_full: expr + ) => { + #[unsafe(export_name = "esp_app_desc")] + #[unsafe(link_section = ".flash.appdesc")] + #[used] + /// Application metadata descriptor. + pub static ESP_APP_DESC: $crate::EspAppDesc = $crate::EspAppDesc::new_internal( + $version, + $project_name, + $build_time, + $build_date, + $idf_ver, + $min_efuse_blk_rev_full, + $max_efuse_blk_rev_full, + $mmu_page_size, + ); + }; +} diff --git a/esp-bootloader-esp-idf/src/non_rom.rs b/esp-bootloader-esp-idf/src/non_rom.rs new file mode 100644 index 00000000000..87fb7d01bb8 --- /dev/null +++ b/esp-bootloader-esp-idf/src/non_rom.rs @@ -0,0 +1,54 @@ +use crc::{Algorithm, Crc}; +use md5::Digest; + +static ALGO_CRC32_NORMAL: Algorithm = Algorithm { + width: 32, + poly: 0x04c11db7, + init: 0, + refin: true, + refout: true, + xorout: 0xffffffff, + check: 0, + residue: 0, +}; + +pub struct Crc32 { + algo: Crc, +} + +impl Crc32 { + pub fn new() -> Self { + Self { + algo: Crc::::new(&ALGO_CRC32_NORMAL), + } + } + + pub fn crc(&self, data: &[u8]) -> u32 { + let mut digest = self.algo.digest(); + digest.update(&data); + digest.finalize() + } +} + +pub struct Md5 { + context: md5::Md5, +} + +impl Md5 { + pub fn new() -> Self { + Self { + context: md5::Md5::new(), + } + } + + pub fn update(&mut self, data: &[u8]) { + self.context.update(data); + } + + pub fn finalize(self) -> [u8; 16] { + let digest = self.context.finalize(); + let mut hash = [0; 16]; + hash.copy_from_slice(&digest); + hash + } +} diff --git a/esp-bootloader-esp-idf/src/ota.rs b/esp-bootloader-esp-idf/src/ota.rs new file mode 100644 index 00000000000..3adc181d36d --- /dev/null +++ b/esp-bootloader-esp-idf/src/ota.rs @@ -0,0 +1,757 @@ +//! # Over The Air Updates (OTA) +//! +//! ## Overview +//! The OTA update mechanism allows a device to update itself based on data +//! received while the normal firmware is running (for example, over Wi-Fi, +//! Bluetooth or Ethernet). +//! +//! OTA requires configuring the Partition Tables of the device with at least +//! two OTA app slot partitions (i.e., ota_0 and ota_1) and an OTA Data +//! Partition. +//! +//! The OTA operation functions write a new app firmware image to whichever OTA +//! app slot that is currently not selected for booting. Once the image is +//! verified, the OTA Data partition is updated to specify that this image +//! should be used for the next boot. +//! +//! Note: The prebuilt bootloaders provided by `espflash` _might not_ include +//! OTA support. In that case you need to build the bootloader yourself. +//! +//! The general procedure to change the active slot +//! - read the partition table [crate::partitions::read_partition_table] +//! - find the Data/Ota partition +//! - initialize [Ota] +//! - read the current slot, change the current slot +//! +//! For more details see +use embedded_storage::{ReadStorage, Storage}; + +use crate::partitions::{ + AppPartitionSubType, + DataPartitionSubType, + Error, + FlashRegion, + PartitionType, +}; + +const SLOT0_DATA_OFFSET: u32 = 0x0000; +const SLOT1_DATA_OFFSET: u32 = 0x1000; + +const UNINITALIZED_SEQUENCE: u32 = 0xffffffff; + +/// Representation of the current OTA-data slot. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum OtaDataSlot { + /// If there is a `firmware` app-partition it's used. Otherwise OTA-0 + None, + /// OTA-0 + Slot0, + /// OTA-1 + Slot1, +} + +impl OtaDataSlot { + /// The next logical OTA-data slot + fn next(&self) -> OtaDataSlot { + match self { + OtaDataSlot::None => OtaDataSlot::Slot0, + OtaDataSlot::Slot0 => OtaDataSlot::Slot1, + OtaDataSlot::Slot1 => OtaDataSlot::Slot0, + } + } + + fn offset(&self) -> u32 { + match self { + OtaDataSlot::None => SLOT0_DATA_OFFSET, + OtaDataSlot::Slot0 => SLOT0_DATA_OFFSET, + OtaDataSlot::Slot1 => SLOT1_DATA_OFFSET, + } + } +} + +/// OTA image states for checking operability of the app. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub enum OtaImageState { + /// Monitor the first boot. The bootloader will change this to + /// `PendingVerify` if auto-rollback is enabled. + /// + /// You want to set this state after activating a newly installed update. + New = 0x0, + + /// Bootloader changes [OtaImageState::New] to + /// [OtaImageState::PendingVerify] to indicate the app should confirm the + /// image as working. + PendingVerify = 0x1, + + /// Set by the firmware once it's found to be working. The bootloader will + /// consider this Slot as working and continue to use it. + Valid = 0x2, + + /// Set by the firmware once it's found to be non-working. + /// + /// The bootloader will consider this Slot as non-working and not try to + /// boot it further. + Invalid = 0x3, + + /// The bootloader will change the state to [OtaImageState::Aborted] if the + /// application didn't change [OtaImageState::PendingVerify] + /// to either [OtaImageState::Valid] or [OtaImageState::Invalid]. + Aborted = 0x4, + + /// Undefined. The bootloader won't make any assumptions about the working + /// state of this slot. + #[default] + Undefined = 0xFFFFFFFF, +} + +impl TryFrom for OtaImageState { + type Error = Error; + + fn try_from(value: u32) -> Result { + OtaImageState::from_repr(value).ok_or(Error::Invalid) + } +} + +/// OTA selection entry structure (two copies in the OTA data partition). +/// Size of 32 bytes is friendly to flash encryption. +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +struct OtaSelectEntry { + /// OTA sequence number. + pub ota_seq: u32, + /// Sequence label (unused in the bootloader). + pub seq_label: [u8; 20], + /// OTA image state. + pub ota_state: OtaImageState, + /// CRC32 of the `ota_seq` field only. + pub crc: u32, +} + +impl OtaSelectEntry { + fn as_bytes_mut(&mut self) -> &mut [u8; 0x20] { + debug_assert!(core::mem::size_of::() == 32); + unwrap!( + unsafe { core::slice::from_raw_parts_mut(self as *mut _ as *mut u8, 0x20) }.try_into() + ) + } +} + +/// This is used to manipulate the OTA-data partition. +/// +/// If you are looking for a more high-level way to do this, see [crate::ota_updater::OtaUpdater] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ota<'a, F> +where + F: embedded_storage::Storage, +{ + flash: FlashRegion<'a, F>, + ota_partition_count: usize, +} + +impl<'a, F> Ota<'a, F> +where + F: embedded_storage::Storage, +{ + /// Create a [Ota] instance from the given [FlashRegion] and the count of OTA app partitions + /// (not including "firmware" and "test" partitions) + /// + /// # Errors + /// A [Error::InvalidPartition] if the given flash region + /// doesn't represent a Data/Ota partition or the size is unexpected. + /// + /// [Error::InvalidArgument] if the `ota_partition_count` exceeds the maximum or if it's 0. + pub fn new(flash: FlashRegion<'a, F>, ota_partition_count: usize) -> Result, Error> { + if ota_partition_count == 0 || ota_partition_count > 16 { + return Err(Error::InvalidArgument); + } + + if flash.capacity() != 0x2000 + || flash.raw.partition_type() != PartitionType::Data(DataPartitionSubType::Ota) + { + return Err(Error::InvalidPartition { + expected_size: 0x2000, + expected_type: PartitionType::Data(DataPartitionSubType::Ota), + }); + } + + Ok(Ota { + flash, + ota_partition_count, + }) + } + + /// Returns the currently selected app partition. + /// + /// This might not be the booted partition if the bootloader failed to boot + /// the partition and felt back to the last known working app partition. + /// + /// See [crate::partitions::PartitionTable::booted_partition] to get the booted + /// partition. + pub fn current_app_partition(&mut self) -> Result { + let (seq0, seq1) = self.get_slot_seq()?; + + let slot = if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE { + AppPartitionSubType::Factory + } else if seq0 == UNINITALIZED_SEQUENCE { + AppPartitionSubType::from_ota_app_number( + ((seq1 - 1) % self.ota_partition_count as u32) as u8, + )? + } else if seq1 == UNINITALIZED_SEQUENCE || seq0 > seq1 { + AppPartitionSubType::from_ota_app_number( + ((seq0 - 1) % self.ota_partition_count as u32) as u8, + )? + } else { + let counter = u32::max(seq0, seq1) - 1; + AppPartitionSubType::from_ota_app_number( + (counter % self.ota_partition_count as u32) as u8, + )? + }; + + Ok(slot) + } + + fn get_slot_seq(&mut self) -> Result<(u32, u32), Error> { + let mut buffer1 = OtaSelectEntry::default(); + let mut buffer2 = OtaSelectEntry::default(); + self.flash.read(SLOT0_DATA_OFFSET, buffer1.as_bytes_mut())?; + self.flash.read(SLOT1_DATA_OFFSET, buffer2.as_bytes_mut())?; + let seq0 = buffer1.ota_seq; + let seq1 = buffer2.ota_seq; + Ok((seq0, seq1)) + } + + /// Sets the currently active OTA-slot. + /// + /// Passing [AppPartitionSubType::Factory] will reset the OTA-data. + /// + /// # Errors + /// + /// [Error::InvalidArgument] if [AppPartitionSubType::Test] is given or if the OTA app partition + /// number exceeds the value given to the constructor. + pub fn set_current_app_partition(&mut self, app: AppPartitionSubType) -> Result<(), Error> { + if app == AppPartitionSubType::Factory { + self.flash.write(SLOT0_DATA_OFFSET, &[0xffu8; 0x20])?; + self.flash.write(SLOT1_DATA_OFFSET, &[0xffu8; 0x20])?; + return Ok(()); + } + + if app == AppPartitionSubType::Test { + // cannot switch to the test partition - it's a partition + // which special built bootloaders will boot depending on the state of a pin + return Err(Error::InvalidArgument); + } + + let ota_app_index = app.ota_app_number(); + if ota_app_index >= self.ota_partition_count as u8 { + return Err(Error::InvalidArgument); + } + + let current = self.current_app_partition()?; + + // no need to update any sequence if the partition isn't changed + if current != app { + // the bootloader will look at the two slots in ota-data and get the highest sequence + // number + // + // the booted ota-app-partition is the sequence-nr modulo the number of + // ota-app-partitions + + // calculate the needed increment of the sequence-number to select the requested OTA-app + // partition + let inc = if current == AppPartitionSubType::Factory { + (((app.ota_app_number()) as i32 + 1) + (self.ota_partition_count as i32)) as u32 + % self.ota_partition_count as u32 + } else { + ((((app.ota_app_number()) as i32) - ((current.ota_app_number()) as i32)) + + (self.ota_partition_count as i32)) as u32 + % self.ota_partition_count as u32 + }; + + // the slot we need to write the new sequence number to + let slot = self.current_slot()?.next(); + + let (seq0, seq1) = self.get_slot_seq()?; + let new_seq = { + if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE { + // no ota-app partition is selected + inc + } else if seq0 == UNINITALIZED_SEQUENCE { + // seq1 is the sequence number to increment + seq1 + inc + } else if seq1 == UNINITALIZED_SEQUENCE { + // seq0 is the sequence number to increment + seq0 + inc + } else { + u32::max(seq0, seq1) + inc + } + }; + + let crc = crate::crypto::Crc32::new(); + let checksum = crc.crc(&new_seq.to_le_bytes()); + + let mut buffer = OtaSelectEntry::default(); + self.flash.read(slot.offset(), buffer.as_bytes_mut())?; + buffer.ota_seq = new_seq; + buffer.crc = checksum; + self.flash.write(slot.offset(), buffer.as_bytes_mut())?; + } + + Ok(()) + } + + // determine the current ota-data slot by checking the sequence numbers + fn current_slot(&mut self) -> Result { + let (seq0, seq1) = self.get_slot_seq()?; + + let slot = if seq0 == UNINITALIZED_SEQUENCE && seq1 == UNINITALIZED_SEQUENCE { + OtaDataSlot::None + } else if seq0 == UNINITALIZED_SEQUENCE { + OtaDataSlot::Slot1 + } else if seq1 == UNINITALIZED_SEQUENCE || seq0 > seq1 { + OtaDataSlot::Slot0 + } else { + OtaDataSlot::Slot1 + }; + Ok(slot) + } + + /// Set the [OtaImageState] of the currently selected slot. + /// + /// # Errors + /// A [Error::InvalidState] if no partition is currently selected. + pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> { + if let (UNINITALIZED_SEQUENCE, UNINITALIZED_SEQUENCE) = self.get_slot_seq()? { + Err(Error::InvalidState) + } else { + let offset = self.current_slot()?.offset(); + let mut buffer = OtaSelectEntry::default(); + self.flash.read(offset, buffer.as_bytes_mut())?; + buffer.ota_state = state; + self.flash.write(offset, buffer.as_bytes_mut())?; + Ok(()) + } + } + + /// Get the [OtaImageState] of the currently selected slot. + /// + /// # Errors + /// A [Error::InvalidState] if no partition is currently selected. + pub fn current_ota_state(&mut self) -> Result { + if let (UNINITALIZED_SEQUENCE, UNINITALIZED_SEQUENCE) = self.get_slot_seq()? { + Err(Error::InvalidState) + } else { + let offset = self.current_slot()?.offset(); + let mut buffer = OtaSelectEntry::default(); + self.flash.read(offset, buffer.as_bytes_mut())?; + Ok(buffer.ota_state) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::partitions::PartitionEntry; + + struct MockFlash { + data: [u8; 0x2000], + } + + impl embedded_storage::Storage for MockFlash { + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes); + Ok(()) + } + } + + impl embedded_storage::ReadStorage for MockFlash { + type Error = crate::partitions::Error; + fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> { + let l = buffer.len(); + buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]); + Ok(()) + } + + fn capacity(&self) -> usize { + unimplemented!() + } + } + + const PARTITION_RAW: [u8; 32] = [ + 0xaa, 0x50, // MAGIC + 1, // TYPE = DATA + 0, // SUBTYPE = OTA + 0, 0, 0, 0, // OFFSET + 0, 0x20, 0, 0, // LEN (0x2000) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // LABEL + 0, 0, 0, 0, // FLAGS + ]; + + const SLOT_INITIAL: &[u8] = &[ + 255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]; + + const SLOT_COUNT_1_UNDEFINED: &[u8] = &[ + 1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 154, 152, 67, 71, + ]; + + const SLOT_COUNT_1_VALID: &[u8] = &[ + 1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 2, 0, 0, 0, 154, 152, 67, 71, + ]; + + const SLOT_COUNT_2_NEW: &[u8] = &[ + 2, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 55, 246, 85, + ]; + + const SLOT_COUNT_3_PENDING: &[u8] = &[ + 3, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 1, 0, 0, 0, 17, 80, 74, 237, + ]; + + #[test] + fn test_initial_state_and_next_slot() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 2).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Factory + ); + assert_eq!( + sut.current_ota_state(), + Err(crate::partitions::Error::InvalidState) + ); + assert_eq!( + sut.set_current_ota_state(OtaImageState::New), + Err(crate::partitions::Error::InvalidState) + ); + assert_eq!( + sut.current_ota_state(), + Err(crate::partitions::Error::InvalidState) + ); + + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined)); + + assert_eq!(SLOT_COUNT_1_UNDEFINED, &mock_flash.data[0x0000..][..0x20],); + assert_eq!(SLOT_INITIAL, &mock_flash.data[0x1000..][..0x20],); + } + + #[test] + fn test_slot0_valid_next_slot() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID); + mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_INITIAL); + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 2).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid)); + + sut.set_current_app_partition(AppPartitionSubType::Ota1) + .unwrap(); + sut.set_current_ota_state(OtaImageState::New).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota1 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New)); + + assert_eq!(SLOT_COUNT_1_VALID, &mock_flash.data[0x0000..][..0x20],); + assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],); + } + + #[test] + fn test_slot1_new_next_slot() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID); + mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_COUNT_2_NEW); + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 2).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota1 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New)); + + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + sut.set_current_ota_state(OtaImageState::PendingVerify) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify)); + + assert_eq!(SLOT_COUNT_3_PENDING, &mock_flash.data[0x0000..][..0x20],); + assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],); + } + + #[test] + fn test_multi_updates() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 2).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Factory + ); + assert_eq!( + sut.current_ota_state(), + Err(crate::partitions::Error::InvalidState) + ); + + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + sut.set_current_ota_state(OtaImageState::PendingVerify) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify)); + + sut.set_current_app_partition(AppPartitionSubType::Ota1) + .unwrap(); + sut.set_current_ota_state(OtaImageState::New).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota1 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New)); + + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + sut.set_current_ota_state(OtaImageState::Aborted).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted)); + + // setting same app partition again + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + sut.set_current_ota_state(OtaImageState::Valid).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid)); + } + + #[test] + fn test_multi_updates_4_apps() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 4).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Factory + ); + assert_eq!( + sut.current_ota_state(), + Err(crate::partitions::Error::InvalidState) + ); + + sut.set_current_app_partition(AppPartitionSubType::Ota0) + .unwrap(); + sut.set_current_ota_state(OtaImageState::PendingVerify) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota0 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify)); + + sut.set_current_app_partition(AppPartitionSubType::Ota1) + .unwrap(); + sut.set_current_ota_state(OtaImageState::New).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota1 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New)); + + sut.set_current_app_partition(AppPartitionSubType::Ota2) + .unwrap(); + sut.set_current_ota_state(OtaImageState::Aborted).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota2 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted)); + + sut.set_current_app_partition(AppPartitionSubType::Ota3) + .unwrap(); + sut.set_current_ota_state(OtaImageState::Valid).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota3 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid)); + + // going back to a previous app image + sut.set_current_app_partition(AppPartitionSubType::Ota2) + .unwrap(); + sut.set_current_ota_state(OtaImageState::Invalid).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota2 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Invalid)); + + assert_eq!( + sut.set_current_app_partition(AppPartitionSubType::Ota5), + Err(crate::partitions::Error::InvalidArgument) + ); + + assert_eq!( + sut.set_current_app_partition(AppPartitionSubType::Test), + Err(crate::partitions::Error::InvalidArgument) + ); + } + + #[test] + fn test_multi_updates_skip_parts() { + let mut binary = PARTITION_RAW; + + let mock_entry = PartitionEntry { + binary: &mut binary, + }; + + let mut mock_flash = MockFlash { + data: [0xff; 0x2000], + }; + + let mock_region = FlashRegion { + raw: mock_entry, + flash: &mut mock_flash, + }; + + let mut sut = Ota::new(mock_region, 16).unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Factory + ); + assert_eq!( + sut.current_ota_state(), + Err(crate::partitions::Error::InvalidState) + ); + + sut.set_current_app_partition(AppPartitionSubType::Ota10) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota10 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined)); + + sut.set_current_app_partition(AppPartitionSubType::Ota14) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota14 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined)); + + sut.set_current_app_partition(AppPartitionSubType::Ota5) + .unwrap(); + assert_eq!( + sut.current_app_partition().unwrap(), + AppPartitionSubType::Ota5 + ); + assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined)); + } + + #[test] + fn test_ota_slot_next() { + assert_eq!(OtaDataSlot::None.next(), OtaDataSlot::Slot0); + assert_eq!(OtaDataSlot::Slot0.next(), OtaDataSlot::Slot1); + assert_eq!(OtaDataSlot::Slot1.next(), OtaDataSlot::Slot0); + } +} diff --git a/esp-bootloader-esp-idf/src/ota_updater.rs b/esp-bootloader-esp-idf/src/ota_updater.rs new file mode 100644 index 00000000000..fb241899606 --- /dev/null +++ b/esp-bootloader-esp-idf/src/ota_updater.rs @@ -0,0 +1,158 @@ +//! # A more convenient way to access Over The Air Updates (OTA) functionality. + +use crate::{ + ota::OtaImageState, + partitions::{AppPartitionSubType, Error, FlashRegion, PartitionTable}, +}; + +/// This can be used as more convenient - yet less flexible, way to do OTA updates. +/// +/// If you need lower level access see [crate::ota::Ota] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OtaUpdater<'a, F> +where + F: embedded_storage::Storage, +{ + flash: &'a mut F, + pt: PartitionTable<'a>, + ota_count: usize, +} + +impl<'a, F> OtaUpdater<'a, F> +where + F: embedded_storage::Storage, +{ + /// Create a new instance of [OtaUpdater]. + /// + /// # Errors + /// [Error::Invalid] if no OTA data partition or less than two OTA app partition were found. + pub fn new( + flash: &'a mut F, + buffer: &'a mut [u8; crate::partitions::PARTITION_TABLE_MAX_LEN], + ) -> Result { + let pt = crate::partitions::read_partition_table(flash, buffer)?; + + let mut ota_count = 0; + let mut has_ota_data = false; + for part in pt.iter() { + match part.partition_type() { + crate::partitions::PartitionType::App(subtype) + if subtype != crate::partitions::AppPartitionSubType::Factory + && subtype != crate::partitions::AppPartitionSubType::Test => + { + ota_count += 1; + } + crate::partitions::PartitionType::Data( + crate::partitions::DataPartitionSubType::Ota, + ) => { + has_ota_data = true; + } + _ => {} + } + } + + if !has_ota_data { + return Err(Error::Invalid); + } + + if ota_count < 2 { + return Err(Error::Invalid); + } + + Ok(Self { + flash, + pt, + ota_count, + }) + } + + /// Returns an [`Ota`] for accessing the OTA-data partition. + /// + /// # Errors + /// [Error::Invalid] if no OTA data partition was found. + pub fn ota_data(&mut self) -> Result, Error> { + let ota_part = self + .pt + .find_partition(crate::partitions::PartitionType::Data( + crate::partitions::DataPartitionSubType::Ota, + ))?; + if let Some(ota_part) = ota_part { + let ota_part = ota_part.as_embedded_storage(self.flash); + let ota = crate::ota::Ota::new(ota_part, self.ota_count)?; + Ok(ota) + } else { + Err(Error::Invalid) + } + } + + fn next_ota_part(&mut self) -> Result { + let current = self.selected_partition()?; + let next = match current { + AppPartitionSubType::Factory => AppPartitionSubType::Ota0, + _ => AppPartitionSubType::from_ota_app_number( + (current.ota_app_number() + 1) % self.ota_count as u8, + )?, + }; + + // make sure we don't select the currently booted partition + let booted = self.pt.booted_partition()?; + let next = if let Some(booted) = booted { + if booted.partition_type() == crate::partitions::PartitionType::App(next) { + AppPartitionSubType::from_ota_app_number( + (current.ota_app_number() + 2) % self.ota_count as u8, + )? + } else { + next + } + } else { + next + }; + + Ok(next) + } + + /// Returns the currently selected app partition. + pub fn selected_partition(&mut self) -> Result { + self.ota_data()?.current_app_partition() + } + + /// Get the [OtaImageState] of the currently selected partition. + /// + /// # Errors + /// A [Error::InvalidState] if no partition is currently selected. + pub fn current_ota_state(&mut self) -> Result { + self.ota_data()?.current_ota_state() + } + + /// Set the [OtaImageState] of the currently selected slot. + /// + /// # Errors + /// A [Error::InvalidState] if no partition is currently selected. + pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> { + self.ota_data()?.set_current_ota_state(state) + } + + /// Selects the next active OTA-slot as current. + /// + /// After calling this other functions referencing the current partition will use the newly + /// activated partition. + pub fn activate_next_partition(&mut self) -> Result<(), Error> { + let next_slot = self.next_ota_part()?; + self.ota_data()?.set_current_app_partition(next_slot) + } + + /// Returns a [FlashRegion] along with the [AppPartitionSubType] for the + /// partition which would be selected by [Self::activate_next_partition]. + pub fn next_partition(&mut self) -> Result<(FlashRegion<'_, F>, AppPartitionSubType), Error> { + let next_slot = self.next_ota_part()?; + + let flash_region = self + .pt + .find_partition(crate::partitions::PartitionType::App(next_slot))? + .ok_or(Error::Invalid)? + .as_embedded_storage(self.flash); + + Ok((flash_region, next_slot)) + } +} diff --git a/esp-bootloader-esp-idf/src/partitions.rs b/esp-bootloader-esp-idf/src/partitions.rs new file mode 100644 index 00000000000..7f23c484ed3 --- /dev/null +++ b/esp-bootloader-esp-idf/src/partitions.rs @@ -0,0 +1,965 @@ +//! # Partition Table Support +//! +//! ## Overview +//! +//! This module allows reading the partition table and conveniently +//! writing/reading partition contents. +//! +//! For more information see + +/// Maximum length of a partition table. +pub const PARTITION_TABLE_MAX_LEN: usize = 0xC00; + +const PARTITION_TABLE_OFFSET: u32 = + esp_config::esp_config_int!(u32, "ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET"); + +const RAW_ENTRY_LEN: usize = 32; +const ENTRY_MAGIC: u16 = 0x50aa; +const MD5_MAGIC: u16 = 0xebeb; + +const OTA_SUBTYPE_OFFSET: u8 = 0x10; + +/// Represents a single partition entry. +#[derive(Clone, Copy)] +pub struct PartitionEntry<'a> { + pub(crate) binary: &'a [u8; RAW_ENTRY_LEN], +} + +impl<'a> PartitionEntry<'a> { + fn new(binary: &'a [u8; RAW_ENTRY_LEN]) -> Self { + Self { binary } + } + + /// The magic value of the entry. + pub fn magic(&self) -> u16 { + u16::from_le_bytes(unwrap!(self.binary[..2].try_into())) + } + + /// The partition type in raw representation. + pub fn raw_type(&self) -> u8 { + self.binary[2] + } + + /// The partition sub-type in raw representation. + pub fn raw_subtype(&self) -> u8 { + self.binary[3] + } + + /// Offset of the partition on flash. + pub fn offset(&self) -> u32 { + u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into())) + } + + /// Length of the partition in bytes. + pub fn len(&self) -> u32 { + u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into())) + } + + /// Checks for a zero-length partition. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// The label of the partition. + pub fn label(&self) -> &'a [u8] { + &self.binary[12..][..16] + } + + /// The label of the partition as `&str`. + pub fn label_as_str(&self) -> &'a str { + let array = self.label(); + let len = array + .iter() + .position(|b| *b == 0 || *b == 0xff) + .unwrap_or(array.len()); + unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len)) + } + } + + /// Raw flags of this partition. You probably want to use + /// [Self::is_read_only] and [Self::is_encrypted] instead. + pub fn flags(&self) -> u32 { + u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into())) + } + + /// If the partition is read only. + pub fn is_read_only(&self) -> bool { + self.flags() & 0b01 != 0 + } + + /// If the partition is encrypted. + pub fn is_encrypted(&self) -> bool { + self.flags() & 0b10 != 0 + } + + /// The partition type (type and sub-type). + pub fn partition_type(&self) -> PartitionType { + match self.raw_type() { + 0 => PartitionType::App(unwrap!(self.raw_subtype().try_into())), + 1 => PartitionType::Data(unwrap!(self.raw_subtype().try_into())), + 2 => PartitionType::Bootloader(unwrap!(self.raw_subtype().try_into())), + 3 => PartitionType::PartitionTable(unwrap!(self.raw_subtype().try_into())), + _ => unreachable!(), + } + } + + /// Provides a "view" into the partition allowing to read/write the + /// partition contents by using the given [embedded_storage::Storage] and/or + /// [embedded_storage::ReadStorage] implementation. + pub fn as_embedded_storage(self, flash: &'a mut F) -> FlashRegion<'a, F> + where + F: embedded_storage::ReadStorage, + { + FlashRegion { raw: self, flash } + } +} + +impl core::fmt::Debug for PartitionEntry<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PartitionEntry") + .field("magic", &self.magic()) + .field("raw_type", &self.raw_type()) + .field("raw_subtype", &self.raw_subtype()) + .field("offset", &self.offset()) + .field("len", &self.len()) + .field("label", &self.label_as_str()) + .field("flags", &self.flags()) + .field("is_read_only", &self.is_read_only()) + .field("is_encrypted", &self.is_encrypted()) + .finish() + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for PartitionEntry<'_> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "PartitionEntry (\ + magic = {}, \ + raw_type = {}, \ + raw_subtype = {}, \ + offset = {}, \ + len = {}, \ + label = {}, \ + flags = {}, \ + is_read_only = {}, \ + is_encrypted = {}\ + )", + self.magic(), + self.raw_type(), + self.raw_subtype(), + self.offset(), + self.len(), + self.label_as_str(), + self.flags(), + self.is_read_only(), + self.is_encrypted() + ) + } +} + +/// Errors which can be returned. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::Display)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The partition table is invalid or doesn't contain a needed partition. + Invalid, + /// An operation tries to access data that is out of bounds. + OutOfBounds, + /// An error which originates from the embedded-storage implementation. + StorageError, + /// The partition is write protected. + WriteProtected, + /// The partition is invalid. + InvalidPartition { + expected_size: usize, + expected_type: PartitionType, + }, + /// Invalid tate + InvalidState, + /// The given argument is invalid. + InvalidArgument, +} + +impl core::error::Error for Error {} + +/// A partition table. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PartitionTable<'a> { + binary: &'a [[u8; RAW_ENTRY_LEN]], + entries: usize, +} + +impl<'a> PartitionTable<'a> { + fn new(binary: &'a [u8]) -> Result { + if binary.len() > PARTITION_TABLE_MAX_LEN { + return Err(Error::Invalid); + } + + let (binary, rem) = binary.as_chunks::(); + if !rem.is_empty() { + return Err(Error::Invalid); + } + + if binary.is_empty() { + return Ok(Self { + binary: &[], + entries: 0, + }); + } + + let mut raw_table = Self { + binary, + entries: binary.len(), + }; + + #[cfg(feature = "validation")] + { + let (hash, index) = { + let mut i = 0; + loop { + if let Ok(entry) = raw_table.get_partition(i) { + if entry.magic() == MD5_MAGIC { + break (&entry.binary[16..][..16], i); + } + + i += 1; + if i >= raw_table.entries { + return Err(Error::Invalid); + } + } + } + }; + + let mut hasher = crate::crypto::Md5::new(); + + for i in 0..index { + hasher.update(&raw_table.binary[i]); + } + let calculated_hash = hasher.finalize(); + + if calculated_hash != hash { + return Err(Error::Invalid); + } + } + + let entries = { + let mut i = 0; + loop { + if let Ok(entry) = raw_table.get_partition(i) { + if entry.magic() != ENTRY_MAGIC { + break; + } + + i += 1; + + if i == raw_table.entries { + break; + } + } else { + return Err(Error::Invalid); + } + } + i + }; + + raw_table.entries = entries; + + Ok(raw_table) + } + + /// Number of partitions contained in the partition table. + pub fn len(&self) -> usize { + self.entries + } + + /// Checks if there are no recognized partitions. + pub fn is_empty(&self) -> bool { + self.entries == 0 + } + + /// Get a partition entry. + pub fn get_partition(&self, index: usize) -> Result, Error> { + if index >= self.entries { + return Err(Error::OutOfBounds); + } + Ok(PartitionEntry::new(&self.binary[index])) + } + + /// Get the first partition matching the given partition type. + pub fn find_partition(&self, pt: PartitionType) -> Result>, Error> { + for i in 0..self.entries { + let entry = self.get_partition(i)?; + if entry.partition_type() == pt { + return Ok(Some(entry)); + } + } + Ok(None) + } + + /// Returns an iterator over the partitions. + pub fn iter(&self) -> impl Iterator> { + (0..self.entries).filter_map(|i| self.get_partition(i).ok()) + } + + #[cfg(feature = "std")] + /// Get the currently booted partition. + pub fn booted_partition(&self) -> Result>, Error> { + Err(Error::Invalid) + } + + #[cfg(not(feature = "std"))] + /// Get the currently booted partition. + pub fn booted_partition(&self) -> Result>, Error> { + // Read entry 0 from MMU to know which partition is mapped + // + // See + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + let paddr = unsafe { + ((0x3FF10000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(feature = "esp32s2")] { + let paddr = unsafe { + (((0x61801000 + 128 * 4) as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(feature = "esp32s3")] { + // Revisit this once we support XiP from PSRAM for ESP32-S3 + let paddr = unsafe { + ((0x600C5000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(any(feature = "esp32c2", feature = "esp32c3"))] { + let paddr = unsafe { + ((0x600c5000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(any(feature = "esp32c5", feature = "esp32c6", feature = "esp32h2"))] { + let paddr = unsafe { + ((0x60002000 + 0x380) as *mut u32).write_volatile(0); + (((0x60002000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16 + }; + } + } + + for id in 0..self.len() { + let entry = self.get_partition(id)?; + if entry.offset() == paddr { + return Ok(Some(entry)); + } + } + + Ok(None) + } +} + +/// A partition type including the sub-type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PartitionType { + /// Application. + App(AppPartitionSubType), + /// Data. + Data(DataPartitionSubType), + /// Bootloader. + Bootloader(BootloaderPartitionSubType), + /// Partition table. + PartitionTable(PartitionTablePartitionSubType), +} + +/// A partition type +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum RawPartitionType { + /// Application. + App = 0, + /// Data. + Data, + /// Bootloader. + Bootloader, + /// Partition table. + PartitionTable, +} + +/// Sub-types of an application partition. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum AppPartitionSubType { + /// Factory image + Factory = 0, + /// OTA slot 0 + Ota0 = OTA_SUBTYPE_OFFSET, + /// OTA slot 1 + Ota1, + /// OTA slot 2 + Ota2, + /// OTA slot 3 + Ota3, + /// OTA slot 4 + Ota4, + /// OTA slot 5 + Ota5, + /// OTA slot 6 + Ota6, + /// OTA slot 7 + Ota7, + /// OTA slot 8 + Ota8, + /// OTA slot 9 + Ota9, + /// OTA slot 10 + Ota10, + /// OTA slot 11 + Ota11, + /// OTA slot 12 + Ota12, + /// OTA slot 13 + Ota13, + /// OTA slot 14 + Ota14, + /// OTA slot 15 + Ota15, + /// Test image + Test, +} + +impl AppPartitionSubType { + pub(crate) fn ota_app_number(&self) -> u8 { + *self as u8 - OTA_SUBTYPE_OFFSET + } + + pub(crate) fn from_ota_app_number(number: u8) -> Result { + if number > 16 { + return Err(Error::InvalidArgument); + } + Self::try_from(number + OTA_SUBTYPE_OFFSET) + } +} + +impl TryFrom for AppPartitionSubType { + type Error = Error; + + fn try_from(value: u8) -> Result { + AppPartitionSubType::from_repr(value).ok_or(Error::Invalid) + } +} + +/// Sub-types of the data partition type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum DataPartitionSubType { + /// Data partition which stores information about the currently selected OTA + /// app slot. This partition should be 0x2000 bytes in size. Refer to + /// the OTA documentation for more details. + Ota = 0, + /// Phy is for storing PHY initialization data. This allows PHY to be + /// configured per-device, instead of in firmware. + Phy, + /// Used for Non-Volatile Storage (NVS). + Nvs, + /// Used for storing core dumps while using a custom partition table + Coredump, + /// NvsKeys is used for the NVS key partition. (NVS). + NvsKeys, + /// Used for emulating eFuse bits using Virtual eFuses. + EfuseEm, + /// Implicitly used for data partitions with unspecified (empty) subtype, + /// but it is possible to explicitly mark them as undefined as well. + Undefined, + /// FAT Filesystem Support. + Fat = 0x81, + /// SPIFFS Filesystem. + Spiffs = 0x82, + /// LittleFS filesystem. + LittleFs = 0x83, +} + +impl TryFrom for DataPartitionSubType { + type Error = Error; + + fn try_from(value: u8) -> Result { + DataPartitionSubType::from_repr(value).ok_or(Error::Invalid) + } +} + +/// Sub-type of the bootloader partition type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum BootloaderPartitionSubType { + /// It is the so-called 2nd stage bootloader. + Primary = 0, + /// It is a temporary bootloader partition used by the bootloader OTA update + /// functionality for downloading a new image. + Ota = 1, +} + +impl TryFrom for BootloaderPartitionSubType { + type Error = Error; + + fn try_from(value: u8) -> Result { + BootloaderPartitionSubType::from_repr(value).ok_or(Error::Invalid) + } +} + +/// Sub-type of the partition table type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum PartitionTablePartitionSubType { + /// It is the primary partition table. + Primary = 0, + /// It is a temporary partition table partition used by the partition table + /// OTA update functionality for downloading a new image. + Ota = 1, +} + +impl TryFrom for PartitionTablePartitionSubType { + type Error = Error; + + fn try_from(value: u8) -> Result { + PartitionTablePartitionSubType::from_repr(value).ok_or(Error::Invalid) + } +} + +/// Read the partition table. +/// +/// Pass an implementation of [embedded_storage::Storage] which can read from +/// the whole flash and provide storage to read the partition table into. +pub fn read_partition_table<'a>( + flash: &mut impl embedded_storage::Storage, + storage: &'a mut [u8], +) -> Result, Error> { + flash + .read(PARTITION_TABLE_OFFSET, storage) + .map_err(|_e| Error::StorageError)?; + + PartitionTable::new(storage) +} + +/// A flash region is a "view" into the partition. +/// +/// It allows to read and write to the partition without the need to account for +/// the partition offset. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashRegion<'a, F> { + pub(crate) raw: PartitionEntry<'a>, + pub(crate) flash: &'a mut F, +} + +impl FlashRegion<'_, F> { + /// Returns the size of the partition in bytes. + pub fn partition_size(&self) -> usize { + self.raw.len() as _ + } + + fn range(&self) -> core::ops::Range { + self.raw.offset()..self.raw.offset() + self.raw.len() + } + + fn in_range(&self, start: u32, len: usize) -> bool { + self.range().contains(&start) && (start + len as u32 <= self.range().end) + } +} + +impl embedded_storage::Region for FlashRegion<'_, F> { + fn contains(&self, address: u32) -> bool { + self.range().contains(&address) + } +} + +impl embedded_storage::ReadStorage for FlashRegion<'_, F> +where + F: embedded_storage::ReadStorage, +{ + type Error = Error; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let address = offset + self.raw.offset(); + + if !self.in_range(address, bytes.len()) { + return Err(Error::OutOfBounds); + } + + self.flash + .read(address, bytes) + .map_err(|_e| Error::StorageError) + } + + fn capacity(&self) -> usize { + self.partition_size() + } +} + +impl embedded_storage::Storage for FlashRegion<'_, F> +where + F: embedded_storage::Storage, +{ + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + let address = offset + self.raw.offset(); + + if self.raw.is_read_only() { + return Err(Error::WriteProtected); + } + + if !self.in_range(address, bytes.len()) { + return Err(Error::OutOfBounds); + } + + self.flash + .write(address, bytes) + .map_err(|_e| Error::StorageError) + } +} + +impl embedded_storage::nor_flash::NorFlashError for Error { + fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind { + match self { + Error::OutOfBounds => embedded_storage::nor_flash::NorFlashErrorKind::OutOfBounds, + _ => embedded_storage::nor_flash::NorFlashErrorKind::Other, + } + } +} + +impl embedded_storage::nor_flash::ErrorType for FlashRegion<'_, F> { + type Error = Error; +} + +impl embedded_storage::nor_flash::ReadNorFlash for FlashRegion<'_, F> +where + F: embedded_storage::nor_flash::ReadNorFlash, +{ + const READ_SIZE: usize = F::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let address = offset + self.raw.offset(); + + if !self.in_range(address, bytes.len()) { + return Err(Error::OutOfBounds); + } + + self.flash + .read(address, bytes) + .map_err(|_e| Error::StorageError) + } + + fn capacity(&self) -> usize { + self.partition_size() + } +} + +impl embedded_storage::nor_flash::NorFlash for FlashRegion<'_, F> +where + F: embedded_storage::nor_flash::NorFlash, +{ + const WRITE_SIZE: usize = F::WRITE_SIZE; + + const ERASE_SIZE: usize = F::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + let address_from = from + self.raw.offset(); + let address_to = to + self.raw.offset(); + + if self.raw.is_read_only() { + return Err(Error::WriteProtected); + } + + if !self.range().contains(&address_from) { + return Err(Error::OutOfBounds); + } + + if !self.range().contains(&address_to) { + return Err(Error::OutOfBounds); + } + + self.flash + .erase(address_from, address_to) + .map_err(|_e| Error::StorageError) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + let address = offset + self.raw.offset(); + + if self.raw.is_read_only() { + return Err(Error::WriteProtected); + } + + if !self.in_range(address, bytes.len()) { + return Err(Error::OutOfBounds); + } + + self.flash + .write(address, bytes) + .map_err(|_e| Error::StorageError) + } +} + +impl embedded_storage::nor_flash::MultiwriteNorFlash for FlashRegion<'_, F> where + F: embedded_storage::nor_flash::MultiwriteNorFlash +{ +} + +#[cfg(test)] +mod tests { + use super::*; + + static SIMPLE: &[u8] = include_bytes!("../testdata/single_factory_no_ota.bin"); + static OTA: &[u8] = include_bytes!("../testdata/factory_app_two_ota.bin"); + + #[test] + fn read_simple() { + let pt = PartitionTable::new(SIMPLE).unwrap(); + + assert_eq!(3, pt.len()); + + assert_eq!(1, pt.get_partition(0).unwrap().raw_type()); + assert_eq!(1, pt.get_partition(1).unwrap().raw_type()); + assert_eq!(0, pt.get_partition(2).unwrap().raw_type()); + + assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype()); + assert_eq!(1, pt.get_partition(1).unwrap().raw_subtype()); + assert_eq!(0, pt.get_partition(2).unwrap().raw_subtype()); + + assert_eq!( + PartitionType::Data(DataPartitionSubType::Nvs), + pt.get_partition(0).unwrap().partition_type() + ); + assert_eq!( + PartitionType::Data(DataPartitionSubType::Phy), + pt.get_partition(1).unwrap().partition_type() + ); + assert_eq!( + PartitionType::App(AppPartitionSubType::Factory), + pt.get_partition(2).unwrap().partition_type() + ); + + assert_eq!(0x9000, pt.get_partition(0).unwrap().offset()); + assert_eq!(0xf000, pt.get_partition(1).unwrap().offset()); + assert_eq!(0x10000, pt.get_partition(2).unwrap().offset()); + + assert_eq!(0x6000, pt.get_partition(0).unwrap().len()); + assert_eq!(0x1000, pt.get_partition(1).unwrap().len()); + assert_eq!(0x100000, pt.get_partition(2).unwrap().len()); + + assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str()); + assert_eq!("phy_init", pt.get_partition(1).unwrap().label_as_str()); + assert_eq!("factory", pt.get_partition(2).unwrap().label_as_str()); + + assert_eq!(false, pt.get_partition(0).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(1).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(2).unwrap().is_read_only()); + + assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted()); + } + + #[test] + fn read_ota() { + let pt = PartitionTable::new(OTA).unwrap(); + + assert_eq!(6, pt.len()); + + assert_eq!(1, pt.get_partition(0).unwrap().raw_type()); + assert_eq!(1, pt.get_partition(1).unwrap().raw_type()); + assert_eq!(1, pt.get_partition(2).unwrap().raw_type()); + assert_eq!(0, pt.get_partition(3).unwrap().raw_type()); + assert_eq!(0, pt.get_partition(4).unwrap().raw_type()); + assert_eq!(0, pt.get_partition(5).unwrap().raw_type()); + + assert_eq!(2, pt.get_partition(0).unwrap().raw_subtype()); + assert_eq!(0, pt.get_partition(1).unwrap().raw_subtype()); + assert_eq!(1, pt.get_partition(2).unwrap().raw_subtype()); + assert_eq!(0, pt.get_partition(3).unwrap().raw_subtype()); + assert_eq!(0x10, pt.get_partition(4).unwrap().raw_subtype()); + assert_eq!(0x11, pt.get_partition(5).unwrap().raw_subtype()); + + assert_eq!( + PartitionType::Data(DataPartitionSubType::Nvs), + pt.get_partition(0).unwrap().partition_type() + ); + assert_eq!( + PartitionType::Data(DataPartitionSubType::Ota), + pt.get_partition(1).unwrap().partition_type() + ); + assert_eq!( + PartitionType::Data(DataPartitionSubType::Phy), + pt.get_partition(2).unwrap().partition_type() + ); + assert_eq!( + PartitionType::App(AppPartitionSubType::Factory), + pt.get_partition(3).unwrap().partition_type() + ); + assert_eq!( + PartitionType::App(AppPartitionSubType::Ota0), + pt.get_partition(4).unwrap().partition_type() + ); + assert_eq!( + PartitionType::App(AppPartitionSubType::Ota1), + pt.get_partition(5).unwrap().partition_type() + ); + + assert_eq!(0x9000, pt.get_partition(0).unwrap().offset()); + assert_eq!(0xd000, pt.get_partition(1).unwrap().offset()); + assert_eq!(0xf000, pt.get_partition(2).unwrap().offset()); + assert_eq!(0x10000, pt.get_partition(3).unwrap().offset()); + assert_eq!(0x110000, pt.get_partition(4).unwrap().offset()); + assert_eq!(0x210000, pt.get_partition(5).unwrap().offset()); + + assert_eq!(0x4000, pt.get_partition(0).unwrap().len()); + assert_eq!(0x2000, pt.get_partition(1).unwrap().len()); + assert_eq!(0x1000, pt.get_partition(2).unwrap().len()); + assert_eq!(0x100000, pt.get_partition(3).unwrap().len()); + assert_eq!(0x100000, pt.get_partition(4).unwrap().len()); + assert_eq!(0x100000, pt.get_partition(5).unwrap().len()); + + assert_eq!("nvs", pt.get_partition(0).unwrap().label_as_str()); + assert_eq!("otadata", pt.get_partition(1).unwrap().label_as_str()); + assert_eq!("phy_init", pt.get_partition(2).unwrap().label_as_str()); + assert_eq!("factory", pt.get_partition(3).unwrap().label_as_str()); + assert_eq!("ota_0", pt.get_partition(4).unwrap().label_as_str()); + assert_eq!("ota_1", pt.get_partition(5).unwrap().label_as_str()); + + assert_eq!(false, pt.get_partition(0).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(1).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(2).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(3).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(4).unwrap().is_read_only()); + assert_eq!(false, pt.get_partition(5).unwrap().is_read_only()); + + assert_eq!(false, pt.get_partition(0).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(1).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(2).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(3).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(4).unwrap().is_encrypted()); + assert_eq!(false, pt.get_partition(5).unwrap().is_encrypted()); + } + + #[test] + fn empty_byte_array() { + let pt = PartitionTable::new(&[]).unwrap(); + + assert_eq!(0, pt.len()); + assert!(matches!(pt.get_partition(0), Err(Error::OutOfBounds))); + } + + #[test] + fn validation_fails_wo_hash() { + assert!(matches!( + PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 3]), + Err(Error::Invalid) + )); + } + + #[test] + fn validation_fails_wo_hash_max_entries() { + let mut data = [0u8; PARTITION_TABLE_MAX_LEN]; + for i in 0..96 { + data[(i * RAW_ENTRY_LEN)..][..RAW_ENTRY_LEN].copy_from_slice(&SIMPLE[..32]); + } + + assert!(matches!(PartitionTable::new(&data), Err(Error::Invalid))); + } + + #[test] + fn validation_succeeds_with_enough_entries() { + assert_eq!( + 3, + PartitionTable::new(&SIMPLE[..RAW_ENTRY_LEN * 4]) + .unwrap() + .len() + ); + } +} + +#[cfg(test)] +mod storage_tests { + use embedded_storage::{ReadStorage, Storage}; + + use super::*; + + struct MockFlash { + data: [u8; 0x10000], + } + + impl MockFlash { + fn new() -> Self { + let mut data = [23u8; 0x10000]; + data[PARTITION_TABLE_OFFSET as usize..][..PARTITION_TABLE_MAX_LEN as usize] + .copy_from_slice(include_bytes!("../testdata/single_factory_no_ota.bin")); + Self { data } + } + } + + impl embedded_storage::Storage for MockFlash { + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes); + Ok(()) + } + } + + impl embedded_storage::ReadStorage for MockFlash { + type Error = crate::partitions::Error; + fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> { + let l = buffer.len(); + buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]); + Ok(()) + } + + fn capacity(&self) -> usize { + unimplemented!() + } + } + + #[test] + fn can_read_write_all_of_nvs() { + let mut storage = MockFlash::new(); + + let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN]; + let pt = read_partition_table(&mut storage, &mut buffer).unwrap(); + + let nvs = pt + .find_partition(PartitionType::Data(DataPartitionSubType::Nvs)) + .unwrap() + .unwrap(); + let mut nvs_partition = nvs.as_embedded_storage(&mut storage); + assert_eq!(nvs_partition.raw.offset(), 36864); + + assert_eq!(nvs_partition.capacity(), 24576); + + let mut buffer = [0u8; 24576]; + nvs_partition.read(0, &mut buffer).unwrap(); + assert!(buffer.iter().all(|v| *v == 23)); + buffer.fill(42); + nvs_partition.write(0, &mut buffer).unwrap(); + let mut buffer = [0u8; 24576]; + nvs_partition.read(0, &mut buffer).unwrap(); + assert!(buffer.iter().all(|v| *v == 42)); + } + + #[test] + fn cannot_read_write_more_than_partition_size() { + let mut storage = MockFlash::new(); + + let mut buffer = [0u8; PARTITION_TABLE_MAX_LEN]; + let pt = read_partition_table(&mut storage, &mut buffer).unwrap(); + + let nvs = pt + .find_partition(PartitionType::Data(DataPartitionSubType::Nvs)) + .unwrap() + .unwrap(); + let mut nvs_partition = nvs.as_embedded_storage(&mut storage); + assert_eq!(nvs_partition.raw.offset(), 36864); + + assert_eq!(nvs_partition.capacity(), 24576); + + let mut buffer = [0u8; 24577]; + assert!(nvs_partition.read(0, &mut buffer) == Err(Error::OutOfBounds)); + } +} diff --git a/esp-bootloader-esp-idf/src/rom.rs b/esp-bootloader-esp-idf/src/rom.rs new file mode 100644 index 00000000000..9a71772e729 --- /dev/null +++ b/esp-bootloader-esp-idf/src/rom.rs @@ -0,0 +1,33 @@ +use esp_rom_sys::rom::md5; + +pub struct Crc32 {} + +impl Crc32 { + pub fn new() -> Self { + Self {} + } + + pub fn crc(&self, data: &[u8]) -> u32 { + esp_rom_sys::rom::crc::crc32_le(u32::MAX, data) + } +} + +pub struct Md5 { + context: md5::Context, +} + +impl Md5 { + pub fn new() -> Self { + Self { + context: md5::Context::new(), + } + } + + pub fn update(&mut self, data: &[u8]) { + self.context.consume(data); + } + + pub fn finalize(self) -> [u8; 16] { + self.context.compute().0 + } +} diff --git a/esp-bootloader-esp-idf/testdata/factory_app_two_ota.bin b/esp-bootloader-esp-idf/testdata/factory_app_two_ota.bin new file mode 100644 index 00000000000..d7a2b08b864 Binary files /dev/null and b/esp-bootloader-esp-idf/testdata/factory_app_two_ota.bin differ diff --git a/esp-bootloader-esp-idf/testdata/factory_app_two_ota.csv b/esp-bootloader-esp-idf/testdata/factory_app_two_ota.csv new file mode 100644 index 00000000000..3e7e0cbda12 --- /dev/null +++ b/esp-bootloader-esp-idf/testdata/factory_app_two_ota.csv @@ -0,0 +1,8 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +ota_0, app, ota_0, 0x110000, 1M, +ota_1, app, ota_1, 0x210000, 1M, diff --git a/esp-bootloader-esp-idf/testdata/single_factory_no_ota.bin b/esp-bootloader-esp-idf/testdata/single_factory_no_ota.bin new file mode 100644 index 00000000000..b8fa03b4b35 Binary files /dev/null and b/esp-bootloader-esp-idf/testdata/single_factory_no_ota.bin differ diff --git a/esp-bootloader-esp-idf/testdata/single_factory_no_ota.csv b/esp-bootloader-esp-idf/testdata/single_factory_no_ota.csv new file mode 100644 index 00000000000..439d9834fc7 --- /dev/null +++ b/esp-bootloader-esp-idf/testdata/single_factory_no_ota.csv @@ -0,0 +1,5 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, diff --git a/esp-config/CHANGELOG.md b/esp-config/CHANGELOG.md new file mode 100644 index 00000000000..d1fde2ea041 --- /dev/null +++ b/esp-config/CHANGELOG.md @@ -0,0 +1,80 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + + +### Changed + + +### Fixed + + +### Removed + + +## [v0.6.1] - 2025-10-30 + +## [v0.6.0] - 2025-10-13 + +## [v0.5.0] - 2025-07-16 + +### Added + +- Add `ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM` configuration option to pin the RMT driver in RAM (#3778) + +## [v0.4.0] - 2025-06-03 + +### Added + +- `ConfigOption` struct (#3362) +- `Stability` to specify unstable options, and the version in which they became stable (#3365) + +### Changed + +- `generate_config` now takes a slice of `ConfigOption`s instead of tuples. (#3362) +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) +- `ConfigOption` favors `String` over `&str` (#3455) +- Removed the `Custom` validator (#3455) + +## [0.3.1] - 2025-02-24 + +### Added + +- `Enumeration` validator added (#3172) + +## 0.3.0 - 2025-01-15 + +### Fixed + +- Users no longer have to manually import `esp_config_int_parse`. (#2630) + +### Changed + +- Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848) +- Bump MSRV to 1.84 (#2951) + +## 0.2.0 - 2024-11-20 + +### Added + +- Add configuration validation (#2475) + +## 0.1.0 - 2024-10-10 + +### Added + +- Initial release (#2518) + +[0.3.1]: https://github.com/esp-rs/esp-hal/releases/tag/esp-config-v0.3.1 +[v0.4.0]: https://github.com/esp-rs/esp-hal/compare/esp-config-v0.3.1...esp-config-v0.4.0 +[v0.5.0]: https://github.com/esp-rs/esp-hal/compare/esp-config-v0.4.0...esp-config-v0.5.0 +[v0.6.0]: https://github.com/esp-rs/esp-hal/compare/esp-config-v0.5.0...esp-config-v0.6.0 +[v0.6.1]: https://github.com/esp-rs/esp-hal/compare/esp-config-v0.6.0...esp-config-v0.6.1 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-config-v0.6.1...HEAD diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml new file mode 100644 index 00000000000..32654eaf450 --- /dev/null +++ b/esp-config/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "esp-config" +version = "0.6.1" +edition = "2024" +rust-version = "1.86.0" +description = "Configure projects using esp-hal and related packages" +documentation = "https://docs.espressif.com/projects/rust/esp-config/latest/" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +doc-config = { features = ["build"] } +check-configs = [ + { features = [] }, + { features = ["build"] }, +] +clippy-configs = [ + { features = ["tui"] }, +] + +[lib] +bench = false +test = true + +[[bin]] +name = "esp-config" +required-features = ["tui"] + +[dependencies] +document-features = "0.2" + +# used by the `build` and `tui` feature +serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } +serde_yaml = { version = "0.9", optional = true } +somni-expr = { version = "0.2.0", optional = true } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"], optional = true } + +# used by the `tui` feature +clap = { version = "4.5", features = ["derive"], optional = true } +env_logger = { version = "0.11", optional = true } +log = { version = "0.4", optional = true } +ratatui = { version = "0.29", features = ["crossterm", "unstable"], optional = true } +toml_edit = { version = "0.23", optional = true } +tui-textarea = { version = "0.7", optional = true } +cargo_metadata = { version = "0.23", optional = true } + +[dev-dependencies] +temp-env = "0.3.6" +pretty_assertions = "1.4.1" + +[features] +## Enable the generation and parsing of a config +build = ["dep:serde", "dep:serde_yaml", "dep:somni-expr", "dep:esp-metadata-generated"] + +## The TUI +tui = [ + "dep:clap", + "dep:env_logger", + "dep:log", + "dep:ratatui", + "dep:toml_edit", + "dep:tui-textarea", + "dep:cargo_metadata", + "dep:esp-metadata-generated", + "build", +] + +[lints.rust] +# Starting with 1.85.0, the test cfg is considered to be a "userspace" config despite being also set by rustc and should be managed by the build system itself. +# CI started to fail with rustc 1.92.0-nightly (f04e3dfc8 2025-10-19), https://github.com/esp-rs/esp-hal/pull/4386#issuecomment-3491320175 +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test)'] } diff --git a/esp-config/README.md b/esp-config/README.md new file mode 100644 index 00000000000..1ce36898c62 --- /dev/null +++ b/esp-config/README.md @@ -0,0 +1,133 @@ +# esp-config + +[![Crates.io](https://img.shields.io/crates/v/esp-config?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-config) +[![docs.rs](https://img.shields.io/docsrs/esp-config?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-config/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-config?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +## [Documentation](https://docs.espressif.com/projects/rust/esp-config/latest/) + +## Usage + +`esp-config` takes a prefix (usually the crate name) and a set of configuration keys and default values to produce a configuration system that supports: + +- Emitting rustc cfg's for boolean keys +- Emitting environment variables for numbers + - Along with decimal parsing, it supports Hex, Octal and Binary with the respective `0x`, `0o` and `0b` prefixes. +- Emitting environment variables for string values + +### Viewing the configuration + +The possible configuration values are output as a markdown table in the crates `OUT_DIR` with the format `{prefix}_config_table.md`, this can then be included into the crates top level documentation. Here is an example of the output: + + +| Name | Description | Default value | +| ----------------------------------- | --------------------------------------------------- | ------------- | +| **ESP_HAL_PLACE_SPI_DRIVER_IN_RAM** | Places the SPI driver in RAM for better performance | false | + +### Setting configuration options + +For any available configuration option, the environment variable or cfg is _always_ set based on the default value specified in the table. Users can override this by setting environment variables locally in their shell _or_ the preferred option is to utilize cargo's [`env` section](https://doc.rust-lang.org/cargo/reference/config.html#env). + +It's important to note that due to a [bug in cargo](https://github.com/rust-lang/cargo/issues/10358), any modifications to the environment, local or otherwise will only get picked up on a full clean build of the project. + +To see the final selected configuration another table is output to the `OUT_DIR` with the format `{prefix}_selected_config.md`. + +### Capturing configuration values in the downstream crate + +For all supported data types, there are helper macros that emit `const` code for parsing the configuration values. + +- Numbers - `esp_config_int!(integer_type, "ENV")` +- Strings - `esp_config_str!("ENV")` +- Bool - `esp_config_bool!("ENV")` + +In addition to environment variables, for boolean types rust `cfg`'s are emitted in snake case _without_ the prefix. + +## Defining Configuration Options + +Config options should be defined declaratively in a file called `esp_config.yml`. + +Such a file looks like this: +```yaml +crate: esp-bootloader-esp-idf + +options: +- name: mmu_page_size + description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor. + default: + - value: '"64k"' + stability: !Stable stable-since-version + constraints: + - if: true + type: + validator: enumeration + value: + - 8k + - 16k + - 32k + - 64k + +- name: esp_idf_version + description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader. + default: + - if: 'chip == "esp32c6"' + value: '"esp32c6"' + - if: 'chip == "esp32"' + value: '"other"' + active: true + +- name: partition-table-offset + description: "The address of partition table (by default 0x8000). Allows you to \ + move the partition table, it gives more space for the bootloader. Note that the \ + bootloader and app will both need to be compiled with the same \ + PARTITION_TABLE_OFFSET value." + default: + - if: true + value: 32768 + stability: Unstable + active: 'chip == "esp32c6"' + +checks: + - 'ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET >= 32768' +``` + +`if` and `active` are [somni-expr](https://crates.io/crates/somni-expr) expressions evaluating to a boolean. + +The expression supports these custom functions: +|Function|Description| +|---|---| +|feature(String)|`true` if the given chip feature is present| +|cargo_feature(String)|`true` if the given Cargo feature is active| +|ignore_feature_gates|Usually `false` but tooling will set this to `true` to hint that the expression is evaluated by e.g. a TUI| + +`ignore_feature_gates` is useful to enable otherwise disabled functionality - e.g. to offer all possible options regardless of any active / non-active features. + +The `chip` variable is populated with the name of the targeted chip (if the crate is using chip specific features). + +The conditions for `default` and `constraints` are evaluated in order and the first match is taken no matter if there is more. +This way you could have a catch-all condition as the last item by just specifying `true`. + +`checks` is a list of checks which needs to pass for a valid configuration and is checked after all config values for the current config are applied. +You can access the currently configured values to check them. + +For more examples see the various `esp_config.yml` files in this repository. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-config/src/bin/esp-config/main.rs b/esp-config/src/bin/esp-config/main.rs new file mode 100644 index 00000000000..847e3fa269a --- /dev/null +++ b/esp-config/src/bin/esp-config/main.rs @@ -0,0 +1,328 @@ +use std::{ + collections::HashMap, + error::Error, + path::{Path, PathBuf}, + str::FromStr, +}; + +use clap::Parser; +use env_logger::{Builder, Env}; +use esp_config::{ConfigOption, Value}; +use esp_metadata_generated::Chip; +use serde::Deserialize; +use toml_edit::{DocumentMut, Formatted, Item, Table}; + +mod tui; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Root of the project + #[arg(short = 'P', long)] + path: Option, + + /// Chip + #[arg(short = 'C', long, value_parser = chip_from_str)] + chip: Option, + + /// Config file - using `config.toml` by default + #[arg(short = 'c', long)] + config_file: Option, +} + +fn chip_from_str(str: &str) -> Result, String> { + Chip::from_str(str).map(Some) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CrateConfig { + name: String, + options: Vec, + checks: Option>, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +struct ConfigItem { + option: ConfigOption, + actual_value: Value, +} + +fn main() -> Result<(), Box> { + Builder::from_env(Env::default().default_filter_or(log::LevelFilter::Info.as_str())) + .format_target(false) + .init(); + + let args = Args::parse(); + + let work_dir = args.path.clone().unwrap_or(".".into()); + + let config_file = if args.config_file.is_none() { + // if there are multiple config files and none is selected via the command line option + // let the user choose (but don't offer the base config.toml) + + let mut config_file = None; + let cargo_dir = work_dir.join(".cargo"); + + if cargo_dir.exists() { + let files: Vec = cargo_dir + .read_dir()? + .filter_map(|e| e.ok()) + .filter(|entry| { + entry.path().is_file() + && entry.path().extension().unwrap_or_default() == "toml" + && entry.file_name().to_string_lossy() != "config.toml" + }) + .map(|entry| entry.file_name().to_string_lossy().to_string()) + .collect(); + + if !files.is_empty() { + let terminal = tui::init_terminal()?; + let mut chooser = tui::ConfigChooser::new(files); + config_file = chooser.run(terminal)?; + tui::restore_terminal()?; + } else { + config_file = Some("config.toml".to_string()); + } + } + + config_file + } else { + Some( + args.config_file + .as_deref() + .unwrap_or("config.toml") + .to_string(), + ) + }; + + if config_file.is_none() { + return Ok(()); + } + + let config_file = config_file.unwrap(); + + let config_file_path = work_dir.join(".cargo").join(config_file); + if !config_file_path.exists() { + return Err(format!( + "Config file {} does not exist or is not readable.", + config_file_path.display() + ) + .into()); + } + + let (hint_about_config_toml, configs) = parse_configs(&work_dir, args.chip, &config_file_path)?; + let initial_configs = configs.clone(); + let previous_config = initial_configs.clone(); + + let repository = tui::Repository::new(configs.clone()); + + // TUI stuff ahead + let terminal = tui::init_terminal()?; + + // create app and run it + let updated_cfg = tui::App::new(if hint_about_config_toml { + Some("[env] section in base config.toml detected - avoid this and only add [env] sections to individual configs".to_string()) } else { None }, repository) + .run(terminal)?; + + tui::restore_terminal()?; + + // done with the TUI + if let Some(updated_cfg) = updated_cfg { + apply_config(updated_cfg, previous_config, &config_file_path)?; + } + + Ok(()) +} + +fn apply_config( + updated_cfg: Vec, + previous_cfg: Vec, + config_toml_path: &PathBuf, +) -> Result<(), Box> { + let mut config = std::fs::read_to_string(config_toml_path)? + .as_str() + .parse::()?; + + if !config.contains_key("env") { + config.insert("env", Item::Table(Table::new())); + } + + let envs = config.get_mut("env").unwrap().as_table_mut().unwrap(); + + for cfg in updated_cfg { + let previous_crate_cfg = previous_cfg.iter().find(|c| c.name == cfg.name); + + for option in cfg.options { + let previous_option = previous_crate_cfg.and_then(|c| { + c.options + .iter() + .find(|o| o.option.name == option.option.name) + }); + let key = option.option.full_env_var(&cfg.name); + + // avoid updating unchanged options to keep the comments (if any) + if Some(&option.actual_value) != previous_option.map(|option| &option.actual_value) { + if option.actual_value != option.option.default_value { + let value = toml_edit::Value::String(Formatted::new(format!( + "{}", + option.actual_value + ))); + + envs.insert(&key, Item::Value(value)); + } else { + envs.remove(&key); + } + } + } + } + + std::fs::write(config_toml_path, config.to_string().as_bytes())?; + + Ok(()) +} + +fn parse_configs( + path: &Path, + chip_from_args: Option, + config_toml_path: &PathBuf, +) -> Result<(bool, Vec), Box> { + let mut hint_about_configs = false; + + // check if we find multiple potential config files - if yes and if the base `config.toml` + // contains an [env] section let the user know this is not ideal + if let Some(config_toml_dir) = config_toml_path.parent() { + if config_toml_dir + .read_dir()? + .filter(|entry| { + if let Ok(entry) = entry { + entry.path().is_file() && entry.path().extension().unwrap_or_default() == "toml" + } else { + false + } + }) + .count() + > 1 + { + let base_toml = config_toml_dir.join("config.toml"); + if base_toml.exists() { + let base_toml_content = std::fs::read_to_string(base_toml)?; + let base_toml = base_toml_content.as_str().parse::()?; + if base_toml.contains_key("env") { + hint_about_configs = true; + } + } + } + } + + let config_toml_content = std::fs::read_to_string(config_toml_path)?; + let config_toml = config_toml_content.as_str().parse::()?; + + let envs: HashMap = config_toml + .get("env") + .and_then(Item::as_table) + .map(|table| { + table + .iter() + .filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string()))) + .collect() + }) + .unwrap_or_default(); + + // Get the metadata of the project to + // - discover configurable crates + // - get the active features on crates (e.g. to guess the chip the project is targeting) + // this might fetch the dependencies from registries and/or git repositories + // so this + // - might take a few seconds (while it's usually very quick) + // - might need an internet connection to succeed + + let meta = cargo_metadata::MetadataCommand::new() + .current_dir(path) + .verbose(true) // entertain the user by showing what exactly is going on + .exec() + // with verbose output the error we get doesn't contain anything useful or interesting + .map_err(|_| "`cargo metadata` failed for your project. Make sure it's buildable.")?; + + // try to guess the chip from the metadata by looking at an active chip feature + // for esp-hal + let chip_from_meta = || { + let mut chip = None; + for pkg in &meta.root_package().unwrap().dependencies { + if pkg.name == "esp-hal" { + chip = pkg.features.iter().flat_map(|f| Chip::from_str(f)).next(); + } + } + chip + }; + + // the "ESP_CONFIG_CHIP" hint env-var if present (could be set in a config.toml) + let chip_from_config = || { + envs.get("ESP_CONFIG_CHIP") + .and_then(|chip_str| Chip::from_str(&chip_str.to_lowercase()).ok()) + }; + + // - if given as a parameter, use it + // - if there is a hint in the config.toml, use it + // - if we can infer it from metadata, use it + // otherwise, fail + let Some(chip) = chip_from_args + .or_else(chip_from_config) + .or_else(chip_from_meta) + else { + return Err("No chip given or inferred. Try using the `--chip` argument.".into()); + }; + + let mut configs = Vec::new(); + let features = vec![]; + for krate in meta.packages { + let maybe_cfg = krate.manifest_path.parent().unwrap().join("esp_config.yml"); + if maybe_cfg.exists() { + let yaml = std::fs::read_to_string(&maybe_cfg)?; + let (cfg, options) = + esp_config::evaluate_yaml_config(&yaml, Some(chip), features.clone(), true) + .map_err(|e| { + format!( + "Error evaluating YAML config for crate {}: {}", + krate.name, e + ) + })?; + + let crate_name = cfg.krate.clone(); + + let options: Vec = options + .iter() + .map(|cfg| { + Ok::>(ConfigItem { + option: cfg.clone(), + actual_value: { + let key = cfg.full_env_var(&crate_name); + let def_val = &cfg.default_value.to_string(); + let val = envs.get(&key).unwrap_or(def_val); + + let mut parsed_val = cfg.default_value.clone(); + parsed_val.parse_in_place(val).map_err(|_| { + >>::into(format!( + "Unable to parse '{val}' for option '{}'", + &cfg.name + )) + })?; + parsed_val + }, + }) + }) + .collect::, Box>>()?; + + configs.push(CrateConfig { + name: crate_name.clone(), + checks: cfg.checks.clone(), + options, + }); + } + } + + if configs.is_empty() { + return Err("No config files found.".into()); + } + + Ok((hint_about_configs, configs)) +} diff --git a/esp-config/src/bin/esp-config/tui.rs b/esp-config/src/bin/esp-config/tui.rs new file mode 100644 index 00000000000..95a400c5d9d --- /dev/null +++ b/esp-config/src/bin/esp-config/tui.rs @@ -0,0 +1,860 @@ +use std::{collections::HashMap, error::Error, io}; + +use esp_config::{DisplayHint, Stability, Validator, Value}; +use ratatui::{ + crossterm::{ + ExecutableCommand, + event::{self, Event, KeyCode, KeyEventKind}, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, + }, + prelude::*, + style::palette::tailwind, + widgets::*, +}; +use tui_textarea::{CursorMove, TextArea}; + +use crate::CrateConfig; + +type AppResult = Result>; + +pub struct Repository { + configs: Vec, + current_crate: Option, +} + +enum Item { + TopLevel(String), + CrateLevel(crate::ConfigItem), +} + +impl Item { + fn title(&self, _width: u16, ui_elements: &UiElements) -> String { + match self { + Item::TopLevel(crate_name) => crate_name.clone(), + Item::CrateLevel(config_option) => { + let display_value = config_option + .option + .display_hint + .format_value(&config_option.actual_value); + let default_indicator = + if config_option.actual_value == config_option.option.default_value { + ui_elements.default_value + } else { + "" + }; + + let unstable_indicator = if config_option.option.stability == Stability::Unstable { + ui_elements.unstable + } else { + "" + }; + + format!( + "{} ({}{}){}", + config_option.option.name, display_value, default_indicator, unstable_indicator + ) + } + } + } + + fn help_text(&self) -> String { + match self { + Item::TopLevel(crate_name) => format!("The `{crate_name}` crate"), + Item::CrateLevel(config_option) => config_option.option.description.clone(), + } + .replace("

", "") + .replace("

", "\n") + .replace("
", "\n") + .to_string() + } + + fn value(&self) -> Value { + match self { + Item::TopLevel(_) => unreachable!(), + Item::CrateLevel(config_option) => config_option.actual_value.clone(), + } + } + + fn constraint(&self) -> Option { + match self { + Item::TopLevel(_) => unreachable!(), + Item::CrateLevel(config_option) => config_option.option.constraint.clone(), + } + } + + fn display_hint(&self) -> DisplayHint { + match self { + Item::TopLevel(_) => unreachable!(), + Item::CrateLevel(config_option) => config_option.option.display_hint, + } + } +} + +impl Repository { + pub fn new(options: Vec) -> Self { + Self { + configs: options, + current_crate: None, + } + } + + fn current_level(&self) -> Vec { + if let Some(crate_index) = self.current_crate { + Vec::from_iter( + self.configs[crate_index] + .options + .iter() + .map(|option| Item::CrateLevel(option.clone())), + ) + } else { + Vec::from_iter( + self.configs + .iter() + .map(|config| Item::TopLevel(config.name.clone())), + ) + } + } + + fn enter_group(&mut self, index: usize) { + if self.current_crate.is_none() { + self.current_crate = Some(index); + } + } + + fn up(&mut self) { + if self.current_crate.is_some() { + self.current_crate = None; + } + } + + fn set_current(&mut self, index: usize, new_value: Value) -> Result<(), String> { + if self.current_crate.is_none() { + return Ok(()); + } + + let crate_config = &mut self.configs[self.current_crate.unwrap()]; + let previous = crate_config.options[index].actual_value.clone(); + crate_config.options[index].actual_value = new_value; + + let res = validate_config(crate_config); + if let Err(error) = res { + crate_config.options[index].actual_value = previous; + return Err(error.to_string()); + } + + Ok(()) + } + + // true if this is a configurable option + fn is_option(&self, _index: usize) -> bool { + self.current_crate.is_some() + } + + // What to show in the list + fn current_level_desc(&self, width: u16, ui_elements: &UiElements) -> Vec { + let level = self.current_level(); + + level.iter().map(|v| v.title(width, ui_elements)).collect() + } +} + +pub fn init_terminal() -> AppResult> { + enable_raw_mode()?; + io::stdout().execute(EnterAlternateScreen)?; + let backend = CrosstermBackend::new(io::stdout()); + let terminal = Terminal::new(backend)?; + Ok(terminal) +} + +pub fn restore_terminal() -> AppResult<()> { + disable_raw_mode()?; + io::stdout().execute(LeaveAlternateScreen)?; + Ok(()) +} + +struct UiElements { + default_value: &'static str, + unstable: &'static str, + popup_highlight_symbol: &'static str, +} + +struct Colors { + header_bg: Color, + normal_row_color: Color, + help_row_color: Color, + text_color: Color, + + selected_active_style: Style, + edit_invalid_style: Style, + edit_valid_style: Style, + border_style: Style, + border_error_style: Style, +} + +impl Colors { + const RGB: Self = Self { + header_bg: tailwind::BLUE.c950, + normal_row_color: tailwind::SLATE.c950, + help_row_color: tailwind::SLATE.c800, + text_color: tailwind::SLATE.c200, + + selected_active_style: Style::new() + .add_modifier(Modifier::BOLD) + .fg(tailwind::SLATE.c200) + .bg(tailwind::BLUE.c950), + edit_invalid_style: Style::new().add_modifier(Modifier::BOLD).fg(Color::Red), + edit_valid_style: Style::new() + .add_modifier(Modifier::BOLD) + .fg(tailwind::SLATE.c200), + border_style: Style::new() + .add_modifier(Modifier::BOLD) + .fg(Color::LightBlue), + border_error_style: Style::new().add_modifier(Modifier::BOLD).fg(Color::Red), + }; + const ANSI: Self = Self { + header_bg: Color::DarkGray, + normal_row_color: Color::Black, + help_row_color: Color::DarkGray, + text_color: Color::Gray, + + selected_active_style: Style::new() + .add_modifier(Modifier::BOLD) + .fg(Color::White) + .bg(Color::Blue), + edit_invalid_style: Style::new().add_modifier(Modifier::BOLD).fg(Color::Red), + edit_valid_style: Style::new().add_modifier(Modifier::BOLD).fg(Color::Gray), + border_style: Style::new() + .add_modifier(Modifier::BOLD) + .fg(Color::LightBlue), + border_error_style: Style::new().add_modifier(Modifier::BOLD).fg(Color::Red), + }; +} + +impl UiElements { + const FANCY: Self = Self { + default_value: " ⭐", + unstable: " 🚧", + popup_highlight_symbol: "▶️ ", + }; + const FALLBACK: Self = Self { + default_value: " *", + unstable: " !", + popup_highlight_symbol: "> ", + }; +} + +pub struct App<'a> { + repository: Repository, + + state: Vec, + + confirm_quit: bool, + + editing: bool, + textarea: TextArea<'a>, + editing_constraints: Option, + input_valid: bool, + + showing_selection_popup: bool, + list_popup: List<'a>, + list_popup_state: ListState, + + show_error_message: bool, + initial_message: Option, + + ui_elements: UiElements, + colors: Colors, +} + +impl App<'_> { + pub fn new(errors_to_show: Option, repository: Repository) -> Self { + let (ui_elements, colors) = match std::env::var("TERM_PROGRAM").as_deref() { + Ok("vscode") => (UiElements::FALLBACK, Colors::RGB), + Ok("Apple_Terminal") => (UiElements::FALLBACK, Colors::ANSI), + _ => (UiElements::FANCY, Colors::RGB), + }; + + let mut initial_state = ListState::default(); + initial_state.select(Some(0)); + + Self { + repository, + state: vec![initial_state], + confirm_quit: false, + editing: false, + textarea: TextArea::default(), + editing_constraints: None, + input_valid: true, + showing_selection_popup: false, + list_popup: List::default(), + list_popup_state: ListState::default(), + show_error_message: errors_to_show.is_some(), + initial_message: errors_to_show, + ui_elements, + colors, + } + } + + pub fn selected(&self) -> usize { + if let Some(current) = self.state.last() { + current.selected().unwrap_or_default() + } else { + 0 + } + } + + pub fn select_next(&mut self) { + if let Some(current) = self.state.last_mut() { + current.select_next(); + } + } + pub fn select_previous(&mut self) { + if let Some(current) = self.state.last_mut() { + current.select_previous(); + } + } + pub fn enter_menu(&mut self) { + let mut new_state = ListState::default(); + new_state.select(Some(0)); + self.state.push(new_state); + } + pub fn exit_menu(&mut self) { + if self.state.len() > 1 { + self.state.pop(); + } + } +} + +impl App<'_> { + pub fn run( + &mut self, + mut terminal: Terminal, + ) -> AppResult>> { + loop { + self.draw(&mut terminal)?; + + if let Event::Key(key) = event::read()? { + if self.editing { + match key.code { + KeyCode::Enter if key.kind == KeyEventKind::Press => { + if !self.input_valid { + continue; + } + + let selected = self.selected(); + if self.repository.is_option(selected) { + let current = self.repository.current_level()[selected].value(); + let text = self.textarea.lines().join("").to_string(); + let mut value = current.clone(); + if value.parse_in_place(&text).is_ok() { + let set_res = self.repository.set_current(selected, value); + self.handle_error(set_res); + } else { + self.handle_error(Err("Invalid value".to_string())); + } + } + + self.editing = false; + } + KeyCode::Esc => { + self.editing = false; + } + _ => { + if self.textarea.input(key) { + let selected = self.selected(); + if self.repository.is_option(selected) { + let current = self.repository.current_level()[selected].value(); + let text = self.textarea.lines().join("").to_string(); + let mut parsed_value = current.clone(); + let parse_res = parsed_value.parse_in_place(&text); + let validator_failed = if let Some(constraint) = + &self.editing_constraints + { + match parse_res { + Ok(()) => constraint.validate(&parsed_value).is_err(), + _ => false, + } + } else { + false + }; + + let invalid = parse_res.is_err() || validator_failed; + + self.textarea.set_style(if invalid { + self.colors.edit_invalid_style + } else { + self.colors.edit_valid_style + }); + self.input_valid = !invalid; + } + } + } + } + } else if self.showing_selection_popup && key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Char('q') | KeyCode::Esc => { + self.showing_selection_popup = false; + } + KeyCode::Down | KeyCode::Char('j') => self.list_popup_state.select_next(), + KeyCode::Up | KeyCode::Char('k') => self.list_popup_state.select_previous(), + KeyCode::Enter if key.kind == KeyEventKind::Press => { + let selected = self.selected(); + if let Some(Validator::Enumeration(items)) = &self.repository.configs + [self.repository.current_crate.unwrap()] + .options[selected] + .option + .constraint + { + let set_res = self.repository.set_current( + selected, + Value::String( + items[self.list_popup_state.selected().unwrap()].clone(), + ), + ); + self.handle_error(set_res); + } + self.showing_selection_popup = false; + } + _ => (), + } + } else if self.show_error_message { + match key.code { + KeyCode::Enter if key.kind == KeyEventKind::Press => { + self.show_error_message = false; + } + _ => (), + } + } else if key.kind == KeyEventKind::Press { + use KeyCode::*; + + if self.confirm_quit { + match key.code { + Char('y') | Char('Y') => return Ok(None), + _ => self.confirm_quit = false, + } + continue; + } + + match key.code { + Char('q') => self.confirm_quit = true, + Char('s') | Char('S') => return Ok(Some(self.repository.configs.clone())), + Esc => { + if self.state.len() == 1 { + self.confirm_quit = true; + } else { + self.repository.up(); + self.exit_menu(); + } + } + Char('h') | Left => { + self.repository.up(); + self.exit_menu(); + } + Char('l') | Char(' ') | Right | Enter => { + let selected = self.selected(); + if self.repository.is_option(selected) { + let current = self.repository.current_level()[selected].value(); + let constraint = + self.repository.current_level()[selected].constraint(); + + match current { + Value::Bool(value) => { + let set_res = self + .repository + .set_current(selected, Value::Bool(!value)); + self.handle_error(set_res); + } + Value::Integer(_) => { + let display_value = self.repository.current_level() + [selected] + .display_hint() + .format_value(¤t); + self.textarea = + make_text_area(&display_value, &self.colors); + self.editing_constraints = constraint; + self.editing = true; + } + Value::String(s) => match constraint { + Some(Validator::Enumeration(items)) => { + let selected_option = + items.iter().position(|v| *v == s); + self.list_popup = + make_popup(items, &self.ui_elements, &self.colors); + self.list_popup_state = ListState::default(); + self.list_popup_state.select(selected_option); + self.showing_selection_popup = true; + } + _ => { + self.textarea = make_text_area(&s, &self.colors); + self.editing_constraints = None; + self.editing = true; + } + }, + } + } else { + self.repository.enter_group(self.selected()); + self.enter_menu(); + } + } + Char('j') | Down => { + self.select_next(); + } + Char('k') | Up => { + self.select_previous(); + } + _ => {} + } + } + } + } + } + + fn draw(&mut self, terminal: &mut Terminal) -> AppResult<()> { + terminal.draw(|f| { + f.render_widget(self, f.area()); + })?; + + Ok(()) + } + + fn handle_error(&mut self, result: Result<(), String>) { + if let Err(error) = result { + self.show_error_message = true; + self.initial_message = Some(error); + } + } +} + +fn make_text_area<'a>(s: &str, colors: &Colors) -> TextArea<'a> { + let mut text_area = TextArea::new(vec![s.to_string()]); + text_area.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(colors.border_style) + .title("Input"), + ); + text_area.set_style(colors.edit_valid_style); + text_area.set_cursor_line_style(Style::default()); + text_area.move_cursor(CursorMove::End); + text_area +} + +fn make_popup<'a>(items: Vec, ui_elements: &UiElements, colors: &Colors) -> List<'a> { + List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(colors.border_style) + .title("Choose"), + ) + .highlight_style(colors.selected_active_style) + .highlight_symbol(ui_elements.popup_highlight_symbol) + .repeat_highlight_symbol(true) +} + +impl Widget for &mut App<'_> { + fn render(self, area: Rect, buf: &mut Buffer) { + let vertical = Layout::vertical([ + Constraint::Length(2), + Constraint::Fill(1), + Constraint::Length(self.help_lines(area)), + Constraint::Length(self.footer_lines(area)), + ]); + let [header_area, rest_area, help_area, footer_area] = vertical.areas(area); + + self.render_title(header_area, buf); + self.render_item(rest_area, buf); + self.render_help(help_area, buf); + self.render_footer(footer_area, buf); + + if self.editing { + let area = Rect { + x: 5, + y: area.height / 2 - 2, + width: area.width - 10, + height: 3, + }; + + ratatui::widgets::Clear.render(area, buf); + self.textarea.render(area, buf); + } + + if self.showing_selection_popup { + let area = Rect { + x: 5, + y: area.height / 2 - 3, + width: area.width - 10, + height: 6, + }; + + ratatui::widgets::Clear.render(area, buf); + StatefulWidget::render(&self.list_popup, area, buf, &mut self.list_popup_state); + } + + if self.show_error_message { + let area = Rect { + x: 5, + y: area.height / 2 - 5, + width: area.width - 10, + height: 5, + }; + + let block = Paragraph::new(self.initial_message.as_ref().unwrap().clone()) + .style(self.colors.edit_invalid_style) + .block( + Block::bordered() + .style(self.colors.border_error_style) + .padding(Padding::uniform(1)), + ); + + ratatui::widgets::Clear.render(area, buf); + block.render(area, buf); + } + } +} + +impl App<'_> { + fn render_title(&self, area: Rect, buf: &mut Buffer) { + Paragraph::new("esp-config") + .bold() + .centered() + .render(area, buf); + } + + fn render_item(&mut self, area: Rect, buf: &mut Buffer) { + // We create two blocks, one is for the header (outer) and the other is for the + // list (inner). + let outer_block = Block::default() + .borders(Borders::NONE) + .fg(self.colors.text_color) + .bg(self.colors.header_bg) + .title_alignment(Alignment::Center); + let inner_block = Block::default() + .borders(Borders::NONE) + .fg(self.colors.text_color) + .bg(self.colors.normal_row_color); + + // We get the inner area from outer_block. We'll use this area later to render + // the table. + let outer_area = area; + let inner_area = outer_block.inner(outer_area); + + // We can render the header in outer_area. + outer_block.render(outer_area, buf); + + // Iterate through all elements in the `items` and stylize them. + let items: Vec = self + .repository + .current_level_desc(area.width, &self.ui_elements) + .into_iter() + .map(|value| ListItem::new(value).style(Style::default())) + .collect(); + + // We can now render the item list + // (look carefully, we are using StatefulWidget's render.) + // ratatui::widgets::StatefulWidget::render as stateful_render + let current_state = self + .state + .last_mut() + .expect("State should always have at least one element"); + let list_widget = List::new(items) + .block(inner_block) + .highlight_style(self.colors.selected_active_style) + .highlight_spacing(HighlightSpacing::Always); + StatefulWidget::render(list_widget, inner_area, buf, current_state); + } + + fn help_paragraph(&self) -> Option> { + let selected = self + .selected() + .min(self.repository.current_level().len() - 1); + let option = &self.repository.current_level()[selected]; + let help_text = option.help_text(); + if help_text.is_empty() { + return None; + } + + let help_block = Block::default() + .borders(Borders::NONE) + .fg(self.colors.text_color) + .bg(self.colors.help_row_color); + + Some( + Paragraph::new(help_text) + .centered() + .wrap(Wrap { trim: false }) + .block(help_block), + ) + } + + fn help_lines(&self, area: Rect) -> u16 { + if let Some(paragraph) = self.help_paragraph() { + paragraph.line_count(area.width) as u16 + } else { + 0 + } + } + + fn render_help(&self, area: Rect, buf: &mut Buffer) { + if let Some(paragraph) = self.help_paragraph() { + paragraph.render(area, buf); + } + } + + fn footer_paragraph(&self) -> Paragraph<'_> { + let text = if self.confirm_quit { + "Are you sure you want to quit? (y/N)" + } else if self.editing { + "ENTER to confirm, ESC to cancel" + } else if self.showing_selection_popup { + "Use ↓↑ to move, ENTER to confirm, ESC to cancel" + } else if self.show_error_message { + "ENTER to confirm" + } else { + "Use ↓↑ to move, ESC/← to go up, → to go deeper or change the value, s/S to save and generate, ESC/q to cancel" + }; + + Paragraph::new(text).centered().wrap(Wrap { trim: false }) + } + + fn footer_lines(&self, area: Rect) -> u16 { + self.footer_paragraph().line_count(area.width) as u16 + } + + fn render_footer(&self, area: Rect, buf: &mut Buffer) { + self.footer_paragraph().render(area, buf); + } +} + +pub(super) fn validate_config(config: &CrateConfig) -> Result<(), String> { + let cfg: HashMap = config + .options + .iter() + .map(|option| { + ( + option.option.full_env_var(&config.name), + option.actual_value.clone(), + ) + }) + .collect(); + if let Err(error) = esp_config::do_checks(config.checks.as_ref(), &cfg) { + return Err(error.to_string()); + } + Ok(()) +} + +pub struct ConfigChooser { + config_files: Vec, + state: ListState, + colors: Colors, +} + +impl ConfigChooser { + pub fn new(config_files: Vec) -> Self { + let colors = match std::env::var("TERM_PROGRAM").as_deref() { + Ok("vscode") => Colors::RGB, + Ok("Apple_Terminal") => Colors::ANSI, + _ => Colors::RGB, + }; + + let state = ListState::default().with_selected(Some(0)); + + Self { + config_files, + state, + colors, + } + } + + pub fn run(&mut self, mut terminal: Terminal) -> AppResult> { + loop { + self.draw(&mut terminal)?; + + if let Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Char('q') => return Ok(None), + KeyCode::Esc => return Ok(None), + KeyCode::Char('j') | KeyCode::Down => { + self.state.select_next(); + } + KeyCode::Char('k') | KeyCode::Up => { + self.state.select_previous(); + } + KeyCode::Enter => { + let selected = self.state.selected().unwrap_or_default(); + return Ok(Some(self.config_files[selected].clone())); + } + _ => {} + } + } + } + } + } + + fn draw(&mut self, terminal: &mut Terminal) -> AppResult<()> { + terminal.draw(|f| { + f.render_widget(self, f.area()); + })?; + + Ok(()) + } +} + +impl Widget for &mut ConfigChooser { + fn render(self, area: Rect, buf: &mut Buffer) { + let vertical = Layout::vertical([ + Constraint::Length(2), + Constraint::Fill(1), + Constraint::Length(1), + ]); + let [header_area, rest_area, footer_area] = vertical.areas(area); + + Paragraph::new("esp-config") + .bold() + .centered() + .render(header_area, buf); + + Paragraph::new("Choose a config to edit") + .bold() + .centered() + .render(footer_area, buf); + + // We create two blocks, one is for the header (outer) and the other is for the + // list (inner). + let outer_block = Block::default() + .borders(Borders::NONE) + .fg(self.colors.text_color) + .bg(self.colors.header_bg) + .title_alignment(Alignment::Center); + let inner_block = Block::default() + .borders(Borders::NONE) + .fg(self.colors.text_color) + .bg(self.colors.normal_row_color); + + // We get the inner area from outer_block. We'll use this area later to render + // the table. + let outer_area = rest_area; + let inner_area = outer_block.inner(outer_area); + + // We can render the header in outer_area. + outer_block.render(outer_area, buf); + + // Iterate through all elements in the `items` and stylize them. + let items: Vec = self + .config_files + .iter() + .map(|value| ListItem::new(value.as_str()).style(Style::default())) + .collect(); + + // We can now render the item list + // (look carefully, we are using StatefulWidget's render.) + // ratatui::widgets::StatefulWidget::render as stateful_render + let state = &mut self.state; + let list_widget = List::new(items) + .block(inner_block) + .highlight_style(self.colors.selected_active_style) + .highlight_spacing(HighlightSpacing::Always); + StatefulWidget::render(list_widget, inner_area, buf, state); + } +} diff --git a/esp-config/src/generate/markdown.rs b/esp-config/src/generate/markdown.rs new file mode 100644 index 00000000000..15ff94ea813 --- /dev/null +++ b/esp-config/src/generate/markdown.rs @@ -0,0 +1,36 @@ +use std::fmt::Write; + +use crate::{ConfigOption, Value}; + +pub(crate) const DOC_TABLE_HEADER: &str = r#" +| Option | Stability | Default value | Allowed values | +|--------|:---------:|:------------------:|:-------------------:| +"#; + +pub(crate) const SELECTED_TABLE_HEADER: &str = r#" +| Name | Selected value | +|------|----------------| +"#; + +pub(crate) fn write_doc_table_line(mut table: impl Write, name: &str, option: &ConfigOption) { + let allowed_values = option + .constraint + .as_ref() + .and_then(|validator| validator.description()) + .unwrap_or_default(); + + writeln!( + table, + "|

**{key}**

{description}

| {stability} | {default} | {allowed}", + description = option.description, + key = name, + stability = option.stability, + default = option.display_hint.format_value(&option.default_value), + allowed = allowed_values + ) + .unwrap(); +} + +pub(crate) fn write_summary_table_line(mut table: impl Write, name: &str, value: &Value) { + writeln!(table, "|**{name}**|{value}|").unwrap(); +} diff --git a/esp-config/src/generate/mod.rs b/esp-config/src/generate/mod.rs new file mode 100644 index 00000000000..84abfeca9e8 --- /dev/null +++ b/esp-config/src/generate/mod.rs @@ -0,0 +1,1129 @@ +use core::fmt::Display; +use std::{collections::HashMap, env, fmt, fs, io::Write, path::PathBuf}; + +use serde::{Deserialize, Serialize}; +use somni_expr::TypeSet128; + +use crate::generate::{validator::Validator, value::Value}; + +mod markdown; +pub(crate) mod validator; +pub(crate) mod value; + +/// Configuration errors. +#[derive(Clone, PartialEq, Eq)] +pub enum Error { + /// Parse errors. + Parse(String), + /// Validation errors. + Validation(String), +} + +impl Error { + /// Convenience function for creating parse errors. + pub fn parse(message: S) -> Self + where + S: Into, + { + Self::Parse(message.into()) + } + + /// Convenience function for creating validation errors. + pub fn validation(message: S) -> Self + where + S: Into, + { + Self::Validation(message.into()) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Parse(message) => write!(f, "{message}"), + Error::Validation(message) => write!(f, "{message}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + None + } + + fn description(&self) -> &str { + "description() is deprecated; use Display" + } + + fn cause(&self) -> Option<&dyn core::error::Error> { + self.source() + } +} + +/// The root node of a configuration. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct Config { + /// The crate name. + #[serde(rename = "crate")] + pub krate: String, + /// The config options for this crate. + pub options: Vec, + /// Optionally additional checks. + pub checks: Option>, +} + +fn true_default() -> String { + "true".to_string() +} + +fn unstable_default() -> Stability { + Stability::Unstable +} + +/// A default value for a configuration option. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CfgDefaultValue { + /// Condition which makes this default value used. + /// You can and have to have exactly one active default value. + #[serde(rename = "if")] + #[serde(default = "true_default")] + pub if_: String, + /// The default value. + pub value: Value, +} + +/// A configuration option. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CfgOption { + /// Name of the configuration option + pub name: String, + /// Description of the configuration option. + /// This will be visible in the documentation and in the tooling. + pub description: String, + /// A condition which specified when this option is active. + #[serde(default = "true_default")] + pub active: String, + /// The default value. + /// Exactly one of the items needs to be active at any time. + pub default: Vec, + /// Constraints (Validators) to use. + /// If given at most one item is allowed to be active at any time. + pub constraints: Option>, + /// A display hint for the value. + /// This is meant for tooling and/or documentation. + pub display_hint: Option, + /// The stability guarantees of this option. + #[serde(default = "unstable_default")] + pub stability: Stability, +} + +/// A conditional constraint / validator for a config option. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CfgConstraint { + /// Condition which makes this validator used. + #[serde(rename = "if")] + #[serde(default = "true_default")] + if_: String, + /// The validator to be used. + #[serde(rename = "type")] + type_: Validator, +} + +/// Generate the config from a YAML definition. +/// +/// After deserializing the config and normalizing it, this will call +/// [generate_config] to finally get the currently active configuration. +pub fn generate_config_from_yaml_definition( + yaml: &str, + enable_unstable: bool, + emit_md_tables: bool, + chip: Option, +) -> Result, Error> { + let features: Vec = env::vars() + .filter(|(k, _)| k.starts_with("CARGO_FEATURE_")) + .map(|(k, _)| k) + .map(|v| { + v.strip_prefix("CARGO_FEATURE_") + .unwrap_or_default() + .to_string() + }) + .collect(); + + let (config, options) = evaluate_yaml_config(yaml, chip, features, false)?; + + let cfg = generate_config(&config.krate, &options, enable_unstable, emit_md_tables); + + do_checks(config.checks.as_ref(), &cfg)?; + + Ok(cfg) +} + +/// Check the given actual values by applying checking the given checks +pub fn do_checks(checks: Option<&Vec>, cfg: &HashMap) -> Result<(), Error> { + if let Some(checks) = checks { + let mut eval_ctx = somni_expr::Context::::new_with_types(); + for (k, v) in cfg.iter() { + match v { + Value::Bool(v) => eval_ctx.add_variable(k, *v), + Value::Integer(v) => eval_ctx.add_variable(k, *v), + Value::String(v) => eval_ctx.add_variable::<&str>(k, v), + } + } + for check in checks { + if !eval_ctx + .evaluate::(check) + .map_err(|err| Error::Parse(format!("Validation error: {err:?}")))? + { + return Err(Error::Validation(format!("Validation error: '{check}'"))); + } + } + }; + Ok(()) +} + +/// Evaluate the given YAML representation of a config definition. +pub fn evaluate_yaml_config( + yaml: &str, + chip: Option, + features: Vec, + ignore_feature_gates: bool, +) -> Result<(Config, Vec), Error> { + let config: Config = serde_yaml::from_str(yaml).map_err(|err| Error::Parse(err.to_string()))?; + let mut options = Vec::new(); + let mut eval_ctx = somni_expr::Context::new(); + if let Some(chip) = chip { + eval_ctx.add_variable("chip", chip.name()); + eval_ctx.add_variable("ignore_feature_gates", ignore_feature_gates); + eval_ctx.add_function("feature", move |feature: &str| chip.contains(feature)); + eval_ctx.add_function("cargo_feature", |feature: &str| { + features.contains(&feature.to_uppercase().replace("-", "_")) + }); + } + for option in &config.options { + let active = eval_ctx + .evaluate::(&option.active) + .map_err(|err| Error::Parse(format!("{err:?}")))?; + + let constraint = { + let mut active_constraint = None; + if let Some(constraints) = &option.constraints { + for constraint in constraints { + if eval_ctx + .evaluate::(&constraint.if_) + .map_err(|err| Error::Parse(format!("{err:?}")))? + { + active_constraint = Some(constraint.type_.clone()); + break; + } + } + }; + + if option.constraints.is_some() && active_constraint.is_none() { + panic!( + "No constraint active for crate {}, option {}", + config.krate, option.name + ); + } + + active_constraint + }; + + let default_value = { + let mut default_value = None; + for value in &option.default { + if eval_ctx + .evaluate::(&value.if_) + .map_err(|err| Error::Parse(format!("{err:?}")))? + { + default_value = Some(value.value.clone()); + break; + } + } + + if default_value.is_none() { + panic!( + "No default value active for crate {}, option {}", + config.krate, option.name + ); + } + + default_value + }; + + let option = ConfigOption { + name: option.name.clone(), + description: option.description.clone(), + default_value: default_value.ok_or_else(|| { + Error::Parse(format!("No default value found for {}", option.name)) + })?, + constraint, + stability: option.stability.clone(), + active, + display_hint: option.display_hint.unwrap_or(DisplayHint::None), + }; + options.push(option); + } + Ok((config, options)) +} + +/// Generate and parse config from a prefix, and an array of [ConfigOption]. +/// +/// This function will parse any `SCREAMING_SNAKE_CASE` environment variables +/// that match the given prefix. It will then attempt to parse the [`Value`] and +/// run any validators which have been specified. +/// +/// [`Stability::Unstable`] features will only be enabled if the `unstable` +/// feature is enabled in the dependant crate. If the `unstable` feature is not +/// enabled, setting these options will result in a build error. +/// +/// Once the config has been parsed, this function will emit `snake_case` cfg's +/// _without_ the prefix which can be used in the dependant crate. After that, +/// it will create a markdown table in the `OUT_DIR` under the name +/// `{prefix}_config_table.md` where prefix has also been converted to +/// `snake_case`. This can be included in crate documentation to outline the +/// available configuration options for the crate. +/// +/// Passing a value of true for the `emit_md_tables` argument will create and +/// write markdown files of the available configuration and selected +/// configuration which can be included in documentation. +/// +/// Unknown keys with the supplied prefix will cause this function to panic. +pub fn generate_config( + crate_name: &str, + config: &[ConfigOption], + enable_unstable: bool, + emit_md_tables: bool, +) -> HashMap { + let configs = generate_config_internal(std::io::stdout(), crate_name, config, enable_unstable); + + if emit_md_tables { + let file_name = snake_case(crate_name); + + let mut doc_table = markdown::DOC_TABLE_HEADER.replace( + "{prefix}", + format!("{}_CONFIG_*", screaming_snake_case(crate_name)).as_str(), + ); + let mut selected_config = String::from(markdown::SELECTED_TABLE_HEADER); + + for (name, option, value) in configs.iter() { + if !option.active { + continue; + } + markdown::write_doc_table_line(&mut doc_table, name, option); + markdown::write_summary_table_line(&mut selected_config, name, value); + } + + write_out_file(format!("{file_name}_config_table.md"), doc_table); + write_out_file(format!("{file_name}_selected_config.md"), selected_config); + } + + // Remove the ConfigOptions from the output + configs.into_iter().map(|(k, _, v)| (k, v)).collect() +} + +pub fn generate_config_internal<'a>( + mut stdout: impl Write, + crate_name: &str, + config: &'a [ConfigOption], + enable_unstable: bool, +) -> Vec<(String, &'a ConfigOption, Value)> { + // Only rebuild if `build.rs` changed. Otherwise, Cargo will rebuild if any + // other file changed. + writeln!(stdout, "cargo:rerun-if-changed=build.rs").ok(); + + // Ensure that the prefix is `SCREAMING_SNAKE_CASE`: + let prefix = format!("{}_CONFIG_", screaming_snake_case(crate_name)); + + let mut configs = create_config(&prefix, config); + capture_from_env(crate_name, &prefix, &mut configs, enable_unstable); + + for (_, option, value) in configs.iter() { + if let Some(ref validator) = option.constraint { + validator.validate(value).unwrap(); + } + } + + emit_configuration(&mut stdout, &configs); + + configs +} + +/// The stability of the configuration option. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum Stability { + /// Unstable options need to be activated with the `unstable` feature + /// of the package that defines them. + Unstable, + /// Stable options contain the first version in which they were + /// stabilized. + Stable(String), +} + +impl Display for Stability { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Stability::Unstable => write!(f, "⚠️ Unstable"), + Stability::Stable(version) => write!(f, "Stable since {version}"), + } + } +} + +/// A display hint (for tooling only) +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum DisplayHint { + /// No display hint + None, + + /// Use a binary representation + Binary, + + /// Use a hexadecimal representation + Hex, + + /// Use a octal representation + Octal, +} + +impl DisplayHint { + /// Converts a [Value] to String applying the correct display hint. + pub fn format_value(self, value: &Value) -> String { + match value { + Value::Bool(b) => b.to_string(), + Value::Integer(i) => match self { + DisplayHint::None => format!("{i}"), + DisplayHint::Binary => format!("0b{i:0b}"), + DisplayHint::Hex => format!("0x{i:X}"), + DisplayHint::Octal => format!("0o{i:o}"), + }, + Value::String(s) => s.clone(), + } + } +} + +/// A configuration option. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct ConfigOption { + /// The name of the configuration option. + /// + /// The associated environment variable has the format of + /// `_CONFIG_`. + pub name: String, + + /// The description of the configuration option. + /// + /// The description will be included in the generated markdown + /// documentation. + pub description: String, + + /// The default value of the configuration option. + pub default_value: Value, + + /// An optional validator for the configuration option. + pub constraint: Option, + + /// The stability of the configuration option. + pub stability: Stability, + + /// Whether the config option should be offered to the user. + /// + /// Inactive options are not included in the documentation, and accessing + /// them provides the default value. + pub active: bool, + + /// A display hint (for tooling) + pub display_hint: DisplayHint, +} + +impl ConfigOption { + /// Get the corresponding ENV_VAR name given the crate-name + pub fn full_env_var(&self, crate_name: &str) -> String { + self.env_var(&format!("{}_CONFIG_", screaming_snake_case(crate_name))) + } + + fn env_var(&self, prefix: &str) -> String { + format!("{}{}", prefix, screaming_snake_case(&self.name)) + } + + fn cfg_name(&self) -> String { + snake_case(&self.name) + } + + fn is_stable(&self) -> bool { + matches!(self.stability, Stability::Stable(_)) + } +} + +fn create_config<'a>( + prefix: &str, + config: &'a [ConfigOption], +) -> Vec<(String, &'a ConfigOption, Value)> { + let mut configs = Vec::with_capacity(config.len()); + + for option in config { + configs.push((option.env_var(prefix), option, option.default_value.clone())); + } + + configs +} + +fn capture_from_env( + crate_name: &str, + prefix: &str, + configs: &mut Vec<(String, &ConfigOption, Value)>, + enable_unstable: bool, +) { + let mut unknown = Vec::new(); + let mut failed = Vec::new(); + let mut unstable = Vec::new(); + + // Try and capture input from the environment: + for (var, value) in env::vars() { + if var.starts_with(prefix) { + let Some((_, option, cfg)) = configs.iter_mut().find(|(k, _, _)| k == &var) else { + unknown.push(var); + continue; + }; + + if !option.active { + unknown.push(var); + continue; + } + + if !enable_unstable && !option.is_stable() { + unstable.push(var); + continue; + } + + if let Err(e) = cfg.parse_in_place(&value) { + failed.push(format!("{var}: {e}")); + } + } + } + + if !failed.is_empty() { + panic!("Invalid configuration options detected: {failed:?}"); + } + + if !unstable.is_empty() { + panic!( + "The following configuration options are unstable: {unstable:?}. You can enable it by \ + activating the 'unstable' feature in {crate_name}." + ); + } + + if !unknown.is_empty() { + panic!("Unknown configuration options detected: {unknown:?}"); + } +} + +fn emit_configuration(mut stdout: impl Write, configs: &[(String, &ConfigOption, Value)]) { + for (env_var_name, option, value) in configs.iter() { + let cfg_name = option.cfg_name(); + + // Output the raw configuration as an env var. Values that haven't been seen + // will be output here with the default value. Also trigger a rebuild if config + // environment variable changed. + writeln!(stdout, "cargo:rustc-env={env_var_name}={value}").ok(); + writeln!(stdout, "cargo:rerun-if-env-changed={env_var_name}").ok(); + + // Emit known config symbol: + writeln!(stdout, "cargo:rustc-check-cfg=cfg({cfg_name})").ok(); + + // Emit specially-handled values: + if let Value::Bool(true) = value { + writeln!(stdout, "cargo:rustc-cfg={cfg_name}").ok(); + } + + // Emit extra symbols based on the validator (e.g. enumerated values): + if let Some(validator) = option.constraint.as_ref() { + validator.emit_cargo_extras(&mut stdout, &cfg_name, value); + } + } +} + +fn write_out_file(file_name: String, json: String) { + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join(file_name); + fs::write(out_file, json).unwrap(); +} + +fn snake_case(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_lowercase(); + + name +} + +fn screaming_snake_case(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_uppercase(); + + name +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::generate::{validator::Validator, value::Value}; + + #[test] + fn value_number_formats() { + const INPUTS: &[&str] = &["0xAA", "0o252", "0b0000000010101010", "170"]; + let mut v = Value::Integer(0); + + for input in INPUTS { + v.parse_in_place(input).unwrap(); + // no matter the input format, the output format should be decimal + assert_eq!(v.to_string(), "170"); + } + } + + #[test] + fn value_bool_inputs() { + let mut v = Value::Bool(false); + + v.parse_in_place("true").unwrap(); + assert_eq!(v.to_string(), "true"); + + v.parse_in_place("false").unwrap(); + assert_eq!(v.to_string(), "false"); + + v.parse_in_place("else") + .expect_err("Only true or false are valid"); + } + + #[test] + fn env_override() { + temp_env::with_vars( + [ + ("ESP_TEST_CONFIG_NUMBER", Some("0xaa")), + ("ESP_TEST_CONFIG_NUMBER_SIGNED", Some("-999")), + ("ESP_TEST_CONFIG_STRING", Some("Hello world!")), + ("ESP_TEST_CONFIG_BOOL", Some("true")), + ], + || { + let configs = generate_config( + "esp-test", + &[ + ConfigOption { + name: String::from("number"), + description: String::from("NA"), + default_value: Value::Integer(999), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("number_signed"), + description: String::from("NA"), + default_value: Value::Integer(-777), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("string"), + description: String::from("NA"), + default_value: Value::String("Demo".to_string()), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("bool"), + description: String::from("NA"), + default_value: Value::Bool(false), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("number_default"), + description: String::from("NA"), + default_value: Value::Integer(999), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("string_default"), + description: String::from("NA"), + default_value: Value::String("Demo".to_string()), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("bool_default"), + description: String::from("NA"), + default_value: Value::Bool(false), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ], + false, + false, + ); + + // some values have changed + assert_eq!(configs["ESP_TEST_CONFIG_NUMBER"], Value::Integer(0xaa)); + assert_eq!( + configs["ESP_TEST_CONFIG_NUMBER_SIGNED"], + Value::Integer(-999) + ); + assert_eq!( + configs["ESP_TEST_CONFIG_STRING"], + Value::String("Hello world!".to_string()) + ); + assert_eq!(configs["ESP_TEST_CONFIG_BOOL"], Value::Bool(true)); + + // the rest are the defaults + assert_eq!( + configs["ESP_TEST_CONFIG_NUMBER_DEFAULT"], + Value::Integer(999) + ); + assert_eq!( + configs["ESP_TEST_CONFIG_STRING_DEFAULT"], + Value::String("Demo".to_string()) + ); + assert_eq!(configs["ESP_TEST_CONFIG_BOOL_DEFAULT"], Value::Bool(false)); + }, + ) + } + + #[test] + fn builtin_validation_passes() { + temp_env::with_vars( + [ + ("ESP_TEST_CONFIG_POSITIVE_NUMBER", Some("7")), + ("ESP_TEST_CONFIG_NEGATIVE_NUMBER", Some("-1")), + ("ESP_TEST_CONFIG_NON_NEGATIVE_NUMBER", Some("0")), + ("ESP_TEST_CONFIG_RANGE", Some("9")), + ], + || { + generate_config( + "esp-test", + &[ + ConfigOption { + name: String::from("positive_number"), + description: String::from("NA"), + default_value: Value::Integer(-1), + constraint: Some(Validator::PositiveInteger), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("negative_number"), + description: String::from("NA"), + default_value: Value::Integer(1), + constraint: Some(Validator::NegativeInteger), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("non_negative_number"), + description: String::from("NA"), + default_value: Value::Integer(-1), + constraint: Some(Validator::NonNegativeInteger), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: String::from("range"), + description: String::from("NA"), + default_value: Value::Integer(0), + constraint: Some(Validator::IntegerInRange(5..10)), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }, + ], + false, + false, + ) + }, + ); + } + + #[test] + #[should_panic] + fn builtin_validation_bails() { + temp_env::with_vars([("ESP_TEST_CONFIG_POSITIVE_NUMBER", Some("-99"))], || { + generate_config( + "esp-test", + &[ConfigOption { + name: String::from("positive_number"), + description: String::from("NA"), + default_value: Value::Integer(-1), + constraint: Some(Validator::PositiveInteger), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }], + false, + false, + ) + }); + } + + #[test] + #[should_panic] + fn env_unknown_bails() { + temp_env::with_vars( + [ + ("ESP_TEST_CONFIG_NUMBER", Some("0xaa")), + ("ESP_TEST_CONFIG_RANDOM_VARIABLE", Some("")), + ], + || { + generate_config( + "esp-test", + &[ConfigOption { + name: String::from("number"), + description: String::from("NA"), + default_value: Value::Integer(999), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }], + false, + false, + ); + }, + ); + } + + #[test] + #[should_panic] + fn env_invalid_values_bails() { + temp_env::with_vars([("ESP_TEST_CONFIG_NUMBER", Some("Hello world"))], || { + generate_config( + "esp-test", + &[ConfigOption { + name: String::from("number"), + description: String::from("NA"), + default_value: Value::Integer(999), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }], + false, + false, + ); + }); + } + + #[test] + fn env_unknown_prefix_is_ignored() { + temp_env::with_vars( + [("ESP_TEST_OTHER_CONFIG_NUMBER", Some("Hello world"))], + || { + generate_config( + "esp-test", + &[ConfigOption { + name: String::from("number"), + description: String::from("NA"), + default_value: Value::Integer(999), + constraint: None, + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }], + false, + false, + ); + }, + ); + } + + #[test] + fn enumeration_validator() { + let mut stdout = Vec::new(); + temp_env::with_vars([("ESP_TEST_CONFIG_SOME_KEY", Some("variant-0"))], || { + generate_config_internal( + &mut stdout, + "esp-test", + &[ConfigOption { + name: String::from("some-key"), + description: String::from("NA"), + default_value: Value::String("variant-0".to_string()), + constraint: Some(Validator::Enumeration(vec![ + "variant-0".to_string(), + "variant-1".to_string(), + ])), + stability: Stability::Stable(String::from("testing")), + active: true, + display_hint: DisplayHint::None, + }], + false, + ); + }); + + let cargo_lines: Vec<&str> = std::str::from_utf8(&stdout).unwrap().lines().collect(); + assert!(cargo_lines.contains(&"cargo:rustc-check-cfg=cfg(some_key)")); + assert!(cargo_lines.contains(&"cargo:rustc-env=ESP_TEST_CONFIG_SOME_KEY=variant-0")); + assert!(cargo_lines.contains(&"cargo:rustc-check-cfg=cfg(some_key_variant_0)")); + assert!(cargo_lines.contains(&"cargo:rustc-check-cfg=cfg(some_key_variant_1)")); + assert!(cargo_lines.contains(&"cargo:rustc-cfg=some_key_variant_0")); + } + + #[test] + #[should_panic] + fn unstable_option_panics_unless_enabled() { + let mut stdout = Vec::new(); + temp_env::with_vars([("ESP_TEST_CONFIG_SOME_KEY", Some("variant-0"))], || { + generate_config_internal( + &mut stdout, + "esp-test", + &[ConfigOption { + name: String::from("some-key"), + description: String::from("NA"), + default_value: Value::String("variant-0".to_string()), + constraint: Some(Validator::Enumeration(vec![ + "variant-0".to_string(), + "variant-1".to_string(), + ])), + stability: Stability::Unstable, + active: true, + display_hint: DisplayHint::None, + }], + false, + ); + }); + } + + #[test] + #[should_panic] + fn inactive_option_panics() { + let mut stdout = Vec::new(); + temp_env::with_vars([("ESP_TEST_CONFIG_SOME_KEY", Some("variant-0"))], || { + generate_config_internal( + &mut stdout, + "esp-test", + &[ConfigOption { + name: String::from("some-key"), + description: String::from("NA"), + default_value: Value::String("variant-0".to_string()), + constraint: Some(Validator::Enumeration(vec![ + "variant-0".to_string(), + "variant-1".to_string(), + ])), + stability: Stability::Stable(String::from("testing")), + active: false, + display_hint: DisplayHint::None, + }], + false, + ); + }); + } + + #[test] + fn deserialization() { + let yml = r#" +crate: esp-bootloader-esp-idf + +options: +- name: mmu_page_size + description: ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor. + default: + - value: '"64k"' + stability: !Stable xxxx + constraints: + - if: true + type: + validator: enumeration + value: + - 8k + - 16k + - 32k + - 64k + +- name: esp_idf_version + description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader. + default: + - if: 'chip == "esp32c6"' + value: '"esp32c6"' + - if: 'chip == "esp32"' + value: '"other"' + active: true + +- name: partition-table-offset + description: "The address of partition table (by default 0x8000). Allows you to \ + move the partition table, it gives more space for the bootloader. Note that the \ + bootloader and app will both need to be compiled with the same \ + PARTITION_TABLE_OFFSET value." + default: + - if: true + value: 32768 + stability: Unstable + active: 'chip == "esp32c6"' +"#; + + let (cfg, options) = evaluate_yaml_config( + yml, + Some(esp_metadata_generated::Chip::Esp32c6), + vec![], + false, + ) + .unwrap(); + + assert_eq!("esp-bootloader-esp-idf", cfg.krate); + + assert_eq!( + vec![ + ConfigOption { + name: "mmu_page_size".to_string(), + description: "ESP32-C2, ESP32-C6 and ESP32-H2 support configurable page sizes. This is currently only used to populate the app descriptor.".to_string(), + default_value: Value::String("64k".to_string()), + constraint: Some( + Validator::Enumeration( + vec![ + "8k".to_string(), + "16k".to_string(), + "32k".to_string(), + "64k".to_string(), + ], + ), + ), + stability: Stability::Stable("xxxx".to_string()), + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: "esp_idf_version".to_string(), + description: "ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.".to_string(), + default_value: Value::String("esp32c6".to_string()), + constraint: None, + stability: Stability::Unstable, + active: true, + display_hint: DisplayHint::None, + }, + ConfigOption { + name: "partition-table-offset".to_string(), + description: "The address of partition table (by default 0x8000). Allows you to move the partition table, it gives more space for the bootloader. Note that the bootloader and app will both need to be compiled with the same PARTITION_TABLE_OFFSET value.".to_string(), + default_value: Value::Integer(32768), + constraint: None, + stability: Stability::Unstable, + active: true, + display_hint: DisplayHint::None, + }, + ], + options + ); + } + + #[test] + fn deserialization_fallback_default() { + let yml = r#" +crate: esp-bootloader-esp-idf + +options: +- name: esp_idf_version + description: ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader. + default: + - if: 'chip == "esp32c6"' + value: '"esp32c6"' + - if: 'chip == "esp32"' + value: '"other"' + - value: '"default"' + active: true +"#; + + let (cfg, options) = evaluate_yaml_config( + yml, + Some(esp_metadata_generated::Chip::Esp32c3), + vec![], + false, + ) + .unwrap(); + + assert_eq!("esp-bootloader-esp-idf", cfg.krate); + + assert_eq!( + vec![ + ConfigOption { + name: "esp_idf_version".to_string(), + description: "ESP-IDF version used in the application descriptor. Currently it's not checked by the bootloader.".to_string(), + default_value: Value::String("default".to_string()), + constraint: None, + stability: Stability::Unstable, + active: true, + display_hint: DisplayHint::None, + }, + ], + options + ); + } + + #[test] + fn deserialization_fallback_contraint() { + let yml = r#" +crate: esp-bootloader-esp-idf + +options: +- name: option + description: Desc + default: + - value: 100 + constraints: + - if: 'chip == "esp32c6"' + type: + validator: integer_in_range + value: + start: 0 + end: 100 + - if: true + type: + validator: integer_in_range + value: + start: 0 + end: 50 + active: true +"#; + + let (cfg, options) = evaluate_yaml_config( + yml, + Some(esp_metadata_generated::Chip::Esp32), + vec![], + false, + ) + .unwrap(); + + assert_eq!("esp-bootloader-esp-idf", cfg.krate); + + assert_eq!( + vec![ConfigOption { + name: "option".to_string(), + description: "Desc".to_string(), + default_value: Value::Integer(100), + constraint: Some(Validator::IntegerInRange(0..50)), + stability: Stability::Unstable, + active: true, + display_hint: DisplayHint::None, + },], + options + ); + } +} diff --git a/esp-config/src/generate/validator.rs b/esp-config/src/generate/validator.rs new file mode 100644 index 00000000000..7a1b45f028f --- /dev/null +++ b/esp-config/src/generate/validator.rs @@ -0,0 +1,152 @@ +use std::{io::Write, ops::Range}; + +use serde::{Deserialize, Serialize}; + +use super::{Error, snake_case, value::Value}; + +/// Configuration value validation functions. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "validator", content = "value", rename_all = "snake_case")] +pub enum Validator { + /// Only allow negative integers, i.e. any values less than 0. + NegativeInteger, + /// Only allow non-negative integers, i.e. any values greater than or + /// equal to 0. + NonNegativeInteger, + /// Only allow positive integers, i.e. any values greater than to 0. + PositiveInteger, + /// Ensure that an integer value falls within the specified range. + IntegerInRange(Range), + /// String-Enumeration. Only allows one of the given Strings. + Enumeration(Vec), +} + +impl Validator { + /// Validate the value + pub fn validate(&self, value: &Value) -> Result<(), Error> { + match self { + Validator::NegativeInteger => negative_integer(value)?, + Validator::NonNegativeInteger => non_negative_integer(value)?, + Validator::PositiveInteger => positive_integer(value)?, + Validator::IntegerInRange(range) => integer_in_range(range, value)?, + Validator::Enumeration(values) => enumeration(values, value)?, + } + + Ok(()) + } + + pub(crate) fn description(&self) -> Option { + match self { + Validator::NegativeInteger => Some(String::from("Negative integer")), + Validator::NonNegativeInteger => Some(String::from("Positive integer or 0")), + Validator::PositiveInteger => Some(String::from("Positive integer")), + Validator::IntegerInRange(range) => { + Some(format!("Integer in range {}..{}", range.start, range.end)) + } + Validator::Enumeration(values) => Some(format!( + "One of:
    {}
", + values + .iter() + .map(|v| format!("
  • {v}
  • ")) + .collect::>() + .join("") + )), + } + } + + pub(crate) fn emit_cargo_extras( + &self, + mut stdout: impl Write, + config_key: &str, + actual_value: &Value, + ) { + if let Validator::Enumeration(values) = self { + for possible_value in values { + writeln!( + stdout, + "cargo:rustc-check-cfg=cfg({config_key}_{})", + snake_case(possible_value) + ) + .ok(); + } + + writeln!( + stdout, + "cargo:rustc-cfg={config_key}_{}", + snake_case(&actual_value.to_string()) + ) + .ok(); + } + } +} + +pub(crate) fn enumeration(values: &Vec, value: &Value) -> Result<(), Error> { + if let Value::String(value) = value { + if !values.contains(value) { + return Err(Error::validation(format!( + "Expected one of {values:?}, found '{value}'" + ))); + } + + Ok(()) + } else { + Err(Error::parse( + "Validator::Enumeration can only be used with string values", + )) + } +} + +pub(crate) fn negative_integer(value: &Value) -> Result<(), Error> { + if !value.is_integer() { + return Err(Error::validation( + "Validator::NegativeInteger can only be used with integer values", + )); + } else if value.as_integer() >= 0 { + return Err(Error::validation(format!( + "Expected negative integer, found '{}'", + value.as_integer() + ))); + } + + Ok(()) +} + +pub(crate) fn non_negative_integer(value: &Value) -> Result<(), Error> { + if !value.is_integer() { + return Err(Error::validation( + "Validator::NonNegativeInteger can only be used with integer values", + )); + } else if value.as_integer() < 0 { + return Err(Error::validation(format!( + "Expected non-negative integer, found '{}'", + value.as_integer() + ))); + } + + Ok(()) +} + +pub(crate) fn positive_integer(value: &Value) -> Result<(), Error> { + if !value.is_integer() { + return Err(Error::validation( + "Validator::PositiveInteger can only be used with integer values", + )); + } else if value.as_integer() <= 0 { + return Err(Error::validation(format!( + "Expected positive integer, found '{}'", + value.as_integer() + ))); + } + + Ok(()) +} + +pub(crate) fn integer_in_range(range: &Range, value: &Value) -> Result<(), Error> { + if !value.is_integer() || !range.contains(&value.as_integer()) { + Err(Error::validation(format!( + "Value '{value}' does not fall within range '{range:?}'" + ))) + } else { + Ok(()) + } +} diff --git a/esp-config/src/generate/value.rs b/esp-config/src/generate/value.rs new file mode 100644 index 00000000000..6caaec03ef2 --- /dev/null +++ b/esp-config/src/generate/value.rs @@ -0,0 +1,213 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; + +use super::Error; + +/// Supported configuration value types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + /// Booleans. + Bool(bool), + /// Integers. + Integer(i128), + /// Strings. + String(String), +} + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Value::String(s) => serializer.serialize_str(&format!("\"{s}\"")), + Value::Integer(n) => serializer.serialize_str(&format!("{n}")), + Value::Bool(b) => serializer.serialize_str(&format!("{b}")), + } + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor; + + impl serde::de::Visitor<'_> for ValueVisitor { + type Value = String; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a String representing the Value") + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(v) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(v.to_string()) + } + } + + let str_repr = deserializer.deserialize_string(ValueVisitor)?; + let str_repr = str_repr.as_str(); + + if let Some(remaining) = str_repr.strip_prefix("\"") { + let s = &remaining[..remaining.len() - 1]; + return Ok(Value::String(s.to_string())); + } + + if str_repr == "true" { + return Ok(Value::Bool(true)); + } + + if str_repr == "false" { + return Ok(Value::Bool(false)); + } + + Ok(Value::Integer(str_repr.parse().map_err( + |e: core::num::ParseIntError| serde::de::Error::custom(e.to_string()), + )?)) + } +} + +// TODO: Do we want to handle negative values for non-decimal values? +impl Value { + /// Try to parse the given String + pub fn parse_in_place(&mut self, s: &str) -> Result<(), Error> { + *self = match self { + Value::Bool(_) => match s { + "true" => Value::Bool(true), + "false" => Value::Bool(false), + _ => { + return Err(Error::parse(format!( + "Expected 'true' or 'false', found: '{s}'" + ))); + } + }, + Value::Integer(_) => { + let inner = match s.as_bytes() { + [b'0', b'x', ..] => i128::from_str_radix(&s[2..], 16), + [b'0', b'o', ..] => i128::from_str_radix(&s[2..], 8), + [b'0', b'b', ..] => i128::from_str_radix(&s[2..], 2), + _ => s.parse(), + } + .map_err(|_| Error::parse(format!("Expected valid intger value, found: '{s}'")))?; + + Value::Integer(inner) + } + Value::String(_) => Value::String(s.into()), + }; + + Ok(()) + } + + /// Convert the value to a [bool]. + pub fn as_bool(&self) -> bool { + match self { + Value::Bool(value) => *value, + _ => panic!("attempted to convert non-bool value to a bool"), + } + } + + /// Convert the value to an [i128]. + pub fn as_integer(&self) -> i128 { + match self { + Value::Integer(value) => *value, + _ => panic!("attempted to convert non-integer value to an integer"), + } + } + + /// Convert the value to a [String]. + pub fn as_string(&self) -> String { + match self { + Value::String(value) => value.to_owned(), + _ => panic!("attempted to convert non-string value to a string"), + } + } + + /// Is the value a bool? + pub fn is_bool(&self) -> bool { + matches!(self, Value::Bool(_)) + } + + /// Is the value an integer? + pub fn is_integer(&self) -> bool { + matches!(self, Value::Integer(_)) + } + + /// Is the value a string? + pub fn is_string(&self) -> bool { + matches!(self, Value::String(_)) + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Bool(b) => write!(f, "{b}"), + Value::Integer(i) => write!(f, "{i}"), + Value::String(s) => write!(f, "{s}"), + } + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value::Bool(value) + } +} + +impl From for Value { + fn from(value: i128) -> Self { + Value::Integer(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Value::String(value.to_string()) + } +} + +impl From for Value { + fn from(value: String) -> Self { + Value::String(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialization_number() { + assert_eq!( + serde_yaml::from_str::("128").unwrap(), + Value::Integer(128) + ); + assert_eq!( + serde_yaml::from_str::(&format!("{}", i128::MAX)).unwrap(), + Value::Integer(i128::MAX) + ); + assert_eq!( + serde_yaml::from_str::(&format!("{}", i128::MIN)).unwrap(), + Value::Integer(i128::MIN) + ); + } + + #[test] + fn deserialization_string() { + let yml = "'\"Hello\"'"; + let value: Value = serde_yaml::from_str(yml).unwrap(); + assert_eq!(value, Value::String("Hello".to_string())); + } +} diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs new file mode 100644 index 00000000000..7947756331d --- /dev/null +++ b/esp-config/src/lib.rs @@ -0,0 +1,99 @@ +#![doc = include_str!("../README.md")] +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![cfg_attr(not(feature = "build"), no_std)] +#![deny(missing_docs, rust_2018_idioms, rustdoc::all)] + +#[cfg(feature = "build")] +mod generate; +#[cfg(feature = "build")] +pub use generate::{ + ConfigOption, + DisplayHint, + Error, + Stability, + generate_config, + generate_config_from_yaml_definition, + validator::Validator, + value::Value, +}; +#[cfg(feature = "tui")] +pub use generate::{do_checks, evaluate_yaml_config}; + +/// Parse the value of an environment variable as a [bool] at compile time. +#[macro_export] +macro_rules! esp_config_bool { + ( $var:expr ) => { + match env!($var).as_bytes() { + b"true" => true, + b"false" => false, + _ => ::core::panic!("boolean value must be either 'true' or 'false'"), + } + }; +} + +/// Parse the value of an environment variable as an integer at compile time. +#[macro_export] +macro_rules! esp_config_int { + ( $ty:ty, $var:expr ) => { + const { $crate::esp_config_int_parse!($ty, env!($var)) } + }; +} + +/// Get the string value of an environment variable at compile time. +#[macro_export] +macro_rules! esp_config_str { + ( $var:expr ) => { + env!($var) + }; +} + +/// Parse a string like "777" into an integer, which _can_ be used in a `const` +/// context +/// +/// Not inlined into `esp_config_int` to make this easy to test. +#[doc(hidden)] // To avoid confusion with `esp_config_int`, hide this in the docs +#[macro_export] +macro_rules! esp_config_int_parse { + ( $ty:ty, $s:expr ) => {{ + let val: $ty = match <$ty>::from_str_radix($s, 10) { + Ok(val) => val as $ty, + Err(_) => { + core::assert!(false, "Unable to parse a config value as a number."); + 0 + } + }; + val + }}; +} + +#[cfg(test)] +mod tests { + // We can only test success in the const context + const _: () = { + core::assert!(esp_config_int_parse!(i64, "-77777") == -77777); + core::assert!(esp_config_int_parse!(isize, "-7777") == -7777); + core::assert!(esp_config_int_parse!(i32, "-999") == -999); + core::assert!(esp_config_int_parse!(i16, "-99") == -99); + core::assert!(esp_config_int_parse!(i8, "-9") == -9); + + core::assert!(esp_config_int_parse!(u64, "77777") == 77777); + core::assert!(esp_config_int_parse!(usize, "7777") == 7777); + core::assert!(esp_config_int_parse!(u32, "999") == 999); + core::assert!(esp_config_int_parse!(u16, "99") == 99); + core::assert!(esp_config_int_parse!(u8, "9") == 9); + }; + + #[test] + #[should_panic] + fn test_expect_positive() { + esp_config_int_parse!(u8, "-5"); + } + + #[test] + #[should_panic] + fn test_invalid_digit() { + esp_config_int_parse!(u32, "a"); + } +} diff --git a/esp-hal-common/.gitignore b/esp-hal-common/.gitignore deleted file mode 100644 index 8f61cef6fae..00000000000 --- a/esp-hal-common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -rust-toolchain.toml diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml deleted file mode 100644 index 493f4197e30..00000000000 --- a/esp-hal-common/Cargo.toml +++ /dev/null @@ -1,105 +0,0 @@ -[package] -name = "esp-hal-common" -version = "0.9.0" -authors = [ - "Jesse Braham ", - "Björn Quentin ", -] -edition = "2021" -rust-version = "1.65.0" -description = "HAL implementations for peripherals common among Espressif devices; should not be used directly" -repository = "https://github.com/esp-rs/esp-hal" -license = "MIT OR Apache-2.0" - -[dependencies] -bitflags = "2.2.1" -cfg-if = "1.0.0" -critical-section = "1.1.1" -embedded-can = { version = "0.4.1", optional = true } -embedded-dma = "0.2.0" -embedded-hal = { version = "0.2.7", features = ["unproven"] } -embedded-hal-1 = { version = "=1.0.0-alpha.10", optional = true, package = "embedded-hal" } -embedded-hal-nb = { version = "=1.0.0-alpha.2", optional = true } -esp-synopsys-usb-otg = { version = "0.3.1", optional = true, features = ["fs", "esp32sx"] } -fugit = "0.3.6" -lock_api = { version = "0.4.9", optional = true } -nb = "1.1.0" -paste = "1.0.12" -procmacros = { version = "0.5.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -strum = { version = "0.24.1", default-features = false, features = ["derive"] } -void = { version = "1.0.2", default-features = false } -usb-device = { version = "0.2.9", optional = true } - -# async -embedded-hal-async = { version = "0.2.0-alpha.1", optional = true } -embassy-sync = { version = "0.2.0", optional = true } -embassy-time = { version = "0.1.1", features = ["nightly"], optional = true } -embassy-futures = { version = "0.1.0", optional = true } - -# RISC-V -esp-riscv-rt = { version = "0.3.0", optional = true } -riscv-atomic-emulation-trap = { version = "0.4.0", optional = true } - -# Xtensa -xtensa-lx = { version = "0.8.0", optional = true } -xtensa-lx-rt = { version = "0.15.0", optional = true } - -# Part of `ufmt` containing only `uWrite` trait -ufmt-write = { version = "0.1.0", optional = true } - -# IMPORTANT: -# Each supported device MUST have its PAC included below along with a -# corresponding feature. -esp32 = { version = "0.23.0", features = ["critical-section"], optional = true } -esp32c2 = { version = "0.11.0", features = ["critical-section"], optional = true } -esp32c3 = { version = "0.14.0", features = ["critical-section"], optional = true } -esp32c6 = { version = "0.4.0", features = ["critical-section"], optional = true } -esp32s2 = { version = "0.14.0", features = ["critical-section"], optional = true } -esp32s3 = { version = "0.18.0", features = ["critical-section"], optional = true } - -[build-dependencies] -basic-toml = "0.1.2" -serde = { version = "1.0.160", features = ["derive"] } - -[features] -esp32 = ["esp32/rt" , "xtensa", "xtensa-lx/esp32", "xtensa-lx-rt/esp32", "lock_api", "procmacros/esp32"] -esp32c2 = ["esp32c2/rt", "riscv", "procmacros/esp32c2"] -esp32c3 = ["esp32c3/rt", "riscv", "procmacros/esp32c3"] -esp32c6 = ["esp32c6/rt", "riscv", "procmacros/esp32c6"] -esp32s2 = ["esp32s2/rt", "xtensa", "xtensa-lx/esp32s2", "xtensa-lx-rt/esp32s2", "esp-synopsys-usb-otg", "usb-device", "procmacros/esp32s2"] -esp32s3 = ["esp32s3/rt", "xtensa", "xtensa-lx/esp32s3", "xtensa-lx-rt/esp32s3", "lock_api", "esp-synopsys-usb-otg", "usb-device", "procmacros/esp32s3"] - -esp32_40mhz = [] -esp32_26mhz = [] - -esp32c2_40mhz = [] -esp32c2_26mhz = [] - -psram_2m = [] -psram_4m = [] -psram_8m = [] - -# Implement the `embedded-hal==1.0.0-alpha.x` traits -eh1 = ["embedded-hal-1", "embedded-hal-nb", "embedded-can"] - -# To support `ufmt` -ufmt = ["ufmt-write"] - -# To use vectored interrupts (calling the handlers defined in the PAC) -vectored = ["procmacros/interrupt"] - -# Implement the `embedded-hal-async==1.0.0-alpha.x` traits -async = ["embedded-hal-async", "eh1", "embassy-sync", "embassy-futures"] -embassy = ["embassy-time"] - -embassy-time-systick = [] -embassy-time-timg0 = [] - -# Architecture-specific features (intended for internal use) -riscv = ["critical-section/restore-state-u8", "procmacros/riscv", "esp-riscv-rt", "riscv-atomic-emulation-trap", "esp-riscv-rt/zero-bss"] -xtensa = ["critical-section/restore-state-u32", "procmacros/xtensa"] - -# Initialize / clear data sections and RTC memory -rv-init-data = ["esp-riscv-rt/init-data", "esp-riscv-rt/init-rw-text"] -rv-zero-rtc-bss = ["esp-riscv-rt/zero-rtc-fast-bss"] -rv-init-rtc-data = ["esp-riscv-rt/init-rtc-fast-data", "esp-riscv-rt/init-rtc-fast-text"] diff --git a/esp-hal-common/README.md b/esp-hal-common/README.md deleted file mode 100644 index 63d364a08a7..00000000000 --- a/esp-hal-common/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# esp-hal-common - -[![Crates.io](https://img.shields.io/crates/v/esp-hal-common?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-hal-common) -[![docs.rs](https://img.shields.io/docsrs/esp-hal-common?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-hal-common) -![Crates.io](https://img.shields.io/crates/l/esp-hal-common?labelColor=1C2C2E&style=flat-square) -[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) - -`no_std` HAL implementations for the peripherals which are common among Espressif devices. Implements a number of the traits defined by [embedded-hal](https://github.com/rust-embedded/embedded-hal). - -This crate should not be used directly; you should use one of the device-specific HAL crates instead: - -- [esp32-hal](../esp32-hal/README.md) -- [esp32c2-hal](../esp32c2-hal/README.md) -- [esp32c3-hal](../esp32c3-hal/README.md) -- [esp32c6-hal](../esp32c6-hal/README.md) -- [esp32s2-hal](../esp32s2-hal/README.md) -- [esp32s3-hal](../esp32s3-hal/README.md) - -## [Documentation] - -[documentation]: https://docs.rs/esp-hal-common/ - -## License - -Licensed under either of: - -- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in -the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without -any additional terms or conditions. diff --git a/esp-hal-common/build.rs b/esp-hal-common/build.rs deleted file mode 100644 index 8bc51bb075e..00000000000 --- a/esp-hal-common/build.rs +++ /dev/null @@ -1,181 +0,0 @@ -use std::{env, fs, path::PathBuf}; - -use serde::Deserialize; - -// Macros taken from: -// https://github.com/TheDan64/inkwell/blob/36c3b10/src/lib.rs#L81-L110 - -macro_rules! assert_unique_features { - () => {}; - - ( $first:tt $(,$rest:tt)* ) => { - $( - #[cfg(all(feature = $first, feature = $rest))] - compile_error!(concat!("Features \"", $first, "\" and \"", $rest, "\" cannot be used together")); - )* - assert_unique_features!($($rest),*); - }; -} - -macro_rules! assert_used_features { - ( $($all:tt),* ) => { - #[cfg(not(any($(feature = $all),*)))] - compile_error!(concat!("One of the feature flags must be provided: ", $($all, ", "),*)); - } -} - -macro_rules! assert_unique_used_features { - ( $($all:tt),* ) => { - assert_unique_features!($($all),*); - assert_used_features!($($all),*); - } -} - -#[derive(Debug, Deserialize)] -enum Arch { - #[serde(rename = "riscv")] - RiscV, - #[serde(rename = "xtensa")] - Xtensa, -} - -impl core::fmt::Display for Arch { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Arch::RiscV => "riscv", - Arch::Xtensa => "xtensa", - } - ) - } -} - -#[derive(Debug, Deserialize)] -enum CoreCount { - #[serde(rename = "single_core")] - Single, - #[serde(rename = "multi_core")] - Multi, -} - -impl core::fmt::Display for CoreCount { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - CoreCount::Single => "single_core", - CoreCount::Multi => "multi_core", - } - ) - } -} - -#[derive(Debug, Deserialize)] -struct Device { - pub arch: Arch, - pub cores: CoreCount, - pub peripherals: Vec, -} - -#[derive(Debug, Deserialize)] -struct Config { - pub device: Device, -} - -fn main() { - // NOTE: update when adding new device support! - // Ensure that exactly one chip has been specified: - assert_unique_used_features!("esp32", "esp32c2", "esp32c3", "esp32c6", "esp32s2", "esp32s3"); - - // Handle the features for the ESP32's different crystal frequencies: - #[cfg(feature = "esp32")] - { - assert_unique_used_features!("esp32_26mhz", "esp32_40mhz"); - } - - // Handle the features for the ESP32-C2's different crystal frequencies: - #[cfg(feature = "esp32c2")] - { - assert_unique_used_features!("esp32c2_26mhz", "esp32c2_40mhz"); - } - - // NOTE: update when adding new device support! - // Determine the name of the configured device: - let device_name = if cfg!(feature = "esp32") { - "esp32" - } else if cfg!(feature = "esp32c2") { - "esp32c2" - } else if cfg!(feature = "esp32c3") { - "esp32c3" - } else if cfg!(feature = "esp32c6") { - "esp32c6" - } else if cfg!(feature = "esp32s2") { - "esp32s2" - } else if cfg!(feature = "esp32s3") { - "esp32s3" - } else { - unreachable!() // We've confirmed exactly one known device was selected - }; - - // Load the configuration file for the configured device: - let chip_toml_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("devices") - .join(format!("{}.toml", device_name)) - .canonicalize() - .unwrap(); - - let config = fs::read_to_string(chip_toml_path).unwrap(); - let config: Config = basic_toml::from_str(&config).unwrap(); - let device = config.device; - - // Define all necessary configuration symbols for the configured device: - println!("cargo:rustc-cfg={}", device_name); - println!("cargo:rustc-cfg={}", device.arch); - println!("cargo:rustc-cfg={}", device.cores); - - for peripheral in &device.peripherals { - println!("cargo:rustc-cfg={peripheral}"); - } - - // check PSRAM features are only given if the target supports PSRAM - if !&device.peripherals.contains(&String::from("psram")) - && (cfg!(feature = "psram_2m") || cfg!(feature = "psram_4m") || cfg!(feature = "psram_8m")) - { - panic!("The target does not support PSRAM"); - } - - // Place all linker scripts in `OUT_DIR`, and instruct Cargo how to find these - // files: - let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - println!("cargo:rustc-link-search={}", out.display()); - - if cfg!(feature = "esp32") || cfg!(feature = "esp32s2") || cfg!(feature = "esp32s3") { - fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x")).unwrap(); - fs::copy("ld/xtensa/rom.x", out.join("alias.x")).unwrap(); - } else { - fs::copy("ld/riscv/hal-defaults.x", out.join("hal-defaults.x")).unwrap(); - fs::copy("ld/riscv/asserts.x", out.join("asserts.x")).unwrap(); - fs::copy("ld/riscv/debug.x", out.join("debug.x")).unwrap(); - } - copy_dir_all("ld/sections", out).unwrap(); -} - -fn copy_dir_all( - src: impl AsRef, - dst: impl AsRef, -) -> std::io::Result<()> { - fs::create_dir_all(&dst)?; - for entry in fs::read_dir(src)? { - let entry = entry?; - let ty = entry.file_type()?; - if ty.is_dir() { - copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; - } else { - fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; - } - } - Ok(()) -} diff --git a/esp-hal-common/devices/esp32.toml b/esp-hal-common/devices/esp32.toml deleted file mode 100644 index 2ea0d05486c..00000000000 --- a/esp-hal-common/devices/esp32.toml +++ /dev/null @@ -1,65 +0,0 @@ -[device] -arch = "xtensa" -cores = "multi_core" - -peripherals = [ - # Peripherals available in the PAC: - "aes", - "apb_ctrl", - "bb", - "dport", - "efuse", - "flash_encryption", - "frc_timer", - "gpio", - "gpio_sd", - "hinf", - "i2c0", - "i2c1", - "i2s0", - "i2s1", - "io_mux", - "ledc", - "mcpwm0", - "mcpwm1", - "nrx", - "pcnt", - "rmt", - "rng", - "rsa", - "rtc_cntl", - "rtc_i2c", - "rtc_io", - "sdmmc", - "sens", - "sha", - "slc", - "slchost", - "spi0", - "spi1", - "spi2", - "spi3", - "timg0", - "timg1", - "twai0", - "uart0", - "uart1", - "uart2", - "uhci0", - "uhci1", - - # Additional peripherals defined by us (the developers): - "adc", - "dac", - "pdma", - "radio", - "phy", - "bt", - "wifi", - - # Wakeup SOC based on ESP-IDF: - "pm_support_ext0_wakeup", - "pm_support_ext1_wakeup", - "pm_support_touch_sensor_wakeup", - "ulp_supported", -] diff --git a/esp-hal-common/devices/esp32c2.toml b/esp-hal-common/devices/esp32c2.toml deleted file mode 100644 index cbde774781d..00000000000 --- a/esp-hal-common/devices/esp32c2.toml +++ /dev/null @@ -1,47 +0,0 @@ -[device] -arch = "riscv" -cores = "single_core" - -peripherals = [ - # Peripherals available in the PAC: - "apb_ctrl", - "apb_saradc", - "assist_debug", - "dma", - "ecc", - "efuse", - "extmem", - "gpio", - "i2c0", - "interrupt_core0", - "io_mux", - "ledc", - "rng", - "rtc_cntl", - "sensitive", - "sha", - "spi0", - "spi1", - "spi2", - "system", - "systimer", - "timg0", - "uart0", - "uart1", - "xts_aes", - "assist_debug_sp_monitor", - - # Additional peripherals defined by us (the developers): - "adc", - "gdma", - "radio", - "phy", - "bt", - "wifi", - - # Wakeup SOC based on ESP-IDF: - "pm_support_wifi_wakeup", - "pm_support_bt_wakeup", - "uart_support_wakeup_int", - "gpio_support_deepsleep_wakeup", -] diff --git a/esp-hal-common/devices/esp32c3.toml b/esp-hal-common/devices/esp32c3.toml deleted file mode 100644 index f2e2ea5defa..00000000000 --- a/esp-hal-common/devices/esp32c3.toml +++ /dev/null @@ -1,59 +0,0 @@ -[device] -arch = "riscv" -cores = "single_core" - -peripherals = [ - # Peripherals available in the PAC: - "aes", - "apb_ctrl", - "apb_saradc", - "assist_debug", - "dma", - "ds", - "efuse", - "extmem", - "gpio", - "gpio_sd", - "hmac", - "i2c0", - "i2s0", - "interrupt_core0", - "io_mux", - "ledc", - "rmt", - "rng", - "rsa", - "rtc_cntl", - "sensitive", - "sha", - "spi0", - "spi1", - "spi2", - "system", - "systimer", - "timg0", - "timg1", - "twai0", - "uart0", - "uart1", - "uhci0", - "uhci1", - "usb_device", - "xts_aes", - "assist_debug_sp_monitor", - "assist_debug_region_monitor", - - # Additional peripherals defined by us (the developers): - "adc", - "gdma", - "radio", - "phy", - "bt", - "wifi", - - # Wakeup SOC based on ESP-IDF: - "pm_support_wifi_wakeup", - "pm_support_bt_wakeup", - "uart_support_wakeup_int", - "gpio_support_deepsleep_wakeup", -] diff --git a/esp-hal-common/devices/esp32c6.toml b/esp-hal-common/devices/esp32c6.toml deleted file mode 100644 index d2351becd15..00000000000 --- a/esp-hal-common/devices/esp32c6.toml +++ /dev/null @@ -1,90 +0,0 @@ -[device] -arch = "riscv" -cores = "single_core" - -peripherals = [ - # Peripherals available in the PAC: - "aes", - "apb_saradc", - "assist_debug", - "atomic", - "dma", - "ds", - "ecc", - "efuse", - "extmem", - "gpio", - "gpio_sd", - "hinf", - "hmac", - "hp_apm", - "hp_sys", - "i2c0", - "i2s0", - "interrupt_core0", - "intpri", - "io_mux", - "ledc", - "lp_ana", - "lp_aon", - "lp_apm", - "lp_apm0", - "lp_clkrst", - "lp_i2c0", - "lp_i2c_ana_mst", - "lp_io", - "lp_peri", - "lp_tee", - "lp_timer", - "lp_uart", - "lp_wdt", - "mcpwm0", - "mem_monitor", - "otp_debug", - "parl_io", - "pau", - "pcnt", - "pcr", - "pmu", - "rmt", - "rng", - "rsa", - "sha", - "slchost", - "soc_etm", - "spi0", - "spi1", - "spi2", - "systimer", - "tee", - "timg0", - "timg1", - "trace", - "twai0", - "twai1", - "uart0", - "uart1", - "uhci0", - "usb_device", - "assist_debug_sp_monitor", - "assist_debug_region_monitor", - - # Additional peripherals defined by us (the developers): - "adc", - "gdma", - "large_intr_status", - "plic", - "radio", - "phy", - "bt", - "wifi", - "ieee802154", - - # Wakeup SOC based on ESP-IDF: - "pm_support_wifi_wakeup", - "pm_support_beacon_wakeup", - "pm_support_bt_wakeup", - "gpio_support_deepsleep_wakeup", - "uart_support_wakeup_int", - "pm_support_ext1_wakeup", -] diff --git a/esp-hal-common/devices/esp32s2.toml b/esp-hal-common/devices/esp32s2.toml deleted file mode 100644 index ba0af07cac3..00000000000 --- a/esp-hal-common/devices/esp32s2.toml +++ /dev/null @@ -1,66 +0,0 @@ -[device] -arch = "xtensa" -cores = "single_core" - -peripherals = [ - # Peripherals available in the PAC: - "aes", - "apb_saradc", - "dedicated_gpio", - "ds", - "efuse", - "extmem", - "gpio", - "gpio_sd", - "hmac", - "i2c0", - "i2c1", - "i2s0", - "interrupt_core0", - "io_mux", - "ledc", - "pcnt", - "pms", - "rmt", - "rng", - "rsa", - "rtc_cntl", - "rtc_i2c", - "rtc_io", - "sens", - "sha", - "spi0", - "spi1", - "spi2", - "spi3", - "spi4", - "system", - "systimer", - "timg0", - "timg1", - "twai0", - "uart0", - "uart1", - "uhci0", - "usb0", - "usb_wrap", - "xts_aes", - - # Additional peripherals defined by us (the developers): - "adc", - "dac", - "pdma", - "radio", - "phy", - "wifi", - "psram", - - # Wakeup SOC based on ESP-IDF: - "pm_support_ext0_wakeup", - "pm_support_ext1_wakeup", - "pm_support_touch_sensor_wakeup", - "pm_support_wifi_wakeup", - "uart_support_wakeup_int", - "ulp_supported", - "riscv_coproc_supported", -] diff --git a/esp-hal-common/devices/esp32s3.toml b/esp-hal-common/devices/esp32s3.toml deleted file mode 100644 index 4bb8f87148b..00000000000 --- a/esp-hal-common/devices/esp32s3.toml +++ /dev/null @@ -1,78 +0,0 @@ -[device] -arch = "xtensa" -cores = "multi_core" - -peripherals = [ - # Peripherals available in the PAC: - "aes", - "apb_ctrl", - "apb_saradc", - "assist_debug", - "dma", - "ds", - "efuse", - "extmem", - "gpio", - "gpio_sd", - "hmac", - "i2c0", - "i2c1", - "i2s0", - "i2s1", - "interrupt_core0", - "interrupt_core1", - "io_mux", - "lcd_cam", - "ledc", - "mcpwm0", - "mcpwm1", - "pcnt", - "peri_backup", - "rmt", - "rng", - "rsa", - "rtc_cntl", - "rtc_i2c", - "rtc_io", - "sens", - "sensitive", - "sha", - "spi0", - "spi1", - "spi2", - "spi3", - "system", - "systimer", - "timg0", - "timg1", - "twai0", - "uart0", - "uart1", - "uart2", - "uhci0", - "uhci1", - "usb0", - "usb_device", - "usb_wrap", - "wcl", - "xts_aes", - "assist_debug_region_monitor", - - # Additional peripherals defined by us (the developers): - "adc", - "gdma", - "radio", - "phy", - "bt", - "wifi", - - # Wakeup SOC based on ESP-IDF: - "pm_support_ext0_wakeup", - "pm_support_ext1_wakeup", - "pm_support_touch_sensor_wakeup", - "pm_support_wifi_wakeup", - "pm_support_bt_wakeup", - "uart_support_wakeup_int", - "ulp_supported", - "riscv_coproc_supported", -] diff --git a/esp-hal-common/ld/riscv/debug.x b/esp-hal-common/ld/riscv/debug.x deleted file mode 100644 index b4d5bd60409..00000000000 --- a/esp-hal-common/ld/riscv/debug.x +++ /dev/null @@ -1,6 +0,0 @@ - - -SECTIONS { - .eh_frame (INFO) : { KEEP(*(.eh_frame)) } - .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } -} \ No newline at end of file diff --git a/esp-hal-common/ld/riscv/hal-defaults.x b/esp-hal-common/ld/riscv/hal-defaults.x deleted file mode 100644 index a7bbdbb0bb7..00000000000 --- a/esp-hal-common/ld/riscv/hal-defaults.x +++ /dev/null @@ -1,33 +0,0 @@ -PROVIDE(interrupt1 = DefaultHandler); -PROVIDE(interrupt2 = DefaultHandler); -PROVIDE(interrupt3 = DefaultHandler); -PROVIDE(interrupt4 = DefaultHandler); -PROVIDE(interrupt5 = DefaultHandler); -PROVIDE(interrupt6 = DefaultHandler); -PROVIDE(interrupt7 = DefaultHandler); -PROVIDE(interrupt8 = DefaultHandler); -PROVIDE(interrupt9 = DefaultHandler); -PROVIDE(interrupt10 = DefaultHandler); -PROVIDE(interrupt11 = DefaultHandler); -PROVIDE(interrupt12 = DefaultHandler); -PROVIDE(interrupt13 = DefaultHandler); -PROVIDE(interrupt14 = DefaultHandler); -PROVIDE(interrupt15 = DefaultHandler); -PROVIDE(interrupt16 = DefaultHandler); -PROVIDE(interrupt17 = DefaultHandler); -PROVIDE(interrupt18 = DefaultHandler); -PROVIDE(interrupt19 = DefaultHandler); -PROVIDE(interrupt20 = DefaultHandler); -PROVIDE(interrupt21 = DefaultHandler); -PROVIDE(interrupt22 = DefaultHandler); -PROVIDE(interrupt23 = DefaultHandler); -PROVIDE(interrupt24 = DefaultHandler); -PROVIDE(interrupt25 = DefaultHandler); -PROVIDE(interrupt26 = DefaultHandler); -PROVIDE(interrupt27 = DefaultHandler); -PROVIDE(interrupt28 = DefaultHandler); -PROVIDE(interrupt29 = DefaultHandler); -PROVIDE(interrupt30 = DefaultHandler); -PROVIDE(interrupt31 = DefaultHandler); - -INCLUDE "device.x" diff --git a/esp-hal-common/ld/sections/external.x b/esp-hal-common/ld/sections/external.x deleted file mode 100644 index 70167fc7d33..00000000000 --- a/esp-hal-common/ld/sections/external.x +++ /dev/null @@ -1,35 +0,0 @@ - - -SECTIONS { - .external.data : - { - _external_data_start = ABSOLUTE(.); - . = ALIGN(4); - *(.external.data .external.data.*) - _external_data_end = ABSOLUTE(.); - } > psram_seg AT > RODATA - - .external.bss (NOLOAD) : - { - _external_bss_start = ABSOLUTE(.); - . = ALIGN(4); - *(.external.bss .external.bss.*) - _external_bss_end = ABSOLUTE(.); - } > psram_seg - - .external.noinit (NOLOAD) : - { - . = ALIGN(4); - *(.external.noinit .external.noinit.*) - } > psram_seg - - /* must be last segment using psram_seg */ - .external_heap_start (NOLOAD) : - { - . = ALIGN (4); - _external_heap_start = ABSOLUTE(.); - } > psram_seg -} - -_external_ram_start = ABSOLUTE(ORIGIN(psram_seg)); -_external_ram_end = ABSOLUTE(ORIGIN(psram_seg)+LENGTH(psram_seg)); \ No newline at end of file diff --git a/esp-hal-common/ld/sections/fixups/rodata_dummy.x b/esp-hal-common/ld/sections/fixups/rodata_dummy.x deleted file mode 100644 index 8993dd9d186..00000000000 --- a/esp-hal-common/ld/sections/fixups/rodata_dummy.x +++ /dev/null @@ -1,27 +0,0 @@ - - - -SECTIONS { - .rodata_dummy (NOLOAD) : - { - /* This dummy section represents the .flash.text section but in RODATA. - * Thus, it must have its alignment and (at least) its size. - */ - - /* Start at the same alignment constraint than .flash.text */ - - . = ALIGN(ALIGNOF(.text)); - - /* Create an empty gap as big as .text section */ - - . = SIZEOF(.text); - - /* Prepare the alignment of the section above. Few bytes (0x20) must be - * added for the mapping header. - */ - - . = ALIGN(0x10000) + 0x20; - _rodata_reserved_start = .; - } > RODATA -} -INSERT BEFORE .rodata; \ No newline at end of file diff --git a/esp-hal-common/ld/sections/rodata.x b/esp-hal-common/ld/sections/rodata.x deleted file mode 100644 index 2d9bd9c050a..00000000000 --- a/esp-hal-common/ld/sections/rodata.x +++ /dev/null @@ -1,17 +0,0 @@ - - -SECTIONS { - .rodata : ALIGN(4) - { - _rodata_start = ABSOLUTE(.); - . = ALIGN (4); - *(.rodata .rodata.*) - *(.srodata .srodata.*) - _rodata_end = ABSOLUTE(.); - } > RODATA - - .rodata.wifi : ALIGN(4) - { - *( .rodata_wlog_*.* ) - } > RODATA -} \ No newline at end of file diff --git a/esp-hal-common/ld/sections/rtc_fast.x b/esp-hal-common/ld/sections/rtc_fast.x deleted file mode 100644 index 59df8341e4a..00000000000 --- a/esp-hal-common/ld/sections/rtc_fast.x +++ /dev/null @@ -1,30 +0,0 @@ - - -SECTIONS { - .rtc_fast.text : { - . = ALIGN(4); - *(.rtc_fast.literal .rtc_fast.text .rtc_fast.literal.* .rtc_fast.text.*) - } > RTC_FAST_RWTEXT AT > RODATA - - .rtc_fast.data : - { - . = ALIGN(4); - _rtc_fast_data_start = ABSOLUTE(.); - *(.rtc_fast.data .rtc_fast.data.*) - _rtc_fast_data_end = ABSOLUTE(.); - } > RTC_FAST_RWDATA AT > RODATA - - .rtc_fast.bss (NOLOAD) : - { - . = ALIGN(4); - _rtc_fast_bss_start = ABSOLUTE(.); - *(.rtc_fast.bss .rtc_fast.bss.*) - _rtc_fast_bss_end = ABSOLUTE(.); - } > RTC_FAST_RWDATA - - .rtc_fast.noinit (NOLOAD) : - { - . = ALIGN(4); - *(.rtc_fast.noinit .rtc_fast.noinit.*) - } > RTC_FAST_RWDATA -} \ No newline at end of file diff --git a/esp-hal-common/ld/sections/rtc_slow.x b/esp-hal-common/ld/sections/rtc_slow.x deleted file mode 100644 index 0f90c5f271e..00000000000 --- a/esp-hal-common/ld/sections/rtc_slow.x +++ /dev/null @@ -1,30 +0,0 @@ - - -SECTIONS { - .rtc_slow.text : { - . = ALIGN(4); - *(.rtc_slow.literal .rtc_slow.text .rtc_slow.literal.* .rtc_slow.text.*) - } > rtc_slow_seg AT > RODATA - - .rtc_slow.data : - { - . = ALIGN(4); - _rtc_slow_data_start = ABSOLUTE(.); - *(.rtc_slow.data .rtc_slow.data.*) - _rtc_slow_data_end = ABSOLUTE(.); - } > rtc_slow_seg AT > RODATA - - .rtc_slow.bss (NOLOAD) : - { - . = ALIGN(4); - _rtc_slow_bss_start = ABSOLUTE(.); - *(.rtc_slow.bss .rtc_slow.bss.*) - _rtc_slow_bss_end = ABSOLUTE(.); - } > rtc_slow_seg - - .rtc_slow.noinit (NOLOAD) : - { - . = ALIGN(4); - *(.rtc_slow.noinit .rtc_slow.noinit.*) - } > rtc_slow_seg -} \ No newline at end of file diff --git a/esp-hal-common/ld/sections/rwdata.x b/esp-hal-common/ld/sections/rwdata.x deleted file mode 100644 index 61a1b024f59..00000000000 --- a/esp-hal-common/ld/sections/rwdata.x +++ /dev/null @@ -1,55 +0,0 @@ - - -SECTIONS { - .data : ALIGN(4) - { - _data_start = ABSOLUTE(.); - . = ALIGN (4); - *(.sdata .sdata.* .sdata2 .sdata2.*); - *(.data .data.*); - *(.data1) - _data_end = ABSOLUTE(.); - } > RWDATA AT > RODATA - - /* LMA of .data */ - _sidata = LOADADDR(.data); - - .bss (NOLOAD) : ALIGN(4) - { - _bss_start = ABSOLUTE(.); - . = ALIGN (4); - *(.dynsbss) - *(.sbss) - *(.sbss.*) - *(.gnu.linkonce.sb.*) - *(.scommon) - *(.sbss2) - *(.sbss2.*) - *(.gnu.linkonce.sb2.*) - *(.dynbss) - *(.sbss .sbss.* .bss .bss.*); - *(.share.mem) - *(.gnu.linkonce.b.*) - *(COMMON) - _bss_end = ABSOLUTE(.); - } > RWDATA - - .noinit (NOLOAD) : ALIGN(4) - { - . = ALIGN(4); - *(.noinit .noinit.*) - } > RWDATA - - .data.wifi : - { - . = ALIGN(4); - *( .dram1 .dram1.*) - } > RWDATA AT > RODATA - - /* must be last segment using RWDATA */ - .heap_start (NOLOAD) : ALIGN(4) - { - . = ALIGN (4); - _heap_start = ABSOLUTE(.); - } > RWDATA -} \ No newline at end of file diff --git a/esp-hal-common/ld/sections/rwtext.x b/esp-hal-common/ld/sections/rwtext.x deleted file mode 100644 index 6cb5f5f0851..00000000000 --- a/esp-hal-common/ld/sections/rwtext.x +++ /dev/null @@ -1,20 +0,0 @@ - - -SECTIONS { - .rwtext : ALIGN(4) - { - . = ALIGN (4); - *(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*) - } > RWTEXT - - .rwtext.wifi : - { - . = ALIGN(4); - *( .wifi0iram .wifi0iram.*) - *( .wifirxiram .wifirxiram.*) - *( .wifislprxiram .wifislprxiram.*) - *( .wifislpiram .wifislpiram.*) - *( .phyiram .phyiram.*) - *( .iram1 .iram1.*) - } > RWTEXT AT > RODATA -} \ No newline at end of file diff --git a/esp-hal-common/ld/sections/text.x b/esp-hal-common/ld/sections/text.x deleted file mode 100644 index bda6b67b69f..00000000000 --- a/esp-hal-common/ld/sections/text.x +++ /dev/null @@ -1,10 +0,0 @@ - - -SECTIONS { - - .text : ALIGN(4) - { - *(.literal .text .literal.* .text.*) - } > ROTEXT - -} \ No newline at end of file diff --git a/esp-hal-common/ld/xtensa/hal-defaults.x b/esp-hal-common/ld/xtensa/hal-defaults.x deleted file mode 100644 index f58999823fc..00000000000 --- a/esp-hal-common/ld/xtensa/hal-defaults.x +++ /dev/null @@ -1,7 +0,0 @@ -PROVIDE(level1_interrupt = DefaultHandler); -PROVIDE(level2_interrupt = DefaultHandler); -PROVIDE(level3_interrupt = DefaultHandler); -PROVIDE(level4_interrupt = DefaultHandler); -PROVIDE(level5_interrupt = DefaultHandler); -PROVIDE(level6_interrupt = DefaultHandler); -PROVIDE(level7_interrupt = DefaultHandler); diff --git a/esp-hal-common/ld/xtensa/rom.x b/esp-hal-common/ld/xtensa/rom.x deleted file mode 100644 index 979933d44b8..00000000000 --- a/esp-hal-common/ld/xtensa/rom.x +++ /dev/null @@ -1,6 +0,0 @@ -REGION_ALIAS("ROTEXT", irom_seg); -REGION_ALIAS("RWTEXT", iram_seg); -REGION_ALIAS("RODATA", drom_seg); -REGION_ALIAS("RWDATA", dram_seg); -REGION_ALIAS("RTC_FAST_RWTEXT", rtc_fast_iram_seg); -REGION_ALIAS("RTC_FAST_RWDATA", rtc_fast_dram_seg); diff --git a/esp-hal-common/src/aes/esp32.rs b/esp-hal-common/src/aes/esp32.rs deleted file mode 100644 index 52fac57dd1c..00000000000 --- a/esp-hal-common/src/aes/esp32.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{ - aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, ALIGN_SIZE}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -impl<'d> Aes<'d> { - pub(super) fn init(&mut self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(PeripheralEnable::Aes); - self.write_endianness( - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - ); - } - - pub(super) fn write_key(&mut self, key: &[u8]) { - debug_assert!(key.len() <= self.aes.key_.len() * ALIGN_SIZE); - debug_assert_eq!(key.len() % ALIGN_SIZE, 0); - Self::write_to_regset(key, self.aes.key_.len(), &mut self.aes.key_[0]); - } - - pub(super) fn write_block(&mut self, block: &[u8]) { - debug_assert_eq!(block.len(), self.aes.text_.len() * ALIGN_SIZE); - Self::write_to_regset(block, self.aes.text_.len(), &mut self.aes.text_[0]); - } - - pub(super) fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.aes.mode, mode); - } - - /// Configures how the state matrix would be laid out - pub fn write_endianness( - &mut self, - input_text_word_endianess: Endianness, - input_text_byte_endianess: Endianness, - output_text_word_endianess: Endianness, - output_text_byte_endianess: Endianness, - key_word_endianess: Endianness, - key_byte_endianess: Endianness, - ) { - let mut to_write = 0_u32; - to_write |= key_byte_endianess as u32; - to_write |= (key_word_endianess as u32) << 1; - to_write |= (input_text_byte_endianess as u32) << 2; - to_write |= (input_text_word_endianess as u32) << 3; - to_write |= (output_text_byte_endianess as u32) << 4; - to_write |= (output_text_word_endianess as u32) << 5; - Self::write_to_register(&mut self.aes.endian, to_write); - } - - pub(super) fn write_start(&mut self) { - self.aes.start.write(|w| w.start().set_bit()) - } - - pub(super) fn read_idle(&mut self) -> bool { - self.aes.idle.read().idle().bit_is_set() - } - - pub(super) fn read_block(&self, block: &mut [u8]) { - debug_assert_eq!(block.len(), self.aes.text_.len() * ALIGN_SIZE); - Self::read_from_regset(block, self.aes.text_.len(), &self.aes.text_[0]); - } -} - -impl AesFlavour for Aes128 { - type KeyType<'b> = &'b [u8; 16]; - const ENCRYPT_MODE: u32 = 0; - const DECRYPT_MODE: u32 = 4; -} - -impl AesFlavour for Aes192 { - type KeyType<'b> = &'b [u8; 24]; - const ENCRYPT_MODE: u32 = 1; - const DECRYPT_MODE: u32 = 5; -} - -impl AesFlavour for Aes256 { - type KeyType<'b> = &'b [u8; 32]; - const ENCRYPT_MODE: u32 = 2; - const DECRYPT_MODE: u32 = 6; -} diff --git a/esp-hal-common/src/aes/esp32cX.rs b/esp-hal-common/src/aes/esp32cX.rs deleted file mode 100644 index e0bc104a23e..00000000000 --- a/esp-hal-common/src/aes/esp32cX.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{ - aes::{Aes, Aes128, Aes256, AesFlavour, ALIGN_SIZE}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -impl<'d> Aes<'d> { - pub(super) fn init(&mut self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(PeripheralEnable::Aes); - self.write_dma(false); - } - - fn write_dma(&mut self, enable_dma: bool) { - match enable_dma { - true => self.aes.dma_enable.write(|w| w.dma_enable().set_bit()), - false => self.aes.dma_enable.write(|w| w.dma_enable().clear_bit()), - } - } - - pub(super) fn write_key(&mut self, key: &[u8]) { - debug_assert!(key.len() <= 8 * ALIGN_SIZE); - debug_assert_eq!(key.len() % ALIGN_SIZE, 0); - Self::write_to_regset(key, 8, &mut self.aes.key_0); - } - - pub(super) fn write_block(&mut self, block: &[u8]) { - debug_assert_eq!(block.len(), 4 * ALIGN_SIZE); - Self::write_to_regset(block, 4, &mut self.aes.text_in_0); - } - - pub(super) fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.aes.mode, mode); - } - - pub(super) fn write_start(&mut self) { - self.aes.trigger.write(|w| w.trigger().set_bit()) - } - - pub(super) fn read_idle(&mut self) -> bool { - self.aes.state.read().state().bits() == 0 - } - - pub(super) fn read_block(&self, block: &mut [u8]) { - debug_assert_eq!(block.len(), 4 * ALIGN_SIZE); - Self::read_from_regset(block, 4, &self.aes.text_out_0); - } -} - -impl AesFlavour for Aes128 { - type KeyType<'b> = &'b [u8; 16]; - const ENCRYPT_MODE: u32 = 0; - const DECRYPT_MODE: u32 = 4; -} - -impl AesFlavour for Aes256 { - type KeyType<'b> = &'b [u8; 32]; - const ENCRYPT_MODE: u32 = 2; - const DECRYPT_MODE: u32 = 6; -} diff --git a/esp-hal-common/src/aes/esp32s2.rs b/esp-hal-common/src/aes/esp32s2.rs deleted file mode 100644 index 331ed36ff29..00000000000 --- a/esp-hal-common/src/aes/esp32s2.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::{ - aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, ALIGN_SIZE}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -impl<'d> Aes<'d> { - pub(super) fn init(&mut self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(PeripheralEnable::Aes); - self.write_dma(false); - self.write_endianness( - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - Endianness::BigEndian, - ); - } - - fn write_dma(&mut self, enable_dma: bool) { - match enable_dma { - true => self.aes.dma_enable.write(|w| w.dma_enable().set_bit()), - false => self.aes.dma_enable.write(|w| w.dma_enable().clear_bit()), - } - } - - pub(super) fn write_key(&mut self, key: &[u8]) { - debug_assert!(key.len() <= self.aes.key_.len() * ALIGN_SIZE); - debug_assert_eq!(key.len() % ALIGN_SIZE, 0); - Self::write_to_regset(key, self.aes.key_.len(), &mut self.aes.key_[0]); - } - - pub(super) fn write_block(&mut self, block: &[u8]) { - debug_assert_eq!(block.len(), self.aes.text_in_.len() * ALIGN_SIZE); - Self::write_to_regset(block, self.aes.text_in_.len(), &mut self.aes.text_in_[0]); - } - - pub(super) fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.aes.mode, mode); - } - - /// Configures how the state matrix would be laid out. - pub fn write_endianness( - &mut self, - input_text_word_endianess: Endianness, - input_text_byte_endianess: Endianness, - output_text_word_endianess: Endianness, - output_text_byte_endianess: Endianness, - key_word_endianess: Endianness, - key_byte_endianess: Endianness, - ) { - let mut to_write = 0_u32; - to_write |= key_byte_endianess as u32; - to_write |= (key_word_endianess as u32) << 1; - to_write |= (input_text_byte_endianess as u32) << 2; - to_write |= (input_text_word_endianess as u32) << 3; - to_write |= (output_text_byte_endianess as u32) << 4; - to_write |= (output_text_word_endianess as u32) << 5; - Self::write_to_register(&mut self.aes.endian, to_write); - } - - pub(super) fn write_start(&mut self) { - self.aes.trigger.write(|w| w.trigger().set_bit()) - } - - pub(super) fn read_idle(&mut self) -> bool { - self.aes.state.read().state().bits() == 0 - } - - pub(super) fn read_block(&self, block: &mut [u8]) { - debug_assert_eq!(block.len(), self.aes.text_out_.len() * ALIGN_SIZE); - Self::read_from_regset(block, self.aes.text_out_.len(), &self.aes.text_out_[0]); - } -} - -impl AesFlavour for Aes128 { - type KeyType<'b> = &'b [u8; 16]; - const ENCRYPT_MODE: u32 = 0; - const DECRYPT_MODE: u32 = 4; -} - -impl AesFlavour for Aes192 { - type KeyType<'b> = &'b [u8; 24]; - const ENCRYPT_MODE: u32 = 1; - const DECRYPT_MODE: u32 = 5; -} - -impl AesFlavour for Aes256 { - type KeyType<'b> = &'b [u8; 32]; - const ENCRYPT_MODE: u32 = 2; - const DECRYPT_MODE: u32 = 6; -} diff --git a/esp-hal-common/src/aes/esp32s3.rs b/esp-hal-common/src/aes/esp32s3.rs deleted file mode 100644 index f80ee57466a..00000000000 --- a/esp-hal-common/src/aes/esp32s3.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{ - aes::{Aes, Aes128, Aes256, AesFlavour, ALIGN_SIZE}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -impl<'d> Aes<'d> { - pub(super) fn init(&mut self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(PeripheralEnable::Aes); - self.write_dma(false); - } - - fn write_dma(&mut self, enable_dma: bool) { - match enable_dma { - true => self.aes.dma_enable.write(|w| w.dma_enable().set_bit()), - false => self.aes.dma_enable.write(|w| w.dma_enable().clear_bit()), - } - } - - pub(super) fn write_key(&mut self, key: &[u8]) { - debug_assert!(key.len() <= self.aes.key_.len() * ALIGN_SIZE); - debug_assert_eq!(key.len() % ALIGN_SIZE, 0); - Self::write_to_regset(key, self.aes.key_.len(), &mut self.aes.key_[0]); - } - - pub(super) fn write_block(&mut self, block: &[u8]) { - debug_assert_eq!(block.len(), self.aes.text_in_.len() * ALIGN_SIZE); - Self::write_to_regset(block, self.aes.text_in_.len(), &mut self.aes.text_in_[0]); - } - - pub(super) fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.aes.mode, mode); - } - - pub(super) fn write_start(&mut self) { - self.aes.trigger.write(|w| w.trigger().set_bit()) - } - - pub(super) fn read_idle(&mut self) -> bool { - self.aes.state.read().state().bits() == 0 - } - - pub(super) fn read_block(&self, block: &mut [u8]) { - debug_assert_eq!(block.len(), self.aes.text_out_.len() * ALIGN_SIZE); - Self::read_from_regset(block, self.aes.text_out_.len(), &self.aes.text_out_[0]); - } -} - -impl AesFlavour for Aes128 { - type KeyType<'b> = &'b [u8; 16]; - const ENCRYPT_MODE: u32 = 0; - const DECRYPT_MODE: u32 = 4; -} - -impl AesFlavour for Aes256 { - type KeyType<'b> = &'b [u8; 32]; - const ENCRYPT_MODE: u32 = 2; - const DECRYPT_MODE: u32 = 6; -} diff --git a/esp-hal-common/src/aes/mod.rs b/esp-hal-common/src/aes/mod.rs deleted file mode 100644 index bb1d9e216f1..00000000000 --- a/esp-hal-common/src/aes/mod.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Advanced Encryption Standard (AES) support. -//! -//! This module provides functions and structs for AES encryption and -//! decryption. -//! -//! ### Features -//! The AES peripheral has the following features available on individual chips: -//! -//! | Feature | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | -//! |------------------|----------|----------|----------|----------| -//! |AES128 |Y |Y |Y |Y | -//! |AES192 |Y |N |Y |N | -//! |AES256 |Y |Y |Y |Y | -//! |Custom endianness |Y |N |Y |N | -//! -//! ### Implementation State -//! * DMA mode is currently not supported. - -use core::marker::PhantomData; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::{ - generic::{Readable, Reg, RegisterSpec, Resettable, Writable}, - AES, - }, - system::PeripheralClockControl, -}; - -#[cfg_attr(esp32, path = "esp32.rs")] -#[cfg_attr(esp32s3, path = "esp32s3.rs")] -#[cfg_attr(esp32s2, path = "esp32s2.rs")] -#[cfg_attr(esp32c3, path = "esp32cX.rs")] -#[cfg_attr(esp32c6, path = "esp32cX.rs")] -mod aes_spec_impl; - -const ALIGN_SIZE: usize = core::mem::size_of::(); - -/// AES peripheral container -pub struct Aes<'d> { - aes: PeripheralRef<'d, AES>, -} - -impl<'d> Aes<'d> { - pub fn new( - aes: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(aes); - let mut ret = Self { aes: aes }; - ret.init(peripheral_clock_control); - ret - } - - fn write_to_regset(input: &[u8], n_offset: usize, reg_0: &mut Reg) - where - T: RegisterSpec + Resettable + Writable, - { - let chunks = input.chunks_exact(ALIGN_SIZE); - for (offset, chunk) in (0..n_offset).zip(chunks) { - let to_write = u32::from_ne_bytes(chunk.try_into().unwrap()); - unsafe { - let p = reg_0.as_ptr().add(offset); - p.write_volatile(to_write); - } - } - } - - fn read_from_regset(out_buf: &mut [u8], n_offset: usize, reg_0: &Reg) - where - T: RegisterSpec + Readable, - { - let chunks = out_buf.chunks_exact_mut(ALIGN_SIZE); - for (offset, chunk) in (0..n_offset).zip(chunks) { - unsafe { - let p = reg_0.as_ptr().add(offset); - let read_val: [u8; ALIGN_SIZE] = p.read_volatile().to_ne_bytes(); - chunk.copy_from_slice(&read_val); - } - } - } - - fn write_to_register(reg: &mut Reg, data: u32) - where - T: RegisterSpec + Resettable + Writable, - { - reg.write(|w| unsafe { w.bits(data) }); - } -} - -mod sealed { - /// Specifications for AES flavours - pub trait AesFlavour { - type KeyType<'b>; - const ENCRYPT_MODE: u32; - const DECRYPT_MODE: u32; - } -} - -use sealed::AesFlavour; - -/// Marker type for AES-128 -pub struct Aes128; - -/// Marker type for AES-192 -#[cfg(any(esp32, esp32s2))] -pub struct Aes192; - -/// Marker type for AES-256 -pub struct Aes256; - -/// Block cipher -pub struct Cipher<'a, 'd, T: AesFlavour> { - aes: &'a mut Aes<'d>, - phantom: PhantomData, -} - -impl<'a, 'd, T: AesFlavour> Cipher<'a, 'd, T> { - /// Creates and returns a new cipher - pub fn new(aes: &'a mut Aes<'d>, key: &Key) -> Self { - aes.write_key(key.key); - Self { - aes, - phantom: PhantomData, - } - } - /// Encrypts the given buffer - pub fn encrypt_block(&mut self, block: &mut [u8; 16]) { - self.set_mode(T::ENCRYPT_MODE); - self.set_block(block); - self.start(); - while !(self.is_idle()) {} - self.get_block(block); - } - - /// Decrypts the given buffer - pub fn decrypt_block(&mut self, block: &mut [u8; 16]) { - self.set_mode(T::DECRYPT_MODE); - self.set_block(block); - self.start(); - while !(self.is_idle()) {} - self.get_block(block); - } - - fn set_mode(&mut self, mode: u32) { - self.aes.write_mode(mode); - } - - fn is_idle(&mut self) -> bool { - self.aes.read_idle() - } - - fn set_block(&mut self, block: &[u8; 16]) { - self.aes.write_block(block); - } - - fn get_block(&self, block: &mut [u8; 16]) { - self.aes.read_block(block); - } - - fn start(&mut self) { - self.aes.write_start(); - } -} - -/// Aes cipher key -/// -/// A `Key` can be initialized from an array of appropriate length: -/// -/// ``` plain -/// let key = Key::::from(&[0_u8;16]); -/// let key = Key::::from(&[0_u8;24]); -/// let key = Key::::from(&[0_u8;32]); -/// ``` -pub struct Key<'b, T: AesFlavour> { - key: &'b [u8], - phantom: PhantomData, -} - -impl<'b, T, const N: usize> From<&'b [u8; N]> for Key<'b, T> -where - T: AesFlavour = &'b [u8; N]>, -{ - fn from(value: T::KeyType<'b>) -> Self { - Key { - key: value, - phantom: PhantomData, - } - } -} -/// State matrix endianness -#[cfg(any(esp32, esp32s2))] -pub enum Endianness { - BigEndian = 1, - LittleEndian = 0, -} diff --git a/esp-hal-common/src/analog/adc/esp32.rs b/esp-hal-common/src/analog/adc/esp32.rs deleted file mode 100644 index 75b3a457f82..00000000000 --- a/esp-hal-common/src/analog/adc/esp32.rs +++ /dev/null @@ -1,477 +0,0 @@ -use core::marker::PhantomData; - -use embedded_hal::adc::{Channel, OneShot}; - -use crate::{ - analog::{ADC1, ADC2}, - peripheral::PeripheralRef, - peripherals::{RTC_IO, SENS}, -}; - -/// The sampling/readout resolution of the ADC -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Resolution { - Resolution9Bit = 0b00, - Resolution10Bit = 0b01, - Resolution11Bit = 0b10, - Resolution12Bit = 0b11, -} - -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Attenuation { - Attenuation0dB = 0b00, - Attenuation2p5dB = 0b01, - Attenuation6dB = 0b10, - Attenuation11dB = 0b11, -} - -pub struct AdcPin { - pub pin: PIN, - _phantom: PhantomData, -} - -impl, ADCI> Channel for AdcPin { - type ID = u8; - - fn channel() -> Self::ID { - PIN::channel() - } -} - -pub struct AdcConfig { - pub resolution: Resolution, - pub attenuations: [Option; 10], - _phantom: PhantomData, -} - -impl AdcConfig -where - ADCI: RegisterAccess, -{ - pub fn new() -> AdcConfig { - crate::into_ref!(); - Self::default() - } - - pub fn enable_pin>( - &mut self, - pin: PIN, - attenuation: Attenuation, - ) -> AdcPin { - self.attenuations[PIN::channel() as usize] = Some(attenuation); - - AdcPin { - pin, - _phantom: PhantomData::default(), - } - } -} - -impl Default for AdcConfig { - fn default() -> Self { - AdcConfig { - resolution: Resolution::Resolution12Bit, - attenuations: [None; 10], - _phantom: PhantomData::default(), - } - } -} - -pub trait RegisterAccess { - fn set_bit_width(resolution: u8); - - fn set_sample_bit(resolution: u8); - - fn set_attenuation(channel: usize, attenuation: u8); - - fn clear_dig_force(); - - fn set_start_force(); - - fn set_en_pad_force(); - - fn set_en_pad(channel: u8); - - fn clear_start_sar(); - - fn set_start_sar(); - - fn read_done_sar() -> bool; - - fn read_data_sar() -> u16; -} - -#[doc(hidden)] -impl RegisterAccess for ADC1 { - fn set_bit_width(resolution: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_start_force - .modify(|_, w| unsafe { w.sar1_bit_width().bits(resolution) }); - } - - fn set_sample_bit(resolution: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_read_ctrl - .modify(|_, w| unsafe { w.sar1_sample_bit().bits(resolution) }); - } - - fn set_attenuation(channel: usize, attenuation: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_atten1.modify(|r, w| { - let new_value = (r.bits() & !(0b11 << (channel * 2))) - | (((attenuation as u8 & 0b11) as u32) << (channel * 2)); - - unsafe { w.sar1_atten().bits(new_value) } - }); - } - - fn clear_dig_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_read_ctrl - .modify(|_, w| w.sar1_dig_force().clear_bit()); - } - - fn set_start_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start1 - .modify(|_, w| w.meas1_start_force().set_bit()); - } - - fn set_en_pad_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start1 - .modify(|_, w| w.sar1_en_pad_force().set_bit()); - } - - fn set_en_pad(channel: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start1 - .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) }); - } - - fn clear_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start1 - .modify(|_, w| w.meas1_start_sar().clear_bit()); - } - - fn set_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start1 - .modify(|_, w| w.meas1_start_sar().set_bit()); - } - - fn read_done_sar() -> bool { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas_start1.read().meas1_done_sar().bit_is_set() - } - - fn read_data_sar() -> u16 { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas_start1.read().meas1_data_sar().bits() as u16 - } -} - -impl RegisterAccess for ADC2 { - fn set_bit_width(resolution: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_start_force - .modify(|_, w| unsafe { w.sar2_bit_width().bits(resolution) }); - } - - fn set_sample_bit(resolution: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_read_ctrl2 - .modify(|_, w| unsafe { w.sar2_sample_bit().bits(resolution) }); - } - - fn set_attenuation(channel: usize, attenuation: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_atten2.modify(|r, w| { - let new_value = (r.bits() & !(0b11 << (channel * 2))) - | (((attenuation as u8 & 0b11) as u32) << (channel * 2)); - - unsafe { w.sar2_atten().bits(new_value) } - }); - } - - fn clear_dig_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_read_ctrl2 - .modify(|_, w| w.sar2_dig_force().clear_bit()); - } - - fn set_start_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start2 - .modify(|_, w| w.meas2_start_force().set_bit()); - } - - fn set_en_pad_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start2 - .modify(|_, w| w.sar2_en_pad_force().set_bit()); - } - - fn set_en_pad(channel: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start2 - .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) }); - } - - fn clear_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start2 - .modify(|_, w| w.meas2_start_sar().clear_bit()); - } - - fn set_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas_start2 - .modify(|_, w| w.meas2_start_sar().set_bit()); - } - - fn read_done_sar() -> bool { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas_start2.read().meas2_done_sar().bit_is_set() - } - - fn read_data_sar() -> u16 { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas_start2.read().meas2_data_sar().bits() as u16 - } -} - -pub struct ADC<'d, ADC> { - _adc: PeripheralRef<'d, ADC>, - attenuations: [Option; 10], - active_channel: Option, -} - -impl<'d, ADCI> ADC<'d, ADCI> -where - ADCI: RegisterAccess, -{ - pub fn adc( - adc_instance: impl crate::peripheral::Peripheral

    + 'd, - config: AdcConfig, - ) -> Result { - let sensors = unsafe { &*SENS::ptr() }; - - // Set reading and sampling resolution - let resolution: u8 = config.resolution as u8; - - ADCI::set_bit_width(resolution); - ADCI::set_sample_bit(resolution); - - // Set attenuation for pins - let attenuations = config.attenuations; - - for channel in 0..attenuations.len() { - if let Some(attenuation) = attenuations[channel] { - ADC1::set_attenuation(channel, attenuation as u8); - } - } - - // Set controller to RTC - ADCI::clear_dig_force(); - ADCI::set_start_force(); - ADCI::set_en_pad_force(); - sensors - .sar_touch_ctrl1 - .modify(|_, w| w.xpd_hall_force().set_bit()); - sensors - .sar_touch_ctrl1 - .modify(|_, w| w.hall_phase_force().set_bit()); - - // Set power to SW power on - sensors - .sar_meas_wait2 - .modify(|_, w| unsafe { w.force_xpd_sar().bits(0b11) }); - - // disable AMP - sensors - .sar_meas_wait2 - .modify(|_, w| unsafe { w.force_xpd_amp().bits(0b10) }); - sensors - .sar_meas_ctrl - .modify(|_, w| unsafe { w.amp_rst_fb_fsm().bits(0) }); - sensors - .sar_meas_ctrl - .modify(|_, w| unsafe { w.amp_short_ref_fsm().bits(0) }); - sensors - .sar_meas_ctrl - .modify(|_, w| unsafe { w.amp_short_ref_gnd_fsm().bits(0) }); - sensors - .sar_meas_wait1 - .modify(|_, w| unsafe { w.sar_amp_wait1().bits(1) }); - sensors - .sar_meas_wait1 - .modify(|_, w| unsafe { w.sar_amp_wait2().bits(1) }); - sensors - .sar_meas_wait2 - .modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) }); - - let adc = ADC { - _adc: adc_instance.into_ref(), - attenuations: config.attenuations, - active_channel: None, - }; - - Ok(adc) - } -} - -impl<'d, ADC1> ADC<'d, ADC1> { - pub fn enable_hall_sensor() { - // Connect hall sensor - let rtcio = unsafe { &*RTC_IO::ptr() }; - rtcio.hall_sens.modify(|_, w| w.xpd_hall().set_bit()); - } - - pub fn disable_hall_sensor() { - // Disconnect hall sensor - let rtcio = unsafe { &*RTC_IO::ptr() }; - rtcio.hall_sens.modify(|_, w| w.xpd_hall().clear_bit()); - } -} - -impl<'d, ADCI, WORD, PIN> OneShot> for ADC<'d, ADCI> -where - WORD: From, - PIN: Channel, - ADCI: RegisterAccess, -{ - type Error = (); - - fn read(&mut self, _pin: &mut AdcPin) -> nb::Result { - if self.attenuations[AdcPin::::channel() as usize] == None { - panic!( - "Channel {} is not configured reading!", - AdcPin::::channel() - ); - } - - if let Some(active_channel) = self.active_channel { - // There is conversion in progress: - // - if it's for a different channel try again later - // - if it's for the given channel, go ahead and check progress - if active_channel != AdcPin::::channel() { - return Err(nb::Error::WouldBlock); - } - } else { - // If no conversions are in progress, start a new one for given channel - self.active_channel = Some(AdcPin::::channel()); - - ADCI::set_en_pad(AdcPin::::channel() as u8); - - ADCI::clear_start_sar(); - ADCI::set_start_sar(); - } - - // Wait for ADC to finish conversion - let conversion_finished = ADCI::read_done_sar(); - if !conversion_finished { - return Err(nb::Error::WouldBlock); - } - - // Get converted value - let converted_value = ADCI::read_data_sar(); - - // Mark that no conversions are currently in progress - self.active_channel = None; - - Ok(converted_value.into()) - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! impl_adc_interface { - ($adc:ident [ - $( ($pin:ident, $channel:expr) ,)+ - ]) => { - - $( - impl Channel<$adc> for $pin { - type ID = u8; - - fn channel() -> u8 { $channel } - } - )+ - } -} - -pub use impl_adc_interface; - -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from two - //! analog to digital converters available on the ESP32: `ADC1` and `ADC2`. - //! - //! The following pins can be configured for analog readout: - //! - //! | Channel | ADC1 | ADC2 | - //! |---------|----------------------|---------------| - //! | 0 | GPIO36 (SENSOR_VP) | GPIO4 | - //! | 1 | GPIO37 (SENSOR_CAPP) | GPIO0 | - //! | 2 | GPIO38 (SENSOR_CAPN) | GPIO2 | - //! | 3 | GPIO39 (SENSOR_VN) | GPIO15 (MTDO) | - //! | 4 | GPIO33 (32K_XP) | GPIO13 (MTCK) | - //! | 5 | GPIO32 (32K_XN) | GPIO12 (MTDI) | - //! | 6 | GPIO34 (VDET_1) | GPIO14 (MTMS) | - //! | 7 | GPIO35 (VDET_2) | GPIO27 | - //! | 8 | | GPIO25 | - //! | 9 | | GPIO26 | - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1, ADC2}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio36, 0), // Alt. name: SENSOR_VP - (Gpio37, 1), // Alt. name: SENSOR_CAPP - (Gpio38, 2), // Alt. name: SENSOR_CAPN - (Gpio39, 3), // Alt. name: SENSOR_VN - (Gpio33, 4), // Alt. name: 32K_XP - (Gpio32, 5), // Alt. name: 32K_XN - (Gpio34, 6), // Alt. name: VDET_1 - (Gpio35, 7), // Alt. name: VDET_2 - ] - } - - impl_adc_interface! { - ADC2 [ - (Gpio4, 0), - (Gpio0, 1), - (Gpio2, 2), - (Gpio15, 3), // Alt. name: MTDO - (Gpio13, 4), // Alt. name: MTCK - (Gpio12, 5), // Alt. name: MTDI - (Gpio14, 6), // Alt. name: MTMS - (Gpio27, 7), - (Gpio25, 8), - (Gpio26, 9), - ] - } -} diff --git a/esp-hal-common/src/analog/adc/riscv.rs b/esp-hal-common/src/analog/adc/riscv.rs deleted file mode 100644 index f0c025c9504..00000000000 --- a/esp-hal-common/src/analog/adc/riscv.rs +++ /dev/null @@ -1,369 +0,0 @@ -use core::marker::PhantomData; - -use embedded_hal::adc::{Channel, OneShot}; - -#[cfg(esp32c3)] -use crate::analog::ADC2; -use crate::{ - analog::ADC1, - peripheral::PeripheralRef, - peripherals::APB_SARADC, - system::{Peripheral, PeripheralClockControl}, -}; - -/// The sampling/readout resolution of the ADC -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Resolution { - Resolution12Bit, -} - -/// The attenuation of the ADC pin -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Attenuation { - Attenuation0dB = 0b00, - Attenuation2p5dB = 0b01, - Attenuation6dB = 0b10, - Attenuation11dB = 0b11, -} - -pub struct AdcPin { - pub pin: PIN, - _phantom: PhantomData, -} - -impl, ADCI> Channel for AdcPin { - type ID = u8; - - fn channel() -> Self::ID { - PIN::channel() - } -} - -pub struct AdcConfig { - pub resolution: Resolution, - pub attenuations: [Option; 5], - _phantom: PhantomData, -} - -impl AdcConfig -where - ADCI: RegisterAccess, -{ - pub fn new() -> AdcConfig { - Self::default() - } - - pub fn enable_pin>( - &mut self, - pin: PIN, - attenuation: Attenuation, - ) -> AdcPin { - self.attenuations[PIN::channel() as usize] = Some(attenuation); - - AdcPin { - pin, - _phantom: PhantomData::default(), - } - } -} - -impl Default for AdcConfig { - fn default() -> Self { - AdcConfig { - resolution: Resolution::Resolution12Bit, - attenuations: [None; 5], - _phantom: PhantomData::default(), - } - } -} - -#[doc(hidden)] -pub trait RegisterAccess { - fn start_onetime_sample(channel: u8, attenuation: u8); - - fn is_done() -> bool; - - fn read_data() -> u16; - - fn reset(); -} - -impl RegisterAccess for ADC1 { - fn start_onetime_sample(channel: u8, attenuation: u8) { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc.onetime_sample.modify(|_, w| unsafe { - w.saradc1_onetime_sample() - .set_bit() - .saradc_onetime_channel() - .bits(channel) - .saradc_onetime_atten() - .bits(attenuation) - .saradc_onetime_start() - .set_bit() - }); - } - - fn is_done() -> bool { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc.int_raw.read().apb_saradc1_done_int_raw().bit() - } - - fn read_data() -> u16 { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - (sar_adc.sar1data_status.read().apb_saradc1_data().bits() as u16) & 0xfff - } - - fn reset() { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc - .int_clr - .write(|w| w.apb_saradc1_done_int_clr().set_bit()); - - sar_adc - .onetime_sample - .modify(|_, w| w.saradc_onetime_start().clear_bit()); - } -} - -#[cfg(esp32c3)] -impl RegisterAccess for ADC2 { - fn start_onetime_sample(channel: u8, attenuation: u8) { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc.onetime_sample.modify(|_, w| unsafe { - w.saradc2_onetime_sample() - .set_bit() - .saradc_onetime_channel() - .bits(channel) - .saradc_onetime_atten() - .bits(attenuation) - .saradc_onetime_start() - .set_bit() - }); - } - - fn is_done() -> bool { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc.int_raw.read().apb_saradc2_done_int_raw().bit() - } - - fn read_data() -> u16 { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - (sar_adc.sar2data_status.read().apb_saradc2_data().bits() as u16) & 0xfff - } - - fn reset() { - let sar_adc = unsafe { &*APB_SARADC::PTR }; - - sar_adc - .int_clr - .write(|w| w.apb_saradc2_done_int_clr().set_bit()); - - sar_adc - .onetime_sample - .modify(|_, w| w.saradc_onetime_start().clear_bit()); - } -} - -pub struct ADC<'d, ADCI> { - _adc: PeripheralRef<'d, ADCI>, - attenuations: [Option; 5], - active_channel: Option, -} - -impl<'d, ADCI> ADC<'d, ADCI> -where - ADCI: RegisterAccess, -{ - pub fn adc( - peripheral_clock_controller: &mut PeripheralClockControl, - adc_instance: impl crate::peripheral::Peripheral

    + 'd, - config: AdcConfig, - ) -> Result { - peripheral_clock_controller.enable(Peripheral::ApbSarAdc); - - let sar_adc = unsafe { &*APB_SARADC::PTR }; - sar_adc.ctrl.modify(|_, w| unsafe { - w.saradc_start_force() - .set_bit() - .saradc_start() - .set_bit() - .saradc_sar_clk_gated() - .set_bit() - .saradc_xpd_sar_force() - .bits(0b11) - }); - let adc = ADC { - _adc: adc_instance.into_ref(), - attenuations: config.attenuations, - active_channel: None, - }; - - Ok(adc) - } -} - -impl<'d, ADCI, WORD, PIN> OneShot> for ADC<'d, ADCI> -where - WORD: From, - PIN: Channel, - ADCI: RegisterAccess, -{ - type Error = (); - - fn read(&mut self, _pin: &mut AdcPin) -> nb::Result { - if self.attenuations[AdcPin::::channel() as usize] == None { - panic!( - "Channel {} is not configured reading!", - AdcPin::::channel() - ); - } - - if let Some(active_channel) = self.active_channel { - // There is conversion in progress: - // - if it's for a different channel try again later - // - if it's for the given channel, go ahead and check progress - if active_channel != AdcPin::::channel() { - return Err(nb::Error::WouldBlock); - } - } else { - // If no conversions are in progress, start a new one for given channel - self.active_channel = Some(AdcPin::::channel()); - - let channel = self.active_channel.unwrap(); - let attenuation = self.attenuations[channel as usize].unwrap() as u8; - ADCI::start_onetime_sample(channel, attenuation); - } - - // Wait for ADC to finish conversion - let conversion_finished = ADCI::is_done(); - if !conversion_finished { - return Err(nb::Error::WouldBlock); - } - - // Get converted value - let converted_value = ADCI::read_data(); - ADCI::reset(); - - // There is a hardware limitation. If the APB clock frequency is high, the step - // of this reg signal: ``onetime_start`` may not be captured by the - // ADC digital controller (when its clock frequency is too slow). A rough - // estimate for this step should be at least 3 ADC digital controller - // clock cycle. - // - // This limitation will be removed in hardware future versions. - // We reset ``onetime_start`` in `reset` and assume enough time has passed until - // the next sample is requested. - - // Mark that no conversions are currently in progress - self.active_channel = None; - - Ok(converted_value.into()) - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! impl_adc_interface { - ($adc:ident [ - $( ($pin:ident, $channel:expr) ,)+ - ]) => { - - $( - impl Channel<$adc> for $pin { - type ID = u8; - - fn channel() -> u8 { $channel } - } - )+ - } -} - -pub use impl_adc_interface; - -#[cfg(esp32c2)] -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from the - //! analog to digital converter available on the ESP32-C2: `ADC1`. - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio0, 0), - (Gpio1, 1), - (Gpio2, 2), - (Gpio3, 3), - (Gpio4, 4), - ] - } -} - -#[cfg(esp32c3)] -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from two - //! analog to digital converters available on the ESP32-C3: `ADC1` and - //! `ADC2`. - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1, ADC2}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio0, 0), - (Gpio1, 1), - (Gpio2, 2), - (Gpio3, 3), - (Gpio4, 4), - ] - } - - impl_adc_interface! { - ADC2 [ - (Gpio5, 4), - ] - } -} - -#[cfg(esp32c6)] -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from one - //! analog to digital converter available on the ESP32-C6: `ADC1`. - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio0, 0), - (Gpio1, 1), - (Gpio2, 2), - (Gpio3, 3), - (Gpio4, 4), - (Gpio5, 5), - (Gpio6, 6), - ] - } -} diff --git a/esp-hal-common/src/analog/adc/xtensa.rs b/esp-hal-common/src/analog/adc/xtensa.rs deleted file mode 100644 index 30d42812ef6..00000000000 --- a/esp-hal-common/src/analog/adc/xtensa.rs +++ /dev/null @@ -1,501 +0,0 @@ -use core::marker::PhantomData; - -use embedded_hal::adc::{Channel, OneShot}; - -use crate::{ - analog::{ADC1, ADC2}, - peripheral::PeripheralRef, - peripherals::{APB_SARADC, SENS}, -}; - -/// The sampling/readout resolution of the ADC -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Resolution { - Resolution13Bit, -} - -/// The attenuation of the ADC pin -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Attenuation { - Attenuation0dB = 0b00, - Attenuation2p5dB = 0b01, - Attenuation6dB = 0b10, - Attenuation11dB = 0b11, -} - -pub struct AdcPin { - pub pin: PIN, - _phantom: PhantomData, -} - -impl, ADCI> Channel for AdcPin { - type ID = u8; - - fn channel() -> Self::ID { - PIN::channel() - } -} - -pub struct AdcConfig { - pub resolution: Resolution, - pub attenuations: [Option; 10], - _phantom: PhantomData, -} - -impl AdcConfig -where - ADCI: RegisterAccess, -{ - pub fn new() -> AdcConfig { - Self::default() - } - - pub fn enable_pin>( - &mut self, - pin: PIN, - attenuation: Attenuation, - ) -> AdcPin { - self.attenuations[PIN::channel() as usize] = Some(attenuation); - - AdcPin { - pin, - _phantom: PhantomData::default(), - } - } -} - -impl Default for AdcConfig { - fn default() -> Self { - AdcConfig { - resolution: Resolution::Resolution13Bit, - attenuations: [None; 10], - _phantom: PhantomData::default(), - } - } -} - -#[doc(hidden)] -pub trait RegisterAccess { - fn set_bit_width(resolution: u8); - - fn set_sample_bit(resolution: u8); - - fn set_attenuation(channel: usize, attenuation: u8); - - fn clear_dig_force(); - - fn set_start_force(); - - fn set_en_pad_force(); - - fn set_en_pad(channel: u8); - - fn clear_start_sar(); - - fn set_start_sar(); - - fn read_done_sar() -> bool; - - fn read_data_sar() -> u16; -} - -impl RegisterAccess for ADC1 { - fn set_bit_width(_resolution: u8) { - // no-op - } - - fn set_sample_bit(_resolution: u8) { - // no-op - } - - fn set_attenuation(channel: usize, attenuation: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_atten1.modify(|r, w| { - let new_value = (r.bits() & !(0b11 << (channel * 2))) - | (((attenuation as u8 & 0b11) as u32) << (channel * 2)); - - unsafe { w.sar1_atten().bits(new_value) } - }); - } - - fn clear_dig_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_mux - .modify(|_, w| w.sar1_dig_force().clear_bit()); - } - - fn set_start_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_ctrl2 - .modify(|_, w| w.meas1_start_force().set_bit()); - } - - fn set_en_pad_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_ctrl2 - .modify(|_, w| w.sar1_en_pad_force().set_bit()); - } - - fn set_en_pad(channel: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_ctrl2 - .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) }); - } - - fn clear_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_ctrl2 - .modify(|_, w| w.meas1_start_sar().clear_bit()); - } - - fn set_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas1_ctrl2 - .modify(|_, w| w.meas1_start_sar().set_bit()); - } - - fn read_done_sar() -> bool { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas1_ctrl2.read().meas1_done_sar().bit_is_set() - } - - fn read_data_sar() -> u16 { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas1_ctrl2.read().meas1_data_sar().bits() as u16 - } -} - -impl RegisterAccess for ADC2 { - fn set_bit_width(_resolution: u8) { - // no-op - } - - fn set_sample_bit(_resolution: u8) { - // no-op - } - - fn set_attenuation(channel: usize, attenuation: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_atten2.modify(|r, w| { - let new_value = (r.bits() & !(0b11 << (channel * 2))) - | (((attenuation as u8 & 0b11) as u32) << (channel * 2)); - - unsafe { w.sar2_atten().bits(new_value) } - }); - } - - fn clear_dig_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_mux - .modify(|_, w| w.sar2_rtc_force().set_bit()); - - let sar_apb = unsafe { &*APB_SARADC::ptr() }; - sar_apb - .arb_ctrl - .modify(|_, w| w.adc_arb_rtc_force().set_bit()); - } - - fn set_start_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_ctrl2 - .modify(|_, w| w.meas2_start_force().set_bit()); - } - - fn set_en_pad_force() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_ctrl2 - .modify(|_, w| w.sar2_en_pad_force().set_bit()); - } - - fn set_en_pad(channel: u8) { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_ctrl2 - .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) }); - } - - fn clear_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_ctrl2 - .modify(|_, w| w.meas2_start_sar().clear_bit()); - } - - fn set_start_sar() { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_meas2_ctrl2 - .modify(|_, w| w.meas2_start_sar().set_bit()); - } - - fn read_done_sar() -> bool { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas2_ctrl2.read().meas2_done_sar().bit_is_set() - } - - fn read_data_sar() -> u16 { - let sensors = unsafe { &*SENS::ptr() }; - sensors.sar_meas2_ctrl2.read().meas2_data_sar().bits() as u16 - } -} - -pub struct ADC<'d, ADC> { - _adc: PeripheralRef<'d, ADC>, - attenuations: [Option; 10], - active_channel: Option, -} - -impl<'d, ADCI> ADC<'d, ADCI> -where - ADCI: RegisterAccess, -{ - pub fn adc( - adc_instance: impl crate::peripheral::Peripheral

    + 'd, - config: AdcConfig, - ) -> Result { - let sensors = unsafe { &*SENS::ptr() }; - - // Set reading and sampling resolution - let resolution: u8 = config.resolution as u8; - - ADCI::set_bit_width(resolution); - ADCI::set_sample_bit(resolution); - - // Set attenuation for pins - let attenuations = config.attenuations; - - for channel in 0..attenuations.len() { - if let Some(attenuation) = attenuations[channel] { - ADC1::set_attenuation(channel, attenuation as u8); - } - } - - // Set controller to RTC - ADCI::clear_dig_force(); - ADCI::set_start_force(); - ADCI::set_en_pad_force(); - sensors - .sar_hall_ctrl - .modify(|_, w| w.xpd_hall_force().set_bit()); - sensors - .sar_hall_ctrl - .modify(|_, w| w.hall_phase_force().set_bit()); - - // Set power to SW power on - #[cfg(esp32s2)] - sensors - .sar_meas1_ctrl1 - .modify(|_, w| w.rtc_saradc_clkgate_en().set_bit()); - - #[cfg(esp32s3)] - sensors - .sar_peri_clk_gate_conf - .modify(|_, w| w.saradc_clk_en().set_bit()); - - sensors - .sar_power_xpd_sar - .modify(|_, w| w.sarclk_en().set_bit()); - - sensors - .sar_power_xpd_sar - .modify(|_, w| unsafe { w.force_xpd_sar().bits(0b11) }); - - // disable AMP - sensors - .sar_meas1_ctrl1 - .modify(|_, w| unsafe { w.force_xpd_amp().bits(0b11) }); - sensors - .sar_amp_ctrl3 - .modify(|_, w| unsafe { w.amp_rst_fb_fsm().bits(0) }); - sensors - .sar_amp_ctrl3 - .modify(|_, w| unsafe { w.amp_short_ref_fsm().bits(0) }); - sensors - .sar_amp_ctrl3 - .modify(|_, w| unsafe { w.amp_short_ref_gnd_fsm().bits(0) }); - sensors - .sar_amp_ctrl1 - .modify(|_, w| unsafe { w.sar_amp_wait1().bits(1) }); - sensors - .sar_amp_ctrl1 - .modify(|_, w| unsafe { w.sar_amp_wait2().bits(1) }); - sensors - .sar_amp_ctrl2 - .modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) }); - - let adc = ADC { - _adc: adc_instance.into_ref(), - attenuations: config.attenuations, - active_channel: None, - }; - - Ok(adc) - } -} - -impl<'d, ADCI, WORD, PIN> OneShot> for ADC<'d, ADCI> -where - WORD: From, - PIN: Channel, - ADCI: RegisterAccess, -{ - type Error = (); - - fn read(&mut self, _pin: &mut AdcPin) -> nb::Result { - if self.attenuations[AdcPin::::channel() as usize] == None { - panic!( - "Channel {} is not configured reading!", - AdcPin::::channel() - ); - } - - if let Some(active_channel) = self.active_channel { - // There is conversion in progress: - // - if it's for a different channel try again later - // - if it's for the given channel, go ahead and check progress - if active_channel != AdcPin::::channel() { - return Err(nb::Error::WouldBlock); - } - } else { - // If no conversions are in progress, start a new one for given channel - self.active_channel = Some(AdcPin::::channel()); - - ADCI::set_en_pad(AdcPin::::channel() as u8); - - ADCI::clear_start_sar(); - ADCI::set_start_sar(); - } - - // Wait for ADC to finish conversion - let conversion_finished = ADCI::read_done_sar(); - if !conversion_finished { - return Err(nb::Error::WouldBlock); - } - - // Get converted value - let converted_value = ADCI::read_data_sar(); - - // Mark that no conversions are currently in progress - self.active_channel = None; - - Ok(converted_value.into()) - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! impl_adc_interface { - ($adc:ident [ - $( ($pin:ident, $channel:expr) ,)+ - ]) => { - - $( - impl Channel<$adc> for $pin { - type ID = u8; - - fn channel() -> u8 { $channel } - } - )+ - } -} - -pub use impl_adc_interface; - -#[cfg(esp32s3)] -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from two - //! analog to digital converters available on the ESP32-S3: `ADC1` and - //! `ADC2`. - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1, ADC2}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio1, 0), - (Gpio2, 1), - (Gpio3, 2), - (Gpio4, 3), - (Gpio5, 4), - (Gpio6, 5), - (Gpio7, 6), - (Gpio8, 7), - (Gpio9, 8), - (Gpio10,9), - ] - } - - impl_adc_interface! { - ADC2 [ - (Gpio11, 0), - (Gpio12, 1), - (Gpio13, 2), - (Gpio14, 3), - (Gpio15, 4), - (Gpio16, 5), - (Gpio17, 6), - (Gpio18, 7), - (Gpio19, 8), - (Gpio20, 9), - ] - } -} - -#[cfg(esp32s2)] -pub mod implementation { - //! Analog to digital (ADC) conversion support. - //! - //! This module provides functions for reading analog values from two - //! analog to digital converters available on the ESP32-S2: `ADC1` and - //! `ADC2`. - - use embedded_hal::adc::Channel; - - use super::impl_adc_interface; - pub use crate::analog::{adc::*, ADC1, ADC2}; - use crate::gpio::*; - - impl_adc_interface! { - ADC1 [ - (Gpio1, 0), - (Gpio2, 1), - (Gpio3, 2), - (Gpio4, 3), - (Gpio5, 4), - (Gpio6, 5), - (Gpio7, 6), - (Gpio8, 7), - (Gpio9, 8), - (Gpio10,9), - ] - } - - impl_adc_interface! { - ADC2 [ - (Gpio11, 0), - (Gpio12, 1), - (Gpio13, 2), - (Gpio14, 3), - (Gpio15, 4), - (Gpio16, 5), - (Gpio17, 6), - (Gpio18, 7), - (Gpio19, 8), - (Gpio20, 9), - ] - } -} diff --git a/esp-hal-common/src/analog/dac.rs b/esp-hal-common/src/analog/dac.rs deleted file mode 100644 index 4804ad7b884..00000000000 --- a/esp-hal-common/src/analog/dac.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::{ - peripheral::PeripheralRef, - peripherals::{RTC_IO, SENS}, -}; -pub trait DAC { - fn write(&mut self, value: u8); -} - -#[doc(hidden)] -pub trait DAC1Impl { - fn set_power(self) -> Self - where - Self: Sized, - { - #[cfg(esp32s2)] - { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_dac_ctrl1 - .modify(|_, w| w.dac_clkgate_en().set_bit()); - } - - let rtcio = unsafe { &*RTC_IO::ptr() }; - - rtcio.pad_dac1.modify(|_, w| { - w.pdac1_dac_xpd_force().set_bit(); - w.pdac1_xpd_dac().set_bit() - }); - - self - } - - fn write(&mut self, value: u8) { - let rtcio = unsafe { &*RTC_IO::ptr() }; - - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_dac_ctrl2 - .modify(|_, w| w.dac_cw_en1().clear_bit()); - - rtcio - .pad_dac1 - .modify(|_, w| unsafe { w.pdac1_dac().bits(value) }); - } -} - -#[doc(hidden)] -pub trait DAC2Impl { - fn set_power(self) -> Self - where - Self: Sized, - { - #[cfg(esp32s2)] - { - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_dac_ctrl1 - .modify(|_, w| w.dac_clkgate_en().set_bit()); - } - - let rtcio = unsafe { &*RTC_IO::ptr() }; - - rtcio.pad_dac2.modify(|_, w| { - w.pdac2_dac_xpd_force().set_bit(); - w.pdac2_xpd_dac().set_bit() - }); - - self - } - - fn write(&mut self, value: u8) { - let rtcio = unsafe { &*RTC_IO::ptr() }; - - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_dac_ctrl2 - .modify(|_, w| w.dac_cw_en2().clear_bit()); - - rtcio - .pad_dac2 - .modify(|_, w| unsafe { w.pdac2_dac().bits(value) }); - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! impl_dac { - ($($number:literal => $gpio:ident,)+) => { - use core::marker::PhantomData; - use crate::gpio; - - $( - paste::paste! { - pub use $crate::analog::dac::[]; - - /// DAC channel - pub struct []<'d, DAC> { - _dac: PeripheralRef<'d, DAC>, - _private: PhantomData<()>, - } - - impl<'d, DAC> [] for []<'d, DAC> {} - - impl<'d, DAC> []<'d, DAC> { - /// Constructs a new DAC instance - pub fn dac( - dac: impl $crate::peripheral::Peripheral

    +'d, - _pin: gpio::$gpio<$crate::gpio::Analog>, - ) -> Result { - let dac = Self { - _dac: dac.into_ref(), - _private: PhantomData, - } - .set_power(); - Ok(dac) - } - - /// Write the given value - /// - /// For each DAC channel, the output analog voltage can be calculated as follows: - /// DACn_OUT = VDD3P3_RTC * PDACn_DAC/256 - pub fn write(&mut self, value: u8) { - []::write(self, value) - } - } - } - )+ - }; -} - -pub use impl_dac; - -#[cfg(esp32)] -pub mod implementation { - //! Digital to analog (DAC) conversion. - //! - //! This module provides functions for controling two digital to - //! analog converters, available on ESP32: `DAC1` and `DAC2`. - //! - //! The DAC1 is available on the GPIO pin 25, and DAC2 on pin 26. - - pub use super::*; - use crate::impl_dac; - - impl_dac!(1 => Gpio25, 2 => Gpio26,); -} - -#[cfg(esp32s2)] -pub mod implementation { - //! Digital to analog (DAC) conversion. - //! - //! This module provides functions for controling two digital to - //! analog converters, available on ESP32: `DAC1` and `DAC2`. - //! - //! The DAC1 is available on the GPIO pin 17, and DAC2 on pin 18. - - pub use super::*; - use crate::impl_dac; - - impl_dac!(1 => Gpio17, 2 => Gpio18,); -} diff --git a/esp-hal-common/src/analog/mod.rs b/esp-hal-common/src/analog/mod.rs deleted file mode 100644 index f148a163672..00000000000 --- a/esp-hal-common/src/analog/mod.rs +++ /dev/null @@ -1,145 +0,0 @@ -#[cfg_attr(esp32, path = "adc/esp32.rs")] -#[cfg_attr(any(esp32c2, esp32c3, esp32c6), path = "adc/riscv.rs")] -#[cfg_attr(any(esp32s2, esp32s3), path = "adc/xtensa.rs")] -pub mod adc; -#[cfg(dac)] -pub mod dac; - -pub struct ADC1 { - _private: (), -} - -pub struct ADC2 { - _private: (), -} - -pub struct DAC1 { - _private: (), -} - -pub struct DAC2 { - _private: (), -} - -impl core::ops::Deref for ADC1 { - type Target = ADC1; - - fn deref(&self) -> &Self::Target { - self - } -} - -impl core::ops::DerefMut for ADC1 { - fn deref_mut(&mut self) -> &mut Self::Target { - self - } -} - -impl crate::peripheral::Peripheral for ADC1 { - type P = ADC1; - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - ADC1 { _private: () } - } -} - -impl crate::peripheral::sealed::Sealed for ADC1 {} - -impl crate::peripheral::Peripheral for ADC2 { - type P = ADC2; - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - ADC2 { _private: () } - } -} - -impl crate::peripheral::sealed::Sealed for ADC2 {} - -impl crate::peripheral::Peripheral for DAC1 { - type P = DAC1; - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - DAC1 { _private: () } - } -} - -impl crate::peripheral::sealed::Sealed for DAC1 {} - -impl crate::peripheral::Peripheral for DAC2 { - type P = DAC2; - - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - DAC2 { _private: () } - } -} - -impl crate::peripheral::sealed::Sealed for DAC2 {} - -cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2, esp32s3))] { - use crate::peripherals::SENS; - - pub struct AvailableAnalog { - pub adc1: ADC1, - pub adc2: ADC2, - pub dac1: DAC1, - pub dac2: DAC2, - } - - /// Extension trait to split a SENS peripheral in independent parts - pub trait SensExt { - fn split(self) -> AvailableAnalog; - } - - impl SensExt for SENS { - fn split(self) -> AvailableAnalog { - AvailableAnalog { - adc1: ADC1 { - _private: (), - }, - adc2: ADC2 { - _private: (), - }, - dac1: DAC1 { - _private: (), - }, - dac2: DAC2 { - _private: (), - }, - } - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(any(esp32c2, esp32c3, esp32c6))] { - use crate::peripherals::APB_SARADC; - - pub struct AvailableAnalog { - pub adc1: ADC1, - #[cfg(esp32c3)] - pub adc2: ADC2, - } - - /// Extension trait to split a APB_SARADC peripheral in independent parts - pub trait SarAdcExt { - fn split(self) -> AvailableAnalog; - } - - impl<'d, T: crate::peripheral::Peripheral

    + 'd> SarAdcExt for T { - fn split(self) -> AvailableAnalog { - AvailableAnalog { - adc1: ADC1 { - _private: (), - }, - #[cfg(esp32c3)] - adc2: ADC2 { - _private: (), - }, - } - } - } - } -} diff --git a/esp-hal-common/src/assist_debug.rs b/esp-hal-common/src/assist_debug.rs deleted file mode 100644 index e95aa3dc66b..00000000000 --- a/esp-hal-common/src/assist_debug.rs +++ /dev/null @@ -1,486 +0,0 @@ -//! Debug Assistant -//! -//! Debug Assistant is an auxiliary module that features a set of functions to -//! help locate bugs and issues during software debugging. -//! -//! While all the targets support PC logging it's API is not exposed here. -//! Instead the ROM bootloader will always enable it and print the last seen PC -//! (e.g. _Saved PC:0x42002ff2_). Make sure the reset was triggered by a TIMG -//! watchdog. Not an RTC or SWD watchdog. -//! -//! Not all targets support all the features. -//! -//! Bus write access logging is not available via this API. - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - system::PeripheralClockControl, -}; - -pub struct DebugAssist<'d> { - debug_assist: PeripheralRef<'d, crate::peripherals::ASSIST_DEBUG>, -} - -impl<'d> DebugAssist<'d> { - pub fn new( - debug_assist: impl Peripheral

    + 'd, - _peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(debug_assist); - - // we should use peripheral clock control to enable the debug assist however - // it's always enabled in ROM code already - - DebugAssist { debug_assist } - } -} - -#[cfg(assist_debug_sp_monitor)] -impl<'d> DebugAssist<'d> { - pub fn enable_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) { - self.debug_assist - .core_0_sp_min - .write(|w| w.core_0_sp_min().variant(lower_bound)); - - self.debug_assist - .core_0_sp_max - .write(|w| w.core_0_sp_max().variant(upper_bound)); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_sp_spill_min_ena() - .set_bit() - .core_0_sp_spill_max_ena() - .set_bit() - }); - - self.clear_sp_monitor_interrupt(); - - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_sp_spill_max_intr_ena() - .set_bit() - .core_0_sp_spill_min_intr_ena() - .set_bit() - }); - } - - pub fn disable_sp_monitor(&mut self) { - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_sp_spill_max_intr_ena() - .clear_bit() - .core_0_sp_spill_min_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_sp_spill_min_ena() - .clear_bit() - .core_0_sp_spill_max_ena() - .clear_bit() - }); - } - - pub fn clear_sp_monitor_interrupt(&mut self) { - self.debug_assist.core_0_intr_clr.write(|w| { - w.core_0_sp_spill_max_clr() - .set_bit() - .core_0_sp_spill_min_clr() - .set_bit() - }); - } - - pub fn is_sp_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_0_intr_raw - .read() - .core_0_sp_spill_max_raw() - .bit_is_set() - || self - .debug_assist - .core_0_intr_raw - .read() - .core_0_sp_spill_min_raw() - .bit_is_set() - } - - pub fn get_sp_monitor_pc(&self) -> u32 { - self.debug_assist.core_0_sp_pc.read().core_0_sp_pc().bits() - } -} - -#[cfg(all(assist_debug_sp_monitor, multi_core))] -impl<'d> DebugAssist<'d> { - pub fn enable_core1_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) { - self.debug_assist - .core_1_sp_min - .write(|w| w.core_1_sp_min().variant(lower_bound)); - - self.debug_assist - .core_1_sp_max - .write(|w| w.core_1_sp_max().variant(upper_bound)); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_sp_spill_min_ena() - .set_bit() - .core_1_sp_spill_max_ena() - .set_bit() - }); - - self.clear_core1_sp_monitor_interrupt(); - - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_sp_spill_max_intr_ena() - .set_bit() - .core_1_sp_spill_min_intr_ena() - .set_bit() - }); - } - - pub fn disable_core1_sp_monitor(&mut self) { - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_sp_spill_max_intr_ena() - .clear_bit() - .core_1_sp_spill_min_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_sp_spill_min_ena() - .clear_bit() - .core_1_sp_spill_max_ena() - .clear_bit() - }); - } - - pub fn clear_core1_sp_monitor_interrupt(&mut self) { - self.debug_assist.core_1_intr_clr.write(|w| { - w.core_1_sp_spill_max_clr() - .set_bit() - .core_1_sp_spill_min_clr() - .set_bit() - }); - } - - pub fn is_core1_sp_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_1_intr_raw - .read() - .core_1_sp_spill_max_raw() - .bit_is_set() - || self - .debug_assist - .core_1_intr_raw - .read() - .core_1_sp_spill_min_raw() - .bit_is_set() - } - - pub fn get_core1_sp_monitor_pc(&self) -> u32 { - self.debug_assist.core_1_sp_pc.read().core_1_sp_pc().bits() - } -} - -#[cfg(assist_debug_region_monitor)] -impl<'d> DebugAssist<'d> { - pub fn enable_region0_monitor( - &mut self, - lower_bound: u32, - upper_bound: u32, - reads: bool, - writes: bool, - ) { - self.debug_assist - .core_0_area_dram0_0_min - .write(|w| w.core_0_area_dram0_0_min().variant(lower_bound)); - - self.debug_assist - .core_0_area_dram0_0_max - .write(|w| w.core_0_area_dram0_0_max().variant(upper_bound)); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_area_dram0_0_rd_ena() - .bit(reads) - .core_0_area_dram0_0_wr_ena() - .bit(writes) - }); - - self.clear_region0_monitor_interrupt(); - - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_area_dram0_0_rd_intr_ena() - .set_bit() - .core_0_area_dram0_0_wr_intr_ena() - .set_bit() - }); - } - - pub fn disable_region0_monitor(&mut self) { - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_area_dram0_0_rd_intr_ena() - .clear_bit() - .core_0_area_dram0_0_wr_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_area_dram0_0_rd_ena() - .clear_bit() - .core_0_area_dram0_0_wr_ena() - .clear_bit() - }); - } - - pub fn clear_region0_monitor_interrupt(&mut self) { - self.debug_assist.core_0_intr_clr.write(|w| { - w.core_0_area_dram0_0_rd_clr() - .set_bit() - .core_0_area_dram0_0_wr_clr() - .set_bit() - }); - } - - pub fn is_region0_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_0_intr_raw - .read() - .core_0_area_dram0_0_rd_raw() - .bit_is_set() - || self - .debug_assist - .core_0_intr_raw - .read() - .core_0_area_dram0_0_wr_raw() - .bit_is_set() - } - - pub fn enable_region1_monitor( - &mut self, - lower_bound: u32, - upper_bound: u32, - reads: bool, - writes: bool, - ) { - self.debug_assist - .core_0_area_dram0_1_min - .write(|w| w.core_0_area_dram0_1_min().variant(lower_bound)); - - self.debug_assist - .core_0_area_dram0_1_max - .write(|w| w.core_0_area_dram0_1_max().variant(upper_bound)); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_area_dram0_1_rd_ena() - .bit(reads) - .core_0_area_dram0_1_wr_ena() - .bit(writes) - }); - - self.clear_region1_monitor_interrupt(); - - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_area_dram0_1_rd_intr_ena() - .set_bit() - .core_0_area_dram0_1_wr_intr_ena() - .set_bit() - }); - } - - pub fn disable_region1_monitor(&mut self) { - self.debug_assist.core_0_intr_ena.modify(|_, w| { - w.core_0_area_dram0_1_rd_intr_ena() - .clear_bit() - .core_0_area_dram0_1_wr_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_0_montr_ena.modify(|_, w| { - w.core_0_area_dram0_1_rd_ena() - .clear_bit() - .core_0_area_dram0_1_wr_ena() - .clear_bit() - }); - } - - pub fn clear_region1_monitor_interrupt(&mut self) { - self.debug_assist.core_0_intr_clr.write(|w| { - w.core_0_area_dram0_1_rd_clr() - .set_bit() - .core_0_area_dram0_1_wr_clr() - .set_bit() - }); - } - - pub fn is_region1_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_0_intr_raw - .read() - .core_0_area_dram0_1_rd_raw() - .bit_is_set() - || self - .debug_assist - .core_0_intr_raw - .read() - .core_0_area_dram0_1_wr_raw() - .bit_is_set() - } - - pub fn get_region_monitor_pc(&self) -> u32 { - self.debug_assist - .core_0_area_pc - .read() - .core_0_area_pc() - .bits() - } -} - -#[cfg(all(assist_debug_region_monitor, multi_core))] -impl<'d> DebugAssist<'d> { - pub fn enable_core1_region0_monitor( - &mut self, - lower_bound: u32, - upper_bound: u32, - reads: bool, - writes: bool, - ) { - self.debug_assist - .core_1_area_dram0_0_min - .write(|w| w.core_1_area_dram0_0_min().variant(lower_bound)); - - self.debug_assist - .core_1_area_dram0_0_max - .write(|w| w.core_1_area_dram0_0_max().variant(upper_bound)); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_area_dram0_0_rd_ena() - .bit(reads) - .core_1_area_dram0_0_wr_ena() - .bit(writes) - }); - - self.clear_core1_region0_monitor_interrupt(); - - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_area_dram0_0_rd_intr_ena() - .set_bit() - .core_1_area_dram0_0_wr_intr_ena() - .set_bit() - }); - } - - pub fn disable_core1_region0_monitor(&mut self) { - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_area_dram0_0_rd_intr_ena() - .clear_bit() - .core_1_area_dram0_0_wr_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_area_dram0_0_rd_ena() - .clear_bit() - .core_1_area_dram0_0_wr_ena() - .clear_bit() - }); - } - - pub fn clear_core1_region0_monitor_interrupt(&mut self) { - self.debug_assist.core_1_intr_clr.write(|w| { - w.core_1_area_dram0_0_rd_clr() - .set_bit() - .core_1_area_dram0_0_wr_clr() - .set_bit() - }); - } - - pub fn is_core1_region0_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_1_intr_raw - .read() - .core_1_area_dram0_0_rd_raw() - .bit_is_set() - || self - .debug_assist - .core_1_intr_raw - .read() - .core_1_area_dram0_0_wr_raw() - .bit_is_set() - } - - pub fn enable_core1_region1_monitor( - &mut self, - lower_bound: u32, - upper_bound: u32, - reads: bool, - writes: bool, - ) { - self.debug_assist - .core_1_area_dram0_1_min - .write(|w| w.core_1_area_dram0_1_min().variant(lower_bound)); - - self.debug_assist - .core_1_area_dram0_1_max - .write(|w| w.core_1_area_dram0_1_max().variant(upper_bound)); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_area_dram0_1_rd_ena() - .bit(reads) - .core_1_area_dram0_1_wr_ena() - .bit(writes) - }); - - self.clear_core1_region1_monitor_interrupt(); - - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_area_dram0_1_rd_intr_ena() - .set_bit() - .core_1_area_dram0_1_wr_intr_ena() - .set_bit() - }); - } - - pub fn disable_core1_region1_monitor(&mut self) { - self.debug_assist.core_1_intr_ena.modify(|_, w| { - w.core_1_area_dram0_1_rd_intr_ena() - .clear_bit() - .core_1_area_dram0_1_wr_intr_ena() - .clear_bit() - }); - - self.debug_assist.core_1_montr_ena.modify(|_, w| { - w.core_1_area_dram0_1_rd_ena() - .clear_bit() - .core_1_area_dram0_1_wr_ena() - .clear_bit() - }); - } - - pub fn clear_core1_region1_monitor_interrupt(&mut self) { - self.debug_assist.core_1_intr_clr.write(|w| { - w.core_1_area_dram0_1_rd_clr() - .set_bit() - .core_1_area_dram0_1_wr_clr() - .set_bit() - }); - } - - pub fn is_core1_region1_monitor_interrupt_set(&self) -> bool { - self.debug_assist - .core_1_intr_raw - .read() - .core_1_area_dram0_1_rd_raw() - .bit_is_set() - || self - .debug_assist - .core_1_intr_raw - .read() - .core_1_area_dram0_1_wr_raw() - .bit_is_set() - } - - pub fn get_core1_region_monitor_pc(&self) -> u32 { - self.debug_assist - .core_1_area_pc - .read() - .core_1_area_pc() - .bits() - } -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32.rs b/esp-hal-common/src/clock/clocks_ll/esp32.rs deleted file mode 100644 index 2430e435933..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::clock::{Clock, PllClock, XtalClock}; - -const REF_CLK_FREQ: u32 = 1000000; - -const MHZ: u32 = 1000000; -const UINT16_MAX: u32 = 0xffff; - -const RTC_CNTL_DBIAS_1V10: u32 = 4; -const RTC_CNTL_DBIAS_1V25: u32 = 7; - -const DIG_DBIAS_80M_160M: u32 = RTC_CNTL_DBIAS_1V10; -const DIG_DBIAS_XTAL: u32 = RTC_CNTL_DBIAS_1V10; - -const I2C_BBPLL: u32 = 0x66; -const I2C_BBPLL_HOSTID: u32 = 4; - -const I2C_BBPLL_IR_CAL_DELAY: u32 = 0; -const I2C_BBPLL_IR_CAL_EXT_CAP: u32 = 1; -const I2C_BBPLL_OC_ENB_FCAL: u32 = 4; -const I2C_BBPLL_OC_ENB_VCON: u32 = 10; -const I2C_BBPLL_BBADC_CAL_7_0: u32 = 12; - -const BBPLL_IR_CAL_DELAY_VAL: u32 = 0x18; -const BBPLL_IR_CAL_EXT_CAP_VAL: u32 = 0x20; -const BBPLL_OC_ENB_FCAL_VAL: u32 = 0x9a; -const BBPLL_OC_ENB_VCON_VAL: u32 = 0x00; -const BBPLL_BBADC_CAL_7_0_VAL: u32 = 0x00; - -const I2C_BBPLL_ENDIV5: u32 = 11; - -const BBPLL_ENDIV5_VAL_320M: u32 = 0x43; -const BBPLL_BBADC_DSMP_VAL_320M: u32 = 0x84; -const BBPLL_ENDIV5_VAL_480M: u32 = 0xc3; -const BBPLL_BBADC_DSMP_VAL_480M: u32 = 0x74; - -const I2C_BBPLL_BBADC_DSMP: u32 = 9; -const I2C_BBPLL_OC_LREF: u32 = 2; -const I2C_BBPLL_OC_DIV_7_0: u32 = 3; -const I2C_BBPLL_OC_DCUR: u32 = 5; - -pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock) { - let efuse = unsafe { &*crate::peripherals::EFUSE::ptr() }; - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - unsafe { - let rtc_cntl_dbias_hp_volt: u32 = - RTC_CNTL_DBIAS_1V25 - efuse.blk0_rdata5.read().rd_vol_level_hp_inv().bits() as u32; - let dig_dbias_240_m: u32 = rtc_cntl_dbias_hp_volt; - - let div_ref: u32; - let div7_0: u32; - let div10_8: u32; - let lref: u32; - let dcur: u32; - let bw: u32; - let i2c_bbpll_lref: u32; - let i2c_bbpll_div_7_0: u32; - let i2c_bbpll_dcur: u32; - - if matches!(pll_freq, PllClock::Pll320MHz) { - // Raise the voltage, if needed - rtc_cntl - .reg - .modify(|_, w| w.dig_dbias_wak().variant(DIG_DBIAS_80M_160M as u8)); - - // Configure 320M PLL - match xtal_freq { - XtalClock::RtcXtalFreq40M => { - div_ref = 0; - div7_0 = 32; - div10_8 = 0; - lref = 0; - dcur = 6; - bw = 3; - } - - XtalClock::RtcXtalFreq26M => { - div_ref = 12; - div7_0 = 224; - div10_8 = 4; - lref = 1; - dcur = 0; - bw = 1; - } - - XtalClock::RtcXtalFreq24M => { - div_ref = 11; - div7_0 = 224; - div10_8 = 4; - lref = 1; - dcur = 0; - bw = 1; - } - - XtalClock::RtcXtalFreqOther(_) => { - div_ref = 12; - div7_0 = 224; - div10_8 = 4; - lref = 0; - dcur = 0; - bw = 0; - } - } - - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_ENDIV5, - BBPLL_ENDIV5_VAL_320M, - ); - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_BBADC_DSMP, - BBPLL_BBADC_DSMP_VAL_320M, - ); - } else { - // Raise the voltage - rtc_cntl - .reg - .modify(|_, w| w.dig_dbias_wak().variant(dig_dbias_240_m as u8)); - - // Configure 480M PLL - match xtal_freq { - XtalClock::RtcXtalFreq40M => { - div_ref = 0; - div7_0 = 28; - div10_8 = 0; - lref = 0; - dcur = 6; - bw = 3; - } - - XtalClock::RtcXtalFreq26M => { - div_ref = 12; - div7_0 = 144; - div10_8 = 4; - lref = 1; - dcur = 0; - bw = 1; - } - - XtalClock::RtcXtalFreq24M => { - div_ref = 11; - div7_0 = 144; - div10_8 = 4; - lref = 1; - dcur = 0; - bw = 1; - } - - XtalClock::RtcXtalFreqOther(_) => { - div_ref = 12; - div7_0 = 224; - div10_8 = 4; - lref = 0; - dcur = 0; - bw = 0; - } - } - - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_ENDIV5, - BBPLL_ENDIV5_VAL_480M, - ); - - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_BBADC_DSMP, - BBPLL_BBADC_DSMP_VAL_480M, - ); - } - - i2c_bbpll_lref = (lref << 7) | (div10_8 << 4) | (div_ref); - i2c_bbpll_div_7_0 = div7_0; - i2c_bbpll_dcur = (bw << 6) | dcur; - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_LREF, - i2c_bbpll_lref, - ); - - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DIV_7_0, - i2c_bbpll_div_7_0, - ); - - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DCUR, - i2c_bbpll_dcur, - ); - } -} - -pub(crate) fn esp32_rtc_bbpll_enable() { - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - unsafe { - rtc_cntl.options0.modify(|_, w| { - w.bias_i2c_force_pd() - .clear_bit() - .bb_i2c_force_pd() - .clear_bit() - .bbpll_force_pd() - .clear_bit() - .bbpll_i2c_force_pd() - .clear_bit() - }); - - // reset BBPLL configuration - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_IR_CAL_DELAY, - BBPLL_IR_CAL_DELAY_VAL, - ); - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_IR_CAL_EXT_CAP, - BBPLL_IR_CAL_EXT_CAP_VAL, - ); - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_ENB_FCAL, - BBPLL_OC_ENB_FCAL_VAL, - ); - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_ENB_VCON, - BBPLL_OC_ENB_VCON_VAL, - ); - i2c_writereg_rtc( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_BBADC_CAL_7_0, - BBPLL_BBADC_CAL_7_0_VAL, - ); - } -} - -#[inline(always)] -unsafe fn i2c_writereg_rtc(block: u32, block_hostid: u32, reg_add: u32, indata: u32) { - const ROM_I2C_WRITEREG: u32 = 0x400041a4; - - // cast to usize is just needed because of the way we run clippy in CI - let rom_i2c_writereg: fn(block: u32, block_hostid: u32, reg_add: u32, indata: u32) -> i32 = - core::mem::transmute(ROM_I2C_WRITEREG as usize); - - rom_i2c_writereg(block, block_hostid, reg_add, indata); -} - -pub(crate) fn esp32_rtc_update_to_xtal(freq: XtalClock, _div: u32) { - let apb_cntl = unsafe { &*crate::peripherals::APB_CTRL::ptr() }; - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - unsafe { - let value = (((freq.hz()) >> 12) & UINT16_MAX) | ((((freq.hz()) >> 12) & UINT16_MAX) << 16); - esp32_update_cpu_freq(freq.hz()); - // set divider from XTAL to APB clock - apb_cntl.sysclk_conf.modify(|_, w| { - w.pre_div_cnt() - .bits(((freq.hz()) / REF_CLK_FREQ - 1) as u16) - }); - - // adjust ref_tick - apb_cntl.xtal_tick_conf.as_ptr().write_volatile( - ((freq.hz()) / REF_CLK_FREQ - 1) | apb_cntl.xtal_tick_conf.as_ptr().read_volatile(), - ); // TODO make it RW in SVD - - // switch clock source - rtc_cntl.clk_conf.modify(|_, w| w.soc_clk_sel().xtal()); - rtc_cntl - .store5 - .modify(|_, w| w.scratch5().bits(value as u32)); - - // lower the voltage - rtc_cntl - .reg - .modify(|_, w| w.dig_dbias_wak().variant(DIG_DBIAS_XTAL as u8)); - } -} - -pub(crate) fn set_cpu_freq(cpu_freq_mhz: crate::clock::CpuClock) { - let efuse = unsafe { &*crate::peripherals::EFUSE::ptr() }; - let dport = unsafe { &*crate::peripherals::DPORT::ptr() }; - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - unsafe { - const RTC_CNTL_DBIAS_1V25: u32 = 7; - - let rtc_cntl_dbias_hp_volt: u32 = - RTC_CNTL_DBIAS_1V25 - efuse.blk0_rdata5.read().rd_vol_level_hp_inv().bits() as u32; - let dig_dbias_240_m: u32 = rtc_cntl_dbias_hp_volt; - - const CPU_80M: u32 = 0; - const CPU_160M: u32 = 1; - const CPU_240M: u32 = 2; - - let mut dbias = DIG_DBIAS_80M_160M; - let per_conf; - - match cpu_freq_mhz { - crate::clock::CpuClock::Clock160MHz => { - per_conf = CPU_160M; - } - crate::clock::CpuClock::Clock240MHz => { - dbias = dig_dbias_240_m; - per_conf = CPU_240M; - } - crate::clock::CpuClock::Clock80MHz => { - per_conf = CPU_80M; - } - } - - let value = (((80 * MHZ) >> 12) & UINT16_MAX) | ((((80 * MHZ) >> 12) & UINT16_MAX) << 16); - dport - .cpu_per_conf - .write(|w| w.cpuperiod_sel().bits(per_conf as u8)); - rtc_cntl - .reg - .modify(|_, w| w.dig_dbias_wak().variant(dbias as u8)); - rtc_cntl.clk_conf.modify(|_, w| w.soc_clk_sel().pll()); - rtc_cntl - .store5 - .modify(|_, w| w.scratch5().bits(value as u32)); - - esp32_update_cpu_freq(cpu_freq_mhz.mhz()); - } -} - -/// Pass the CPU clock in MHz so that ets_delay_us -/// will be accurate. Call this function when CPU frequency is changed. -fn esp32_update_cpu_freq(mhz: u32) { - const G_TICKS_PER_US_PRO: u32 = 0x3ffe01e0; - unsafe { - // Update scale factors used by esp_rom_delay_us - (G_TICKS_PER_US_PRO as *mut u32).write_volatile(mhz); - } -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32c2.rs b/esp-hal-common/src/clock/clocks_ll/esp32c2.rs deleted file mode 100644 index 26466afa06d..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32c2.rs +++ /dev/null @@ -1,180 +0,0 @@ -use paste::paste; - -use crate::{ - clock::{ApbClock, Clock, CpuClock, PllClock, XtalClock}, - regi2c_write, - regi2c_write_mask, - rom::{rom_i2c_writeReg, rom_i2c_writeReg_Mask}, -}; - -extern "C" { - fn ets_update_cpu_frequency_rom(ticks_per_us: u32); -} - -const I2C_BBPLL: u32 = 0x66; -const I2C_BBPLL_HOSTID: u32 = 0; - -const I2C_BBPLL_MODE_HF: u32 = 4; - -const I2C_BBPLL_OC_REF_DIV: u32 = 2; -const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; -const I2C_BBPLL_OC_DIV_7_0: u32 = 3; - -const I2C_BBPLL_OC_DR1: u32 = 5; -const I2C_BBPLL_OC_DR1_MSB: u32 = 2; -const I2C_BBPLL_OC_DR1_LSB: u32 = 0; - -const I2C_BBPLL_OC_DR3: u32 = 5; -const I2C_BBPLL_OC_DR3_MSB: u32 = 6; -const I2C_BBPLL_OC_DR3_LSB: u32 = 4; - -const I2C_BBPLL_OC_DCUR: u32 = 6; - -const I2C_BBPLL_OC_VCO_DBIAS: u32 = 9; -const I2C_BBPLL_OC_VCO_DBIAS_MSB: u32 = 1; -const I2C_BBPLL_OC_VCO_DBIAS_LSB: u32 = 0; - -const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; - -const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; - -const I2C_MST_ANA_CONF0_REG: u32 = 0x6004_E840; -const I2C_MST_BBPLL_STOP_FORCE_HIGH: u32 = 1 << 2; -const I2C_MST_BBPLL_STOP_FORCE_LOW: u32 = 1 << 3; - -pub(crate) fn esp32c2_rtc_bbpll_configure(xtal_freq: XtalClock, _pll_freq: PllClock) { - let system = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - let div_ref: u32; - let div7_0: u32; - let dr1: u32; - let dr3: u32; - let dchgp: u32; - let dcur: u32; - let dbias: u32; - let i2c_bbpll_lref: u32; - let i2c_bbpll_div_7_0: u32; - let i2c_bbpll_dcur: u32; - - let clear_reg_mask = |reg, mask: u32| { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !mask) - }; - let set_reg_mask = |reg, mask: u32| { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | mask) - }; - - clear_reg_mask(I2C_MST_ANA_CONF0_REG, I2C_MST_BBPLL_STOP_FORCE_HIGH); - set_reg_mask(I2C_MST_ANA_CONF0_REG, I2C_MST_BBPLL_STOP_FORCE_LOW); - - // Set this register to let the digital part know 480M PLL is used - system - .cpu_per_conf - .modify(|_, w| w.pll_freq_sel().set_bit()); - - // Configure 480M PLL - match xtal_freq { - XtalClock::RtcXtalFreq26M => { - div_ref = 12; - div7_0 = 236; - dr1 = 4; - dr3 = 4; - dchgp = 0; - dcur = 0; - dbias = 2; - } - XtalClock::RtcXtalFreq40M | XtalClock::RtcXtalFreqOther(_) => { - div_ref = 0; - div7_0 = 8; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - } - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_MODE_HF, 0x6b); - - i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; - i2c_bbpll_div_7_0 = div7_0; - i2c_bbpll_dcur = - (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_REF_DIV, i2c_bbpll_lref); - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_DIV_7_0, i2c_bbpll_div_7_0); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DR1, dr1); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DR3, dr3); - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_DCUR, i2c_bbpll_dcur); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_VCO_DBIAS, dbias); - } -} - -pub(crate) fn esp32c2_rtc_bbpll_enable() { - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - rtc_cntl.options0.modify(|_, w| { - w.bb_i2c_force_pd() - .clear_bit() - .bbpll_force_pd() - .clear_bit() - .bbpll_i2c_force_pd() - .clear_bit() - }); -} - -pub(crate) fn esp32c2_rtc_update_to_xtal(freq: XtalClock, _div: u32) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - ets_update_cpu_frequency_rom(freq.mhz()); - // Set divider from XTAL to APB clock. Need to set divider to 1 (reg. value 0) - // first. - system_control.sysclk_conf.modify(|_, w| { - w.pre_div_cnt() - .bits(0) - .pre_div_cnt() - .bits((_div - 1) as u16) - }); - - // No need to adjust the REF_TICK - - // Switch clock source - system_control - .sysclk_conf - .modify(|_, w| w.soc_clk_sel().bits(0)); - } -} - -pub(crate) fn esp32c2_rtc_freq_to_pll_mhz(cpu_clock_speed: CpuClock) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - system_control - .sysclk_conf - .modify(|_, w| w.pre_div_cnt().bits(0).soc_clk_sel().bits(1)); - system_control.cpu_per_conf.modify(|_, w| { - w.cpuperiod_sel().bits(match cpu_clock_speed { - CpuClock::Clock80MHz => 0, - CpuClock::Clock120MHz => 1, - }) - }); - ets_update_cpu_frequency_rom(cpu_clock_speed.mhz()); - } -} - -pub(crate) fn esp32c2_rtc_apb_freq_update(apb_freq: ApbClock) { - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - let value = ((apb_freq.hz() >> 12) & u16::MAX as u32) - | (((apb_freq.hz() >> 12) & u16::MAX as u32) << 16); - - rtc_cntl - .store5 - .modify(|_, w| unsafe { w.scratch5().bits(value) }); -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32c3.rs b/esp-hal-common/src/clock/clocks_ll/esp32c3.rs deleted file mode 100644 index 604bf241d28..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32c3.rs +++ /dev/null @@ -1,240 +0,0 @@ -use paste::paste; - -use crate::{ - clock::{ApbClock, Clock, CpuClock, PllClock, XtalClock}, - regi2c_write, - regi2c_write_mask, - rom::{rom_i2c_writeReg, rom_i2c_writeReg_Mask}, -}; - -extern "C" { - fn ets_update_cpu_frequency_rom(ticks_per_us: u32); -} - -const I2C_BBPLL: u32 = 0x66; -const I2C_BBPLL_HOSTID: u32 = 0; - -const I2C_BBPLL_MODE_HF: u32 = 4; - -const I2C_BBPLL_OC_REF_DIV: u32 = 2; -const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; -const I2C_BBPLL_OC_DIV_7_0: u32 = 3; - -const I2C_BBPLL_OC_DR1: u32 = 5; -const I2C_BBPLL_OC_DR1_MSB: u32 = 2; -const I2C_BBPLL_OC_DR1_LSB: u32 = 0; - -const I2C_BBPLL_OC_DR3: u32 = 5; -const I2C_BBPLL_OC_DR3_MSB: u32 = 6; -const I2C_BBPLL_OC_DR3_LSB: u32 = 4; - -const I2C_BBPLL_OC_DCUR: u32 = 6; - -const I2C_BBPLL_OC_VCO_DBIAS: u32 = 9; -const I2C_BBPLL_OC_VCO_DBIAS_MSB: u32 = 1; -const I2C_BBPLL_OC_VCO_DBIAS_LSB: u32 = 0; - -const I2C_BBPLL_OC_DHREF_SEL: u32 = 6; -const I2C_BBPLL_OC_DHREF_SEL_MSB: u32 = 5; -const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; - -const I2C_BBPLL_OC_DLREF_SEL: u32 = 6; -const I2C_BBPLL_OC_DLREF_SEL_MSB: u32 = 7; -const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; - -const I2C_MST_ANA_CONF0_REG: u32 = 0x6000_e040; -const I2C_MST_BBPLL_STOP_FORCE_HIGH: u32 = 1 << 3; -const I2C_MST_BBPLL_STOP_FORCE_LOW: u32 = 1 << 2; - -pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock) { - let system = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - let div_ref: u32; - let div7_0: u32; - let dr1: u32; - let dr3: u32; - let dchgp: u32; - let dcur: u32; - let dbias: u32; - let i2c_bbpll_lref: u32; - let i2c_bbpll_div_7_0: u32; - let i2c_bbpll_dcur: u32; - - let clear_reg_mask = |reg, mask: u32| { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !mask) - }; - let set_reg_mask = |reg, mask: u32| { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | mask) - }; - - clear_reg_mask(I2C_MST_ANA_CONF0_REG, I2C_MST_BBPLL_STOP_FORCE_HIGH); - set_reg_mask(I2C_MST_ANA_CONF0_REG, I2C_MST_BBPLL_STOP_FORCE_LOW); - - if matches!(pll_freq, PllClock::Pll480MHz) { - // Set this register to let the digital part know 480M PLL is used - system - .cpu_per_conf - .modify(|_, w| w.pll_freq_sel().set_bit()); - - // Configure 480M PLL - match xtal_freq { - XtalClock::RtcXtalFreq40M => { - div_ref = 0; - div7_0 = 8; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - - XtalClock::RtcXtalFreq32M => { - div_ref = 1; - div7_0 = 26; - dr1 = 1; - dr3 = 1; - dchgp = 4; - dcur = 0; - dbias = 2; - } - - XtalClock::RtcXtalFreqOther(_) => { - div_ref = 0; - div7_0 = 8; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - } - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_MODE_HF, 0x6b); - } else { - // Clear this register to let the digital part know 320M PLL is used - system - .cpu_per_conf - .modify(|_, w| w.pll_freq_sel().clear_bit()); - - // Configure 320M PLL - match xtal_freq { - XtalClock::RtcXtalFreq40M => { - div_ref = 0; - div7_0 = 4; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - - XtalClock::RtcXtalFreq32M => { - div_ref = 1; - div7_0 = 6; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - - XtalClock::RtcXtalFreqOther(_) => { - div_ref = 0; - div7_0 = 4; - dr1 = 0; - dr3 = 0; - dchgp = 5; - dcur = 3; - dbias = 2; - } - } - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_MODE_HF, 0x69); - } - - i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; - i2c_bbpll_div_7_0 = div7_0; - i2c_bbpll_dcur = - (2 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (1 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_REF_DIV, i2c_bbpll_lref); - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_DIV_7_0, i2c_bbpll_div_7_0); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DR1, dr1); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DR3, dr3); - - regi2c_write!(I2C_BBPLL, I2C_BBPLL_OC_DCUR, i2c_bbpll_dcur); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_VCO_DBIAS, dbias); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DHREF_SEL, 2); - - regi2c_write_mask!(I2C_BBPLL, I2C_BBPLL_OC_DLREF_SEL, 1); - } -} - -pub(crate) fn esp32c3_rtc_bbpll_enable() { - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - rtc_cntl.options0.modify(|_, w| { - w.bb_i2c_force_pd() - .clear_bit() - .bbpll_force_pd() - .clear_bit() - .bbpll_i2c_force_pd() - .clear_bit() - }); -} - -pub(crate) fn esp32c3_rtc_update_to_xtal(freq: XtalClock, _div: u32) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - ets_update_cpu_frequency_rom(freq.mhz()); - // Set divider from XTAL to APB clock. Need to set divider to 1 (reg. value 0) - // first. - system_control.sysclk_conf.modify(|_, w| { - w.pre_div_cnt() - .bits(0) - .pre_div_cnt() - .bits((_div - 1) as u16) - }); - - // No need to adjust the REF_TICK - - // Switch clock source - system_control - .sysclk_conf - .modify(|_, w| w.soc_clk_sel().bits(0)); - } -} - -pub(crate) fn esp32c3_rtc_freq_to_pll_mhz(cpu_clock_speed: CpuClock) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::ptr() }; - - unsafe { - system_control - .sysclk_conf - .modify(|_, w| w.pre_div_cnt().bits(0).soc_clk_sel().bits(1)); - system_control.cpu_per_conf.modify(|_, w| { - w.cpuperiod_sel().bits(match cpu_clock_speed { - CpuClock::Clock80MHz => 0, - CpuClock::Clock160MHz => 1, - }) - }); - ets_update_cpu_frequency_rom(cpu_clock_speed.mhz()); - } -} - -pub(crate) fn esp32c3_rtc_apb_freq_update(apb_freq: ApbClock) { - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - let value = ((apb_freq.hz() >> 12) & u16::MAX as u32) - | (((apb_freq.hz() >> 12) & u16::MAX as u32) << 16); - - rtc_cntl - .store5 - .modify(|_, w| unsafe { w.scratch5().bits(value) }); -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32c6.rs b/esp-hal-common/src/clock/clocks_ll/esp32c6.rs deleted file mode 100644 index 80f9886068e..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32c6.rs +++ /dev/null @@ -1,369 +0,0 @@ -use crate::clock::{ApbClock, Clock, CpuClock, PllClock, XtalClock}; - -extern "C" { - fn ets_update_cpu_frequency(ticks_per_us: u32); -} - -const I2C_BBPLL: u8 = 0x66; -const I2C_BBPLL_HOSTID: u8 = 0; - -const I2C_BBPLL_OC_REF_DIV: u8 = 2; -const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; - -const I2C_BBPLL_OC_DIV_7_0: u8 = 3; - -const I2C_BBPLL_OC_DR1: u8 = 5; -const I2C_BBPLL_OC_DR1_MSB: u8 = 2; -const I2C_BBPLL_OC_DR1_LSB: u8 = 0; - -const I2C_BBPLL_OC_DR3: u8 = 5; -const I2C_BBPLL_OC_DR3_MSB: u8 = 6; -const I2C_BBPLL_OC_DR3_LSB: u8 = 4; - -const I2C_BBPLL_OC_DCUR: u8 = 6; - -const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; - -const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; - -const I2C_BBPLL_OC_VCO_DBIAS: u8 = 9; -const I2C_BBPLL_OC_VCO_DBIAS_MSB: u8 = 1; -const I2C_BBPLL_OC_VCO_DBIAS_LSB: u8 = 0; - -// Analog function control register -const I2C_MST_ANA_CONF0_REG: u32 = 0x600AF818; -const I2C_MST_BBPLL_STOP_FORCE_HIGH: u32 = 1 << 2; -const I2C_MST_BBPLL_STOP_FORCE_LOW: u32 = 1 << 3; -const I2C_MST_BBPLL_CAL_DONE: u32 = 1 << 24; - -const MODEM_LPCON_CLK_CONF_FORCE_ON_REG: u32 = DR_REG_MODEM_LPCON_BASE + 0x1c; -const MODEM_LPCON_CLK_I2C_MST_FO: u32 = 1 << 2; -const MODEM_LPCON_I2C_MST_CLK_CONF_REG: u32 = DR_REG_MODEM_LPCON_BASE + 0x10; -const MODEM_LPCON_CLK_I2C_MST_SEL_160M: u32 = 1 << 0; - -pub(crate) fn esp32c6_rtc_bbpll_configure(_xtal_freq: XtalClock, _pll_freq: PllClock) { - unsafe { - // enable i2c mst clk by force on temporarily - (MODEM_LPCON_CLK_CONF_FORCE_ON_REG as *mut u32).write_volatile( - (MODEM_LPCON_CLK_CONF_FORCE_ON_REG as *mut u32).read_volatile() - | MODEM_LPCON_CLK_I2C_MST_FO, - ); - (MODEM_LPCON_I2C_MST_CLK_CONF_REG as *mut u32).write_volatile( - (MODEM_LPCON_I2C_MST_CLK_CONF_REG as *mut u32).read_volatile() - | MODEM_LPCON_CLK_I2C_MST_SEL_160M, - ); - - let i2c_mst_ana_conf0_reg_ptr = I2C_MST_ANA_CONF0_REG as *mut u32; - // BBPLL CALIBRATION START - i2c_mst_ana_conf0_reg_ptr.write_volatile( - i2c_mst_ana_conf0_reg_ptr.read_volatile() & !I2C_MST_BBPLL_STOP_FORCE_HIGH, - ); - i2c_mst_ana_conf0_reg_ptr.write_volatile( - i2c_mst_ana_conf0_reg_ptr.read_volatile() | I2C_MST_BBPLL_STOP_FORCE_LOW, - ); - - let div_ref = 0u32; - let div7_0 = 8u32; - let dr1 = 0u32; - let dr3 = 0u32; - let dchgp = 5u32; - let dcur = 3u32; - let dbias = 2u32; - - let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; - let i2c_bbpll_div_7_0 = div7_0; - let i2c_bbpll_dcur = - (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; - - regi2c_write( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_REF_DIV, - i2c_bbpll_lref as u8, - ); - regi2c_write( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DIV_7_0, - i2c_bbpll_div_7_0 as u8, - ); - regi2c_write_mask( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DR1, - I2C_BBPLL_OC_DR1_MSB, - I2C_BBPLL_OC_DR1_LSB, - dr1 as u8, - ); - regi2c_write_mask( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DR3, - I2C_BBPLL_OC_DR3_MSB, - I2C_BBPLL_OC_DR3_LSB, - dr3 as u8, - ); - regi2c_write( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_DCUR, - i2c_bbpll_dcur as u8, - ); - regi2c_write_mask( - I2C_BBPLL, - I2C_BBPLL_HOSTID, - I2C_BBPLL_OC_VCO_DBIAS, - I2C_BBPLL_OC_VCO_DBIAS_MSB, - I2C_BBPLL_OC_VCO_DBIAS_LSB, - dbias as u8, - ); - - // WAIT CALIBRATION DONE - while (i2c_mst_ana_conf0_reg_ptr.read_volatile() & I2C_MST_BBPLL_CAL_DONE) == 0 {} - - // BBPLL CALIBRATION STOP - i2c_mst_ana_conf0_reg_ptr.write_volatile( - i2c_mst_ana_conf0_reg_ptr.read_volatile() | I2C_MST_BBPLL_STOP_FORCE_HIGH, - ); - i2c_mst_ana_conf0_reg_ptr.write_volatile( - i2c_mst_ana_conf0_reg_ptr.read_volatile() & !I2C_MST_BBPLL_STOP_FORCE_LOW, - ); - } -} - -pub(crate) fn esp32c6_rtc_bbpll_enable() { - let pmu = unsafe { &*crate::peripherals::PMU::PTR }; - - pmu.imm_hp_ck_power.modify(|_, w| { - w.tie_high_xpd_bb_i2c() - .set_bit() - .tie_high_xpd_bbpll() - .set_bit() - .tie_high_xpd_bbpll_i2c() - .set_bit() - }); - - pmu.imm_hp_ck_power - .modify(|_, w| w.tie_high_global_bbpll_icg().set_bit()); -} - -pub(crate) fn esp32c6_rtc_update_to_xtal(freq: XtalClock, _div: u8) { - let pcr = unsafe { &*crate::peripherals::PCR::PTR }; - unsafe { - ets_update_cpu_frequency(freq.mhz()); - // Set divider from XTAL to APB clock. Need to set divider to 1 (reg. value 0) - // first. - pcr.apb_freq_conf - .modify(|_, w| w.apb_div_num().bits(0).apb_div_num().bits(_div - 1)); - - // Switch clock source - pcr.sysclk_conf.modify(|_, w| w.soc_clk_sel().bits(0)); - } -} - -pub(crate) fn esp32c6_rtc_freq_to_pll_mhz(cpu_clock_speed: CpuClock) { - // On ESP32C6, MSPI source clock's default HS divider leads to 120MHz, which is - // unusable before calibration Therefore, before switching SOC_ROOT_CLK to - // HS, we need to set MSPI source clock HS divider to make it run at - // 80MHz after the switch. PLL = 480MHz, so divider is 6. - clk_ll_mspi_fast_set_hs_divider(6); - - let pcr = unsafe { &*crate::peripherals::PCR::PTR }; - unsafe { - pcr.cpu_freq_conf.modify(|_, w| { - w.cpu_hs_div_num() - .bits(((480 / cpu_clock_speed.mhz() / 3) - 1) as u8) - .cpu_hs_120m_force() - .clear_bit() - }); - - pcr.cpu_freq_conf - .modify(|_, w| w.cpu_hs_120m_force().clear_bit()); - - pcr.sysclk_conf.modify(|_, w| { - w.soc_clk_sel().bits(1) // PLL = 1 - }); - ets_update_cpu_frequency(cpu_clock_speed.mhz()); - } -} - -pub(crate) fn esp32c6_rtc_apb_freq_update(apb_freq: ApbClock) { - let lp_aon = unsafe { &*crate::peripherals::LP_AON::ptr() }; - let value = ((apb_freq.hz() >> 12) & u16::MAX as u32) - | (((apb_freq.hz() >> 12) & u16::MAX as u32) << 16); - - lp_aon - .store5 - .modify(|_, w| unsafe { w.lp_aon_store5().bits(value) }); -} - -fn clk_ll_mspi_fast_set_hs_divider(divider: u32) { - // SOC_ROOT_CLK ------> MSPI_FAST_CLK - // HS divider option: 4, 5, 6 (PCR_MSPI_FAST_HS_DIV_NUM=3, 4, 5) - let pcr = unsafe { &*crate::peripherals::PCR::PTR }; - - unsafe { - match divider { - 4 => pcr - .mspi_clk_conf - .modify(|_, w| w.mspi_fast_hs_div_num().bits(3)), - 5 => pcr - .mspi_clk_conf - .modify(|_, w| w.mspi_fast_hs_div_num().bits(4)), - 6 => pcr - .mspi_clk_conf - .modify(|_, w| w.mspi_fast_hs_div_num().bits(5)), - _ => panic!("Unsupported HS MSPI_FAST divider"), - } - } -} - -fn reg_set_bit(reg: u32, bit: u32) { - unsafe { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | bit); - } -} - -fn reg_clr_bit(reg: u32, bit: u32) { - unsafe { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !bit); - } -} - -fn reg_write(reg: u32, v: u32) { - unsafe { - (reg as *mut u32).write_volatile(v); - } -} - -fn reg_get_bit(reg: u32, b: u32) -> u32 { - unsafe { (reg as *mut u32).read_volatile() & b } -} - -fn reg_get_field(reg: u32, s: u32, v: u32) -> u32 { - unsafe { ((reg as *mut u32).read_volatile() >> s) & v } -} - -const DR_REG_MODEM_LPCON_BASE: u32 = 0x600AF000; -const MODEM_LPCON_CLK_CONF_REG: u32 = DR_REG_MODEM_LPCON_BASE + 0x18; -const MODEM_LPCON_CLK_I2C_MST_EN: u32 = 1 << 2; -const DR_REG_LP_I2C_ANA_MST_BASE: u32 = 0x600B2400; -const LP_I2C_ANA_MST_DATE_REG: u32 = DR_REG_LP_I2C_ANA_MST_BASE + 0x3fc; -const LP_I2C_ANA_MST_I2C_MAT_CLK_EN: u32 = 1 << 28; -const REGI2C_BIAS: u8 = 0x6a; -const REGI2C_DIG_REG: u8 = 0x6d; -const REGI2C_ULP_CAL: u8 = 0x61; -const REGI2C_SAR_I2C: u8 = 0x69; - -const LP_I2C_ANA_MST_DEVICE_EN_REG: u32 = DR_REG_LP_I2C_ANA_MST_BASE + 0x14; -const REGI2C_BBPLL_DEVICE_EN: u32 = 1 << 5; -const REGI2C_BIAS_DEVICE_EN: u32 = 1 << 4; -const REGI2C_DIG_REG_DEVICE_EN: u32 = 1 << 8; -const REGI2C_ULP_CAL_DEVICE_EN: u32 = 1 << 6; -const REGI2C_SAR_I2C_DEVICE_EN: u32 = 1 << 7; - -const REGI2C_RTC_SLAVE_ID_V: u8 = 0xFF; -const REGI2C_RTC_SLAVE_ID_S: u8 = 0; -const REGI2C_RTC_ADDR_V: u8 = 0xFF; -const REGI2C_RTC_ADDR_S: u8 = 8; -const REGI2C_RTC_WR_CNTL_V: u8 = 0x1; -const REGI2C_RTC_WR_CNTL_S: u8 = 24; -const REGI2C_RTC_DATA_V: u8 = 0xFF; -const REGI2C_RTC_DATA_S: u8 = 16; - -const LP_I2C_ANA_MST_I2C0_CTRL_REG: u32 = DR_REG_LP_I2C_ANA_MST_BASE + 0x0; -const LP_I2C_ANA_MST_I2C0_BUSY: u32 = 1 << 25; - -const LP_I2C_ANA_MST_I2C0_DATA_REG: u32 = DR_REG_LP_I2C_ANA_MST_BASE + 0x8; -const LP_I2C_ANA_MST_I2C0_RDATA_V: u32 = 0x000000FF; -const LP_I2C_ANA_MST_I2C0_RDATA_S: u32 = 0; - -const REGI2C_BBPLL: u8 = 0x66; - -fn regi2c_enable_block(block: u8) { - reg_set_bit(MODEM_LPCON_CLK_CONF_REG, MODEM_LPCON_CLK_I2C_MST_EN); - reg_set_bit(LP_I2C_ANA_MST_DATE_REG, LP_I2C_ANA_MST_I2C_MAT_CLK_EN); - - // Before config I2C register, enable corresponding slave. - match block { - REGI2C_BBPLL => { - reg_set_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_BBPLL_DEVICE_EN); - } - REGI2C_BIAS => { - reg_set_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_BIAS_DEVICE_EN); - } - REGI2C_DIG_REG => { - reg_set_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_DIG_REG_DEVICE_EN); - } - REGI2C_ULP_CAL => { - reg_set_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_ULP_CAL_DEVICE_EN); - } - REGI2C_SAR_I2C => { - reg_set_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_SAR_I2C_DEVICE_EN); - } - _ => (), - } -} - -fn regi2c_disable_block(block: u8) { - match block { - REGI2C_BBPLL => { - reg_clr_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_BBPLL_DEVICE_EN); - } - REGI2C_BIAS => { - reg_clr_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_BIAS_DEVICE_EN); - } - REGI2C_DIG_REG => { - reg_clr_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_DIG_REG_DEVICE_EN); - } - REGI2C_ULP_CAL => { - reg_clr_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_ULP_CAL_DEVICE_EN); - } - REGI2C_SAR_I2C => { - reg_clr_bit(LP_I2C_ANA_MST_DEVICE_EN_REG, REGI2C_SAR_I2C_DEVICE_EN); - } - _ => (), - } -} - -pub(crate) fn regi2c_write(block: u8, _host_id: u8, reg_add: u8, data: u8) { - regi2c_enable_block(block); - - let temp: u32 = ((block as u32 & REGI2C_RTC_SLAVE_ID_V as u32) << REGI2C_RTC_SLAVE_ID_S as u32) - | ((reg_add as u32 & REGI2C_RTC_ADDR_V as u32) << REGI2C_RTC_ADDR_S as u32) - | ((0x1 & REGI2C_RTC_WR_CNTL_V as u32) << REGI2C_RTC_WR_CNTL_S as u32) // 0: READ I2C register; 1: Write I2C register; - | (((data as u32) & REGI2C_RTC_DATA_V as u32) << REGI2C_RTC_DATA_S as u32); - reg_write(LP_I2C_ANA_MST_I2C0_CTRL_REG, temp); - while reg_get_bit(LP_I2C_ANA_MST_I2C0_CTRL_REG, LP_I2C_ANA_MST_I2C0_BUSY) != 0 {} - - regi2c_disable_block(block); -} - -pub(crate) fn regi2c_write_mask(block: u8, _host_id: u8, reg_add: u8, msb: u8, lsb: u8, data: u8) { - assert!(msb - lsb < 8); - regi2c_enable_block(block); - - // Read the i2c bus register - let mut temp: u32 = ((block as u32 & REGI2C_RTC_SLAVE_ID_V as u32) - << REGI2C_RTC_SLAVE_ID_S as u32) - | (reg_add as u32 & REGI2C_RTC_ADDR_V as u32) << REGI2C_RTC_ADDR_S as u32; - reg_write(LP_I2C_ANA_MST_I2C0_CTRL_REG, temp); - while reg_get_bit(LP_I2C_ANA_MST_I2C0_CTRL_REG, LP_I2C_ANA_MST_I2C0_BUSY) != 0 {} - temp = reg_get_field( - LP_I2C_ANA_MST_I2C0_DATA_REG, - LP_I2C_ANA_MST_I2C0_RDATA_S, - LP_I2C_ANA_MST_I2C0_RDATA_V, - ); - // Write the i2c bus register - temp &= (!(0xFFFFFFFF << lsb)) | (0xFFFFFFFF << (msb + 1)); - temp = - ((data as u32 & (!(0xFFFFFFFF << (msb as u32 - lsb as u32 + 1)))) << (lsb as u32)) | temp; - temp = ((block as u32 & REGI2C_RTC_SLAVE_ID_V as u32) << REGI2C_RTC_SLAVE_ID_S as u32) - | ((reg_add as u32 & REGI2C_RTC_ADDR_V as u32) << REGI2C_RTC_ADDR_S as u32) - | ((0x1 & REGI2C_RTC_WR_CNTL_V as u32) << REGI2C_RTC_WR_CNTL_S as u32) - | ((temp & REGI2C_RTC_DATA_V as u32) << REGI2C_RTC_DATA_S as u32); - reg_write(LP_I2C_ANA_MST_I2C0_CTRL_REG, temp); - while reg_get_bit(LP_I2C_ANA_MST_I2C0_CTRL_REG, LP_I2C_ANA_MST_I2C0_BUSY) != 0 {} - - regi2c_disable_block(block); -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32s2.rs b/esp-hal-common/src/clock/clocks_ll/esp32s2.rs deleted file mode 100644 index b435e34a026..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32s2.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::clock::CpuClock; - -const MHZ: u32 = 1000000; -const UINT16_MAX: u32 = 0xffff; - -const RTC_CNTL_DBIAS_1V25: u32 = 7; - -// when not running with 80MHz Flash frequency we could use RTC_CNTL_DBIAS_1V10 -// for DIG_DBIAS_80M_160M - but RTC_CNTL_DBIAS_1V25 shouldn't hurt -const DIG_DBIAS_80M_160M: u32 = RTC_CNTL_DBIAS_1V25; -const DIG_DBIAS_240M: u32 = RTC_CNTL_DBIAS_1V25; - -pub(crate) fn set_cpu_clock(cpu_clock_speed: CpuClock) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::PTR }; - let rtc_cntl = unsafe { &*crate::peripherals::RTC_CNTL::ptr() }; - - unsafe { - system_control - .sysclk_conf - .modify(|_, w| w.soc_clk_sel().bits(1)); - system_control.cpu_per_conf.modify(|_, w| { - w.pll_freq_sel() - .set_bit() - .cpuperiod_sel() - .bits(match cpu_clock_speed { - CpuClock::Clock80MHz => 0, - CpuClock::Clock160MHz => 1, - CpuClock::Clock240MHz => 2, - }) - }); - - rtc_cntl.reg.modify(|_, w| { - w.dig_reg_dbias_wak().bits(match cpu_clock_speed { - CpuClock::Clock80MHz => DIG_DBIAS_80M_160M, - CpuClock::Clock160MHz => DIG_DBIAS_80M_160M, - CpuClock::Clock240MHz => DIG_DBIAS_240M, - } as u8) - }); - - let value = (((80 * MHZ) >> 12) & UINT16_MAX) | ((((80 * MHZ) >> 12) & UINT16_MAX) << 16); - rtc_cntl - .store5 - .modify(|_, w| w.scratch5().bits(value as u32)); - } -} diff --git a/esp-hal-common/src/clock/clocks_ll/esp32s3.rs b/esp-hal-common/src/clock/clocks_ll/esp32s3.rs deleted file mode 100644 index d6f9c282d3e..00000000000 --- a/esp-hal-common/src/clock/clocks_ll/esp32s3.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::clock::CpuClock; - -pub(crate) fn set_cpu_clock(cpu_clock_speed: CpuClock) { - let system_control = unsafe { &*crate::peripherals::SYSTEM::PTR }; - - unsafe { - system_control - .sysclk_conf - .modify(|_, w| w.soc_clk_sel().bits(1)); - system_control.cpu_per_conf.modify(|_, w| { - w.pll_freq_sel() - .set_bit() - .cpuperiod_sel() - .bits(match cpu_clock_speed { - CpuClock::Clock80MHz => 0, - CpuClock::Clock160MHz => 1, - CpuClock::Clock240MHz => 2, - }) - }); - } -} diff --git a/esp-hal-common/src/clock/mod.rs b/esp-hal-common/src/clock/mod.rs deleted file mode 100644 index 2503dedcc3e..00000000000 --- a/esp-hal-common/src/clock/mod.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! # Clock Control -use fugit::HertzU32; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - system::SystemClockControl, -}; - -#[cfg_attr(esp32, path = "clocks_ll/esp32.rs")] -#[cfg_attr(esp32c2, path = "clocks_ll/esp32c2.rs")] -#[cfg_attr(esp32c3, path = "clocks_ll/esp32c3.rs")] -#[cfg_attr(esp32c6, path = "clocks_ll/esp32c6.rs")] -#[cfg_attr(esp32s2, path = "clocks_ll/esp32s2.rs")] -#[cfg_attr(esp32s3, path = "clocks_ll/esp32s3.rs")] -pub(crate) mod clocks_ll; - -pub trait Clock { - fn frequency(&self) -> HertzU32; - - fn mhz(&self) -> u32 { - self.frequency().to_MHz() - } - - fn hz(&self) -> u32 { - self.frequency().to_Hz() - } -} - -/// CPU clock speed -#[derive(Debug, Clone, Copy)] -pub enum CpuClock { - Clock80MHz, - #[cfg(esp32c2)] - Clock120MHz, - #[cfg(not(esp32c2))] - Clock160MHz, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Clock240MHz, -} - -#[allow(dead_code)] -impl Clock for CpuClock { - fn frequency(&self) -> HertzU32 { - match self { - CpuClock::Clock80MHz => HertzU32::MHz(80), - #[cfg(esp32c2)] - CpuClock::Clock120MHz => HertzU32::MHz(120), - #[cfg(not(esp32c2))] - CpuClock::Clock160MHz => HertzU32::MHz(160), - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - CpuClock::Clock240MHz => HertzU32::MHz(240), - } - } -} - -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -pub(crate) enum XtalClock { - #[cfg(esp32)] - RtcXtalFreq24M, - #[cfg(any(esp32, esp32c2))] - RtcXtalFreq26M, - #[cfg(any(esp32c3, esp32s3))] - RtcXtalFreq32M, - RtcXtalFreq40M, - RtcXtalFreqOther(u32), -} - -impl Clock for XtalClock { - fn frequency(&self) -> HertzU32 { - match self { - #[cfg(esp32)] - XtalClock::RtcXtalFreq24M => HertzU32::MHz(24), - #[cfg(any(esp32, esp32c2))] - XtalClock::RtcXtalFreq26M => HertzU32::MHz(26), - #[cfg(any(esp32c3, esp32s3))] - XtalClock::RtcXtalFreq32M => HertzU32::MHz(32), - XtalClock::RtcXtalFreq40M => HertzU32::MHz(40), - XtalClock::RtcXtalFreqOther(mhz) => HertzU32::MHz(*mhz), - } - } -} - -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -pub(crate) enum PllClock { - #[cfg(not(any(esp32c2, esp32c6)))] - Pll320MHz, - Pll480MHz, -} - -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -pub(crate) enum ApbClock { - ApbFreq40MHz, - ApbFreq80MHz, - ApbFreqOther(u32), -} - -impl Clock for ApbClock { - fn frequency(&self) -> HertzU32 { - match self { - ApbClock::ApbFreq40MHz => HertzU32::MHz(40), - ApbClock::ApbFreq80MHz => HertzU32::MHz(80), - ApbClock::ApbFreqOther(mhz) => HertzU32::MHz(*mhz), - } - } -} - -/// Frozen clock frequencies -/// -/// The existence of this value indicates that the clock configuration can no -/// longer be changed -pub struct Clocks<'d> { - _private: PeripheralRef<'d, SystemClockControl>, - pub cpu_clock: HertzU32, - pub apb_clock: HertzU32, - pub xtal_clock: HertzU32, - pub i2c_clock: HertzU32, - #[cfg(esp32)] - pub pwm_clock: HertzU32, - #[cfg(esp32s3)] - pub crypto_pwm_clock: HertzU32, - #[cfg(esp32c6)] - pub crypto_clock: HertzU32, - // TODO chip specific additional ones as needed -} - -#[doc(hidden)] -impl<'d> Clocks<'d> { - /// This should not be used in user code. - /// The whole point this exists is make it possible to have other crates - /// (i.e. esp-wifi) create `Clocks` - #[doc(hidden)] - pub fn from_raw_clocks( - system_clock_control: PeripheralRef<'d, SystemClockControl>, - raw_clocks: RawClocks, - ) -> Clocks<'d> { - Self { - _private: system_clock_control, - cpu_clock: raw_clocks.cpu_clock, - apb_clock: raw_clocks.apb_clock, - xtal_clock: raw_clocks.xtal_clock, - i2c_clock: raw_clocks.i2c_clock, - #[cfg(esp32)] - pwm_clock: raw_clocks.pwm_clock, - #[cfg(esp32s3)] - crypto_pwm_clock: raw_clocks.crypto_pwm_clock, - #[cfg(esp32c6)] - crypto_clock: raw_clocks.crypto_clock, - } - } -} - -#[doc(hidden)] -pub struct RawClocks { - pub cpu_clock: HertzU32, - pub apb_clock: HertzU32, - pub xtal_clock: HertzU32, - pub i2c_clock: HertzU32, - #[cfg(esp32)] - pub pwm_clock: HertzU32, - #[cfg(esp32s3)] - pub crypto_pwm_clock: HertzU32, - #[cfg(esp32c6)] - pub crypto_clock: HertzU32, - // TODO chip specific additional ones as needed -} - -/// Used to configure the frequencies of the clocks present in the chip. -/// -/// After setting all frequencies, call the freeze function to apply the -/// configuration. -pub struct ClockControl<'d> { - _private: PeripheralRef<'d, SystemClockControl>, - desired_rates: RawClocks, -} - -impl<'d> ClockControl<'d> { - /// Applies the clock configuration and returns a Clocks struct that - /// signifies that the clocks are frozen, and contains the frequencies - /// used. After this function is called, the clocks can not change - pub fn freeze(self) -> Clocks<'d> { - Clocks::from_raw_clocks(self._private, self.desired_rates) - } -} - -#[cfg(esp32)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - #[cfg(feature = "esp32_40mhz")] - return ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(80), - pwm_clock: HertzU32::MHz(160), - }, - }; - - #[cfg(feature = "esp32_26mhz")] - return ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(26), - i2c_clock: HertzU32::MHz(80), - pwm_clock: HertzU32::MHz(160), - }, - }; - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - // like NuttX use 40M hardcoded - if it turns out to be a problem - // we will take care then - #[cfg(feature = "esp32_40mhz")] - let xtal_freq = XtalClock::RtcXtalFreq40M; - #[cfg(feature = "esp32_26mhz")] - let xtal_freq = XtalClock::RtcXtalFreq26M; - let pll_freq = match cpu_clock_speed { - CpuClock::Clock80MHz => PllClock::Pll320MHz, - CpuClock::Clock160MHz => PllClock::Pll320MHz, - CpuClock::Clock240MHz => PllClock::Pll480MHz, - }; - - clocks_ll::esp32_rtc_update_to_xtal(xtal_freq, 1); - clocks_ll::esp32_rtc_bbpll_enable(); - clocks_ll::esp32_rtc_bbpll_configure(xtal_freq, pll_freq); - clocks_ll::set_cpu_freq(cpu_clock_speed); - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - // The docs are unclear here. pwm_clock seems to be tied to clocks.apb_clock - // while simultaneously being fixed at 160 MHz. - // Testing showed 160 MHz to be correct for current clock configurations. - pwm_clock: HertzU32::MHz(160), - }, - } - } -} - -#[cfg(esp32c2)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - #[cfg(feature = "esp32c2_40mhz")] - return ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(40), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - }, - }; - - #[cfg(feature = "esp32c2_26mhz")] - return ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(40), - xtal_clock: HertzU32::MHz(26), - i2c_clock: HertzU32::MHz(26), - }, - }; - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - let apb_freq; - #[cfg(feature = "esp32c2_40mhz")] - let xtal_freq = XtalClock::RtcXtalFreq40M; - #[cfg(feature = "esp32c2_26mhz")] - let xtal_freq = XtalClock::RtcXtalFreq26M; - let pll_freq = PllClock::Pll480MHz; - - if cpu_clock_speed.mhz() <= xtal_freq.mhz() { - apb_freq = ApbClock::ApbFreqOther(cpu_clock_speed.mhz()); - clocks_ll::esp32c2_rtc_update_to_xtal(xtal_freq, 1); - clocks_ll::esp32c2_rtc_apb_freq_update(apb_freq); - } else { - apb_freq = ApbClock::ApbFreq40MHz; - clocks_ll::esp32c2_rtc_bbpll_enable(); - clocks_ll::esp32c2_rtc_bbpll_configure(xtal_freq, pll_freq); - clocks_ll::esp32c2_rtc_freq_to_pll_mhz(cpu_clock_speed); - clocks_ll::esp32c2_rtc_apb_freq_update(apb_freq); - } - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: apb_freq.frequency(), - xtal_clock: xtal_freq.frequency(), - i2c_clock: HertzU32::MHz(40), - }, - } - } -} - -#[cfg(esp32c3)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - }, - } - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - let apb_freq; - let xtal_freq = XtalClock::RtcXtalFreq40M; - let pll_freq = PllClock::Pll480MHz; - - if cpu_clock_speed.mhz() <= xtal_freq.mhz() { - apb_freq = ApbClock::ApbFreqOther(cpu_clock_speed.mhz()); - clocks_ll::esp32c3_rtc_update_to_xtal(xtal_freq, 1); - clocks_ll::esp32c3_rtc_apb_freq_update(apb_freq); - } else { - apb_freq = ApbClock::ApbFreq80MHz; - clocks_ll::esp32c3_rtc_bbpll_enable(); - clocks_ll::esp32c3_rtc_bbpll_configure(xtal_freq, pll_freq); - clocks_ll::esp32c3_rtc_freq_to_pll_mhz(cpu_clock_speed); - clocks_ll::esp32c3_rtc_apb_freq_update(apb_freq); - } - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: apb_freq.frequency(), - xtal_clock: xtal_freq.frequency(), - i2c_clock: HertzU32::MHz(40), - }, - } - } -} - -#[cfg(esp32c6)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - crypto_clock: HertzU32::MHz(160), - }, - } - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - let apb_freq; - let xtal_freq = XtalClock::RtcXtalFreq40M; - let pll_freq = PllClock::Pll480MHz; - - if cpu_clock_speed.mhz() <= xtal_freq.mhz() { - apb_freq = ApbClock::ApbFreqOther(cpu_clock_speed.mhz()); - clocks_ll::esp32c6_rtc_update_to_xtal(xtal_freq, 1); - clocks_ll::esp32c6_rtc_apb_freq_update(apb_freq); - } else { - apb_freq = ApbClock::ApbFreq80MHz; - clocks_ll::esp32c6_rtc_bbpll_enable(); - clocks_ll::esp32c6_rtc_bbpll_configure(xtal_freq, pll_freq); - clocks_ll::esp32c6_rtc_freq_to_pll_mhz(cpu_clock_speed); - clocks_ll::esp32c6_rtc_apb_freq_update(apb_freq); - } - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: apb_freq.frequency(), - xtal_clock: xtal_freq.frequency(), - i2c_clock: HertzU32::MHz(40), - crypto_clock: HertzU32::MHz(160), - }, - } - } -} - -#[cfg(esp32s2)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(80), - }, - } - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - clocks_ll::set_cpu_clock(cpu_clock_speed); - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - }, - } - } -} - -#[cfg(esp32s3)] -impl<'d> ClockControl<'d> { - /// Use what is considered the default settings after boot. - #[allow(unused)] - pub fn boot_defaults( - clock_control: impl Peripheral

    + 'd, - ) -> ClockControl<'d> { - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: HertzU32::MHz(80), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - crypto_pwm_clock: HertzU32::MHz(160), - }, - } - } - - /// Configure the CPU clock speed. - #[allow(unused)] - pub fn configure( - clock_control: impl Peripheral

    + 'd, - cpu_clock_speed: CpuClock, - ) -> ClockControl<'d> { - clocks_ll::set_cpu_clock(cpu_clock_speed); - - ClockControl { - _private: clock_control.into_ref(), - desired_rates: RawClocks { - cpu_clock: cpu_clock_speed.frequency(), - apb_clock: HertzU32::MHz(80), - xtal_clock: HertzU32::MHz(40), - i2c_clock: HertzU32::MHz(40), - crypto_pwm_clock: HertzU32::MHz(160), - }, - } - } -} diff --git a/esp-hal-common/src/delay.rs b/esp-hal-common/src/delay.rs deleted file mode 100644 index c5f0bd287d7..00000000000 --- a/esp-hal-common/src/delay.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Delay driver -//! -//! Implement the `DelayMs` and `DelayUs` traits from [embedded-hal]. -//! -//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ - -pub use self::delay::Delay; - -impl embedded_hal::blocking::delay::DelayMs for Delay -where - T: Into, -{ - fn delay_ms(&mut self, ms: T) { - for _ in 0..ms.into() { - self.delay(1000u32); - } - } -} - -impl embedded_hal::blocking::delay::DelayUs for Delay -where - T: Into, -{ - fn delay_us(&mut self, us: T) { - self.delay(us.into()); - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::delay::DelayUs for Delay { - fn delay_us(&mut self, us: u32) { - self.delay(us); - } -} - -#[cfg(riscv)] -mod delay { - use fugit::HertzU64; - - use crate::{clock::Clocks, systimer::SystemTimer}; - - /// Uses the `SYSTIMER` peripheral for counting clock cycles, as - /// unfortunately the ESP32-C3 does NOT implement the `mcycle` CSR, which is - /// how we would normally do this. - pub struct Delay { - freq: HertzU64, - } - - impl Delay { - /// Create a new Delay instance - pub fn new(clocks: &Clocks) -> Self { - // The counters and comparators are driven using `XTAL_CLK`. The average clock - // frequency is fXTAL_CLK/2.5, which is 16 MHz. The timer counting is - // incremented by 1/16 μs on each `CNT_CLK` cycle. - - Self { - freq: HertzU64::MHz((clocks.xtal_clock.to_MHz() * 10 / 25) as u64), - } - } - - /// Delay for the specified number of microseconds - pub fn delay(&self, us: u32) { - let t0 = SystemTimer::now(); - let clocks = (us as u64 * self.freq.raw()) / HertzU64::MHz(1).raw(); - - while SystemTimer::now().wrapping_sub(t0) & SystemTimer::BIT_MASK <= clocks {} - } - } -} - -#[cfg(xtensa)] -mod delay { - use fugit::HertzU64; - - use crate::clock::Clocks; - - /// Delay driver - /// - /// Uses the built-in Xtensa timer from the `xtensa_lx` crate. - pub struct Delay { - freq: HertzU64, - } - - impl Delay { - /// Instantiate the `Delay` driver - pub fn new(clocks: &Clocks) -> Self { - Self { - freq: HertzU64::MHz(clocks.cpu_clock.to_MHz() as u64), - } - } - - /// Delay for the specified number of microseconds - pub fn delay(&self, us: u32) { - let clocks = (us as u64 * self.freq.raw()) / HertzU64::MHz(1).raw(); - xtensa_lx::timer::delay(clocks as u32); - } - } -} diff --git a/esp-hal-common/src/dma/gdma.rs b/esp-hal-common/src/dma/gdma.rs deleted file mode 100644 index 82dfc285773..00000000000 --- a/esp-hal-common/src/dma/gdma.rs +++ /dev/null @@ -1,504 +0,0 @@ -//! Direct Memory Access - -use crate::{ - dma::*, - peripheral::PeripheralRef, - system::{Peripheral, PeripheralClockControl}, -}; - -macro_rules! impl_channel { - ($num: literal) => { - paste::paste! { - pub struct [] {} - - impl RegisterAccess for [] { - fn init_channel() { - // nothing special to be done here - } - - fn set_out_burstmode(burst_mode: bool) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_,w| { - w.out_data_burst_en().bit(burst_mode) - .outdscr_burst_en().bit(burst_mode) - }); - } - - fn set_out_priority(priority: DmaPriority) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].write(|w| { - w.tx_pri().variant(priority as u8) - }); - } - - fn clear_out_interrupts() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - dma.[].write(|w| { - w.out_eof() - .set_bit() - .out_dscr_err() - .set_bit() - .out_done() - .set_bit() - .out_total_eof() - .set_bit() - .outfifo_ovf() - .set_bit() - .outfifo_udf() - .set_bit() - }); - - #[cfg(esp32c6)] - dma.[].write(|w| { - w.out_eof() - .set_bit() - .out_dscr_err() - .set_bit() - .out_done() - .set_bit() - .out_total_eof() - .set_bit() - .outfifo_ovf() - .set_bit() - .outfifo_udf() - .set_bit() - }); - - #[cfg(esp32s3)] - dma.[].write(|w| { - w.out_eof() - .set_bit() - .out_dscr_err() - .set_bit() - .out_done() - .set_bit() - .out_total_eof() - .set_bit() - .outfifo_ovf_l1() - .set_bit() - .outfifo_ovf_l3() - .set_bit() - .outfifo_udf_l1() - .set_bit() - .outfifo_udf_l3() - .set_bit() - }); - } - - fn reset_out() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.out_rst().set_bit()); - dma.[].modify(|_, w| w.out_rst().clear_bit()); - } - - fn set_out_descriptors(address: u32) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| unsafe { w.outlink_addr().bits(address) }); - } - - fn has_out_descriptor_error() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - let ret = dma.[].read().out_dscr_err().bit(); - #[cfg(any(esp32c6, esp32s3))] - let ret = dma.[].read().out_dscr_err().bit(); - - ret - } - - fn set_out_peripheral(peripheral: u8) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.peri_out_sel().variant(peripheral)); - } - - fn start_out() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.outlink_start().set_bit()); - } - - fn is_out_done() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - let ret = dma.[].read().out_total_eof().bit(); - #[cfg(any(esp32c6, esp32s3))] - let ret = dma.[].read().out_total_eof().bit(); - - ret - } - - fn last_out_dscr_address() -> usize { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - dma.[].read().out_eof_des_addr().bits() as usize - } - - fn is_out_eof_interrupt_set() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - let ret = dma.[].read().out_eof().bit(); - #[cfg(any(esp32c6, esp32s3))] - let ret = dma.[].read().out_eof().bit(); - - ret - } - - fn reset_out_eof_interrupt() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - dma.[].write(|w| { - w.out_eof() - .set_bit() - }); - - #[cfg(any(esp32c6, esp32s3))] - dma.[].write(|w| { - w.out_eof() - .set_bit() - }); - } - - fn set_in_burstmode(burst_mode: bool) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_,w| { - w.in_data_burst_en().bit(burst_mode).indscr_burst_en().bit(burst_mode) - }); - } - - fn set_in_priority(priority: DmaPriority) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].write(|w| { - w.rx_pri().variant(priority as u8) - }); - } - - fn clear_in_interrupts() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - dma.[].write(|w| { - w.in_suc_eof() - .set_bit() - .in_err_eof() - .set_bit() - .in_dscr_err() - .set_bit() - .in_dscr_empty() - .set_bit() - .in_done() - .set_bit() - .infifo_ovf() - .set_bit() - .infifo_udf() - .set_bit() - }); - - #[cfg(esp32c6)] - dma.[].write(|w| { - w.in_suc_eof() - .set_bit() - .in_err_eof() - .set_bit() - .in_dscr_err() - .set_bit() - .in_dscr_empty() - .set_bit() - .in_done() - .set_bit() - .infifo_ovf() - .set_bit() - .infifo_udf() - .set_bit() - }); - - #[cfg(esp32s3)] - dma.[].write(|w| { - w.in_suc_eof() - .set_bit() - .in_err_eof() - .set_bit() - .in_dscr_err() - .set_bit() - .in_dscr_empty() - .set_bit() - .in_done() - .set_bit() - .infifo_ovf_l1() - .set_bit() - .infifo_ovf_l3() - .set_bit() - .infifo_udf_l1() - .set_bit() - .infifo_udf_l3() - .set_bit() - }); - } - - fn reset_in() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.in_rst().set_bit()); - dma.[].modify(|_, w| w.in_rst().clear_bit()); - } - - fn set_in_descriptors(address: u32) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| unsafe { w.inlink_addr().bits(address) }); - } - - fn has_in_descriptor_error() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - let ret = dma.[].read().in_dscr_err().bit(); - #[cfg(any(esp32c6, esp32s3))] - let ret = dma.[].read().in_dscr_err().bit(); - - ret - } - - fn set_in_peripheral(peripheral: u8) { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.peri_in_sel().variant(peripheral)); - } - - fn start_in() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - dma.[].modify(|_, w| w.inlink_start().set_bit()); - } - - fn is_in_done() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - - #[cfg(not(any(esp32c6, esp32s3)))] - let ret = dma.[].read().in_suc_eof().bit(); - #[cfg(any(esp32c6, esp32s3))] - let ret = dma.[].read().in_suc_eof().bit(); - - ret - } - - fn last_in_dscr_address() -> usize { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - dma.[].read().inlink_dscr_bf0().bits() as usize - } - - fn is_listening_in_eof() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].read().in_suc_eof().bit_is_set() - } else { - dma.[].read().in_suc_eof().bit_is_set() - } - } - } - - fn is_listening_out_eof() -> bool { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].read().out_total_eof().bit_is_set() - } else { - dma.[].read().out_total_eof().bit_is_set() - } - } - } - - fn listen_in_eof() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].modify(|_, w| w.in_suc_eof().set_bit()) - } else { - dma.[].modify(|_, w| w.in_suc_eof().set_bit()) - } - } - } - - fn listen_out_eof() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].modify(|_, w| w.out_total_eof().set_bit()) - } else { - dma.[].modify(|_, w| w.out_total_eof().set_bit()) - } - } - } - - fn unlisten_in_eof() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].modify(|_, w| w.in_suc_eof().clear_bit()) - } else { - dma.[].modify(|_, w| w.in_suc_eof().clear_bit()) - } - } - } - - fn unlisten_out_eof() { - let dma = unsafe { &*crate::peripherals::DMA::PTR }; - cfg_if::cfg_if! { - if #[cfg(any(esp32c6, esp32s3))] { - dma.[].modify(|_, w| w.out_total_eof().clear_bit()) - } else { - dma.[].modify(|_, w| w.out_total_eof().clear_bit()) - } - } - } - } - - pub struct [] {} - - impl<'a> TxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl<'a> RxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl [] { - /// Configure the channel for use - /// - /// Descriptors should be sized as (BUFFERSIZE / 4092) * 3 - pub fn configure<'a>( - self, - burst_mode: bool, - tx_descriptors: &'a mut [u32], - rx_descriptors: &'a mut [u32], - priority: DmaPriority, - ) -> Channel], []>, ChannelRx<'a, [], []>, []> { - let mut tx_impl = [] {}; - tx_impl.init(burst_mode, priority); - - let tx_channel = ChannelTx { - descriptors: tx_descriptors, - burst_mode, - tx_impl: tx_impl, - write_offset: 0, - write_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - buffer_start: core::ptr::null(), - buffer_len: 0, - _phantom: PhantomData::default(), - }; - - let mut rx_impl = [] {}; - rx_impl.init(burst_mode, priority); - - let rx_channel = ChannelRx { - descriptors: rx_descriptors, - burst_mode, - rx_impl: rx_impl, - read_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - read_buffer_start: core::ptr::null(), - _phantom: PhantomData::default(), - }; - - Channel { - tx: tx_channel, - rx: rx_channel, - _phantom: PhantomData::default(), - } - } - } - - pub struct [] {} - impl PeripheralMarker for [] {} - - // with GDMA every channel can be used for any peripheral - impl SpiPeripheral for [] {} - impl Spi2Peripheral for [] {} - impl I2sPeripheral for [] {} - impl I2s0Peripheral for [] {} - impl I2s1Peripheral for [] {} - } - }; -} - -impl_channel!(0); -#[cfg(not(esp32c2))] -impl_channel!(1); -#[cfg(not(esp32c2))] -impl_channel!(2); -#[cfg(esp32s3)] -impl_channel!(3); -#[cfg(esp32s3)] -impl_channel!(4); - -/// GDMA Peripheral -/// -/// This offers the available DMA channels. -pub struct Gdma<'d> { - _inner: PeripheralRef<'d, crate::peripherals::DMA>, - pub channel0: ChannelCreator0, - #[cfg(not(esp32c2))] - pub channel1: ChannelCreator1, - #[cfg(not(esp32c2))] - pub channel2: ChannelCreator2, - #[cfg(esp32s3)] - pub channel3: ChannelCreator3, - #[cfg(esp32s3)] - pub channel4: ChannelCreator4, -} - -impl<'d> Gdma<'d> { - /// Create a DMA instance. - pub fn new( - dma: impl crate::peripheral::Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Gdma<'d> { - crate::into_ref!(dma); - - peripheral_clock_control.enable(Peripheral::Gdma); - dma.misc_conf.modify(|_, w| w.ahbm_rst_inter().set_bit()); - dma.misc_conf.modify(|_, w| w.ahbm_rst_inter().clear_bit()); - dma.misc_conf.modify(|_, w| w.clk_en().set_bit()); - - Gdma { - _inner: dma, - channel0: ChannelCreator0 {}, - #[cfg(not(esp32c2))] - channel1: ChannelCreator1 {}, - #[cfg(not(esp32c2))] - channel2: ChannelCreator2 {}, - #[cfg(esp32s3)] - channel3: ChannelCreator3 {}, - #[cfg(esp32s3)] - channel4: ChannelCreator4 {}, - } - } -} diff --git a/esp-hal-common/src/dma/mod.rs b/esp-hal-common/src/dma/mod.rs deleted file mode 100644 index 6574c2ae035..00000000000 --- a/esp-hal-common/src/dma/mod.rs +++ /dev/null @@ -1,1237 +0,0 @@ -//! Direct Memory Access Commons -//! -//! Descriptors should be sized as (BUFFERSIZE / 4092) * 3 - -use core::{marker::PhantomData, sync::atomic::compiler_fence}; - -#[cfg(gdma)] -pub mod gdma; -#[cfg(pdma)] -pub mod pdma; - -const CHUNK_SIZE: usize = 4092; - -/// DMA Errors -#[derive(Debug, Clone, Copy)] -pub enum DmaError { - InvalidAlignment, - OutOfDescriptors, - InvalidDescriptorSize, - DescriptorError, - Overflow, - Exhausted, - BufferTooSmall, -} - -/// DMA Priorities -#[cfg(gdma)] -#[derive(Clone, Copy)] -pub enum DmaPriority { - Priority0 = 0, - Priority1 = 1, - Priority2 = 2, - Priority3 = 3, - Priority4 = 4, - Priority5 = 5, - Priority6 = 6, - Priority7 = 7, - Priority8 = 8, - Priority9 = 9, -} - -/// DMA Priorities -/// The values need to match the TRM -#[cfg(pdma)] -#[derive(Clone, Copy)] -pub enum DmaPriority { - Priority0 = 0, -} - -/// DMA capable peripherals -/// The values need to match the TRM -#[derive(Clone, Copy)] -pub enum DmaPeripheral { - Spi2 = 0, - #[cfg(any(pdma, esp32s3))] - Spi3 = 1, - #[cfg(any(esp32c3, esp32c6, esp32s3))] - Uhci0 = 2, - #[cfg(any(esp32, esp32s2, esp32c3, esp32c6, esp32s3))] - I2s0 = 3, - #[cfg(any(esp32, esp32s3))] - I2s1 = 4, - #[cfg(esp32s3)] - LcdCam = 5, - #[cfg(any(esp32c3, esp32c6, esp32s3))] - Aes = 6, - #[cfg(gdma)] - Sha = 7, - #[cfg(any(esp32c3, esp32c6, esp32s3))] - Adc = 8, - #[cfg(esp32s3)] - Rmt = 9, -} - -#[derive(PartialEq, PartialOrd)] -enum Owner { - Cpu = 0, - Dma = 1, -} - -impl From for Owner { - fn from(value: u32) -> Self { - match value { - 0 => Owner::Cpu, - _ => Owner::Dma, - } - } -} - -trait DmaLinkedListDw0 { - fn set_size(&mut self, len: u16); - fn get_size(&mut self) -> u16; - fn set_length(&mut self, len: u16); - fn get_length(&mut self) -> u16; - fn set_err_eof(&mut self, err_eof: bool); - #[cfg(not(esp32))] - fn get_err_eof(&mut self) -> bool; - fn set_suc_eof(&mut self, suc_eof: bool); - fn get_suc_eof(&mut self) -> bool; - fn set_owner(&mut self, owner: Owner); - fn get_owner(&mut self) -> Owner; -} - -impl DmaLinkedListDw0 for &mut u32 { - fn set_size(&mut self, len: u16) { - let mask = 0b111111111111; - let bit_s = 0; - **self = (**self & !(mask << bit_s)) | (len as u32) << bit_s; - } - - fn get_size(&mut self) -> u16 { - let mask = 0b111111111111; - let bit_s = 0; - ((**self & (mask << bit_s)) >> bit_s) as u16 - } - - fn set_length(&mut self, len: u16) { - let mask = 0b111111111111; - let bit_s = 12; - **self = (**self & !(mask << bit_s)) | (len as u32) << bit_s; - } - - fn get_length(&mut self) -> u16 { - let mask = 0b111111111111; - let bit_s = 12; - ((**self & (mask << bit_s)) >> bit_s) as u16 - } - - fn set_err_eof(&mut self, err_eof: bool) { - let mask = 0b1; - let bit_s = 28; - **self = (**self & !(mask << bit_s)) | (err_eof as u32) << bit_s; - } - - #[cfg(not(esp32))] - fn get_err_eof(&mut self) -> bool { - let mask = 0b1; - let bit_s = 28; - ((**self & (mask << bit_s)) >> bit_s) != 0 - } - - fn set_suc_eof(&mut self, suc_eof: bool) { - let mask = 0b1; - let bit_s = 30; - **self = (**self & !(mask << bit_s)) | (suc_eof as u32) << bit_s; - } - - fn get_suc_eof(&mut self) -> bool { - let mask = 0b1; - let bit_s = 30; - ((**self & (mask << bit_s)) >> bit_s) != 0 - } - - fn set_owner(&mut self, owner: Owner) { - let mask = 0b1; - let bit_s = 31; - **self = (**self & !(mask << bit_s)) | (owner as u32) << bit_s; - } - - fn get_owner(&mut self) -> Owner { - let mask = 0b1; - let bit_s = 31; - ((**self & (mask << bit_s)) >> bit_s).into() - } -} - -/// Marks channels as useable for SPI -pub trait SpiPeripheral: PeripheralMarker {} - -/// Marks channels as useable for SPI2 -pub trait Spi2Peripheral: SpiPeripheral + PeripheralMarker {} - -/// Marks channels as useable for SPI3 -#[cfg(any(esp32, esp32s2, esp32s3))] -pub trait Spi3Peripheral: SpiPeripheral + PeripheralMarker {} - -/// Marks channels as useable for I2S -pub trait I2sPeripheral: PeripheralMarker {} - -/// Marks channels as useable for I2S0 -pub trait I2s0Peripheral: I2sPeripheral + PeripheralMarker {} - -/// Marks channels as useable for I2S1 -pub trait I2s1Peripheral: I2sPeripheral + PeripheralMarker {} - -/// DMA Rx -pub trait Rx: RxPrivate {} - -/// DMA Tx -pub trait Tx: TxPrivate {} - -/// Marker trait -pub trait PeripheralMarker {} - -/// The functions here are not meant to be used outside the HAL -pub trait RxPrivate { - fn init(&mut self, burst_mode: bool, priority: DmaPriority); - - fn init_channel(&mut self); - - fn prepare_transfer( - &mut self, - circular: bool, - peri: DmaPeripheral, - data: *mut u8, - len: usize, - ) -> Result<(), DmaError>; - - fn is_done(&self) -> bool; - - fn is_listening_eof(&self) -> bool; - - fn listen_eof(&self); - - fn unlisten_eof(&self); - - fn available(&mut self) -> usize; - - fn pop(&mut self, data: &mut [u8]) -> Result; - - fn drain_buffer(&mut self, dst: &mut [u8]) -> Result; - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; -} - -pub trait RxChannel -where - R: RegisterAccess, -{ - fn init(&mut self, burst_mode: bool, priority: DmaPriority) { - R::set_in_burstmode(burst_mode); - R::set_in_priority(priority); - } - - fn prepare_transfer( - &mut self, - descriptors: &mut [u32], - circular: bool, - peri: DmaPeripheral, - data: *mut u8, - len: usize, - ) -> Result<(), DmaError> { - for descr in descriptors.iter_mut() { - *descr = 0; - } - - compiler_fence(core::sync::atomic::Ordering::SeqCst); - - let mut processed = 0; - let mut descr = 0; - loop { - let chunk_size = usize::min(CHUNK_SIZE, len - processed); - let last = processed + chunk_size >= len; - - descriptors[descr + 1] = data as u32 + processed as u32; - - let mut dw0 = &mut descriptors[descr]; - - dw0.set_suc_eof(false); - dw0.set_owner(Owner::Dma); - dw0.set_size(chunk_size as u16); // align to 32 bits? - dw0.set_length(0); // actual size of the data!? - - if !last { - descriptors[descr + 2] = (&descriptors[descr + 3]) as *const _ as *const () as u32; - } else { - descriptors[descr + 2] = if circular { - descriptors.as_ptr() as *const () as u32 - } else { - 0 - }; - } - - processed += chunk_size; - descr += 3; - - if processed >= len { - break; - } - } - - R::clear_in_interrupts(); - R::reset_in(); - R::set_in_descriptors(descriptors.as_ptr() as u32); - R::set_in_peripheral(peri as u8); - R::start_in(); - - if R::has_in_descriptor_error() { - return Err(DmaError::DescriptorError); - } - - Ok(()) - } - - fn is_done(&self) -> bool { - R::is_in_done() - } - - fn last_in_dscr_address(&self) -> usize { - R::last_in_dscr_address() - } - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; -} - -pub struct ChannelRx<'a, T, R> -where - T: RxChannel, - R: RegisterAccess, -{ - pub descriptors: &'a mut [u32], - pub burst_mode: bool, - pub rx_impl: T, - pub read_descr_ptr: *const u32, - pub available: usize, - pub last_seen_handled_descriptor_ptr: *const u32, - pub read_buffer_start: *const u8, - pub _phantom: PhantomData, -} - -impl<'a, T, R> Rx for ChannelRx<'a, T, R> -where - T: RxChannel, - R: RegisterAccess, -{ -} - -impl<'a, T, R> RxPrivate for ChannelRx<'a, T, R> -where - T: RxChannel, - R: RegisterAccess, -{ - fn init(&mut self, burst_mode: bool, priority: DmaPriority) { - self.rx_impl.init(burst_mode, priority); - } - - fn prepare_transfer( - &mut self, - circular: bool, - peri: DmaPeripheral, - data: *mut u8, - len: usize, - ) -> Result<(), DmaError> { - if self.descriptors.len() % 3 != 0 { - return Err(DmaError::InvalidDescriptorSize); - } - - if self.descriptors.len() / 3 < len / CHUNK_SIZE { - return Err(DmaError::OutOfDescriptors); - } - - if self.burst_mode && (len % 4 != 0 || data as u32 % 4 != 0) { - return Err(DmaError::InvalidAlignment); - } - - if circular && len < CHUNK_SIZE * 2 { - return Err(DmaError::BufferTooSmall); - } - - self.available = 0; - self.read_descr_ptr = self.descriptors.as_ptr() as *const u32; - self.last_seen_handled_descriptor_ptr = core::ptr::null(); - self.read_buffer_start = data; - - self.rx_impl - .prepare_transfer(self.descriptors, circular, peri, data, len)?; - Ok(()) - } - - fn is_done(&self) -> bool { - self.rx_impl.is_done() - } - - fn init_channel(&mut self) { - R::init_channel(); - } - - fn available(&mut self) -> usize { - if self.last_seen_handled_descriptor_ptr.is_null() { - self.last_seen_handled_descriptor_ptr = self.descriptors.as_mut_ptr(); - return 0; - } - - if self.available != 0 { - return self.available; - } - - let descr_address = self.last_seen_handled_descriptor_ptr as *mut u32; - let mut dw0 = unsafe { &mut descr_address.read_volatile() }; - - if dw0.get_owner() == Owner::Cpu && dw0.get_length() != 0 { - let descriptor_buffer = unsafe { descr_address.offset(1).read_volatile() } as *const u8; - let next_descriptor = unsafe { descr_address.offset(2).read_volatile() } as *const u32; - - self.read_buffer_start = descriptor_buffer; - self.available = dw0.get_length() as usize; - - dw0.set_owner(Owner::Dma); - dw0.set_length(0); - dw0.set_suc_eof(false); - - unsafe { - descr_address.write_volatile(*dw0); - } - - if !next_descriptor.is_null() { - self.last_seen_handled_descriptor_ptr = next_descriptor; - } else { - self.last_seen_handled_descriptor_ptr = self.descriptors.as_ptr(); - } - } - - self.available - } - - fn pop(&mut self, data: &mut [u8]) -> Result { - let avail = self.available; - - if avail < data.len() { - return Err(DmaError::Exhausted); - } - - unsafe { - let dst = data.as_mut_ptr(); - let src = self.read_buffer_start; - let count = self.available; - core::ptr::copy_nonoverlapping(src, dst, count); - } - - self.available = 0; - Ok(data.len()) - } - - fn drain_buffer(&mut self, dst: &mut [u8]) -> Result { - let mut len: usize = 0; - let mut dscr = self.descriptors.as_ptr() as *mut u32; - loop { - let mut dw0 = unsafe { &mut dscr.read_volatile() }; - let buffer_ptr = unsafe { dscr.offset(1).read_volatile() } as *const u8; - let next_dscr = unsafe { dscr.offset(2).read_volatile() } as *const u8; - let chunk_len = dw0.get_length() as usize; - unsafe { - core::ptr::copy_nonoverlapping( - buffer_ptr, - dst.as_mut_ptr().offset(len as isize), - chunk_len, - ) - }; - - len += chunk_len; - - if next_dscr.is_null() { - break; - } - - dscr = unsafe { dscr.offset(3) }; - } - - Ok(len) - } - - fn is_listening_eof(&self) -> bool { - R::is_listening_in_eof() - } - - fn listen_eof(&self) { - R::listen_in_eof() - } - - fn unlisten_eof(&self) { - R::unlisten_in_eof() - } - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - T::waker() - } -} - -/// The functions here are not meant to be used outside the HAL -pub trait TxPrivate { - fn init(&mut self, burst_mode: bool, priority: DmaPriority); - - fn init_channel(&mut self); - - fn prepare_transfer( - &mut self, - peri: DmaPeripheral, - circular: bool, - data: *const u8, - len: usize, - ) -> Result<(), DmaError>; - - fn is_done(&self) -> bool; - - fn is_listening_eof(&self) -> bool; - - fn listen_eof(&self); - - fn unlisten_eof(&self); - - fn available(&mut self) -> usize; - - fn push(&mut self, data: &[u8]) -> Result; - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; -} - -pub trait TxChannel -where - R: RegisterAccess, -{ - fn init(&mut self, burst_mode: bool, priority: DmaPriority) { - R::set_out_burstmode(burst_mode); - R::set_out_priority(priority); - } - - fn prepare_transfer( - &mut self, - descriptors: &mut [u32], - circular: bool, - peri: DmaPeripheral, - data: *const u8, - len: usize, - ) -> Result<(), DmaError> { - for descr in descriptors.iter_mut() { - *descr = 0; - } - - compiler_fence(core::sync::atomic::Ordering::SeqCst); - - let mut processed = 0; - let mut descr = 0; - loop { - let chunk_size = usize::min(CHUNK_SIZE, len - processed); - let last = processed + chunk_size >= len; - - descriptors[descr + 1] = data as u32 + processed as u32; - - let mut dw0 = &mut descriptors[descr]; - - dw0.set_suc_eof(circular || last); - dw0.set_owner(Owner::Dma); - dw0.set_size(chunk_size as u16); // align to 32 bits? - dw0.set_length(chunk_size as u16); // actual size of the data!? - - if !last { - descriptors[descr + 2] = (&descriptors[descr + 3]) as *const _ as *const () as u32; - } else { - if !circular { - descriptors[descr + 2] = 0; - } else { - descriptors[descr + 2] = descriptors.as_ptr() as u32; - } - } - - processed += chunk_size; - descr += 3; - - if processed >= len { - break; - } - } - - R::clear_out_interrupts(); - R::reset_out(); - R::set_out_descriptors(descriptors.as_ptr() as u32); - R::set_out_peripheral(peri as u8); - R::start_out(); - - if R::has_out_descriptor_error() { - return Err(DmaError::DescriptorError); - } - - Ok(()) - } - - fn is_done(&self) -> bool { - R::is_out_done() - } - - fn descriptors_handled(&self) -> bool { - R::is_out_eof_interrupt_set() - } - - fn reset_descriptors_handled(&self) { - R::reset_out_eof_interrupt(); - } - - fn last_out_dscr_address(&self) -> usize { - R::last_out_dscr_address() - } - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; -} - -pub struct ChannelTx<'a, T, R> -where - T: TxChannel, - R: RegisterAccess, -{ - pub descriptors: &'a mut [u32], - #[allow(unused)] - pub burst_mode: bool, - pub tx_impl: T, - pub write_offset: usize, - pub write_descr_ptr: *const u32, - pub available: usize, - pub last_seen_handled_descriptor_ptr: *const u32, - pub buffer_start: *const u8, - pub buffer_len: usize, - pub _phantom: PhantomData, -} - -impl<'a, T, R> Tx for ChannelTx<'a, T, R> -where - T: TxChannel, - R: RegisterAccess, -{ -} - -impl<'a, T, R> TxPrivate for ChannelTx<'a, T, R> -where - T: TxChannel, - R: RegisterAccess, -{ - fn init(&mut self, burst_mode: bool, priority: DmaPriority) { - self.tx_impl.init(burst_mode, priority); - } - - fn init_channel(&mut self) { - R::init_channel(); - } - - fn prepare_transfer( - &mut self, - peri: DmaPeripheral, - circular: bool, - data: *const u8, - len: usize, - ) -> Result<(), DmaError> { - if self.descriptors.len() % 3 != 0 { - return Err(DmaError::InvalidDescriptorSize); - } - - if self.descriptors.len() / 3 < len / CHUNK_SIZE { - return Err(DmaError::OutOfDescriptors); - } - - if circular && len < CHUNK_SIZE * 2 { - return Err(DmaError::BufferTooSmall); - } - - self.write_offset = 0; - self.available = 0; - self.write_descr_ptr = self.descriptors.as_ptr() as *const u32; - self.last_seen_handled_descriptor_ptr = self.descriptors.as_ptr() as *const u32; - self.buffer_start = data; - self.buffer_len = len; - - self.tx_impl - .prepare_transfer(self.descriptors, circular, peri, data, len)?; - - Ok(()) - } - - fn is_done(&self) -> bool { - self.tx_impl.is_done() - } - - fn available(&mut self) -> usize { - if self.tx_impl.descriptors_handled() { - self.tx_impl.reset_descriptors_handled(); - let descr_address = self.tx_impl.last_out_dscr_address() as *const u32; - - if descr_address >= self.last_seen_handled_descriptor_ptr { - let mut ptr = self.last_seen_handled_descriptor_ptr as *const u32; - - unsafe { - while ptr < descr_address as *const u32 { - let mut dw0 = &mut ptr.read_volatile(); - self.available += dw0.get_length() as usize; - ptr = ptr.offset(3); - } - } - } else { - let mut ptr = self.last_seen_handled_descriptor_ptr as *const u32; - - unsafe { - loop { - if ptr.offset(2).read_volatile() == 0 { - break; - } - - let mut dw0 = &mut ptr.read_volatile(); - self.available += dw0.get_length() as usize; - ptr = ptr.offset(3); - } - } - } - - if self.available >= self.buffer_len { - unsafe { - let segment_len = - (&mut self.write_descr_ptr.read_volatile()).get_length() as usize; - self.available -= segment_len; - self.write_offset = (self.write_offset + segment_len) % self.buffer_len; - let next_descriptor = - self.write_descr_ptr.offset(2).read_volatile() as *const u32; - self.write_descr_ptr = if next_descriptor.is_null() { - self.descriptors.as_ptr() as *const u32 - } else { - next_descriptor - } - } - } - - self.last_seen_handled_descriptor_ptr = descr_address; - } - - self.available - } - - fn push(&mut self, data: &[u8]) -> Result { - let avail = self.available(); - - if avail < data.len() { - return Err(DmaError::Overflow); - } - - unsafe { - let src = data.as_ptr(); - let dst = self.buffer_start.offset(self.write_offset as isize) as *mut u8; - let count = usize::min(data.len(), self.buffer_len - self.write_offset); - core::ptr::copy_nonoverlapping(src, dst, count); - } - - if self.write_offset + data.len() >= self.buffer_len { - let remainder = (self.write_offset + data.len()) % self.buffer_len; - let dst = self.buffer_start as *mut u8; - unsafe { - let src = data.as_ptr().offset((data.len() - remainder) as isize); - core::ptr::copy_nonoverlapping(src, dst, remainder); - } - } - - let mut forward = data.len(); - loop { - unsafe { - let next_descriptor = self.write_descr_ptr.offset(2).read_volatile() as *const u32; - let segment_len = (&mut self.write_descr_ptr.read_volatile()).get_length() as usize; - self.write_descr_ptr = if next_descriptor.is_null() { - self.descriptors.as_ptr() as *const u32 - } else { - next_descriptor - }; - - if forward <= segment_len { - break; - } - - forward -= segment_len; - - if forward == 0 { - break; - } - } - } - - self.write_offset = (self.write_offset + data.len()) % self.buffer_len; - self.available -= data.len(); - - Ok(data.len()) - } - - fn is_listening_eof(&self) -> bool { - R::is_listening_out_eof() - } - - fn listen_eof(&self) { - R::listen_out_eof() - } - - fn unlisten_eof(&self) { - R::unlisten_out_eof() - } - - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - T::waker() - } -} - -pub trait RegisterAccess { - fn init_channel(); - fn set_out_burstmode(burst_mode: bool); - fn set_out_priority(priority: DmaPriority); - fn clear_out_interrupts(); - fn reset_out(); - fn set_out_descriptors(address: u32); - fn has_out_descriptor_error() -> bool; - fn set_out_peripheral(peripheral: u8); - fn start_out(); - fn is_out_done() -> bool; - fn is_out_eof_interrupt_set() -> bool; - fn reset_out_eof_interrupt(); - fn last_out_dscr_address() -> usize; - - fn set_in_burstmode(burst_mode: bool); - fn set_in_priority(priority: DmaPriority); - fn clear_in_interrupts(); - fn reset_in(); - fn set_in_descriptors(address: u32); - fn has_in_descriptor_error() -> bool; - fn set_in_peripheral(peripheral: u8); - fn start_in(); - fn is_in_done() -> bool; - fn last_in_dscr_address() -> usize; - - fn is_listening_in_eof() -> bool; - fn is_listening_out_eof() -> bool; - - fn listen_in_eof(); - fn listen_out_eof(); - fn unlisten_in_eof(); - fn unlisten_out_eof(); -} -/// DMA Channel -pub struct Channel -where - TX: Tx, - RX: Rx, - P: PeripheralMarker, -{ - pub(crate) tx: TX, - pub(crate) rx: RX, - _phantom: PhantomData

    , -} - -/// Trait to be implemented for an in progress dma transfer. -#[allow(drop_bounds)] -pub trait DmaTransfer: Drop { - /// Wait for the transfer to finish. - fn wait(self) -> (B, T); -} - -/// Trait to be implemented for an in progress dma transfer. -#[allow(drop_bounds)] -pub trait DmaTransferRxTx: Drop { - /// Wait for the transfer to finish. - fn wait(self) -> (BR, BT, T); -} - -#[cfg(feature = "async")] -pub(crate) mod asynch { - use core::task::Poll; - - use super::*; - use crate::macros::interrupt; - - pub struct DmaTxFuture<'a, TX> { - pub(crate) tx: &'a mut TX, - _a: (), - } - - impl<'a, TX> DmaTxFuture<'a, TX> - where - TX: Tx, - { - pub fn new(tx: &'a mut TX) -> Self { - tx.listen_eof(); - Self { tx, _a: () } - } - } - - impl<'a, TX> core::future::Future for DmaTxFuture<'a, TX> - where - TX: Tx, - { - type Output = (); // TODO handle DMA errors - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll { - TX::waker().register(cx.waker()); - if self.tx.is_listening_eof() { - Poll::Pending - } else { - Poll::Ready(()) - } - } - } - - pub struct DmaRxFuture<'a, RX> { - pub(crate) rx: &'a mut RX, - _a: (), - } - - impl<'a, RX> DmaRxFuture<'a, RX> - where - RX: Rx, - { - pub fn new(rx: &'a mut RX) -> Self { - rx.listen_eof(); - Self { rx, _a: () } - } - } - - impl<'a, RX> core::future::Future for DmaRxFuture<'a, RX> - where - RX: Rx, - { - type Output = (); // TODO handle DMA errors - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll { - RX::waker().register(cx.waker()); - if self.rx.is_listening_eof() { - Poll::Pending - } else { - Poll::Ready(()) - } - } - } - - #[cfg(esp32c2)] - mod interrupt { - use super::*; - - #[interrupt] - fn DMA_CH0() { - use crate::dma::gdma::{ - Channel0 as Channel, - Channel0RxImpl as ChannelRxImpl, - Channel0TxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - } - - #[cfg(esp32c3)] - mod interrupt { - use super::*; - - #[interrupt] - fn DMA_CH0() { - use crate::dma::gdma::{ - Channel0 as Channel, - Channel0RxImpl as ChannelRxImpl, - Channel0TxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_CH1() { - use crate::dma::gdma::{ - Channel1 as Channel, - Channel1RxImpl as ChannelRxImpl, - Channel1TxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_CH2() { - use crate::dma::gdma::{ - Channel2 as Channel, - Channel2RxImpl as ChannelRxImpl, - Channel2TxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - } - - #[cfg(esp32c6)] - mod interrupt { - use super::*; - - #[interrupt] - fn DMA_IN_CH0() { - use crate::dma::gdma::{Channel0 as Channel, Channel0RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH0() { - use crate::dma::gdma::{Channel0 as Channel, Channel0TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_IN_CH1() { - use crate::dma::gdma::{Channel1 as Channel, Channel1RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH1() { - use crate::dma::gdma::{Channel1 as Channel, Channel1TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_IN_CH2() { - use crate::dma::gdma::{Channel2 as Channel, Channel2RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH2() { - use crate::dma::gdma::{Channel2 as Channel, Channel2TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - } - - #[cfg(esp32s3)] - mod interrupt { - use super::*; - - #[interrupt] - fn DMA_IN_CH0() { - use crate::dma::gdma::{Channel0 as Channel, Channel0RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH0() { - use crate::dma::gdma::{Channel0 as Channel, Channel0TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_IN_CH1() { - use crate::dma::gdma::{Channel1 as Channel, Channel1RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH1() { - use crate::dma::gdma::{Channel1 as Channel, Channel1TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_IN_CH3() { - use crate::dma::gdma::{Channel3 as Channel, Channel3RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH3() { - use crate::dma::gdma::{Channel3 as Channel, Channel3TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_IN_CH4() { - use crate::dma::gdma::{Channel4 as Channel, Channel4RxImpl as ChannelRxImpl}; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - } - - #[interrupt] - fn DMA_OUT_CH4() { - use crate::dma::gdma::{Channel4 as Channel, Channel4TxImpl as ChannelTxImpl}; - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - } - - #[cfg(any(esp32, esp32s2))] - mod interrupt { - use super::*; - - #[interrupt] - fn SPI2_DMA() { - use crate::dma::pdma::{ - Spi2DmaChannel as Channel, - Spi2DmaChannelRxImpl as ChannelRxImpl, - Spi2DmaChannelTxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - - #[interrupt] - fn SPI3_DMA() { - use crate::dma::pdma::{ - Spi3DmaChannel as Channel, - Spi3DmaChannelRxImpl as ChannelRxImpl, - Spi3DmaChannelTxImpl as ChannelTxImpl, - }; - - if Channel::is_in_done() { - Channel::clear_in_interrupts(); - Channel::unlisten_in_eof(); - ChannelRxImpl::waker().wake() - } - - if Channel::is_out_done() { - Channel::clear_out_interrupts(); - Channel::unlisten_out_eof(); - ChannelTxImpl::waker().wake() - } - } - } -} diff --git a/esp-hal-common/src/dma/pdma.rs b/esp-hal-common/src/dma/pdma.rs deleted file mode 100644 index 1dcb87a778d..00000000000 --- a/esp-hal-common/src/dma/pdma.rs +++ /dev/null @@ -1,571 +0,0 @@ -//! Direct Memory Access - -use crate::{ - dma::*, - peripheral::PeripheralRef, - system::{Peripheral, PeripheralClockControl}, -}; - -macro_rules! ImplSpiChannel { - ($num: literal) => { - paste::paste! { - pub struct [] {} - - impl RegisterAccess for [] { - fn init_channel() { - // (only) on ESP32 we need to configure DPORT for the SPI DMA channels - #[cfg(esp32)] - { - let dport = unsafe { &*crate::peripherals::DPORT::PTR }; - - match $num { - 2 => { - dport - .spi_dma_chan_sel - .modify(|_, w| w.spi2_dma_chan_sel().variant(1)); - }, - 3 => { - dport - .spi_dma_chan_sel - .modify(|_, w| w.spi3_dma_chan_sel().variant(2)); - }, - _ => panic!("Only SPI2 and SPI3 supported"), - } - } - } - - fn set_out_burstmode(burst_mode: bool) { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_conf - .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); - } - - fn set_out_priority(_priority: DmaPriority) {} - - fn clear_out_interrupts() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_clr.write(|w| { - w.out_done_int_clr() - .set_bit() - .out_eof_int_clr() - .set_bit() - .out_total_eof_int_clr() - .set_bit() - .outlink_dscr_error_int_clr() - .set_bit() - }); - } - - fn reset_out() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_conf.modify(|_, w| w.out_rst().set_bit()); - spi.dma_conf.modify(|_, w| w.out_rst().clear_bit()); - } - - fn set_out_descriptors(address: u32) { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_out_link - .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); - } - - fn has_out_descriptor_error() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_raw.read().outlink_dscr_error_int_raw().bit() - } - - fn set_out_peripheral(_peripheral: u8) { - // no-op - } - - fn start_out() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_out_link.modify(|_, w| w.outlink_start().set_bit()); - } - - fn is_out_done() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - // FIXME this should be out_total_eof_int_raw? but on esp32 this interrupt doesn't seem to fire - spi.dma_int_raw.read().out_eof_int_raw().bit() - } - - fn last_out_dscr_address() -> usize { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.out_eof_des_addr.read().dma_out_eof_des_addr().bits() as usize - } - - fn is_out_eof_interrupt_set() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_raw.read().out_eof_int_raw().bit() - } - - fn reset_out_eof_interrupt() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_clr.write(|w| { - w.out_eof_int_clr() - .set_bit() - }); - } - - fn set_in_burstmode(burst_mode: bool) { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_conf - .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); - } - - fn set_in_priority(_priority: DmaPriority) {} - - fn clear_in_interrupts() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_clr.write(|w| { - w.in_done_int_clr() - .set_bit() - .in_err_eof_int_clr() - .set_bit() - .in_suc_eof_int_clr() - .set_bit() - .inlink_dscr_error_int_clr() - .set_bit() - }); - } - - fn reset_in() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_conf.modify(|_, w| w.in_rst().set_bit()); - spi.dma_conf.modify(|_, w| w.in_rst().clear_bit()); - } - - fn set_in_descriptors(address: u32) { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_in_link - .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); - } - - fn has_in_descriptor_error() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_raw.read().inlink_dscr_error_int_raw().bit() - } - - fn set_in_peripheral(_peripheral: u8) { - // no-op - } - - fn start_in() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_in_link.modify(|_, w| w.inlink_start().set_bit()); - } - - fn is_in_done() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_raw.read().in_done_int_raw().bit() - } - - fn last_in_dscr_address() -> usize { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.inlink_dscr_bf0.read().dma_inlink_dscr_bf0().bits() as usize - } - - fn is_listening_in_eof() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.read().in_suc_eof_int_ena().bit_is_set() - } - - fn is_listening_out_eof() -> bool { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.read().out_total_eof_int_ena().bit_is_set() - } - - fn listen_in_eof() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.modify(|_, w| w.in_suc_eof_int_ena().set_bit()); - } - - fn listen_out_eof() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.modify(|_, w| w.out_total_eof_int_ena().set_bit()); - } - - fn unlisten_in_eof() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.modify(|_, w| w.in_suc_eof_int_ena().clear_bit()); - } - - fn unlisten_out_eof() { - let spi = unsafe { &*crate::peripherals::[]::PTR }; - spi.dma_int_ena.modify(|_, w| w.out_total_eof_int_ena().clear_bit()); - } - } - - pub struct [] {} - - impl<'a> TxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl<'a> RxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl [] { - /// Configure the channel for use - /// - /// Descriptors should be sized as (BUFFERSIZE / 4092) * 3 - pub fn configure<'a>( - self, - burst_mode: bool, - tx_descriptors: &'a mut [u32], - rx_descriptors: &'a mut [u32], - priority: DmaPriority, - ) -> Channel< - ChannelTx<'a,[], []>, - ChannelRx<'a,[], []>, - [], - > { - let mut tx_impl = [] {}; - tx_impl.init(burst_mode, priority); - - let tx_channel = ChannelTx { - descriptors: tx_descriptors, - burst_mode, - tx_impl: tx_impl, - write_offset: 0, - write_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - buffer_start: core::ptr::null(), - buffer_len: 0, - _phantom: PhantomData::default(), - }; - - let mut rx_impl = [] {}; - rx_impl.init(burst_mode, priority); - - let rx_channel = ChannelRx { - descriptors: rx_descriptors, - burst_mode, - rx_impl: rx_impl, - read_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - read_buffer_start: core::ptr::null(), - _phantom: PhantomData::default(), - }; - - Channel { - tx: tx_channel, - rx: rx_channel, - _phantom: PhantomData::default(), - } - } - } - } - }; -} - -macro_rules! ImplI2sChannel { - ($num: literal, $peripheral: literal) => { - paste::paste! { - pub struct [] {} - - impl RegisterAccess for [] { - fn init_channel() { - // nothing to do - } - - fn set_out_burstmode(burst_mode: bool) { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.lc_conf - .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); - } - - fn set_out_priority(_priority: DmaPriority) {} - - fn clear_out_interrupts() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_clr.write(|w| { - w.out_done_int_clr() - .set_bit() - .out_eof_int_clr() - .set_bit() - .out_total_eof_int_clr() - .set_bit() - .out_dscr_err_int_clr() - .set_bit() - }); - } - - fn reset_out() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.lc_conf.modify(|_, w| w.out_rst().set_bit()); - reg_block.lc_conf.modify(|_, w| w.out_rst().clear_bit()); - } - - fn set_out_descriptors(address: u32) { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.out_link - .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); - } - - fn has_out_descriptor_error() -> bool { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_raw.read().out_dscr_err_int_raw().bit() - } - - fn set_out_peripheral(_peripheral: u8) { - // no-op - } - - fn start_out() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.out_link.modify(|_, w| w.outlink_start().set_bit()); - } - - fn is_out_done() -> bool { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_raw.read().out_done_int_raw().bit() - } - - fn last_out_dscr_address() -> usize { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.out_eof_des_addr.read().out_eof_des_addr().bits() as usize - } - - fn is_out_eof_interrupt_set() -> bool { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_raw.read().out_eof_int_raw().bit() - } - - fn reset_out_eof_interrupt() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_clr.write(|w| { - w.out_eof_int_clr() - .set_bit() - }); - } - - fn set_in_burstmode(burst_mode: bool) { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.lc_conf - .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); - } - - fn set_in_priority(_priority: DmaPriority) {} - - fn clear_in_interrupts() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_clr.write(|w| { - w.in_done_int_clr() - .set_bit() - .in_err_eof_int_clr() - .set_bit() - .in_suc_eof_int_clr() - .set_bit() - .in_dscr_err_int_clr() - .set_bit() - }); - } - - fn reset_in() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.lc_conf.modify(|_, w| w.in_rst().set_bit()); - reg_block.lc_conf.modify(|_, w| w.in_rst().clear_bit()); - } - - fn set_in_descriptors(address: u32) { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.in_link - .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); - } - - fn has_in_descriptor_error() -> bool { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_raw.read().in_dscr_err_int_raw().bit() - } - - fn set_in_peripheral(_peripheral: u8) { - // no-op - } - - fn start_in() { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.in_link.modify(|_, w| w.inlink_start().set_bit()); - } - - fn is_in_done() -> bool { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.int_raw.read().in_done_int_raw().bit() - } - - fn last_in_dscr_address() -> usize { - let reg_block = unsafe { &*crate::peripherals::[<$peripheral>]::PTR }; - reg_block.inlink_dscr_bf0.read().inlink_dscr_bf0().bits() as usize - } - - fn is_listening_in_eof() -> bool { - todo!() - } - fn is_listening_out_eof() -> bool { - todo!() - } - - fn listen_in_eof() { - todo!() - } - fn listen_out_eof() { - todo!() - } - fn unlisten_in_eof() { - todo!() - } - fn unlisten_out_eof() { - todo!() - } - } - - pub struct [] {} - - impl<'a> TxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl<'a> RxChannel<[]> for [] { - #[cfg(feature = "async")] - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker { - static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new(); - &WAKER - } - } - - pub struct [] {} - - impl [] { - /// Configure the channel for use - /// - /// Descriptors should be sized as (BUFFERSIZE / 4092) * 3 - pub fn configure<'a>( - self, - burst_mode: bool, - tx_descriptors: &'a mut [u32], - rx_descriptors: &'a mut [u32], - priority: DmaPriority, - ) -> Channel< - ChannelTx<'a,[], []>, - ChannelRx<'a,[], []>, - [], - > { - let mut tx_impl = [] {}; - tx_impl.init(burst_mode, priority); - - let tx_channel = ChannelTx { - descriptors: tx_descriptors, - burst_mode, - tx_impl: tx_impl, - write_offset: 0, - write_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - buffer_start: core::ptr::null(), - buffer_len: 0, - _phantom: PhantomData::default(), - }; - - let mut rx_impl = [] {}; - rx_impl.init(burst_mode, priority); - - let rx_channel = ChannelRx { - descriptors: rx_descriptors, - burst_mode, - rx_impl: rx_impl, - read_descr_ptr: core::ptr::null(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null(), - read_buffer_start: core::ptr::null(), - _phantom: PhantomData::default(), - }; - - Channel { - tx: tx_channel, - rx: rx_channel, - _phantom: PhantomData::default(), - } - } - } - } - }; -} - -pub struct Spi2DmaSuitablePeripheral {} -impl PeripheralMarker for Spi2DmaSuitablePeripheral {} -impl SpiPeripheral for Spi2DmaSuitablePeripheral {} -impl Spi2Peripheral for Spi2DmaSuitablePeripheral {} - -pub struct Spi3DmaSuitablePeripheral {} -impl PeripheralMarker for Spi3DmaSuitablePeripheral {} -impl SpiPeripheral for Spi3DmaSuitablePeripheral {} -impl Spi3Peripheral for Spi3DmaSuitablePeripheral {} - -ImplSpiChannel!(2); -ImplSpiChannel!(3); - -pub struct I2s0DmaSuitablePeripheral {} -impl PeripheralMarker for I2s0DmaSuitablePeripheral {} -impl I2sPeripheral for I2s0DmaSuitablePeripheral {} -impl I2s0Peripheral for I2s0DmaSuitablePeripheral {} - -ImplI2sChannel!(0, "I2S0"); - -pub struct I2s1DmaSuitablePeripheral {} -impl PeripheralMarker for I2s1DmaSuitablePeripheral {} -impl I2sPeripheral for I2s1DmaSuitablePeripheral {} -impl I2s1Peripheral for I2s1DmaSuitablePeripheral {} - -#[cfg(esp32)] -ImplI2sChannel!(1, "I2S1"); - -/// DMA Peripheral -/// -/// This offers the available DMA channels. -pub struct Dma<'d> { - _inner: PeripheralRef<'d, crate::system::Dma>, - pub spi2channel: Spi2DmaChannelCreator, - pub spi3channel: Spi3DmaChannelCreator, - pub i2s0channel: I2s0DmaChannelCreator, - #[cfg(esp32)] - pub i2s1channel: I2s1DmaChannelCreator, -} - -impl<'d> Dma<'d> { - /// Create a DMA instance. - pub fn new( - dma: impl crate::peripheral::Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Dma<'d> { - peripheral_clock_control.enable(Peripheral::Dma); - - Dma { - _inner: dma.into_ref(), - spi2channel: Spi2DmaChannelCreator {}, - spi3channel: Spi3DmaChannelCreator {}, - i2s0channel: I2s0DmaChannelCreator {}, - #[cfg(esp32)] - i2s1channel: I2s1DmaChannelCreator {}, - } - } -} diff --git a/esp-hal-common/src/embassy/mod.rs b/esp-hal-common/src/embassy/mod.rs deleted file mode 100644 index d036e4db1df..00000000000 --- a/esp-hal-common/src/embassy/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -use core::{cell::Cell, ptr}; - -use embassy_time::driver::{AlarmHandle, Driver}; - -#[cfg_attr( - all(systimer, feature = "embassy-time-systick",), - path = "time_driver_systimer.rs" -)] -#[cfg_attr( - all(timg0, feature = "embassy-time-timg0"), - path = "time_driver_timg.rs" -)] -mod time_driver; - -use time_driver::EmbassyTimer; - -use crate::clock::Clocks; - -pub fn init(clocks: &Clocks, td: time_driver::TimerType) { - EmbassyTimer::init(clocks, td) -} - -pub struct AlarmState { - pub timestamp: Cell, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - pub callback: Cell<*const ()>, - pub ctx: Cell<*mut ()>, - pub allocated: Cell, -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - pub const fn new() -> Self { - Self { - timestamp: Cell::new(u64::MAX), - callback: Cell::new(ptr::null()), - ctx: Cell::new(ptr::null_mut()), - allocated: Cell::new(false), - } - } -} - -impl Driver for EmbassyTimer { - fn now(&self) -> u64 { - EmbassyTimer::now() - } - - unsafe fn allocate_alarm(&self) -> Option { - return critical_section::with(|cs| { - let alarms = self.alarms.borrow(cs); - for i in 0..time_driver::ALARM_COUNT { - let c = alarms.get_unchecked(i); - if !c.allocated.get() { - // set alarm so it is not overwritten - c.allocated.set(true); - return Option::Some(AlarmHandle::new(i as u8)); - } - } - return Option::None; - }); - } - - fn set_alarm_callback( - &self, - alarm: embassy_time::driver::AlarmHandle, - callback: fn(*mut ()), - ctx: *mut (), - ) { - critical_section::with(|cs| { - let alarm = unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }; - alarm.callback.set(callback as *const ()); - alarm.ctx.set(ctx); - }) - } - - fn set_alarm(&self, alarm: embassy_time::driver::AlarmHandle, timestamp: u64) -> bool { - self.set_alarm(alarm, timestamp) - } -} diff --git a/esp-hal-common/src/embassy/time_driver_systimer.rs b/esp-hal-common/src/embassy/time_driver_systimer.rs deleted file mode 100644 index 3334d2e9bad..00000000000 --- a/esp-hal-common/src/embassy/time_driver_systimer.rs +++ /dev/null @@ -1,122 +0,0 @@ -use critical_section::{CriticalSection, Mutex}; - -use super::AlarmState; -use crate::{ - clock::Clocks, - peripherals, - systimer::{Alarm, SystemTimer, Target}, -}; - -pub const ALARM_COUNT: usize = 3; - -pub type TimerType = SystemTimer<'static>; - -pub struct EmbassyTimer { - pub(crate) alarms: Mutex<[AlarmState; ALARM_COUNT]>, - pub(crate) alarm0: Alarm, - pub(crate) alarm1: Alarm, - pub(crate) alarm2: Alarm, -} - -const ALARM_STATE_NONE: AlarmState = AlarmState::new(); - -embassy_time::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer { - alarms: Mutex::new([ALARM_STATE_NONE; ALARM_COUNT]), - alarm0: unsafe { Alarm::<_, 0>::conjure() }, - alarm1: unsafe { Alarm::<_, 1>::conjure() }, - alarm2: unsafe { Alarm::<_, 2>::conjure() }, -}); - -impl EmbassyTimer { - pub(crate) fn now() -> u64 { - SystemTimer::now() - } - - pub(crate) fn trigger_alarm(&self, n: usize, cs: CriticalSection) { - let alarm = &self.alarms.borrow(cs)[n]; - // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the - // safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { core::mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); - } - - fn on_interrupt(&self, id: u8) { - match id { - 0 => self.alarm0.clear_interrupt(), - 1 => self.alarm1.clear_interrupt(), - 2 => self.alarm2.clear_interrupt(), - _ => unreachable!(), - }; - critical_section::with(|cs| { - self.trigger_alarm(id as usize, cs); - }) - } - - pub fn init(_clocks: &Clocks, _systimer: TimerType) { - use crate::{interrupt, interrupt::Priority, macros::interrupt}; - - interrupt::enable(peripherals::Interrupt::SYSTIMER_TARGET0, Priority::max()).unwrap(); - interrupt::enable(peripherals::Interrupt::SYSTIMER_TARGET1, Priority::max()).unwrap(); - interrupt::enable(peripherals::Interrupt::SYSTIMER_TARGET2, Priority::max()).unwrap(); - - #[interrupt] - fn SYSTIMER_TARGET0() { - DRIVER.on_interrupt(0); - } - #[interrupt] - fn SYSTIMER_TARGET1() { - DRIVER.on_interrupt(1); - } - #[interrupt] - fn SYSTIMER_TARGET2() { - DRIVER.on_interrupt(2); - } - } - - pub(crate) fn set_alarm( - &self, - alarm: embassy_time::driver::AlarmHandle, - timestamp: u64, - ) -> bool { - critical_section::with(|cs| { - let now = Self::now(); - let alarm_state = unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }; - if timestamp < now { - // If alarm timestamp has passed the alarm will not fire. - // Disarm the alarm and return `false` to indicate that. - self.disable_interrupt(alarm.id()); - alarm_state.timestamp.set(u64::MAX); - return false; - } - alarm_state.timestamp.set(timestamp); - match alarm.id() { - 0 => { - self.alarm0.set_target(timestamp); - self.alarm0.interrupt_enable(true); - } - 1 => { - self.alarm1.set_target(timestamp); - self.alarm1.interrupt_enable(true); - } - 2 => { - self.alarm2.set_target(timestamp); - self.alarm2.interrupt_enable(true); - } - _ => panic!(), - } - - true - }) - } - - fn disable_interrupt(&self, id: u8) { - match id { - 0 => self.alarm0.interrupt_enable(false), - 1 => self.alarm1.interrupt_enable(false), - 2 => self.alarm2.interrupt_enable(false), - _ => unreachable!(), - }; - } -} diff --git a/esp-hal-common/src/embassy/time_driver_timg.rs b/esp-hal-common/src/embassy/time_driver_timg.rs deleted file mode 100644 index 584831adfad..00000000000 --- a/esp-hal-common/src/embassy/time_driver_timg.rs +++ /dev/null @@ -1,98 +0,0 @@ -use core::cell::RefCell; - -use critical_section::{CriticalSection, Mutex}; -use peripherals::TIMG0; - -use super::AlarmState; -use crate::{ - clock::Clocks, - peripherals, - prelude::*, - timer::{Timer, Timer0}, -}; - -pub const ALARM_COUNT: usize = 1; - -pub type TimerType = Timer>; - -pub struct EmbassyTimer { - pub(crate) alarms: Mutex<[AlarmState; ALARM_COUNT]>, - pub(crate) timer: Mutex>>, -} - -const ALARM_STATE_NONE: AlarmState = AlarmState::new(); - -embassy_time::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer { - alarms: Mutex::new([ALARM_STATE_NONE; ALARM_COUNT]), - timer: Mutex::new(RefCell::new(None)), -}); - -impl EmbassyTimer { - pub(crate) fn now() -> u64 { - critical_section::with(|cs| DRIVER.timer.borrow_ref(cs).as_ref().unwrap().now()) - } - - pub(crate) fn trigger_alarm(&self, n: usize, cs: CriticalSection) { - let alarm = &self.alarms.borrow(cs)[n]; - // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the - // safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { core::mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); - } - - fn on_interrupt(&self, id: u8) { - critical_section::with(|cs| { - let mut tg = self.timer.borrow_ref_mut(cs); - let tg = tg.as_mut().unwrap(); - tg.clear_interrupt(); - self.trigger_alarm(id as usize, cs); - }); - } - - pub fn init(clocks: &Clocks, mut timer: TimerType) { - use crate::{interrupt, interrupt::Priority}; - - // set divider to get a 1mhz clock. abp (80mhz) / 80 = 1mhz... // TODO assert - // abp clock is the source and its at the correct speed for the divider - timer.set_divider(clocks.apb_clock.to_MHz() as u16); - - critical_section::with(|cs| DRIVER.timer.borrow_ref_mut(cs).replace(timer)); - - interrupt::enable(peripherals::Interrupt::TG0_T0_LEVEL, Priority::max()).unwrap(); - - #[interrupt] - fn TG0_T0_LEVEL() { - DRIVER.on_interrupt(0); - } - } - - pub(crate) fn set_alarm( - &self, - alarm: embassy_time::driver::AlarmHandle, - timestamp: u64, - ) -> bool { - critical_section::with(|cs| { - let now = Self::now(); - let alarm_state = unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }; - let mut tg = self.timer.borrow_ref_mut(cs); - let tg = tg.as_mut().unwrap(); - if timestamp < now { - tg.unlisten(); - alarm_state.timestamp.set(u64::MAX); - return false; - } - alarm_state.timestamp.set(timestamp); - - tg.load_alarm_value(timestamp); - tg.listen(); - tg.set_counter_decrementing(false); - tg.set_auto_reload(false); - tg.set_counter_active(true); - tg.set_alarm_active(true); - - true - }) - } -} diff --git a/esp-hal-common/src/gpio.rs b/esp-hal-common/src/gpio.rs deleted file mode 100644 index 85dfb643dc0..00000000000 --- a/esp-hal-common/src/gpio.rs +++ /dev/null @@ -1,1946 +0,0 @@ -//! General Purpose I/Os -//! -//! To get access to the pins, you first need to convert them into a HAL -//! designed struct from the pac struct `GPIO` and `IO_MUX` using `IO::new`. -//! -//! ```no_run -//! let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); -//! let mut led = io.pins.gpio5.into_push_pull_output(); -//! ``` - -use core::{convert::Infallible, marker::PhantomData}; - -use crate::peripherals::{GPIO, IO_MUX}; -pub use crate::soc::gpio::*; -pub(crate) use crate::{analog, gpio}; - -/// Convenience type-alias for a no-pin / don't care - pin -pub type NoPinType = Gpio0; - -/// Convenience constant for `Option::None` pin -pub const NO_PIN: Option = None; - -#[derive(Copy, Clone)] -pub enum Event { - RisingEdge = 1, - FallingEdge = 2, - AnyEdge = 3, - LowLevel = 4, - HighLevel = 5, -} - -pub struct Unknown {} - -pub struct Input { - _mode: PhantomData, -} - -pub struct RTCInput { - _mode: PhantomData, -} - -pub struct Floating; - -pub struct PullDown; - -pub struct PullUp; - -pub struct Output { - _mode: PhantomData, -} - -pub struct RTCOutput { - _mode: PhantomData, -} - -pub struct OpenDrain; - -pub struct PushPull; - -pub struct Analog; - -pub struct Alternate { - _mode: PhantomData, -} - -#[doc(hidden)] -pub struct AF0; - -#[doc(hidden)] -pub struct AF1; - -#[doc(hidden)] -pub struct AF2; - -pub enum DriveStrength { - I5mA = 0, - I10mA = 1, - I20mA = 2, - I40mA = 3, -} - -#[derive(PartialEq)] -pub enum AlternateFunction { - Function0 = 0, - Function1 = 1, - Function2 = 2, - Function3 = 3, - Function4 = 4, - Function5 = 5, -} - -pub trait RTCPin {} - -pub trait AnalogPin {} - -pub trait Pin { - fn number(&self) -> u8; - - fn sleep_mode(&mut self, on: bool) -> &mut Self; - - fn set_alternate_function(&mut self, alternate: AlternateFunction) -> &mut Self; - - fn listen(&mut self, event: Event) { - self.listen_with_options(event, true, false, false) - } - - fn is_listening(&self) -> bool; - - fn listen_with_options( - &mut self, - event: Event, - int_enable: bool, - nmi_enable: bool, - wake_up_from_light_sleep: bool, - ); - - fn unlisten(&mut self); - - fn clear_interrupt(&mut self); - - fn is_pcore_interrupt_set(&self) -> bool; - - fn is_pcore_non_maskable_interrupt_set(&self) -> bool; - - fn is_acore_interrupt_set(&self) -> bool; - - fn is_acore_non_maskable_interrupt_set(&self) -> bool; - - fn enable_hold(&mut self, on: bool); -} - -pub trait InputPin: Pin { - fn set_to_input(&mut self) -> &mut Self; - - fn enable_input(&mut self, on: bool) -> &mut Self; - - fn enable_input_in_sleep_mode(&mut self, on: bool) -> &mut Self; - - fn is_input_high(&self) -> bool; - - fn connect_input_to_peripheral(&mut self, signal: InputSignal) -> &mut Self { - self.connect_input_to_peripheral_with_options(signal, false, false) - } - - fn connect_input_to_peripheral_with_options( - &mut self, - signal: InputSignal, - invert: bool, - force_via_gpio_mux: bool, - ) -> &mut Self; - - /// Remove a connected `signal` from this input pin. - /// - /// Clears the entry in the GPIO matrix / IO mux that associates this input - /// pin with the given [input `signal`](`InputSignal`). Any other - /// connected signals remain intact. - fn disconnect_input_from_peripheral(&mut self, signal: InputSignal) -> &mut Self; -} - -pub trait OutputPin: Pin { - fn set_to_open_drain_output(&mut self) -> &mut Self; - - fn set_to_push_pull_output(&mut self) -> &mut Self; - - fn enable_output(&mut self, on: bool) -> &mut Self; - - fn set_output_high(&mut self, on: bool) -> &mut Self; - - fn set_drive_strength(&mut self, strength: DriveStrength) -> &mut Self; - - fn enable_open_drain(&mut self, on: bool) -> &mut Self; - - fn enable_output_in_sleep_mode(&mut self, on: bool) -> &mut Self; - - fn internal_pull_up_in_sleep_mode(&mut self, on: bool) -> &mut Self; - - fn internal_pull_down_in_sleep_mode(&mut self, on: bool) -> &mut Self; - - fn connect_peripheral_to_output(&mut self, signal: OutputSignal) -> &mut Self { - self.connect_peripheral_to_output_with_options(signal, false, false, false, false) - } - - fn connect_peripheral_to_output_with_options( - &mut self, - signal: OutputSignal, - invert: bool, - invert_enable: bool, - enable_from_gpio: bool, - force_via_gpio_mux: bool, - ) -> &mut Self; - - /// Remove this output pin from a connected [signal](`InputSignal`). - /// - /// Clears the entry in the GPIO matrix / IO mux that associates this output - /// pin with a previously connected [signal](`InputSignal`). Any other - /// outputs connected to the signal remain intact. - fn disconnect_peripheral_from_output(&mut self) -> &mut Self; - - fn internal_pull_up(&mut self, on: bool) -> &mut Self; - - fn internal_pull_down(&mut self, on: bool) -> &mut Self; -} - -#[doc(hidden)] -pub struct SingleCoreInteruptStatusRegisterAccessBank0; -#[doc(hidden)] -pub struct DualCoreInteruptStatusRegisterAccessBank0; -#[doc(hidden)] -pub struct SingleCoreInteruptStatusRegisterAccessBank1; -#[doc(hidden)] -pub struct DualCoreInteruptStatusRegisterAccessBank1; - -#[doc(hidden)] -pub trait InteruptStatusRegisterAccess { - fn pro_cpu_interrupt_status_read() -> u32; - - fn pro_cpu_nmi_status_read() -> u32; - - fn app_cpu_interrupt_status_read() -> u32; - - fn app_cpu_nmi_status_read() -> u32; -} - -impl InteruptStatusRegisterAccess for SingleCoreInteruptStatusRegisterAccessBank0 { - fn pro_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int.read().bits() - } - - fn pro_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int.read().bits() - } - - fn app_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int.read().bits() - } - - fn app_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int.read().bits() - } -} - -#[cfg(any(esp32, esp32s2, esp32s3))] -impl InteruptStatusRegisterAccess for SingleCoreInteruptStatusRegisterAccessBank1 { - fn pro_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int1.read().bits() - } - - fn pro_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int1.read().bits() - } - - fn app_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int1.read().bits() - } - - fn app_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int1.read().bits() - } -} - -// ESP32S3 is a dual-core chip however pro cpu and app cpu shares the same -// interrupt enable bit see -// https://github.com/espressif/esp-idf/blob/c04803e88b871a4044da152dfb3699cf47354d18/components/hal/esp32s3/include/hal/gpio_ll.h#L32 -// Treating it as SingleCore in the gpio macro makes this work. -#[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32s2, esp32s3)))] -impl InteruptStatusRegisterAccess for DualCoreInteruptStatusRegisterAccessBank0 { - fn pro_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int.read().bits() - } - - fn pro_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int.read().bits() - } - - fn app_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.acpu_int.read().bits() - } - - fn app_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.acpu_nmi_int.read().bits() - } -} - -// ESP32S3 is a dual-core chip however pro cpu and app cpu shares the same -// interrupt enable bit see -// https://github.com/espressif/esp-idf/blob/c04803e88b871a4044da152dfb3699cf47354d18/components/hal/esp32s3/include/hal/gpio_ll.h#L32 -// Treating it as SingleCore in the gpio macro makes this work. -#[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32s2, esp32s3)))] -impl InteruptStatusRegisterAccess for DualCoreInteruptStatusRegisterAccessBank1 { - fn pro_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_int1.read().bits() - } - - fn pro_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.pcpu_nmi_int1.read().bits() - } - - fn app_cpu_interrupt_status_read() -> u32 { - unsafe { &*GPIO::PTR }.acpu_int1.read().bits() - } - - fn app_cpu_nmi_status_read() -> u32 { - unsafe { &*GPIO::PTR }.acpu_nmi_int1.read().bits() - } -} - -#[doc(hidden)] -pub trait InterruptStatusRegisters -where - RegisterAccess: InteruptStatusRegisterAccess, -{ - fn pro_cpu_interrupt_status_read(&self) -> u32 { - RegisterAccess::pro_cpu_interrupt_status_read() - } - - fn pro_cpu_nmi_status_read(&self) -> u32 { - RegisterAccess::pro_cpu_nmi_status_read() - } - - fn app_cpu_interrupt_status_read(&self) -> u32 { - RegisterAccess::app_cpu_interrupt_status_read() - } - - fn app_cpu_nmi_status_read(&self) -> u32 { - RegisterAccess::app_cpu_nmi_status_read() - } -} - -#[doc(hidden)] -pub trait GpioSignal { - fn output_signals() -> [Option; 6]; - fn input_signals() -> [Option; 6]; -} - -#[doc(hidden)] -pub struct Bank0GpioRegisterAccess; - -#[doc(hidden)] -pub struct Bank1GpioRegisterAccess; - -#[doc(hidden)] -pub trait BankGpioRegisterAccess { - fn write_out_en_clear(word: u32); - - fn write_out_en_set(word: u32); - - fn read_input() -> u32; - - fn read_output() -> u32; - - fn write_interrupt_status_clear(word: u32); - - fn write_output_set(word: u32); - - fn write_output_clear(word: u32); - - fn set_output_signal(gpio_num: u8, signal: u32) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.func_out_sel_cfg[gpio_num as usize] - .modify(|_, w| unsafe { w.out_sel().bits(signal as OutputSignalType) }); - } - - fn configure_out_sel(gpio_num: u8, signal: u32, invert: bool, oen: bool, oen_inv: bool) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.func_out_sel_cfg[gpio_num as usize].modify(|_, w| unsafe { - w.out_sel() - .bits(signal as OutputSignalType) - .inv_sel() - .bit(invert) - .oen_sel() - .bit(oen) - .oen_inv_sel() - .bit(oen_inv) - }); - } - - fn set_signal_to_level(signal: u32, high: bool) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(false) - .in_sel() - .bits(if high { ONE_INPUT } else { ZERO_INPUT }) - }); - } - - fn clear_func_in_sel(signal: u32) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.func_in_sel_cfg[signal as usize].modify(|_, w| w.sel().clear_bit()); - } - - fn set_int_enable(gpio_num: u8, int_ena: u32, int_type: u8, wake_up_from_light_sleep: bool) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.pin[gpio_num as usize].modify(|_, w| unsafe { - w.int_ena() - .bits(int_ena as u8) - .int_type() - .bits(int_type as u8) - .wakeup_enable() - .bit(wake_up_from_light_sleep) - }); - } - - fn set_open_drain(&self, gpio_num: u8, open_drain: bool) { - let gpio = unsafe { &*crate::peripherals::GPIO::PTR }; - gpio.pin[gpio_num as usize].modify(|_, w| w.pad_driver().bit(open_drain)); - } -} - -impl BankGpioRegisterAccess for Bank0GpioRegisterAccess { - fn write_out_en_clear(word: u32) { - unsafe { &*GPIO::PTR } - .enable_w1tc - .write(|w| unsafe { w.bits(word) }); - } - - fn write_out_en_set(word: u32) { - unsafe { &*GPIO::PTR } - .enable_w1ts - .write(|w| unsafe { w.bits(word) }); - } - - fn read_input() -> u32 { - unsafe { &*GPIO::PTR }.in_.read().bits() - } - - fn read_output() -> u32 { - unsafe { &*GPIO::PTR }.out.read().bits() - } - - fn write_interrupt_status_clear(word: u32) { - unsafe { &*GPIO::PTR } - .status_w1tc - .write(|w| unsafe { w.bits(word) }); - } - - fn write_output_set(word: u32) { - unsafe { &*GPIO::PTR } - .out_w1ts - .write(|w| unsafe { w.bits(word) }); - } - - fn write_output_clear(word: u32) { - unsafe { &*GPIO::PTR } - .out_w1tc - .write(|w| unsafe { w.bits(word) }); - } -} - -#[cfg(not(any(esp32c2, esp32c3, esp32c6)))] -impl BankGpioRegisterAccess for Bank1GpioRegisterAccess { - fn write_out_en_clear(word: u32) { - unsafe { &*GPIO::PTR } - .enable1_w1tc - .write(|w| unsafe { w.bits(word) }); - } - - fn write_out_en_set(word: u32) { - unsafe { &*GPIO::PTR } - .enable1_w1ts - .write(|w| unsafe { w.bits(word) }); - } - - fn read_input() -> u32 { - unsafe { &*GPIO::PTR }.in1.read().bits() - } - - fn read_output() -> u32 { - unsafe { &*GPIO::PTR }.out1.read().bits() - } - - fn write_interrupt_status_clear(word: u32) { - unsafe { &*GPIO::PTR } - .status1_w1tc - .write(|w| unsafe { w.bits(word) }); - } - - fn write_output_set(word: u32) { - unsafe { &*GPIO::PTR } - .out1_w1ts - .write(|w| unsafe { w.bits(word) }); - } - - fn write_output_clear(word: u32) { - unsafe { &*GPIO::PTR } - .out1_w1tc - .write(|w| unsafe { w.bits(word) }); - } -} - -pub fn connect_low_to_peripheral(signal: InputSignal) { - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(false) - .in_sel() - .bits(ZERO_INPUT) - }); -} - -pub fn connect_high_to_peripheral(signal: InputSignal) { - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(false) - .in_sel() - .bits(ONE_INPUT) - }); -} - -#[doc(hidden)] -pub trait PinType {} - -#[doc(hidden)] -pub trait IsOutputPin: PinType {} - -#[doc(hidden)] -pub trait IsInputPin: PinType {} - -#[doc(hidden)] -pub trait IsAnalogPin: PinType {} - -#[doc(hidden)] -pub struct InputOutputPinType; - -#[doc(hidden)] -pub struct InputOnlyPinType; - -#[doc(hidden)] -pub struct InputOutputAnalogPinType; - -#[doc(hidden)] -pub struct InputOnlyAnalogPinType; - -impl PinType for InputOutputPinType {} -impl IsOutputPin for InputOutputPinType {} -impl IsInputPin for InputOutputPinType {} - -impl PinType for InputOnlyPinType {} -impl IsInputPin for InputOnlyPinType {} - -impl PinType for InputOutputAnalogPinType {} -impl IsOutputPin for InputOutputAnalogPinType {} -impl IsInputPin for InputOutputAnalogPinType {} -impl IsAnalogPin for InputOutputAnalogPinType {} - -impl PinType for InputOnlyAnalogPinType {} -impl IsInputPin for InputOnlyAnalogPinType {} -impl IsAnalogPin for InputOnlyAnalogPinType {} - -pub struct GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, -} - -impl embedded_hal::digital::v2::InputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - type Error = Infallible; - fn is_high(&self) -> Result { - Ok(RA::read_input() & (1 << (GPIONUM % 32)) != 0) - } - fn is_low(&self) -> Result { - Ok(!self.is_high()?) - } -} - -impl embedded_hal::digital::v2::InputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - type Error = Infallible; - fn is_high(&self) -> Result { - Ok(RA::read_input() & (1 << (GPIONUM % 32)) != 0) - } - fn is_low(&self) -> Result { - Ok(!self.is_high()?) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ErrorType - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - type Error = Infallible; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::InputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - fn is_high(&self) -> Result { - Ok(RA::read_input() & (1 << (GPIONUM % 32)) != 0) - } - fn is_low(&self) -> Result { - Ok(!self.is_high()?) - } -} - -impl GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - pub(crate) fn new() -> Self { - Self { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - fn init_input(&self, pull_down: bool, pull_up: bool) { - let gpio = unsafe { &*GPIO::PTR }; - - RA::write_out_en_clear(1 << (GPIONUM % 32)); - gpio.func_out_sel_cfg[GPIONUM as usize] - .modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as OutputSignalType) }); - - #[cfg(esp32)] - crate::soc::gpio::errata36(GPIONUM, pull_up, pull_down); - - // NOTE: Workaround to make GPIO18 and GPIO19 work on the ESP32-C3, which by - // default are assigned to the `USB_SERIAL_JTAG` peripheral. - #[cfg(esp32c3)] - if GPIONUM == 18 || GPIONUM == 19 { - unsafe { &*crate::peripherals::USB_DEVICE::PTR } - .conf0 - .modify(|_, w| w.usb_pad_enable().clear_bit()); - } - - get_io_mux_reg(GPIONUM).modify(|_, w| unsafe { - w.mcu_sel() - .bits(GPIO_FUNCTION as u8) - .fun_ie() - .set_bit() - .fun_wpd() - .bit(pull_down) - .fun_wpu() - .bit(pull_up) - .slp_sel() - .clear_bit() - }); - } - - pub fn into_floating_input(self) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_input(false, false); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - pub fn into_pull_up_input(self) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_input(false, true); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - pub fn into_pull_down_input(self) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_input(true, false); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } -} - -impl InputPin - for GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - fn set_to_input(&mut self) -> &mut Self { - self.init_input(false, false); - self - } - fn enable_input(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.fun_ie().bit(on)); - self - } - fn enable_input_in_sleep_mode(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.mcu_ie().bit(on)); - self - } - fn is_input_high(&self) -> bool { - RA::read_input() & (1 << (GPIONUM % 32)) != 0 - } - fn connect_input_to_peripheral_with_options( - &mut self, - signal: InputSignal, - invert: bool, - force_via_gpio_mux: bool, - ) -> &mut Self { - let af = if force_via_gpio_mux { - GPIO_FUNCTION - } else { - let mut res = GPIO_FUNCTION; - for (i, input_signal) in SIG::input_signals().iter().enumerate() { - if let Some(input_signal) = input_signal { - if *input_signal == signal { - res = match i { - 0 => AlternateFunction::Function0, - 1 => AlternateFunction::Function1, - 2 => AlternateFunction::Function2, - 3 => AlternateFunction::Function3, - 4 => AlternateFunction::Function4, - 5 => AlternateFunction::Function5, - _ => unreachable!(), - }; - break; - } - } - } - res - }; - if af == GPIO_FUNCTION && signal as usize > INPUT_SIGNAL_MAX as usize { - panic!("Cannot connect GPIO to this peripheral"); - } - self.set_alternate_function(af); - if (signal as usize) <= INPUT_SIGNAL_MAX as usize { - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(invert) - .in_sel() - .bits(GPIONUM) - }); - } - self - } - - fn disconnect_input_from_peripheral(&mut self, signal: InputSignal) -> &mut Self { - self.set_alternate_function(GPIO_FUNCTION); - - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| w.sel().clear_bit()); - self - } -} - -impl Pin - for GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - fn number(&self) -> u8 { - GPIONUM - } - - fn sleep_mode(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.slp_sel().bit(on)); - - self - } - - fn set_alternate_function(&mut self, alternate: AlternateFunction) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| unsafe { w.mcu_sel().bits(alternate as u8) }); - self - } - - fn listen_with_options( - &mut self, - event: Event, - int_enable: bool, - nmi_enable: bool, - wake_up_from_light_sleep: bool, - ) { - if wake_up_from_light_sleep { - match event { - Event::AnyEdge | Event::RisingEdge | Event::FallingEdge => { - panic!("Edge triggering is not supported for wake-up from light sleep"); - } - _ => {} - } - } - unsafe { - (&*GPIO::PTR).pin[GPIONUM as usize].modify(|_, w| { - w.int_ena() - .bits(gpio_intr_enable(int_enable, nmi_enable)) - .int_type() - .bits(event as u8) - .wakeup_enable() - .bit(wake_up_from_light_sleep) - }); - } - } - - fn is_listening(&self) -> bool { - let bits = unsafe { &*GPIO::PTR }.pin[GPIONUM as usize] - .read() - .int_ena() - .bits(); - bits != 0 - } - - fn unlisten(&mut self) { - unsafe { - (&*GPIO::PTR).pin[GPIONUM as usize] - .modify(|_, w| w.int_ena().bits(0).int_type().bits(0).int_ena().bits(0)); - } - } - - fn clear_interrupt(&mut self) { - RA::write_interrupt_status_clear(1 << (GPIONUM % 32)); - } - - fn is_pcore_interrupt_set(&self) -> bool { - (IRA::pro_cpu_interrupt_status_read() & (1 << (GPIONUM % 32))) != 0 - } - - fn is_pcore_non_maskable_interrupt_set(&self) -> bool { - (IRA::pro_cpu_nmi_status_read() & (1 << (GPIONUM % 32))) != 0 - } - - fn is_acore_interrupt_set(&self) -> bool { - (IRA::app_cpu_interrupt_status_read() & (1 << (GPIONUM % 32))) != 0 - } - - fn is_acore_non_maskable_interrupt_set(&self) -> bool { - (IRA::app_cpu_nmi_status_read() & (1 << (GPIONUM % 32))) != 0 - } - - fn enable_hold(&mut self, _on: bool) { - todo!(); - } -} - -impl embedded_hal::digital::v2::OutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - type Error = Infallible; - fn set_high(&mut self) -> Result<(), Self::Error> { - RA::write_output_set(1 << (GPIONUM % 32)); - Ok(()) - } - fn set_low(&mut self) -> Result<(), Self::Error> { - RA::write_output_clear(1 << (GPIONUM % 32)); - Ok(()) - } -} - -impl embedded_hal::digital::v2::StatefulOutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn is_set_high(&self) -> Result { - Ok(RA::read_output() & (1 << (GPIONUM % 32)) != 0) - } - fn is_set_low(&self) -> Result { - Ok(!self.is_set_high()?) - } -} - -impl embedded_hal::digital::v2::ToggleableOutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - type Error = Infallible; - fn toggle(&mut self) -> Result<(), Self::Error> { - use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin as _}; - if self.is_set_high()? { - Ok(self.set_low()?) - } else { - Ok(self.set_high()?) - } - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ErrorType - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - type Error = Infallible; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::OutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn set_low(&mut self) -> Result<(), Self::Error> { - RA::write_output_clear(1 << (GPIONUM % 32)); - Ok(()) - } - fn set_high(&mut self) -> Result<(), Self::Error> { - RA::write_output_set(1 << (GPIONUM % 32)); - Ok(()) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::StatefulOutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn is_set_high(&self) -> Result { - Ok(RA::read_output() & (1 << (GPIONUM % 32)) != 0) - } - fn is_set_low(&self) -> Result { - Ok(!self.is_set_high()?) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ToggleableOutputPin - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn toggle(&mut self) -> Result<(), Self::Error> { - use embedded_hal_1::digital::{OutputPin as _, StatefulOutputPin as _}; - if self.is_set_high()? { - Ok(self.set_low()?) - } else { - Ok(self.set_high()?) - } - } -} - -impl crate::peripheral::Peripheral - for GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ - type P = GpioPin; - - unsafe fn clone_unchecked(&mut self) -> Self::P { - core::ptr::read(self as *const _) - } -} - -impl crate::peripheral::sealed::Sealed - for GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: PinType, - SIG: GpioSignal, -{ -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_floating_input() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_pull_up_input() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsInputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_pull_down_input() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_push_pull_output() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_open_drain_output() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_alternate_1() - } -} - -impl - From> - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn from( - pin: GpioPin, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - pin.into_alternate_2() - } -} - -impl GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn init_output(&self, alternate: AlternateFunction, open_drain: bool) { - let gpio = unsafe { &*GPIO::PTR }; - - RA::write_out_en_set(1 << (GPIONUM % 32)); - gpio.pin[GPIONUM as usize].modify(|_, w| w.pad_driver().bit(open_drain)); - - gpio.func_out_sel_cfg[GPIONUM as usize] - .modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as OutputSignalType) }); - - // NOTE: Workaround to make GPIO18 and GPIO19 work on the ESP32-C3, which by - // default are assigned to the `USB_SERIAL_JTAG` peripheral. - #[cfg(esp32c3)] - if GPIONUM == 18 || GPIONUM == 19 { - unsafe { &*crate::peripherals::USB_DEVICE::PTR } - .conf0 - .modify(|_, w| w.usb_pad_enable().clear_bit()); - } - - get_io_mux_reg(GPIONUM).modify(|_, w| unsafe { - w.mcu_sel() - .bits(alternate as u8) - .fun_ie() - .bit(open_drain) - .fun_wpd() - .clear_bit() - .fun_wpu() - .clear_bit() - .fun_drv() - .bits(DriveStrength::I20mA as u8) - .slp_sel() - .clear_bit() - }); - } - - pub fn into_push_pull_output( - self, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_output(GPIO_FUNCTION, false); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - pub fn into_open_drain_output( - self, - ) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_output(GPIO_FUNCTION, true); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - pub fn into_alternate_1(self) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_output(AlternateFunction::Function1, false); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } - - pub fn into_alternate_2(self) -> GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> { - self.init_output(AlternateFunction::Function2, false); - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } -} - -impl OutputPin - for GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsOutputPin, - SIG: GpioSignal, -{ - fn set_to_open_drain_output(&mut self) -> &mut Self { - self.init_output(GPIO_FUNCTION, true); - self - } - - fn set_to_push_pull_output(&mut self) -> &mut Self { - self.init_output(GPIO_FUNCTION, false); - self - } - - fn enable_output(&mut self, on: bool) -> &mut Self { - if on { - RA::write_out_en_set(1 << (GPIONUM % 32)); - } else { - RA::write_out_en_clear(1 << (GPIONUM % 32)); - } - self - } - - fn set_output_high(&mut self, high: bool) -> &mut Self { - if high { - RA::write_output_set(1 << (GPIONUM % 32)); - } else { - RA::write_output_clear(1 << (GPIONUM % 32)); - } - self - } - - fn set_drive_strength(&mut self, strength: DriveStrength) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| unsafe { w.fun_drv().bits(strength as u8) }); - - self - } - - fn enable_open_drain(&mut self, on: bool) -> &mut Self { - unsafe { &*GPIO::PTR }.pin[GPIONUM as usize].modify(|_, w| w.pad_driver().bit(on)); - self - } - - fn internal_pull_up_in_sleep_mode(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.mcu_wpu().bit(on)); - self - } - fn internal_pull_down_in_sleep_mode(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.mcu_wpd().bit(on)); - self - } - fn enable_output_in_sleep_mode(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.mcu_oe().bit(on)); - self - } - - fn connect_peripheral_to_output_with_options( - &mut self, - signal: OutputSignal, - invert: bool, - invert_enable: bool, - enable_from_gpio: bool, - force_via_gpio_mux: bool, - ) -> &mut Self { - let af = if force_via_gpio_mux { - GPIO_FUNCTION - } else { - let mut res = GPIO_FUNCTION; - for (i, output_signal) in SIG::output_signals().iter().enumerate() { - if let Some(output_signal) = output_signal { - if *output_signal == signal { - res = match i { - 0 => AlternateFunction::Function0, - 1 => AlternateFunction::Function1, - 2 => AlternateFunction::Function2, - 3 => AlternateFunction::Function3, - 4 => AlternateFunction::Function4, - 5 => AlternateFunction::Function5, - _ => unreachable!(), - }; - break; - } - } - } - res - }; - if af == GPIO_FUNCTION && signal as usize > OUTPUT_SIGNAL_MAX as usize { - panic!("Cannot connect this peripheral to GPIO"); - } - self.set_alternate_function(af); - let clipped_signal = if signal as usize <= OUTPUT_SIGNAL_MAX as usize { - signal as OutputSignalType - } else { - OUTPUT_SIGNAL_MAX - }; - unsafe { &*GPIO::PTR }.func_out_sel_cfg[GPIONUM as usize].modify(|_, w| unsafe { - w.out_sel() - .bits(clipped_signal) - .inv_sel() - .bit(invert) - .oen_sel() - .bit(enable_from_gpio) - .oen_inv_sel() - .bit(invert_enable) - }); - self - } - - fn disconnect_peripheral_from_output(&mut self) -> &mut Self { - self.set_alternate_function(GPIO_FUNCTION); - unsafe { &*GPIO::PTR }.func_out_sel_cfg[GPIONUM as usize] - .modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as OutputSignalType) }); - self - } - - fn internal_pull_up(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.fun_wpu().bit(on)); - self - } - fn internal_pull_down(&mut self, on: bool) -> &mut Self { - get_io_mux_reg(GPIONUM).modify(|_, w| w.fun_wpd().bit(on)); - self - } -} - -impl GpioPin -where - RA: BankGpioRegisterAccess, - IRA: InteruptStatusRegisterAccess, - PINTYPE: IsAnalogPin, - SIG: GpioSignal, -{ - pub fn into_analog(self) -> GpioPin { - crate::soc::gpio::internal_into_analog(GPIONUM); - - GpioPin { - _mode: PhantomData, - _pintype: PhantomData, - _reg_access: PhantomData, - _ira: PhantomData, - _signals: PhantomData, - } - } -} - -impl embedded_hal::digital::v2::InputPin for AnyPin> { - type Error = core::convert::Infallible; - - fn is_high(&self) -> Result { - let inner = &self.inner; - handle_gpio_input!(inner, target, { target.is_high() }) - } - - fn is_low(&self) -> Result { - let inner = &self.inner; - handle_gpio_input!(inner, target, { target.is_low() }) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ErrorType for AnyPin> { - type Error = Infallible; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::InputPin for AnyPin> { - fn is_high(&self) -> Result { - let inner = &self.inner; - handle_gpio_input!(inner, target, { target.is_high() }) - } - - fn is_low(&self) -> Result { - let inner = &self.inner; - handle_gpio_input!(inner, target, { target.is_low() }) - } -} - -impl embedded_hal::digital::v2::OutputPin for AnyPin> { - type Error = Infallible; - - fn set_low(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.set_low() }) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.set_high() }) - } -} - -impl embedded_hal::digital::v2::StatefulOutputPin for AnyPin> { - fn is_set_high(&self) -> Result { - let inner = &self.inner; - handle_gpio_output!(inner, target, { target.is_set_high() }) - } - - fn is_set_low(&self) -> Result { - let inner = &self.inner; - handle_gpio_output!(inner, target, { target.is_set_low() }) - } -} - -impl embedded_hal::digital::v2::ToggleableOutputPin for AnyPin> { - type Error = Infallible; - - fn toggle(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.toggle() }) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ErrorType for AnyPin> { - type Error = Infallible; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::OutputPin for AnyPin> { - fn set_low(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.set_low() }) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.set_high() }) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::StatefulOutputPin for AnyPin> { - fn is_set_high(&self) -> Result { - let inner = &self.inner; - handle_gpio_output!(inner, target, { target.is_set_high() }) - } - - fn is_set_low(&self) -> Result { - let inner = &self.inner; - handle_gpio_output!(inner, target, { target.is_set_low() }) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::digital::ToggleableOutputPin for AnyPin> { - fn toggle(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_output!(inner, target, { target.toggle() }) - } -} - -#[cfg(feature = "async")] -impl embedded_hal_async::digital::Wait for AnyPin> { - async fn wait_for_high(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_input!(inner, target, { target.wait_for_high().await }) - } - - async fn wait_for_low(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_input!(inner, target, { target.wait_for_low().await }) - } - - async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_input!(inner, target, { target.wait_for_rising_edge().await }) - } - - async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_input!(inner, target, { target.wait_for_falling_edge().await }) - } - - async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { - let inner = &mut self.inner; - handle_gpio_input!(inner, target, { target.wait_for_any_edge().await }) - } -} - -pub struct IO { - _io_mux: IO_MUX, - pub pins: Pins, -} - -impl IO { - pub fn new(gpio: GPIO, io_mux: IO_MUX) -> Self { - let pins = gpio.split(); - let io = IO { - _io_mux: io_mux, - pins, - }; - io - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! gpio { - ( - $cores:ident, - $( - ($gpionum:literal, $bank:literal, $type:ident - $( - ( $( $af_input_num:literal => $af_input_signal:ident )* ) - ( $( $af_output_num:literal => $af_output_signal:ident )* ) - )? - ) - )+ - ) => { - #[doc(hidden)] - pub trait GpioExt { - type Parts; - fn split(self) -> Self::Parts; - } - - paste!{ - impl GpioExt for GPIO { - type Parts = Pins; - fn split(self) -> Self::Parts { - Pins { - $( - [< gpio $gpionum >]: { - GpioPin::new() - }, - )+ - } - } - } - - $( - pub struct [] {} - - impl crate::gpio::GpioSignal for [] { - fn output_signals() -> [Option; 6]{ - #[allow(unused_mut)] - let mut output_signals = [None,None,None,None,None,None]; - - $( - $( - output_signals[ $af_output_num ] = Some( OutputSignal::$af_output_signal ); - )* - )? - - output_signals - } - fn input_signals() -> [Option; 6] { - #[allow(unused_mut)] - let mut input_signals = [None,None,None,None,None,None]; - - $( - $( - input_signals[ $af_input_num ] = Some( InputSignal::$af_input_signal ); - )* - )? - - input_signals - } - } - )+ - - pub struct Pins { - $( - pub [< gpio $gpionum >] : GpioPin], $crate::gpio::[< $cores CoreInteruptStatusRegisterAccessBank $bank >], [< $type PinType >], [], $gpionum>, - )+ - } - - $( - pub type [] = GpioPin], $crate::gpio::[< $cores CoreInteruptStatusRegisterAccessBank $bank >], [< $type PinType >], [], $gpionum>; - )+ - - pub(crate) enum ErasedPin { - $( - []([]), - )+ - } - - pub struct AnyPin { - pub(crate) inner: ErasedPin - } - - $( - impl From< [] > for AnyPin { - fn from(value: []) -> Self { - AnyPin { - inner: ErasedPin::[](value) - } - } - } - - impl [] { - pub fn degrade(self) -> AnyPin { - AnyPin { - inner: ErasedPin::[](self) - } - } - } - - impl TryInto<[]> for AnyPin { - type Error = (); - - fn try_into(self) -> Result<[], Self::Error> { - match self.inner { - ErasedPin::[](gpio) => Ok(gpio), - _ => Err(()), - } - } - } - )+ - - procmacros::make_gpio_enum_dispatch_macro!( - handle_gpio_output - { InputOutputAnalog, InputOutput, } - { - $( - $type,$gpionum - )+ - } - ); - - procmacros::make_gpio_enum_dispatch_macro!( - handle_gpio_input - { InputOutputAnalog, InputOutput, InputOnlyAnalog } - { - $( - $type,$gpionum - )+ - } - ); - } - }; -} - -// Following code enables `into_analog` - -#[doc(hidden)] -pub fn enable_iomux_clk_gate() { - #[cfg(esp32s2)] - { - use crate::peripherals::SENS; - let sensors = unsafe { &*SENS::ptr() }; - sensors - .sar_io_mux_conf - .modify(|_, w| w.iomux_clk_gate_en().set_bit()); - } -} - -#[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32s2)))] -#[doc(hidden)] -#[macro_export] -macro_rules! analog { - ( - $( - ( - $pin_num:expr, $rtc_pin:expr, $pin_reg:expr, - $mux_sel:ident, $fun_sel:ident, $fun_ie:ident $(, $rue:ident, $rde:ident)? - ) - )+ - ) => { - pub(crate) fn internal_into_analog(pin: u8) { - use crate::peripherals::RTC_IO; - let rtcio = unsafe{ &*RTC_IO::ptr() }; - $crate::gpio::enable_iomux_clk_gate(); - - match pin { - $( - $pin_num => { - // disable input - paste! { - rtcio.$pin_reg.modify(|_,w| w.$fun_ie().bit(false)); - - // disable output - rtcio.enable_w1tc.write(|w| unsafe { w.enable_w1tc().bits(1 << $rtc_pin) }); - - // disable open drain - rtcio.pin[$rtc_pin].modify(|_,w| w.pad_driver().bit(false)); - - rtcio.$pin_reg.modify(|_,w| { - w.$fun_ie().clear_bit(); - - // Connect pin to analog / RTC module instead of standard GPIO - w.$mux_sel().set_bit(); - - // Select function "RTC function 1" (GPIO) for analog use - unsafe { w.$fun_sel().bits(0b00) } - }); - - // Disable pull-up and pull-down resistors on the pin, if it has them - $( - rtcio.$pin_reg.modify(|_,w| { - w - .$rue().bit(false) - .$rde().bit(false) - }); - )? - } - } - )+ - _ => unreachable!(), - } - } - } -} - -#[cfg(esp32s2)] -#[doc(hidden)] -#[macro_export] -macro_rules! analog { - ( - $( - ( - $pin_num:expr, $rtc_pin:expr, $pin_reg:expr, - $mux_sel:ident, $fun_sel:ident, $fun_ie:ident $(, $rue:ident, $rde:ident)? - ) - )+ - ) => { - pub(crate) fn internal_into_analog(pin: u8) { - use crate::peripherals::RTC_IO; - let rtcio = unsafe{ &*RTC_IO::ptr() }; - $crate::gpio::enable_iomux_clk_gate(); - - match pin { - $( - $pin_num => { - - paste!{ - use $crate::gpio::[< esp32s2_get_rtc_pad_ $pin_reg>]; - let rtc_pad = [< esp32s2_get_rtc_pad_ $pin_reg>](); - } - - // disable input - rtc_pad.modify(|_,w| w.$fun_ie().bit(false)); - - // disable output - rtcio.enable_w1tc.write(|w| unsafe { w.enable_w1tc().bits(1 << $rtc_pin) }); - - // disable open drain - rtcio.pin[$rtc_pin].modify(|_,w| w.pad_driver().bit(false)); - - rtc_pad.modify(|_,w| { - w.$fun_ie().clear_bit(); - - // Connect pin to analog / RTC module instead of standard GPIO - w.$mux_sel().set_bit(); - - // Select function "RTC function 1" (GPIO) for analog use - unsafe { w.$fun_sel().bits(0b00) } - }); - - // Disable pull-up and pull-down resistors on the pin, if it has them - $( - rtc_pad.modify(|_,w| { - w - .$rue().bit(false) - .$rde().bit(false) - }); - )? - } - )+ - _ => unreachable!(), - } - } - } -} - -#[cfg(any(esp32c2, esp32c3, esp32c6))] -#[doc(hidden)] -#[macro_export] -macro_rules! analog { - ( - $($pin_num:literal)+ - ) => { - pub(crate) fn internal_into_analog(pin: u8) { - use crate::peripherals::IO_MUX; - use crate::peripherals::GPIO; - - let io_mux = unsafe{ &*IO_MUX::PTR }; - let gpio = unsafe{ &*GPIO::PTR }; - - match pin { - $( - $pin_num => { - io_mux.gpio[$pin_num].modify(|_,w| unsafe { - w.mcu_sel().bits(1) - .fun_ie().clear_bit() - .fun_wpu().clear_bit() - .fun_wpd().clear_bit() - }); - - gpio.enable_w1tc.write(|w| unsafe { w.bits(1 << $pin_num) }); - } - )+ - _ => unreachable!() - } - - } - } -} - -#[cfg(feature = "async")] -mod asynch { - use core::task::{Context, Poll}; - - use embassy_sync::waitqueue::AtomicWaker; - use embedded_hal_async::digital::Wait; - - use super::*; - use crate::prelude::*; - - #[allow(clippy::declare_interior_mutable_const)] - const NEW_AW: AtomicWaker = AtomicWaker::new(); - static PIN_WAKERS: [AtomicWaker; NUM_PINS] = [NEW_AW; NUM_PINS]; - - impl Wait - for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> - where - RA: BankGpioRegisterAccess, - PINTYPE: IsInputPin, - IRA: InteruptStatusRegisterAccess, - SIG: GpioSignal, - { - async fn wait_for_high(&mut self) -> Result<(), Self::Error> { - PinFuture::new(self, Event::HighLevel).await - } - - async fn wait_for_low(&mut self) -> Result<(), Self::Error> { - PinFuture::new(self, Event::LowLevel).await - } - - async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { - PinFuture::new(self, Event::RisingEdge).await - } - - async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { - PinFuture::new(self, Event::FallingEdge).await - } - - async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { - PinFuture::new(self, Event::AnyEdge).await - } - } - - pub struct PinFuture<'a, P> { - pin: &'a mut P, - } - - impl<'a, P> PinFuture<'a, P> - where - P: crate::gpio::Pin + embedded_hal_1::digital::ErrorType, - { - pub fn new(pin: &'a mut P, event: Event) -> Self { - pin.listen(event); - Self { pin } - } - } - - impl<'a, P> core::future::Future for PinFuture<'a, P> - where - P: crate::gpio::Pin + embedded_hal_1::digital::ErrorType, - { - type Output = Result<(), P::Error>; - - fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - PIN_WAKERS[self.pin.number() as usize].register(cx.waker()); - - // if pin is no longer listening its been triggered - // therefore the future has resolved - if !self.pin.is_listening() { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - } - - #[interrupt] - unsafe fn GPIO() { - // TODO how to handle dual core reg access - // we need to check which core the interrupt is currently firing on - // and only fire interrupts registered for that core - type Bank0 = SingleCoreInteruptStatusRegisterAccessBank0; - #[cfg(any(esp32, esp32s2, esp32s3))] - type Bank1 = SingleCoreInteruptStatusRegisterAccessBank1; - - let mut intrs = Bank0::pro_cpu_interrupt_status_read() as u64; - - #[cfg(any(esp32, esp32s2, esp32s3))] - { - intrs |= (Bank1::pro_cpu_interrupt_status_read() as u64) << 32; - } - - // clear interrupts - Bank0GpioRegisterAccess::write_interrupt_status_clear(!0); - #[cfg(any(esp32, esp32s2, esp32s3))] - Bank1GpioRegisterAccess::write_interrupt_status_clear(!0); - - while intrs != 0 { - let pin_nr = intrs.trailing_zeros(); - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2, esp32s3))] { - if pin_nr < 32 { - Bank0GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false); - } else { - Bank1GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false); - } - } else { - Bank0GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false); - } - } - PIN_WAKERS[pin_nr as usize].wake(); // wake task - intrs &= !(1 << pin_nr); - } - } -} diff --git a/esp-hal-common/src/i2c.rs b/esp-hal-common/src/i2c.rs deleted file mode 100644 index 283542453da..00000000000 --- a/esp-hal-common/src/i2c.rs +++ /dev/null @@ -1,1223 +0,0 @@ -//! I2C Driver -//! -//! Supports multiple I2C peripheral instances - -use fugit::HertzU32; - -use crate::{ - clock::Clocks, - gpio::{InputPin, InputSignal, OutputPin, OutputSignal}, - peripheral::{Peripheral, PeripheralRef}, - peripherals::i2c0::{RegisterBlock, COMD}, - system::PeripheralClockControl, -}; - -cfg_if::cfg_if! { - if #[cfg(esp32s2)] { - const I2C_LL_INTR_MASK: u32 = 0x1ffff; - } else { - const I2C_LL_INTR_MASK: u32 = 0x3ffff; - } -} - -/// I2C-specific transmission errors -#[derive(Debug)] -pub enum Error { - ExceedingFifo, - AckCheckFailed, - TimeOut, - ArbitrationLost, - ExecIncomplete, - CommandNrExceeded, -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::i2c::Error for Error { - fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { - use embedded_hal_1::i2c::ErrorKind; - - match self { - Self::ExceedingFifo => ErrorKind::Overrun, - Self::ArbitrationLost => ErrorKind::ArbitrationLoss, - _ => ErrorKind::Other, - } - } -} - -/// A generic I2C Command -enum Command { - Start, - Stop, - Write { - /// This bit is to set an expected ACK value for the transmitter. - ack_exp: Ack, - /// Enables checking the ACK value received against the ack_exp value. - ack_check_en: bool, - /// Length of data (in bytes) to be written. The maximum length is 255, - /// while the minimum is 1. - length: u8, - }, - Read { - /// Indicates whether the receiver will send an ACK after this byte has - /// been received. - ack_value: Ack, - /// Length of data (in bytes) to be read. The maximum length is 255, - /// while the minimum is 1. - length: u8, - }, -} - -impl From for u16 { - fn from(c: Command) -> u16 { - let opcode = match c { - Command::Start => Opcode::RStart, - Command::Stop => Opcode::Stop, - Command::Write { .. } => Opcode::Write, - Command::Read { .. } => Opcode::Read, - }; - - let length = match c { - Command::Start | Command::Stop => 0, - Command::Write { length: l, .. } | Command::Read { length: l, .. } => l, - }; - - let ack_exp = match c { - Command::Start | Command::Stop | Command::Read { .. } => Ack::Nack, - Command::Write { ack_exp: exp, .. } => exp, - }; - - let ack_check_en = match c { - Command::Start | Command::Stop | Command::Read { .. } => false, - Command::Write { - ack_check_en: en, .. - } => en, - }; - - let ack_value = match c { - Command::Start | Command::Stop | Command::Write { .. } => Ack::Nack, - Command::Read { ack_value: ack, .. } => ack, - }; - - let mut cmd: u16 = length.into(); - - if ack_check_en { - cmd |= 1 << 8; - } else { - cmd &= !(1 << 8); - } - - if ack_exp == Ack::Nack { - cmd |= 1 << 9; - } else { - cmd &= !(1 << 9); - } - - if ack_value == Ack::Nack { - cmd |= 1 << 10; - } else { - cmd &= !(1 << 10); - } - - cmd |= (opcode as u16) << 11; - - cmd - } -} - -enum OperationType { - Write = 0, - Read = 1, -} - -#[derive(Eq, PartialEq, Copy, Clone)] -enum Ack { - Ack, - Nack, -} - -#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] -enum Opcode { - RStart = 6, - Write = 1, - Read = 3, - Stop = 2, -} - -#[cfg(any(esp32, esp32s2))] -enum Opcode { - RStart = 0, - Write = 1, - Read = 2, - Stop = 3, -} - -/// I2C peripheral container (I2C) -pub struct I2C<'d, T> { - peripheral: PeripheralRef<'d, T>, -} - -impl embedded_hal::blocking::i2c::Read for I2C<'_, T> -where - T: Instance, -{ - type Error = Error; - - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.peripheral.master_read(address, buffer) - } -} - -impl embedded_hal::blocking::i2c::Write for I2C<'_, T> -where - T: Instance, -{ - type Error = Error; - - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.peripheral.master_write(addr, bytes) - } -} - -impl embedded_hal::blocking::i2c::WriteRead for I2C<'_, T> -where - T: Instance, -{ - type Error = Error; - - fn write_read( - &mut self, - address: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - self.peripheral.master_write_read(address, bytes, buffer) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::i2c::ErrorType for I2C<'_, T> { - type Error = Error; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::i2c::I2c for I2C<'_, T> -where - T: Instance, -{ - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.peripheral.master_read(address, buffer) - } - - fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.peripheral.master_write(address, bytes) - } - - fn write_read( - &mut self, - address: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - self.peripheral.master_write_read(address, bytes, buffer) - } - - fn transaction<'a>( - &mut self, - _address: u8, - _operations: &mut [embedded_hal_1::i2c::Operation<'a>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -impl<'d, T> I2C<'d, T> -where - T: Instance, -{ - /// Create a new I2C instance - /// This will enable the peripheral but the peripheral won't get - /// automatically disabled when this gets dropped. - pub fn new( - i2c: impl Peripheral

    + 'd, - sda: impl Peripheral

    + 'd, - scl: impl Peripheral

    + 'd, - frequency: HertzU32, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self { - crate::into_ref!(i2c, sda, scl); - enable_peripheral(&i2c, peripheral_clock_control); - - let mut i2c = I2C { peripheral: i2c }; - - // initialize SCL first to not confuse some devices like MPU6050 - scl.set_to_open_drain_output() - .enable_input(true) - .internal_pull_up(true) - .connect_peripheral_to_output(OutputSignal::I2CEXT0_SCL) - .connect_input_to_peripheral(InputSignal::I2CEXT0_SCL); - - sda.set_to_open_drain_output() - .enable_input(true) - .internal_pull_up(true) - .connect_peripheral_to_output(OutputSignal::I2CEXT0_SDA) - .connect_input_to_peripheral(InputSignal::I2CEXT0_SDA); - - i2c.peripheral.setup(frequency, clocks); - - i2c - } -} - -fn enable_peripheral<'d, T>( - i2c: &PeripheralRef<'d, T>, - peripheral_clock_control: &mut PeripheralClockControl, -) where - T: Instance, -{ - // enable peripheral - match i2c.i2c_number() { - 0 => peripheral_clock_control.enable(crate::system::Peripheral::I2cExt0), - #[cfg(i2c1)] - 1 => peripheral_clock_control.enable(crate::system::Peripheral::I2cExt1), - _ => unreachable!(), // will never happen - } -} - -/// I2C Peripheral Instance -pub trait Instance { - fn register_block(&self) -> &RegisterBlock; - - fn i2c_number(&self) -> usize; - - fn setup(&mut self, frequency: HertzU32, clocks: &Clocks) { - self.register_block().ctr.modify(|_, w| unsafe { - // Clear register - w.bits(0) - // Set I2C controller to master mode - .ms_mode() - .set_bit() - // Use open drain output for SDA and SCL - .sda_force_out() - .set_bit() - .scl_force_out() - .set_bit() - // Use Most Significant Bit first for sending and receiving data - .tx_lsb_first() - .clear_bit() - .rx_lsb_first() - .clear_bit() - // Ensure that clock is enabled - .clk_en() - .set_bit() - }); - - #[cfg(esp32s2)] - self.register_block() - .ctr - .modify(|_, w| w.ref_always_on().set_bit()); - - // Configure filter - // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 - self.set_filter(Some(7), Some(7)); - - // Configure frequency - self.set_frequency(clocks.i2c_clock.convert(), frequency); - - // Propagate configuration changes (only necessary with C2, C3, and S3) - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - self.register_block() - .ctr - .modify(|_, w| w.conf_upgate().set_bit()); - - // Reset entire peripheral (also resets fifo) - self.reset(); - } - - /// Resets the I2C controller (FIFO + FSM + command list) - fn reset(&self) { - // Reset interrupts - // Disable all I2C interrupts - self.register_block() - .int_ena - .write(|w| unsafe { w.bits(0) }); - // Clear all I2C interrupts - self.register_block() - .int_clr - .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); - - // Reset fifo - self.reset_fifo(); - - // Reset the command list - self.reset_command_list(); - - // Reset the FSM - // (the option to reset the FSM is not available - // for the ESP32) - #[cfg(not(esp32))] - self.register_block() - .ctr - .modify(|_, w| w.fsm_rst().set_bit()); - } - - /// Resets the I2C peripheral's command registers - fn reset_command_list(&self) { - // Confirm that all commands that were configured were actually executed - for cmd in self.register_block().comd.iter() { - cmd.reset(); - } - } - - /// Sets the filter with a supplied threshold in clock cycles for which a - /// pulse must be present to pass the filter - fn set_filter(&mut self, sda_threshold: Option, scl_threshold: Option) { - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - let sda_register = &self.register_block().sda_filter_cfg; - let scl_register = &self.register_block().scl_filter_cfg; - } else { - let sda_register = &self.register_block().filter_cfg; - let scl_register = &self.register_block().filter_cfg; - } - } - - match sda_threshold { - Some(threshold) => { - sda_register.modify(|_, w| unsafe { w.sda_filter_thres().bits(threshold) }); - sda_register.modify(|_, w| w.sda_filter_en().set_bit()); - } - None => sda_register.modify(|_, w| w.sda_filter_en().clear_bit()), - } - - match scl_threshold { - Some(threshold) => { - scl_register.modify(|_, w| unsafe { w.scl_filter_thres().bits(threshold) }); - scl_register.modify(|_, w| w.scl_filter_en().set_bit()); - } - None => scl_register.modify(|_, w| w.scl_filter_en().clear_bit()), - } - } - - #[cfg(esp32)] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let half_cycle: u32 = source_clk / bus_freq / 2; - let scl_low = half_cycle; - let scl_high = half_cycle; - let sda_hold = half_cycle / 2; - let sda_sample = scl_high / 2; - let setup = half_cycle; - let hold = half_cycle; - let tout = half_cycle * 20; // default we set the timeout value to 10 bus cycles. - - // SCL period. According to the TRM, we should always subtract 1 to SCL low - // period - let scl_low = scl_low - 1; - // Still according to the TRM, if filter is not enbled, we have to subtract 7, - // if SCL filter is enabled, we have to subtract: - // 8 if SCL filter is between 0 and 2 (included) - // 6 + SCL threshold if SCL filter is between 3 and 7 (included) - // to SCL high period - let mut scl_high = scl_high; - // In the "worst" case, we will subtract 13, make sure the result will still be - // correct - - // FIXME since we always set the filter threshold to 7 we don't need conditional - // code here once that changes we need the conditional code here - scl_high -= 7 + 6; - - // if (filter_cfg_en) { - // if (thres <= 2) { - // scl_high -= 8; - // } else { - // assert(hw->scl_filter_cfg.thres <= 7); - // scl_high -= thres + 6; - // } - // } else { - // scl_high -= 7; - //} - - let scl_high_period = scl_high; - let scl_low_period = scl_low; - // sda sample - let sda_hold_time = sda_hold; - let sda_sample_time = sda_sample; - // setup - let scl_rstart_setup_time = setup; - let scl_stop_setup_time = setup; - // hold - let scl_start_hold_time = hold; - let scl_stop_hold_time = hold; - let time_out_value = tout; - - self.configure_clock( - 0, - scl_low_period, - scl_high_period, - 0, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - true, - ); - } - - #[cfg(esp32s2)] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let half_cycle: u32 = source_clk / bus_freq / 2; - // SCL - let scl_low = half_cycle; - // default, scl_wait_high < scl_high - let scl_high = half_cycle / 2 + 2; - let scl_wait_high = half_cycle - scl_high; - let sda_hold = half_cycle / 2; - // scl_wait_high < sda_sample <= scl_high - let sda_sample = half_cycle / 2 - 1; - let setup = half_cycle; - let hold = half_cycle; - // default we set the timeout value to 10 bus cycles - let tout = half_cycle * 20; - - // scl period - let scl_low_period = scl_low - 1; - let scl_high_period = scl_high; - let scl_wait_high_period = scl_wait_high; - // sda sample - let sda_hold_time = sda_hold; - let sda_sample_time = sda_sample; - // setup - let scl_rstart_setup_time = setup; - let scl_stop_setup_time = setup; - // hold - let scl_start_hold_time = hold - 1; - let scl_stop_hold_time = hold; - let time_out_value = tout; - let time_out_en = true; - - self.configure_clock( - 0, - scl_low_period, - scl_high_period, - scl_wait_high_period, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - time_out_en, - ); - } - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; - let sclk_freq: u32 = source_clk / clkm_div; - let half_cycle: u32 = sclk_freq / bus_freq / 2; - // SCL - let clkm_div = clkm_div; - let scl_low = half_cycle; - // default, scl_wait_high < scl_high - // Make 80KHz as a boundary here, because when working at lower frequency, too - // much scl_wait_high will faster the frequency according to some - // hardware behaviors. - let scl_wait_high = if bus_freq >= 80 * 1000 { - half_cycle / 2 - 2 - } else { - half_cycle / 4 - }; - let scl_high = half_cycle - scl_wait_high; - let sda_hold = half_cycle / 4; - let sda_sample = half_cycle / 2 + scl_wait_high; - let setup = half_cycle; - let hold = half_cycle; - // default we set the timeout value to about 10 bus cycles - // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) - let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2; - - // According to the Technical Reference Manual, the following timings must be - // subtracted by 1. However, according to the practical measurement and - // some hardware behaviour, if wait_high_period and scl_high minus one. - // The SCL frequency would be a little higher than expected. Therefore, the - // solution here is not to minus scl_high as well as scl_wait high, and - // the frequency will be absolutely accurate to all frequency - // to some extent. - let scl_low_period = scl_low - 1; - let scl_high_period = scl_high; - let scl_wait_high_period = scl_wait_high; - // sda sample - let sda_hold_time = sda_hold - 1; - let sda_sample_time = sda_sample - 1; - // setup - let scl_rstart_setup_time = setup - 1; - let scl_stop_setup_time = setup - 1; - // hold - let scl_start_hold_time = hold - 1; - let scl_stop_hold_time = hold - 1; - let time_out_value = tout; - let time_out_en = true; - - self.configure_clock( - clkm_div, - scl_low_period, - scl_high_period, - scl_wait_high_period, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - time_out_en, - ); - } - - #[allow(unused)] - fn configure_clock( - &mut self, - sclk_div: u32, - scl_low_period: u32, - scl_high_period: u32, - scl_wait_high_period: u32, - sda_hold_time: u32, - sda_sample_time: u32, - scl_rstart_setup_time: u32, - scl_stop_setup_time: u32, - scl_start_hold_time: u32, - scl_stop_hold_time: u32, - time_out_value: u32, - time_out_en: bool, - ) { - unsafe { - // divider - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - self.register_block().clk_conf.modify(|_, w| { - w.sclk_sel() - .clear_bit() - .sclk_div_num() - .bits((sclk_div - 1) as u8) - }); - - // scl period - self.register_block() - .scl_low_period - .write(|w| w.scl_low_period().bits(scl_low_period as u16)); - - // for high/wait_high we have to differentiate between the chips - // as the EPS32 does not have a wait_high field - cfg_if::cfg_if! { - if #[cfg(not(esp32))] { - self.register_block().scl_high_period.write(|w| { - w.scl_high_period() - .bits(scl_high_period as u16) - .scl_wait_high_period() - .bits(scl_wait_high_period.try_into().unwrap()) - }); - } - else { - self.register_block().scl_high_period.write(|w| { - w.scl_high_period() - .bits(scl_high_period as u16) - }); - } - } - - // we already did that above but on S2 we need this to make it work - #[cfg(esp32s2)] - self.register_block().scl_high_period.write(|w| { - w.scl_wait_high_period() - .bits(scl_wait_high_period as u16) - .scl_high_period() - .bits(scl_high_period as u16) - }); - - // sda sample - self.register_block() - .sda_hold - .write(|w| w.time().bits(sda_hold_time as u16)); - self.register_block() - .sda_sample - .write(|w| w.time().bits(sda_sample_time as u16)); - - // setup - self.register_block() - .scl_rstart_setup - .write(|w| w.time().bits(scl_rstart_setup_time as u16)); - self.register_block() - .scl_stop_setup - .write(|w| w.time().bits(scl_stop_setup_time as u16)); - - // hold - self.register_block() - .scl_start_hold - .write(|w| w.time().bits(scl_start_hold_time as u16)); - self.register_block() - .scl_stop_hold - .write(|w| w.time().bits(scl_stop_hold_time as u16)); - - // The ESP32 variant does not have an enable flag for the - // timeout mechanism - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // timeout - self.register_block() - .to - .write(|w| w.time_out().bits(time_out_value)); - } - else { - // timeout - // FIXME: Enable timout for other chips! - self.register_block() - .to - .write(|w| w.time_out_en().bit(time_out_en) - .time_out_value() - .variant(time_out_value.try_into().unwrap()) - ); - } - } - } - } - - fn perform_write<'a, I>( - &self, - addr: u8, - bytes: &[u8], - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - if bytes.len() > 254 { - // we could support more by adding multiple write operations - return Err(Error::ExceedingFifo); - } - - // Clear all I2C interrupts - self.clear_all_interrupts(); - - // RSTART command - add_cmd(cmd_iterator, Command::Start)?; - - // WRITE command - add_cmd( - cmd_iterator, - Command::Write { - ack_exp: Ack::Ack, - ack_check_en: true, - length: 1 + bytes.len() as u8, - }, - )?; - - add_cmd(cmd_iterator, Command::Stop)?; - - self.update_config(); - - // Load address and R/W bit into FIFO - write_fifo( - self.register_block(), - addr << 1 | OperationType::Write as u8, - ); - - let index = self.fill_tx_fifo(bytes); - - self.start_transmission(); - - // fill FIFO with remaining bytes - self.write_remaining_tx_fifo(index, bytes)?; - - self.wait_for_completion()?; - - Ok(()) - } - - fn perform_read<'a, I>( - &self, - addr: u8, - buffer: &mut [u8], - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - if buffer.len() > 254 { - // we could support more by adding multiple read operations - return Err(Error::ExceedingFifo); - } - - // Clear all I2C interrupts - self.clear_all_interrupts(); - - // RSTART command - add_cmd(cmd_iterator, Command::Start)?; - - // WRITE command - add_cmd( - cmd_iterator, - Command::Write { - ack_exp: Ack::Ack, - ack_check_en: true, - length: 1, - }, - )?; - - if buffer.len() > 1 { - // READ command (N - 1) - add_cmd( - cmd_iterator, - Command::Read { - ack_value: Ack::Ack, - length: buffer.len() as u8 - 1, - }, - )?; - } - - // READ w/o ACK - add_cmd( - cmd_iterator, - Command::Read { - ack_value: Ack::Nack, - length: 1, - }, - )?; - - add_cmd(cmd_iterator, Command::Stop)?; - - self.update_config(); - - // Load address and R/W bit into FIFO - write_fifo(self.register_block(), addr << 1 | OperationType::Read as u8); - - self.start_transmission(); - - self.read_all_from_fifo(buffer)?; - - self.wait_for_completion()?; - - Ok(()) - } - - #[cfg(not(any(esp32, esp32s2)))] - fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - // Read bytes from FIFO - // FIXME: Handle case where less data has been provided by the slave than - // requested? Or is this prevented from a protocol perspective? - for byte in buffer.iter_mut() { - loop { - self.check_errors()?; - - let reg = self.register_block().fifo_st.read(); - if reg.rxfifo_raddr().bits() != reg.rxfifo_waddr().bits() { - break; - } - } - - *byte = read_fifo(self.register_block()); - } - - Ok(()) - } - - #[cfg(any(esp32, esp32s2))] - fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if buffer.len() > 32 { - panic!("On ESP32 and ESP32-S2 the max I2C read is limited to 32 bytes"); - } - - // wait for completion - then we can just read the data from FIFO - // once we change to non-fifo mode to support larger transfers that - // won't work anymore - self.wait_for_completion()?; - - // Read bytes from FIFO - // FIXME: Handle case where less data has been provided by the slave than - // requested? Or is this prevented from a protocol perspective? - for byte in buffer.iter_mut() { - *byte = read_fifo(self.register_block()); - } - - Ok(()) - } - - fn clear_all_interrupts(&self) { - self.register_block() - .int_clr - .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); - } - - fn wait_for_completion(&self) -> Result<(), Error> { - loop { - let interrupts = self.register_block().int_raw.read(); - - self.check_errors()?; - - // Handle completion cases - // A full transmission was completed - if interrupts.trans_complete_int_raw().bit_is_set() - || interrupts.end_detect_int_raw().bit_is_set() - { - break; - } - } - for cmd in self.register_block().comd.iter() { - if cmd.read().command().bits() != 0x0 && cmd.read().command_done().bit_is_clear() { - return Err(Error::ExecIncomplete); - } - } - - Ok(()) - } - - fn check_errors(&self) -> Result<(), Error> { - let interrupts = self.register_block().int_raw.read(); - - // The ESP32 variant has a slightly different interrupt naming - // scheme! - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // Handle error cases - if interrupts.time_out_int_raw().bit_is_set() { - self.reset(); - return Err(Error::TimeOut); - } else if interrupts.ack_err_int_raw().bit_is_set() { - self.reset(); - return Err(Error::AckCheckFailed); - } else if interrupts.arbitration_lost_int_raw().bit_is_set() { - self.reset(); - return Err(Error::ArbitrationLost); - } - } - else { - // Handle error cases - if interrupts.time_out_int_raw().bit_is_set() { - self.reset(); - return Err(Error::TimeOut); - } else if interrupts.nack_int_raw().bit_is_set() { - self.reset(); - return Err(Error::AckCheckFailed); - } else if interrupts.arbitration_lost_int_raw().bit_is_set() { - self.reset(); - return Err(Error::ArbitrationLost); - } - } - } - - Ok(()) - } - - fn update_config(&self) { - // Ensure that the configuration of the peripheral is correctly propagated - // (only necessary for C3 and S3 variant) - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - self.register_block() - .ctr - .modify(|_, w| w.conf_upgate().set_bit()); - } - - fn start_transmission(&self) { - // Start transmission - self.register_block() - .ctr - .modify(|_, w| w.trans_start().set_bit()); - } - - #[cfg(not(any(esp32, esp32s2)))] - fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { - let mut index = 0; - while index < bytes.len() - && !self - .register_block() - .int_raw - .read() - .txfifo_ovf_int_raw() - .bit_is_set() - { - write_fifo(self.register_block(), bytes[index]); - index += 1; - } - if self - .register_block() - .int_raw - .read() - .txfifo_ovf_int_raw() - .bit_is_set() - { - index -= 1; - self.register_block() - .int_clr - .write(|w| w.txfifo_ovf_int_clr().set_bit()); - } - index - } - - #[cfg(not(any(esp32, esp32s2)))] - fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - let mut index = start_index; - loop { - self.check_errors()?; - - while !self - .register_block() - .int_raw - .read() - .txfifo_wm_int_raw() - .bit_is_set() - {} - - self.register_block() - .int_clr - .write(|w| w.txfifo_wm_int_clr().set_bit()); - - while !self - .register_block() - .int_raw - .read() - .txfifo_wm_int_raw() - .bit_is_set() - {} - - if index >= bytes.len() { - break Ok(()); - } - - write_fifo(self.register_block(), bytes[index]); - index += 1; - } - } - - #[cfg(any(esp32, esp32s2))] - fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if bytes.len() > 31 { - panic!("On ESP32 and ESP32-S2 the max I2C transfer is limited to 31 bytes"); - } - - for b in bytes { - write_fifo(self.register_block(), *b); - } - - bytes.len() - } - - #[cfg(any(esp32, esp32s2))] - fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if start_index >= bytes.len() { - return Ok(()); - } - - // this is only possible when writing the I2C address in release mode - // from [perform_write_read] - for b in bytes { - write_fifo(self.register_block(), *b); - self.check_errors()?; - } - - Ok(()) - } - - /// Resets the transmit and receive FIFO buffers - #[cfg(not(esp32))] - fn reset_fifo(&self) { - // First, reset the fifo buffers - self.register_block().fifo_conf.modify(|_, w| { - w.tx_fifo_rst() - .set_bit() - .rx_fifo_rst() - .set_bit() - .nonfifo_en() - .clear_bit() - .fifo_prt_en() - .set_bit() - .rxfifo_wm_thrhd() - .variant(1) - .txfifo_wm_thrhd() - .variant(8) - }); - - self.register_block() - .fifo_conf - .modify(|_, w| w.tx_fifo_rst().clear_bit().rx_fifo_rst().clear_bit()); - - self.register_block().int_clr.write(|w| { - w.rxfifo_wm_int_clr() - .set_bit() - .txfifo_wm_int_clr() - .set_bit() - }); - - self.update_config(); - } - - /// Resets the transmit and receive FIFO buffers - #[cfg(esp32)] - fn reset_fifo(&self) { - // First, reset the fifo buffers - self.register_block().fifo_conf.modify(|_, w| { - w.tx_fifo_rst() - .set_bit() - .rx_fifo_rst() - .set_bit() - .nonfifo_en() - .clear_bit() - .nonfifo_rx_thres() - .variant(1) - .nonfifo_tx_thres() - .variant(32) - }); - - self.register_block() - .fifo_conf - .modify(|_, w| w.tx_fifo_rst().clear_bit().rx_fifo_rst().clear_bit()); - - self.register_block() - .int_clr - .write(|w| w.rxfifo_full_int_clr().set_bit()); - } - - /// Send data bytes from the `bytes` array to a target slave with the - /// address `addr` - fn master_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - // Reset FIFO and command list - self.reset_fifo(); - self.reset_command_list(); - self.perform_write(addr, bytes, &mut self.register_block().comd.iter())?; - Ok(()) - } - - /// Read bytes from a target slave with the address `addr` - /// The number of read bytes is deterimed by the size of the `buffer` - /// argument - fn master_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { - // Reset FIFO and command list - self.reset_fifo(); - self.reset_command_list(); - self.perform_read(addr, buffer, &mut self.register_block().comd.iter())?; - Ok(()) - } - - /// Write bytes from the `bytes` array first and then read n bytes into - /// the `buffer` array with n being the size of the array. - fn master_write_read( - &mut self, - addr: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Error> { - // it would be possible to combine the write and read - // in one transaction but filling the tx fifo with - // the current code is somewhat slow even in release mode - // which can cause issues - self.master_write(addr, bytes)?; - self.master_read(addr, buffer)?; - Ok(()) - } -} - -fn add_cmd<'a, I>(cmd_iterator: &mut I, command: Command) -> Result<(), Error> -where - I: Iterator, -{ - let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?; - cmd.write(|w| unsafe { w.command().bits(command.into()) }); - Ok(()) -} - -#[cfg(not(any(esp32, esp32s2)))] -fn read_fifo(register_block: &RegisterBlock) -> u8 { - register_block.data.read().fifo_rdata().bits() -} - -#[cfg(not(esp32))] -fn write_fifo(register_block: &RegisterBlock, data: u8) { - register_block - .data - .write(|w| unsafe { w.fifo_rdata().bits(data) }); -} - -#[cfg(esp32s2)] -fn read_fifo(register_block: &RegisterBlock) -> u8 { - let base_addr = register_block.scl_low_period.as_ptr(); - let fifo_ptr = (if base_addr as u32 == 0x3f413000 { - 0x6001301c - } else { - 0x6002701c - }) as *mut u32; - unsafe { (fifo_ptr.read() & 0xff) as u8 } -} - -#[cfg(esp32)] -fn read_fifo(register_block: &RegisterBlock) -> u8 { - register_block.data.read().fifo_rdata().bits() -} - -#[cfg(esp32)] -fn write_fifo(register_block: &RegisterBlock, data: u8) { - let base_addr = register_block.scl_low_period.as_ptr(); - let fifo_ptr = (if base_addr as u32 == 0x3FF53000 { - 0x6001301c - } else { - 0x6002701c - }) as *mut u32; - unsafe { - fifo_ptr.write_volatile(data as u32); - } -} - -impl Instance for crate::peripherals::I2C0 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn i2c_number(&self) -> usize { - 0 - } -} - -#[cfg(i2c1)] -impl Instance for crate::peripherals::I2C1 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn i2c_number(&self) -> usize { - 1 - } -} diff --git a/esp-hal-common/src/i2s.rs b/esp-hal-common/src/i2s.rs deleted file mode 100644 index fca4f4a2ebc..00000000000 --- a/esp-hal-common/src/i2s.rs +++ /dev/null @@ -1,2038 +0,0 @@ -//! I2S Master - -use embedded_dma::{ReadBuffer, WriteBuffer}; -use private::*; - -#[cfg(any(esp32s3))] -use crate::dma::I2s1Peripheral; -use crate::{ - clock::Clocks, - dma::{Channel, DmaError, DmaTransfer, I2s0Peripheral, I2sPeripheral, Rx, Tx}, - gpio::{InputPin, OutputPin}, - peripheral::{Peripheral, PeripheralRef}, - system::PeripheralClockControl, -}; - -#[cfg(any(esp32, esp32s2, esp32s3))] -const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = 6; - -#[cfg(any(esp32c3, esp32c6))] -const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = 9; - -const I2S_LL_MCLK_DIVIDER_MAX: usize = (1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1; - -trait AcceptedWord {} -impl AcceptedWord for u8 {} -impl AcceptedWord for u16 {} -impl AcceptedWord for u32 {} -impl AcceptedWord for i8 {} -impl AcceptedWord for i16 {} -impl AcceptedWord for i32 {} - -/// I2S Error -#[derive(Debug, Clone, Copy)] -pub enum Error { - Unknown, - DmaError(DmaError), - IllegalArgument, -} - -impl From for Error { - fn from(value: DmaError) -> Self { - Error::DmaError(value) - } -} - -/// Supported standards. -pub enum Standard { - Philips, - // Tdm, - // Pdm, -} - -/// Supported data formats -#[cfg(not(any(esp32, esp32s2)))] -pub enum DataFormat { - Data32Channel32, - Data32Channel24, - Data32Channel16, - Data32Channel8, - Data16Channel16, - Data16Channel8, - Data8Channel8, -} - -/// Supported data formats -#[cfg(any(esp32, esp32s2))] -pub enum DataFormat { - Data32Channel32, - Data16Channel16, -} - -#[cfg(not(any(esp32, esp32s2)))] -impl DataFormat { - pub fn data_bits(&self) -> u8 { - match self { - DataFormat::Data32Channel32 => 32, - DataFormat::Data32Channel24 => 32, - DataFormat::Data32Channel16 => 32, - DataFormat::Data32Channel8 => 32, - DataFormat::Data16Channel16 => 16, - DataFormat::Data16Channel8 => 16, - DataFormat::Data8Channel8 => 8, - } - } - - pub fn channel_bits(&self) -> u8 { - match self { - DataFormat::Data32Channel32 => 32, - DataFormat::Data32Channel24 => 24, - DataFormat::Data32Channel16 => 16, - DataFormat::Data32Channel8 => 8, - DataFormat::Data16Channel16 => 16, - DataFormat::Data16Channel8 => 8, - DataFormat::Data8Channel8 => 8, - } - } -} - -#[cfg(any(esp32, esp32s2))] -impl DataFormat { - pub fn data_bits(&self) -> u8 { - match self { - DataFormat::Data32Channel32 => 32, - DataFormat::Data16Channel16 => 16, - } - } - - pub fn channel_bits(&self) -> u8 { - match self { - DataFormat::Data32Channel32 => 32, - DataFormat::Data16Channel16 => 16, - } - } -} - -/// Pins to use for I2S tx -pub struct PinsBclkWsDout<'d, B, W, DO> { - bclk: PeripheralRef<'d, B>, - ws: PeripheralRef<'d, W>, - dout: PeripheralRef<'d, DO>, -} - -impl<'d, B, W, DO> I2sPins for PinsBclkWsDout<'d, B, W, DO> -where - B: OutputPin, - W: OutputPin, - DO: OutputPin, -{ -} - -impl<'d, B, W, DO> PinsBclkWsDout<'d, B, W, DO> -where - B: OutputPin, - W: OutputPin, - DO: OutputPin, -{ - pub fn new( - bclk: impl Peripheral

    + 'd, - ws: impl Peripheral

    + 'd, - dout: impl Peripheral

    + 'd, - ) -> Self { - crate::into_ref!(bclk, ws, dout); - Self { bclk, ws, dout } - } -} - -impl<'d, B, W, DO> I2sTxPins for PinsBclkWsDout<'d, B, W, DO> -where - B: OutputPin, - W: OutputPin, - DO: OutputPin, -{ - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess, - { - self.bclk - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.bclk_signal()); - - self.ws - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.ws_signal()); - - self.dout - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.dout_signal()); - } -} - -/// Pins to use for I2S rx -pub struct PinsBclkWsDin<'d, B, W, DI> { - bclk: PeripheralRef<'d, B>, - ws: PeripheralRef<'d, W>, - din: PeripheralRef<'d, DI>, -} - -impl<'d, B, W, DI> PinsBclkWsDin<'d, B, W, DI> -where - B: OutputPin, - W: OutputPin, - DI: InputPin, -{ - pub fn new( - bclk: impl Peripheral

    + 'd, - ws: impl Peripheral

    + 'd, - din: impl Peripheral

    + 'd, - ) -> Self { - crate::into_ref!(bclk, ws, din); - Self { bclk, ws, din } - } -} - -impl<'d, B, W, DI> I2sPins for PinsBclkWsDin<'d, B, W, DI> -where - B: OutputPin, - W: OutputPin, - DI: InputPin, -{ -} - -impl<'d, B, W, DI> I2sRxPins for PinsBclkWsDin<'d, B, W, DI> -where - B: OutputPin, - W: OutputPin, - DI: InputPin, -{ - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess, - { - self.bclk - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.bclk_rx_signal()); - - self.ws - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.ws_rx_signal()); - - self.din - .set_to_input() - .connect_input_to_peripheral(instance.din_signal()); - } -} - -/// MCLK pin to use -#[cfg(not(esp32))] -pub struct MclkPin<'d, M: OutputPin> { - mclk: PeripheralRef<'d, M>, -} - -#[cfg(not(esp32))] -impl<'d, M> I2sPins for MclkPin<'d, M> where M: OutputPin {} - -#[cfg(not(esp32))] -impl<'d, M: OutputPin> MclkPin<'d, M> { - pub fn new(pin: impl Peripheral

    + 'd) -> Self { - Self { - mclk: pin.into_ref(), - } - } -} - -#[cfg(not(esp32))] -impl<'d, M> I2sMclkPin for MclkPin<'d, M> -where - M: OutputPin, -{ - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess, - { - self.mclk - .set_to_push_pull_output() - .connect_peripheral_to_output(instance.mclk_signal()); - } -} - -/// No MCLK pin -pub struct NoMclk {} - -impl I2sPins for NoMclk {} - -impl I2sMclkPin for NoMclk { - fn configure(&mut self, _instance: &mut I) - where - I: RegisterAccess, - { - // nothing to do - } -} - -/// An in-progress DMA write transfer. -pub struct I2sWriteDmaTransfer -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - i2s_tx: I2sTx, - buffer: BUFFER, -} - -impl<'d, T, P, TX, BUFFER> I2sWriteDmaTransfer -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - /// Amount of bytes which can be pushed. - /// Only useful for circular DMA transfers - pub fn available(&mut self) -> usize { - self.i2s_tx.tx_channel.available() - } - - /// Push bytes into the DMA buffer. - /// Only useful for circular DMA transfers - pub fn push(&mut self, data: &[u8]) -> Result { - Ok(self.i2s_tx.tx_channel.push(data)?) - } -} - -impl<'d, T, P, TX, BUFFER> DmaTransfer> - for I2sWriteDmaTransfer -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - /// Wait for the DMA transfer to complete and return the buffers and the - /// I2sTx instance. - fn wait(self) -> (BUFFER, I2sTx) { - self.i2s_tx.wait_tx_dma_done().ok(); // waiting for the DMA transfer is not enough - - // `DmaTransfer` needs to have a `Drop` implementation, because we accept - // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = core::ptr::read(&self.buffer); - let payload = core::ptr::read(&self.i2s_tx); - core::mem::forget(self); - (buffer, payload) - } - } -} - -impl<'d, T, P, TX, BUFFER> Drop for I2sWriteDmaTransfer -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - fn drop(&mut self) { - self.i2s_tx.wait_tx_dma_done().ok(); - } -} - -/// Blocking I2s Write -pub trait I2sWrite { - fn write(&mut self, words: &[W]) -> Result<(), Error>; -} - -/// Initiate a DMA tx transfer -pub trait I2sWriteDma<'d, T, P, TX, TXBUF> -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - /// Write I2S. - /// Returns [I2sWriteDmaTransfer] which represents the in-ptrogress DMA - /// transfer - fn write_dma(self, words: TXBUF) -> Result, Error> - where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, - TXBUF: ReadBuffer; - - /// Continously write to I2S. Returns [I2sWriteDmaTransfer] which represents - /// the in-ptrogress DMA transfer - fn write_dma_circular( - self, - words: TXBUF, - ) -> Result, Error> - where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, - TXBUF: ReadBuffer; -} - -/// An in-progress DMA read transfer. -pub struct I2sReadDmaTransfer -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - i2s_rx: I2sRx, - buffer: BUFFER, -} - -impl<'d, T, P, RX, BUFFER> I2sReadDmaTransfer -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - /// Amount of bytes which can be poped - pub fn available(&mut self) -> usize { - self.i2s_rx.rx_channel.available() - } - - pub fn pop(&mut self, data: &mut [u8]) -> Result { - Ok(self.i2s_rx.rx_channel.pop(data)?) - } - - /// Wait for the DMA transfer to complete and return the buffers and the - /// I2sTx instance after copying the read data to the given buffer. - /// Length of the received data is returned at the third element of the - /// tuple. - pub fn wait_receive(mut self, dst: &mut [u8]) -> (BUFFER, I2sRx, usize) { - self.i2s_rx.wait_rx_dma_done().ok(); // waiting for the DMA transfer is not enough - - let len = self.i2s_rx.rx_channel.drain_buffer(dst).unwrap(); - - // `DmaTransfer` needs to have a `Drop` implementation, because we accept - // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = core::ptr::read(&self.buffer); - let payload = core::ptr::read(&self.i2s_rx); - core::mem::forget(self); - (buffer, payload, len) - } - } -} - -impl<'d, T, P, RX, BUFFER> DmaTransfer> - for I2sReadDmaTransfer -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - /// Wait for the DMA transfer to complete and return the buffers and the - /// I2sTx instance. - fn wait(self) -> (BUFFER, I2sRx) { - self.i2s_rx.wait_rx_dma_done().ok(); // waiting for the DMA transfer is not enough - - // `DmaTransfer` needs to have a `Drop` implementation, because we accept - // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = core::ptr::read(&self.buffer); - let payload = core::ptr::read(&self.i2s_rx); - core::mem::forget(self); - (buffer, payload) - } - } -} - -impl Drop for I2sReadDmaTransfer -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - fn drop(&mut self) { - self.i2s_rx.wait_rx_dma_done().ok(); - } -} - -/// Blocking I2S Read -pub trait I2sRead { - fn read(&mut self, words: &mut [W]) -> Result<(), Error>; -} - -/// Initate a DMA rx transfer -pub trait I2sReadDma<'d, T, P, RX, RXBUF> -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - /// Read I2S. - /// Returns [I2sReadDmaTransfer] which represents the in-ptrogress DMA - /// transfer - fn read_dma(self, words: RXBUF) -> Result, Error> - where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, - RXBUF: WriteBuffer; - - /// Continously read from I2S. - /// Returns [I2sReadDmaTransfer] which represents the in-ptrogress DMA - /// transfer - fn read_dma_circular(self, words: RXBUF) -> Result, Error> - where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, - RXBUF: WriteBuffer; -} - -/// Instance of the I2S peripheral driver -pub struct I2s<'d, I, T, P, TX, RX> -where - I: Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, -{ - _peripheral: PeripheralRef<'d, I>, - _register_access: T, - _pins: P, - pub i2s_tx: TxCreator, - pub i2s_rx: RxCreator, -} - -impl<'d, I, T, P, TX, RX> I2s<'d, I, T, P, TX, RX> -where - I: Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, -{ - fn new_internal( - i2s: impl Peripheral

    + 'd, - mut pins: P, - standard: Standard, - data_format: DataFormat, - sample_rate: impl Into, - mut channel: Channel, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self - where - IP: I2sPeripheral, - { - // on ESP32-C3 / ESP32-S3 and later RX and TX are independent and - // could be configured totally independently but for now handle all - // the targets the same and force same configuration for both, TX and RX - - crate::into_ref!(i2s); - let mut register_access = i2s.register_access(); - - channel.tx.init_channel(); - peripheral_clock_control.enable(register_access.get_peripheral()); - pins.configure(&mut register_access); - register_access.set_clock(calculate_clock( - sample_rate, - 2, - data_format.channel_bits(), - clocks, - )); - register_access.configure(&standard, &data_format); - register_access.set_master(); - register_access.update(); - - Self { - _peripheral: i2s, - _register_access: register_access.clone(), - _pins: pins, - i2s_tx: TxCreator { - register_access: register_access.clone(), - tx_channel: channel.tx, - }, - i2s_rx: RxCreator { - register_access, - rx_channel: channel.rx, - }, - } - } -} - -/// Construct a new I2S peripheral driver instance for the first I2S peripheral -pub trait I2s0New<'d, I, T, P, TX, RX, IP> -where - I: Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, - IP: I2sPeripheral + I2s0Peripheral, - RX: Rx, -{ - fn new( - i2s: impl Peripheral

    + 'd, - pins: P, - standard: Standard, - data_format: DataFormat, - sample_rate: impl Into, - channel: Channel, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self; -} - -impl<'d, I, T, P, TX, RX, IP> I2s0New<'d, I, T, P, TX, RX, IP> for I2s<'d, I, T, P, TX, RX> -where - I: Instance + I2s0Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, - IP: I2sPeripheral + I2s0Peripheral, -{ - fn new( - i2s: impl Peripheral

    + 'd, - pins: P, - standard: Standard, - data_format: DataFormat, - sample_rate: impl Into, - channel: Channel, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self { - Self::new_internal( - i2s, - pins, - standard, - data_format, - sample_rate, - channel, - peripheral_clock_control, - clocks, - ) - } -} - -/// Construct a new I2S peripheral driver instance for the second I2S peripheral -#[cfg(any(esp32s3))] -pub trait I2s1New<'d, I, T, P, TX, RX, IP> -where - I: Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, - IP: I2sPeripheral + I2s1Peripheral, - RX: Rx, -{ - fn new( - i2s: impl Peripheral

    + 'd, - pins: P, - standard: Standard, - data_format: DataFormat, - sample_rate: impl Into, - channel: Channel, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self; -} - -#[cfg(any(esp32s3))] -impl<'d, I, T, P, TX, RX, IP> I2s1New<'d, I, T, P, TX, RX, IP> for I2s<'d, I, T, P, TX, RX> -where - I: Instance + I2s1Instance, - T: RegisterAccess + Clone, - P: I2sMclkPin, - TX: Tx, - RX: Rx, - IP: I2sPeripheral + I2s1Peripheral, -{ - fn new( - i2s: impl Peripheral

    + 'd, - pins: P, - standard: Standard, - data_format: DataFormat, - sample_rate: impl Into, - channel: Channel, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Self { - Self::new_internal( - i2s, - pins, - standard, - data_format, - sample_rate, - channel, - peripheral_clock_control, - clocks, - ) - } -} - -/// I2S TX channel -pub struct I2sTx -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - register_access: T, - _pins: P, - tx_channel: TX, -} - -impl I2sTx -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - fn new(mut register_access: T, mut pins: P, tx_channel: TX) -> Self { - pins.configure(&mut register_access); - - Self { - register_access, - _pins: pins, - tx_channel, - } - } - - fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { - let ptr = data as *const _ as *const u8; - - // Reset TX unit and TX FIFO - self.register_access.reset_tx(); - - // Enable corresponding interrupts if needed - - // configure DMA outlink - self.tx_channel.prepare_transfer( - self.register_access.get_dma_peripheral(), - false, - ptr, - data.len(), - )?; - - // set I2S_TX_STOP_EN if needed - - // start: set I2S_TX_START - self.register_access.tx_start(); - - // wait until I2S_TX_IDLE is 1 - self.register_access.wait_for_tx_done(); - - Ok(()) - } - - fn start_tx_transfer( - mut self, - words: TXBUF, - circular: bool, - ) -> Result, Error> - where - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { words.read_buffer() }; - - // Reset TX unit and TX FIFO - self.register_access.reset_tx(); - - // Enable corresponding interrupts if needed - - // configure DMA outlink - self.tx_channel.prepare_transfer( - self.register_access.get_dma_peripheral(), - circular, - ptr, - len, - )?; - - // set I2S_TX_STOP_EN if needed - - // start: set I2S_TX_START - self.register_access.tx_start(); - - Ok(I2sWriteDmaTransfer { - i2s_tx: self, - buffer: words, - }) - } - - fn wait_tx_dma_done(&self) -> Result<(), Error> { - // wait until I2S_TX_IDLE is 1 - self.register_access.wait_for_tx_done(); - - Ok(()) - } -} - -impl I2sWrite for I2sTx -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, - W: AcceptedWord, -{ - fn write(&mut self, words: &[W]) -> Result<(), Error> { - self.write_bytes(unsafe { - core::slice::from_raw_parts( - words as *const _ as *const u8, - words.len() * core::mem::size_of::(), - ) - }) - } -} - -impl<'d, T, P, TX, TXBUF> I2sWriteDma<'d, T, P, TX, TXBUF> for I2sTx -where - T: RegisterAccess, - P: I2sTxPins, - TX: Tx, -{ - fn write_dma(self, words: TXBUF) -> Result, Error> - where - TXBUF: ReadBuffer, - { - self.start_tx_transfer(words, false) - } - - fn write_dma_circular(self, words: TXBUF) -> Result, Error> - where - TXBUF: ReadBuffer, - { - self.start_tx_transfer(words, true) - } -} - -/// I2S RX channel -pub struct I2sRx -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - register_access: T, - _pins: P, - rx_channel: RX, -} - -impl<'d, T, P, RX> I2sRx -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - fn new(mut register_access: T, mut pins: P, rx_channel: RX) -> Self { - pins.configure(&mut register_access); - - Self { - register_access, - _pins: pins, - rx_channel, - } - } - - fn read_bytes(&mut self, data: &mut [u8]) -> Result<(), Error> { - let ptr = data as *mut _ as *mut u8; - - // Reset RX unit and RX FIFO - self.register_access.reset_rx(); - - // Enable corresponding interrupts if needed - - // configure DMA outlink - self.rx_channel.prepare_transfer( - false, - self.register_access.get_dma_peripheral(), - ptr, - data.len(), - )?; - - // set I2S_TX_STOP_EN if needed - - // start: set I2S_TX_START - self.register_access.rx_start(data.len() - 1); - - // wait until I2S_TX_IDLE is 1 - self.register_access.wait_for_rx_done(); - - Ok(()) - } - - fn start_rx_transfer( - mut self, - mut words: RXBUF, - circular: bool, - ) -> Result, Error> - where - RXBUF: WriteBuffer, - { - let (ptr, len) = unsafe { words.write_buffer() }; - - if len % 4 != 0 { - return Err(Error::IllegalArgument); - } - - // Reset TX unit and TX FIFO - self.register_access.reset_rx(); - - // Enable corresponding interrupts if needed - - // configure DMA outlink - self.rx_channel.prepare_transfer( - circular, - self.register_access.get_dma_peripheral(), - ptr, - len, - )?; - - // set I2S_TX_STOP_EN if needed - - // start: set I2S_RX_START - #[cfg(not(esp32))] - self.register_access.rx_start(len - 1); - - #[cfg(esp32)] - self.register_access.rx_start(len); - - Ok(I2sReadDmaTransfer { - i2s_rx: self, - buffer: words, - }) - } - - fn wait_rx_dma_done(&self) -> Result<(), Error> { - self.register_access.wait_for_rx_done(); - - Ok(()) - } -} - -impl I2sRead for I2sRx -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, - W: AcceptedWord, -{ - fn read(&mut self, words: &mut [W]) -> Result<(), Error> { - if words.len() * core::mem::size_of::() > 4096 || words.len() == 0 { - return Err(Error::IllegalArgument); - } - - self.read_bytes(unsafe { - core::slice::from_raw_parts_mut( - words as *mut _ as *mut u8, - words.len() * core::mem::size_of::(), - ) - }) - } -} - -impl<'d, T, P, RX, RXBUF> I2sReadDma<'d, T, P, RX, RXBUF> for I2sRx -where - T: RegisterAccess, - P: I2sRxPins, - RX: Rx, -{ - fn read_dma(self, words: RXBUF) -> Result, Error> - where - RXBUF: WriteBuffer, - { - self.start_rx_transfer(words, false) - } - - fn read_dma_circular(self, words: RXBUF) -> Result, Error> - where - RXBUF: WriteBuffer, - { - self.start_rx_transfer(words, true) - } -} - -pub trait RegisterAccess: RegisterAccessPrivate {} - -pub trait I2sTxPins: I2sPins { - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess; -} - -pub trait I2sRxPins: I2sPins { - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess; -} - -pub trait I2sMclkPin: I2sPins { - fn configure(&mut self, instance: &mut I) - where - I: RegisterAccess; -} - -mod private { - use fugit::HertzU32; - - use super::{DataFormat, I2sRx, I2sTx, RegisterAccess, Standard, I2S_LL_MCLK_DIVIDER_MAX}; - #[cfg(not(any(esp32, esp32s3)))] - use crate::peripherals::i2s0::RegisterBlock; - // on ESP32-S3 I2S1 doesn't support all features - use that to avoid using those features - // by accident - #[cfg(any(esp32, esp32s3))] - use crate::peripherals::i2s1::RegisterBlock; - use crate::{ - clock::Clocks, - dma::{DmaPeripheral, Rx, Tx}, - gpio::{InputSignal, OutputSignal}, - peripherals::I2S0, - system::Peripheral, - }; - - pub trait I2sPins {} - - pub struct TxCreator - where - T: RegisterAccess + Clone, - TX: Tx, - { - pub register_access: T, - pub tx_channel: TX, - } - - impl TxCreator - where - T: RegisterAccess + Clone, - TX: Tx, - { - pub fn with_pins

    (self, pins: P) -> I2sTx - where - P: super::I2sTxPins, - { - I2sTx::new(self.register_access, pins, self.tx_channel) - } - } - - pub struct RxCreator - where - T: RegisterAccess + Clone, - RX: Rx, - { - pub register_access: T, - pub rx_channel: RX, - } - - impl RxCreator - where - T: RegisterAccess + Clone, - RX: Rx, - { - pub fn with_pins

    (self, pins: P) -> I2sRx - where - P: super::I2sRxPins, - { - I2sRx::new(self.register_access, pins, self.rx_channel) - } - } - - pub trait I2s0Instance {} - - pub trait I2s1Instance {} - - pub trait Instance - where - R: RegisterAccess, - { - fn register_access(&self) -> R; - } - - impl Instance for I2S0 { - fn register_access(&self) -> I2sPeripheral0 { - I2sPeripheral0 {} - } - } - - impl I2s0Instance for I2S0 {} - - #[cfg(esp32s3)] - impl Instance for crate::peripherals::I2S1 { - fn register_access(&self) -> I2sPeripheral1 { - I2sPeripheral1 {} - } - } - - #[cfg(esp32s3)] - impl I2s1Instance for crate::peripherals::I2S1 {} - - pub trait Signals { - fn get_peripheral(&self) -> Peripheral; - - fn get_dma_peripheral(&self) -> DmaPeripheral; - - fn mclk_signal(&self) -> OutputSignal; - - fn bclk_signal(&self) -> OutputSignal; - - fn ws_signal(&self) -> OutputSignal; - - fn dout_signal(&self) -> OutputSignal; - - fn bclk_rx_signal(&self) -> OutputSignal; - - fn ws_rx_signal(&self) -> OutputSignal; - - fn din_signal(&self) -> InputSignal; - } - - pub trait RegBlock { - fn register_block(&self) -> &'static RegisterBlock; - } - - #[cfg(any(esp32, esp32s2))] - pub trait RegisterAccessPrivate: Signals + RegBlock { - fn set_clock(&self, clock_settings: I2sClockDividers) { - let i2s = self.register_block(); - - i2s.clkm_conf.modify(|r, w| unsafe { - w.bits(r.bits() | (2 << 21)) // select PLL_160M - }); - - #[cfg(esp32)] - i2s.clkm_conf.modify(|_, w| w.clka_ena().clear_bit()); - - i2s.clkm_conf.modify(|_, w| { - w.clk_en() - .set_bit() - .clkm_div_num() - .variant(clock_settings.mclk_divider as u8) - }); - - i2s.clkm_conf.modify(|_, w| { - w.clkm_div_a() - .variant(clock_settings.denominator as u8) - .clkm_div_b() - .variant(clock_settings.numerator as u8) - }); - - i2s.sample_rate_conf.modify(|_, w| { - w.tx_bck_div_num() - .variant(clock_settings.bclk_divider as u8) - .rx_bck_div_num() - .variant(clock_settings.bclk_divider as u8) - }); - } - - fn configure(&self, _standard: &Standard, data_format: &DataFormat) { - let i2s = self.register_block(); - - let fifo_mod = match data_format { - DataFormat::Data32Channel32 => 2, - DataFormat::Data16Channel16 => 0, - }; - - i2s.sample_rate_conf - .modify(|_, w| w.tx_bits_mod().variant(data_format.channel_bits())); - i2s.sample_rate_conf - .modify(|_, w| w.rx_bits_mod().variant(data_format.channel_bits())); - - i2s.conf.modify(|_, w| { - w.tx_slave_mod() - .clear_bit() - .rx_slave_mod() - .clear_bit() - .tx_msb_shift() - .set_bit() // ? - .rx_msb_shift() - .set_bit() // ? - .tx_short_sync() - .variant(false) //?? - .rx_short_sync() - .variant(false) //?? - .tx_msb_right() - .clear_bit() - .rx_msb_right() - .clear_bit() - .tx_right_first() - .clear_bit() - .rx_right_first() - .clear_bit() - .tx_mono() - .clear_bit() - .rx_mono() - .clear_bit() - .sig_loopback() - .clear_bit() - }); - - i2s.fifo_conf.modify(|_, w| { - w.tx_fifo_mod() - .variant(fifo_mod) - .tx_fifo_mod_force_en() - .set_bit() - .dscr_en() - .set_bit() - .rx_fifo_mod() - .variant(fifo_mod) - .rx_fifo_mod_force_en() - .set_bit() - }); - - i2s.conf_chan - .modify(|_, w| w.tx_chan_mod().variant(0).rx_chan_mod().variant(0)); // for now only stereo - - i2s.conf1 - .modify(|_, w| w.tx_pcm_bypass().set_bit().rx_pcm_bypass().set_bit()); - - i2s.pd_conf - .modify(|_, w| w.fifo_force_pu().set_bit().fifo_force_pd().clear_bit()); - - i2s.conf2 - .modify(|_, w| w.camera_en().clear_bit().lcd_en().clear_bit()); - } - - fn set_master(&self) { - let i2s = self.register_block(); - i2s.conf - .modify(|_, w| w.rx_slave_mod().clear_bit().tx_slave_mod().clear_bit()); - } - - fn update(&self) { - // nothing to do - } - - fn reset_tx(&self) { - let i2s = self.register_block(); - i2s.conf - .modify(|_, w| w.tx_reset().set_bit().tx_fifo_reset().set_bit()); - i2s.conf - .modify(|_, w| w.tx_reset().clear_bit().tx_fifo_reset().clear_bit()); - - i2s.lc_conf.modify(|_, w| w.out_rst().set_bit()); - i2s.lc_conf.modify(|_, w| w.out_rst().clear_bit()); - - i2s.int_clr.write(|w| { - w.out_done_int_clr() - .set_bit() - .out_total_eof_int_clr() - .set_bit() - }); - } - - fn tx_start(&self) { - let i2s = self.register_block(); - i2s.conf.modify(|_, w| w.tx_start().set_bit()); - } - - fn wait_for_tx_done(&self) { - let i2s = self.register_block(); - while i2s.state.read().tx_idle().bit_is_clear() { - // wait - } - - i2s.conf.modify(|_, w| w.tx_start().clear_bit()); - } - - fn reset_rx(&self) { - let i2s = self.register_block(); - i2s.conf - .modify(|_, w| w.rx_reset().set_bit().rx_fifo_reset().set_bit()); - i2s.conf - .modify(|_, w| w.rx_reset().clear_bit().rx_fifo_reset().clear_bit()); - - i2s.lc_conf.modify(|_, w| w.in_rst().set_bit()); - i2s.lc_conf.modify(|_, w| w.in_rst().clear_bit()); - - i2s.int_clr - .write(|w| w.in_done_int_clr().set_bit().in_suc_eof_int_clr().set_bit()); - } - - fn rx_start(&self, len: usize) { - let i2s = self.register_block(); - - i2s.int_clr.write(|w| w.in_suc_eof_int_clr().set_bit()); - - #[cfg(not(esp32))] - i2s.rxeof_num - .modify(|_, w| w.rx_eof_num().variant(len as u32)); - - // On ESP32, the eof_num count in words. - #[cfg(esp32)] - i2s.rxeof_num - .modify(|_, w| w.rx_eof_num().variant((len / 4) as u32)); - - i2s.conf.modify(|_, w| w.rx_start().set_bit()); - } - - fn wait_for_rx_done(&self) { - let i2s = self.register_block(); - while i2s.int_raw.read().in_suc_eof_int_raw().bit_is_clear() { - // wait - } - - i2s.int_clr.write(|w| w.in_suc_eof_int_clr().set_bit()); - } - } - - #[cfg(any(esp32c3, esp32c6, esp32s3))] - pub trait RegisterAccessPrivate: Signals + RegBlock { - #[cfg(any(esp32c3, esp32s3))] - fn set_clock(&self, clock_settings: I2sClockDividers) { - let i2s = self.register_block(); - - let clkm_div_x: u32; - let clkm_div_y: u32; - let clkm_div_z: u32; - let clkm_div_yn1: u32; - - if clock_settings.denominator == 0 || clock_settings.numerator == 0 { - clkm_div_x = 0; - clkm_div_y = 0; - clkm_div_z = 0; - clkm_div_yn1 = 1; - } else { - if clock_settings.numerator > clock_settings.denominator / 2 { - clkm_div_x = clock_settings - .denominator - .overflowing_div( - clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0, - ) - .0 - .overflowing_sub(1) - .0; - clkm_div_y = clock_settings.denominator - % (clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0); - clkm_div_z = clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0; - clkm_div_yn1 = 1; - } else { - clkm_div_x = clock_settings.denominator / clock_settings.numerator - 1; - clkm_div_y = clock_settings.denominator % clock_settings.numerator; - clkm_div_z = clock_settings.numerator; - clkm_div_yn1 = 0; - } - } - - i2s.tx_clkm_div_conf.modify(|_, w| { - w.tx_clkm_div_x() - .variant(clkm_div_x as u16) - .tx_clkm_div_y() - .variant(clkm_div_y as u16) - .tx_clkm_div_yn1() - .variant(if clkm_div_yn1 != 0 { true } else { false }) - .tx_clkm_div_z() - .variant(clkm_div_z as u16) - }); - - i2s.tx_clkm_conf.modify(|_, w| { - w.clk_en() - .set_bit() - .tx_clk_active() - .set_bit() - .tx_clk_sel() - .variant(2) // for now fixed at 160MHz - .tx_clkm_div_num() - .variant(clock_settings.mclk_divider as u8) - }); - - i2s.tx_conf1.modify(|_, w| { - w.tx_bck_div_num() - .variant((clock_settings.bclk_divider - 1) as u8) - }); - - i2s.rx_clkm_div_conf.modify(|_, w| { - w.rx_clkm_div_x() - .variant(clkm_div_x as u16) - .rx_clkm_div_y() - .variant(clkm_div_y as u16) - .rx_clkm_div_yn1() - .variant(if clkm_div_yn1 != 0 { true } else { false }) - .rx_clkm_div_z() - .variant(clkm_div_z as u16) - }); - - i2s.rx_clkm_conf.modify(|_, w| { - w.rx_clk_active() - .set_bit() - .rx_clk_sel() - .variant(2) // for now fixed at 160MHz - .rx_clkm_div_num() - .variant(clock_settings.mclk_divider as u8) - .mclk_sel() - .variant(true) - }); - - i2s.rx_conf1.modify(|_, w| { - w.rx_bck_div_num() - .variant((clock_settings.bclk_divider - 1) as u8) - }); - } - - #[cfg(any(esp32c6))] - fn set_clock(&self, clock_settings: I2sClockDividers) { - let i2s = self.register_block(); - let pcr = unsafe { &*esp32c6::PCR::PTR }; // I2S clocks are configured via PCR - - let clkm_div_x: u32; - let clkm_div_y: u32; - let clkm_div_z: u32; - let clkm_div_yn1: u32; - - if clock_settings.denominator == 0 || clock_settings.numerator == 0 { - clkm_div_x = 0; - clkm_div_y = 0; - clkm_div_z = 0; - clkm_div_yn1 = 1; - } else { - if clock_settings.numerator > clock_settings.denominator / 2 { - clkm_div_x = clock_settings - .denominator - .overflowing_div( - clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0, - ) - .0 - .overflowing_sub(1) - .0; - clkm_div_y = clock_settings.denominator - % (clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0); - clkm_div_z = clock_settings - .denominator - .overflowing_sub(clock_settings.numerator) - .0; - clkm_div_yn1 = 1; - } else { - clkm_div_x = clock_settings.denominator / clock_settings.numerator - 1; - clkm_div_y = clock_settings.denominator % clock_settings.numerator; - clkm_div_z = clock_settings.numerator; - clkm_div_yn1 = 0; - } - } - - pcr.i2s_tx_clkm_div_conf.modify(|_, w| { - w.i2s_tx_clkm_div_x() - .variant(clkm_div_x as u16) - .i2s_tx_clkm_div_y() - .variant(clkm_div_y as u16) - .i2s_tx_clkm_div_yn1() - .variant(if clkm_div_yn1 != 0 { true } else { false }) - .i2s_tx_clkm_div_z() - .variant(clkm_div_z as u16) - }); - - pcr.i2s_tx_clkm_conf.modify(|_, w| { - w.i2s_tx_clkm_en() - .set_bit() - .i2s_tx_clkm_sel() - .variant(2) // for now fixed at 160MHz - .i2s_tx_clkm_div_num() - .variant(clock_settings.mclk_divider as u8) - }); - - i2s.tx_conf1.modify(|_, w| { - w.tx_bck_div_num() - .variant((clock_settings.bclk_divider - 1) as u8) - }); - - pcr.i2s_rx_clkm_div_conf.modify(|_, w| { - w.i2s_rx_clkm_div_x() - .variant(clkm_div_x as u16) - .i2s_rx_clkm_div_y() - .variant(clkm_div_y as u16) - .i2s_rx_clkm_div_yn1() - .variant(if clkm_div_yn1 != 0 { true } else { false }) - .i2s_rx_clkm_div_z() - .variant(clkm_div_z as u16) - }); - - pcr.i2s_rx_clkm_conf.modify(|_, w| { - w.i2s_rx_clkm_en() - .set_bit() - .i2s_rx_clkm_sel() - .variant(2) // for now fixed at 160MHz - .i2s_rx_clkm_div_num() - .variant(clock_settings.mclk_divider as u8) - .i2s_mclk_sel() - .variant(true) - }); - - i2s.rx_conf1.modify(|_, w| { - w.rx_bck_div_num() - .variant((clock_settings.bclk_divider - 1) as u8) - }); - } - - fn configure(&self, _standard: &Standard, data_format: &DataFormat) { - let i2s = self.register_block(); - i2s.tx_conf1.modify(|_, w| { - w.tx_tdm_ws_width() - .variant(data_format.channel_bits() - 1) - .tx_bits_mod() - .variant(data_format.data_bits() - 1) - .tx_tdm_chan_bits() - .variant(data_format.channel_bits() - 1) - .tx_half_sample_bits() - .variant(data_format.channel_bits() - 1) - .tx_msb_shift() - .set_bit() - }); - - i2s.tx_conf.modify(|_, w| { - w.tx_mono() - .clear_bit() - .tx_mono_fst_vld() - .set_bit() - .tx_stop_en() - .set_bit() - .tx_chan_equal() - .clear_bit() - .tx_tdm_en() - .set_bit() - .tx_pdm_en() - .clear_bit() - .tx_pcm_bypass() - .set_bit() - .tx_big_endian() - .clear_bit() - .tx_bit_order() - .clear_bit() - .tx_chan_mod() - .variant(0) - }); - - i2s.tx_tdm_ctrl.modify(|_, w| { - w.tx_tdm_tot_chan_num() - .variant(1) - .tx_tdm_chan0_en() - .set_bit() - .tx_tdm_chan1_en() - .set_bit() - .tx_tdm_chan2_en() - .clear_bit() - .tx_tdm_chan3_en() - .clear_bit() - .tx_tdm_chan4_en() - .clear_bit() - .tx_tdm_chan5_en() - .clear_bit() - .tx_tdm_chan6_en() - .clear_bit() - .tx_tdm_chan7_en() - .clear_bit() - .tx_tdm_chan8_en() - .clear_bit() - .tx_tdm_chan9_en() - .clear_bit() - .tx_tdm_chan10_en() - .clear_bit() - .tx_tdm_chan11_en() - .clear_bit() - .tx_tdm_chan12_en() - .clear_bit() - .tx_tdm_chan13_en() - .clear_bit() - .tx_tdm_chan14_en() - .clear_bit() - .tx_tdm_chan15_en() - .clear_bit() - }); - - i2s.rx_conf1.modify(|_, w| { - w.rx_tdm_ws_width() - .variant(data_format.channel_bits() - 1) - .rx_bits_mod() - .variant(data_format.data_bits() - 1) - .rx_tdm_chan_bits() - .variant(data_format.channel_bits() - 1) - .rx_half_sample_bits() - .variant(data_format.channel_bits() - 1) - .rx_msb_shift() - .set_bit() - }); - - i2s.rx_conf.modify(|_, w| { - w.rx_mono() - .clear_bit() - .rx_mono_fst_vld() - .set_bit() - .rx_stop_mode() - .variant(2) - .rx_tdm_en() - .set_bit() - .rx_pdm_en() - .clear_bit() - .rx_pcm_bypass() - .set_bit() - .rx_big_endian() - .clear_bit() - .rx_bit_order() - .clear_bit() - }); - - i2s.rx_tdm_ctrl.modify(|_, w| { - w.rx_tdm_tot_chan_num() - .variant(1) - .rx_tdm_pdm_chan0_en() - .set_bit() - .rx_tdm_pdm_chan1_en() - .set_bit() - .rx_tdm_pdm_chan2_en() - .clear_bit() - .rx_tdm_pdm_chan3_en() - .clear_bit() - .rx_tdm_pdm_chan4_en() - .clear_bit() - .rx_tdm_pdm_chan5_en() - .clear_bit() - .rx_tdm_pdm_chan6_en() - .clear_bit() - .rx_tdm_pdm_chan7_en() - .clear_bit() - .rx_tdm_chan8_en() - .clear_bit() - .rx_tdm_chan9_en() - .clear_bit() - .rx_tdm_chan10_en() - .clear_bit() - .rx_tdm_chan11_en() - .clear_bit() - .rx_tdm_chan12_en() - .clear_bit() - .rx_tdm_chan13_en() - .clear_bit() - .rx_tdm_chan14_en() - .clear_bit() - .rx_tdm_chan15_en() - .clear_bit() - }); - } - - fn set_master(&self) { - let i2s = self.register_block(); - i2s.tx_conf.modify(|_, w| w.tx_slave_mod().clear_bit()); - i2s.rx_conf.modify(|_, w| w.rx_slave_mod().clear_bit()); - } - - fn update(&self) { - let i2s = self.register_block(); - i2s.tx_conf.modify(|_, w| w.tx_update().clear_bit()); - i2s.tx_conf.modify(|_, w| w.tx_update().set_bit()); - - i2s.rx_conf.modify(|_, w| w.rx_update().clear_bit()); - i2s.rx_conf.modify(|_, w| w.rx_update().set_bit()); - } - - fn reset_tx(&self) { - let i2s = self.register_block(); - i2s.tx_conf - .modify(|_, w| w.tx_reset().set_bit().tx_fifo_reset().set_bit()); - i2s.tx_conf - .modify(|_, w| w.tx_reset().clear_bit().tx_fifo_reset().clear_bit()); - - i2s.int_clr - .write(|w| w.tx_done_int_clr().set_bit().tx_hung_int_clr().set_bit()); - } - - fn tx_start(&self) { - let i2s = self.register_block(); - i2s.tx_conf.modify(|_, w| w.tx_start().set_bit()); - } - - fn wait_for_tx_done(&self) { - let i2s = self.register_block(); - while i2s.state.read().tx_idle().bit_is_clear() { - // wait - } - - i2s.tx_conf.modify(|_, w| w.tx_start().clear_bit()); - } - - fn reset_rx(&self) { - let i2s = self.register_block(); - i2s.rx_conf - .modify(|_, w| w.rx_reset().set_bit().rx_fifo_reset().set_bit()); - i2s.rx_conf - .modify(|_, w| w.rx_reset().clear_bit().rx_fifo_reset().clear_bit()); - - i2s.int_clr - .write(|w| w.rx_done_int_clr().set_bit().rx_hung_int_clr().set_bit()); - } - - fn rx_start(&self, len: usize) { - let i2s = self.register_block(); - i2s.rxeof_num.write(|w| w.rx_eof_num().variant(len as u16)); - i2s.rx_conf.modify(|_, w| w.rx_start().set_bit()); - } - - fn wait_for_rx_done(&self) { - let i2s = self.register_block(); - while i2s.int_raw.read().rx_done_int_raw().bit_is_clear() { - // wait - } - - i2s.int_clr.write(|w| w.rx_done_int_clr().set_bit()); - } - } - - #[derive(Clone)] - pub struct I2sPeripheral0 {} - - #[cfg(any(esp32s3, esp32))] - #[derive(Clone)] - pub struct I2sPeripheral1 {} - - #[cfg(any(esp32c3, esp32c6))] - impl Signals for I2sPeripheral0 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s0 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s0 - } - - fn mclk_signal(&self) -> OutputSignal { - OutputSignal::I2S_MCLK - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2SO_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2SO_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2SO_SD - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2SI_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2SI_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2SI_SD - } - } - - #[cfg(esp32s3)] - impl Signals for I2sPeripheral0 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s0 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s0 - } - - fn mclk_signal(&self) -> OutputSignal { - OutputSignal::I2S0_MCLK - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_SD - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2S0I_SD - } - } - - #[cfg(esp32s3)] - impl Signals for I2sPeripheral1 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s1 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s1 - } - - fn mclk_signal(&self) -> OutputSignal { - OutputSignal::I2S1_MCLK - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_SD - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S1I_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S1I_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2S1I_SD - } - } - - #[cfg(esp32)] - impl Signals for I2sPeripheral0 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s0 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s0 - } - - fn mclk_signal(&self) -> OutputSignal { - panic!("MCLK currently not supported on ESP32"); - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_DATA_23 - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2S0I_DATA_15 - } - } - - #[cfg(esp32)] - impl Signals for I2sPeripheral1 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s1 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s1 - } - - fn mclk_signal(&self) -> OutputSignal { - panic!("MCLK currently not supported on ESP32"); - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2S1O_DATA_23 - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S1I_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S1I_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2S1I_DATA_15 - } - } - - #[cfg(esp32s2)] - impl Signals for I2sPeripheral0 { - fn get_peripheral(&self) -> Peripheral { - Peripheral::I2s0 - } - - fn get_dma_peripheral(&self) -> DmaPeripheral { - DmaPeripheral::I2s0 - } - - fn mclk_signal(&self) -> OutputSignal { - OutputSignal::CLK_I2S - } - - fn bclk_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_BCK - } - - fn ws_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_WS - } - - fn dout_signal(&self) -> OutputSignal { - OutputSignal::I2S0O_DATA_OUT23 - } - - fn bclk_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_BCK - } - - fn ws_rx_signal(&self) -> OutputSignal { - OutputSignal::I2S0I_WS - } - - fn din_signal(&self) -> InputSignal { - InputSignal::I2S0I_DATA_IN15 - } - } - - impl RegBlock for I2sPeripheral0 { - fn register_block(&self) -> &'static RegisterBlock { - unsafe { core::mem::transmute(I2S0::PTR) } - } - } - - #[cfg(any(esp32s3, esp32))] - impl RegBlock for I2sPeripheral1 { - fn register_block(&self) -> &'static RegisterBlock { - unsafe { core::mem::transmute(crate::peripherals::I2S1::PTR) } - } - } - - impl RegisterAccessPrivate for I2sPeripheral0 {} - impl super::RegisterAccess for I2sPeripheral0 {} - - #[cfg(any(esp32s3, esp32))] - impl RegisterAccessPrivate for I2sPeripheral1 {} - #[cfg(any(esp32s3, esp32))] - impl super::RegisterAccess for I2sPeripheral1 {} - - pub struct I2sClockDividers { - mclk_divider: u32, - bclk_divider: u32, - denominator: u32, - numerator: u32, - } - - pub fn calculate_clock( - sample_rate: impl Into, - channels: u8, - data_bits: u8, - _clocks: &Clocks, - ) -> I2sClockDividers { - // this loosely corresponds to `i2s_std_calculate_clock` and - // `i2s_ll_tx_set_mclk` in esp-idf - // - // main difference is we are using fixed-point arithmetic here - - // If data_bits is a power of two, use 256 as the mclk_multiple - // If data_bits is 24, use 192 (24 * 8) as the mclk_multiple - let mclk_multiple = if data_bits == 24 { 192 } else { 256 }; - let sclk = 160_000_000; // for now it's fixed PLL_160M_CLK - - let rate_hz: HertzU32 = sample_rate.into(); - let rate = rate_hz.raw(); - - let bclk = rate * channels as u32 * data_bits as u32; - let mclk = rate * mclk_multiple; - let bclk_divider = mclk / bclk; - let mut mclk_divider = sclk / mclk; - - let mut ma: u32; - let mut mb: u32; - let mut denominator: u32 = 0; - let mut numerator: u32 = 0; - - let freq_diff = sclk.abs_diff(mclk * mclk_divider); - - if freq_diff != 0 { - let decimal = freq_diff as u64 * 10000 / mclk as u64; - - // Carry bit if the decimal is greater than 1.0 - 1.0 / (63.0 * 2) = 125.0 / - // 126.0 - if decimal > 1250000 / 126 { - mclk_divider += 1; - } else { - let mut min: u32 = !0; - - for a in 2..=I2S_LL_MCLK_DIVIDER_MAX { - let b = (a as u64) * (freq_diff as u64 * 10000u64 / mclk as u64) + 5000; - ma = ((freq_diff as u64 * 10000u64 * a as u64) / 10000) as u32; - mb = (mclk as u64 * (b / 10000)) as u32; - - if ma == mb { - denominator = a as u32; - numerator = (b / 10000) as u32; - break; - } - - if mb.abs_diff(ma) < min { - denominator = a as u32; - numerator = b as u32; - min = mb.abs_diff(ma); - } - } - } - } - - I2sClockDividers { - mclk_divider, - bclk_divider, - denominator, - numerator, - } - } -} diff --git a/esp-hal-common/src/interrupt/mod.rs b/esp-hal-common/src/interrupt/mod.rs deleted file mode 100644 index d68a8216350..00000000000 --- a/esp-hal-common/src/interrupt/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[cfg(riscv)] -pub use riscv::*; -#[cfg(xtensa)] -pub use xtensa::*; - -#[cfg(riscv)] -mod riscv; -#[cfg(xtensa)] -mod xtensa; diff --git a/esp-hal-common/src/interrupt/riscv.rs b/esp-hal-common/src/interrupt/riscv.rs deleted file mode 100644 index 864f63ad187..00000000000 --- a/esp-hal-common/src/interrupt/riscv.rs +++ /dev/null @@ -1,771 +0,0 @@ -//! Interrupt handling - RISCV -//! -//! When the `vectored` feature is enabled, CPU interrupts 1 through 15 are -//! reserved for each of the possible interrupt priorities. -//! -//! On chips with a PLIC CPU interrupts 1,2,5,6,9 .. 19 are used. -//! -//! ```rust -//! interrupt1() => Priority::Priority1 -//! interrupt2() => Priority::Priority2 -//! ... -//! interrupt15() => Priority::Priority15 -//! ``` - -use esp_riscv_rt::riscv::register::{mcause, mepc, mtvec}; -pub use esp_riscv_rt::TrapFrame; - -#[cfg(not(plic))] -pub use self::classic::*; -#[cfg(plic)] -pub use self::plic::*; -use crate::{ - peripherals::{self, Interrupt}, - Cpu, -}; - -// User code shouldn't usually take the mutable TrapFrame or the TrapFrame in -// general. However this makes things like preemtive multitasking easier in -// future -extern "C" { - fn interrupt1(frame: &mut TrapFrame); - fn interrupt2(frame: &mut TrapFrame); - fn interrupt3(frame: &mut TrapFrame); - fn interrupt4(frame: &mut TrapFrame); - fn interrupt5(frame: &mut TrapFrame); - fn interrupt6(frame: &mut TrapFrame); - fn interrupt7(frame: &mut TrapFrame); - fn interrupt8(frame: &mut TrapFrame); - fn interrupt9(frame: &mut TrapFrame); - fn interrupt10(frame: &mut TrapFrame); - fn interrupt11(frame: &mut TrapFrame); - fn interrupt12(frame: &mut TrapFrame); - fn interrupt13(frame: &mut TrapFrame); - fn interrupt14(frame: &mut TrapFrame); - fn interrupt15(frame: &mut TrapFrame); - fn interrupt16(frame: &mut TrapFrame); - fn interrupt17(frame: &mut TrapFrame); - fn interrupt18(frame: &mut TrapFrame); - fn interrupt19(frame: &mut TrapFrame); - fn interrupt20(frame: &mut TrapFrame); - fn interrupt21(frame: &mut TrapFrame); - fn interrupt22(frame: &mut TrapFrame); - fn interrupt23(frame: &mut TrapFrame); - fn interrupt24(frame: &mut TrapFrame); - fn interrupt25(frame: &mut TrapFrame); - fn interrupt26(frame: &mut TrapFrame); - fn interrupt27(frame: &mut TrapFrame); - fn interrupt28(frame: &mut TrapFrame); - fn interrupt29(frame: &mut TrapFrame); - fn interrupt30(frame: &mut TrapFrame); - fn interrupt31(frame: &mut TrapFrame); -} - -/// Interrupt kind -pub enum InterruptKind { - /// Level interrupt - Level, - /// Edge interrupt - Edge, -} - -/// Enumeration of available CPU interrupts. -/// It is possible to create a handler for each of the interrupts. (e.g. -/// `interrupt3`) -#[repr(u32)] -#[derive(Debug, Copy, Clone)] -pub enum CpuInterrupt { - Interrupt1 = 1, - Interrupt2, - Interrupt3, - Interrupt4, - Interrupt5, - Interrupt6, - Interrupt7, - Interrupt8, - Interrupt9, - Interrupt10, - Interrupt11, - Interrupt12, - Interrupt13, - Interrupt14, - Interrupt15, - Interrupt16, - Interrupt17, - Interrupt18, - Interrupt19, - Interrupt20, - Interrupt21, - Interrupt22, - Interrupt23, - Interrupt24, - Interrupt25, - Interrupt26, - Interrupt27, - Interrupt28, - Interrupt29, - Interrupt30, - Interrupt31, -} - -/// Interrupt priority levels. -#[repr(u8)] -pub enum Priority { - None = 0, - Priority1, - Priority2, - Priority3, - Priority4, - Priority5, - Priority6, - Priority7, - Priority8, - Priority9, - Priority10, - Priority11, - Priority12, - Priority13, - Priority14, - Priority15, -} - -impl Priority { - pub fn max() -> Priority { - Priority::Priority15 - } - - pub fn min() -> Priority { - Priority::Priority1 - } -} - -#[cfg(feature = "vectored")] -pub use vectored::*; - -#[cfg(feature = "vectored")] -mod vectored { - use procmacros::ram; - - use super::*; - - // Setup interrupts ready for vectoring - #[doc(hidden)] - pub(crate) unsafe fn init_vectoring() { - for (prio, num) in PRIORITY_TO_INTERRUPT.iter().enumerate() { - set_kind( - crate::get_core(), - core::mem::transmute(*num as u32), - InterruptKind::Level, - ); - set_priority( - crate::get_core(), - core::mem::transmute(*num as u32), - core::mem::transmute((prio as u8) + 1), - ); - enable_cpu_interrupt(core::mem::transmute(*num as u32)); - } - } - - /// Get the interrupts configured for the core - #[inline] - fn get_configured_interrupts(_core: Cpu, mut status: u128) -> [u128; 16] { - unsafe { - let mut prios = [0u128; 16]; - - while status != 0 { - let interrupt_nr = status.trailing_zeros() as u16; - // safety: cast is safe because of repr(u16) - let cpu_interrupt: CpuInterrupt = - get_assigned_cpu_interrupt(core::mem::transmute(interrupt_nr as u16)); - let prio = get_priority(cpu_interrupt); - - prios[prio as usize] |= 1 << (interrupt_nr as usize); - status &= !(1u128 << interrupt_nr); - } - - prios - } - } - - /// Interrupt Error - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub enum Error { - InvalidInterruptPriority, - } - - /// Enables a interrupt at a given priority - /// - /// Note that interrupts still need to be enabled globally for interrupts - /// to be serviced. - pub fn enable(interrupt: Interrupt, level: Priority) -> Result<(), Error> { - if matches!(level, Priority::None) { - return Err(Error::InvalidInterruptPriority); - } - unsafe { - let cpu_interrupt = - core::mem::transmute(PRIORITY_TO_INTERRUPT[(level as usize) - 1] as u32); - map(crate::get_core(), interrupt, cpu_interrupt); - enable_cpu_interrupt(cpu_interrupt); - } - Ok(()) - } - - #[ram] - unsafe fn handle_interrupts(cpu_intr: CpuInterrupt, context: &mut TrapFrame) { - let status = get_status(crate::get_core()); - - // this has no effect on level interrupts, but the interrupt may be an edge one - // so we clear it anyway - clear(crate::get_core(), cpu_intr); - - let configured_interrupts = get_configured_interrupts(crate::get_core(), status); - let mut interrupt_mask = - status & configured_interrupts[INTERRUPT_TO_PRIORITY[cpu_intr as usize - 1]]; - while interrupt_mask != 0 { - let interrupt_nr = interrupt_mask.trailing_zeros(); - // Interrupt::try_from can fail if interrupt already de-asserted: - // silently ignore - if let Ok(interrupt) = peripherals::Interrupt::try_from(interrupt_nr as u8) { - handle_interrupt(interrupt, context) - } - interrupt_mask &= !(1u128 << interrupt_nr); - } - } - - #[ram] - unsafe fn handle_interrupt(interrupt: Interrupt, save_frame: &mut TrapFrame) { - extern "C" { - // defined in each hal - fn EspDefaultHandler(interrupt: Interrupt); - } - let handler = peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler; - if handler as *const _ == EspDefaultHandler as *const unsafe extern "C" fn() { - EspDefaultHandler(interrupt); - } else { - let handler: fn(&mut TrapFrame) = core::mem::transmute(handler); - handler(save_frame); - } - } - - #[no_mangle] - #[ram] - pub unsafe fn interrupt1(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt1, context) - } - - #[no_mangle] - #[ram] - pub unsafe fn interrupt2(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt2, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt3(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt3, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt4(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt4, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt5(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt5, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt6(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt6, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt7(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt7, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt8(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt8, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt9(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt9, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt10(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt10, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt11(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt11, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt12(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt12, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt13(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt13, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt14(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt14, context) - } - #[no_mangle] - #[ram] - pub unsafe fn interrupt15(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt15, context) - } - #[cfg(plic)] - #[no_mangle] - #[ram] - pub unsafe fn interrupt16(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt16, context) - } - #[cfg(plic)] - #[no_mangle] - #[ram] - pub unsafe fn interrupt17(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt17, context) - } - #[cfg(plic)] - #[no_mangle] - #[ram] - pub unsafe fn interrupt18(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt18, context) - } - #[cfg(plic)] - #[no_mangle] - #[ram] - pub unsafe fn interrupt19(context: &mut TrapFrame) { - handle_interrupts(CpuInterrupt::Interrupt19, context) - } -} - -/// # Safety -/// -/// This function is called from an assembly trap handler. -#[doc(hidden)] -#[link_section = ".trap.rust"] -#[export_name = "_start_trap_rust_hal"] -pub unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) { - extern "C" { - // defined in riscv-rt - pub fn DefaultHandler(); - } - - let cause = mcause::read(); - if cause.is_exception() { - let pc = mepc::read(); - handle_exception(pc, trap_frame); - } else { - let code = mcause::read().code(); - match code { - 1 => interrupt1(trap_frame.as_mut().unwrap()), - 2 => interrupt2(trap_frame.as_mut().unwrap()), - 3 => interrupt3(trap_frame.as_mut().unwrap()), - 4 => interrupt4(trap_frame.as_mut().unwrap()), - 5 => interrupt5(trap_frame.as_mut().unwrap()), - 6 => interrupt6(trap_frame.as_mut().unwrap()), - 7 => interrupt7(trap_frame.as_mut().unwrap()), - 8 => interrupt8(trap_frame.as_mut().unwrap()), - 9 => interrupt9(trap_frame.as_mut().unwrap()), - 10 => interrupt10(trap_frame.as_mut().unwrap()), - 11 => interrupt11(trap_frame.as_mut().unwrap()), - 12 => interrupt12(trap_frame.as_mut().unwrap()), - 13 => interrupt13(trap_frame.as_mut().unwrap()), - 14 => interrupt14(trap_frame.as_mut().unwrap()), - 15 => interrupt15(trap_frame.as_mut().unwrap()), - 16 => interrupt16(trap_frame.as_mut().unwrap()), - 17 => interrupt17(trap_frame.as_mut().unwrap()), - 18 => interrupt18(trap_frame.as_mut().unwrap()), - 19 => interrupt19(trap_frame.as_mut().unwrap()), - 20 => interrupt20(trap_frame.as_mut().unwrap()), - 21 => interrupt21(trap_frame.as_mut().unwrap()), - 22 => interrupt22(trap_frame.as_mut().unwrap()), - 23 => interrupt23(trap_frame.as_mut().unwrap()), - 24 => interrupt24(trap_frame.as_mut().unwrap()), - 25 => interrupt25(trap_frame.as_mut().unwrap()), - 26 => interrupt26(trap_frame.as_mut().unwrap()), - 27 => interrupt27(trap_frame.as_mut().unwrap()), - 28 => interrupt28(trap_frame.as_mut().unwrap()), - 29 => interrupt29(trap_frame.as_mut().unwrap()), - 30 => interrupt30(trap_frame.as_mut().unwrap()), - 31 => interrupt31(trap_frame.as_mut().unwrap()), - _ => DefaultHandler(), - }; - } -} - -/// Apply atomic emulation if needed. Call the default exception handler -/// otherwise. -/// -/// # Safety -/// -/// This function is called from an trap handler. -#[doc(hidden)] -unsafe fn handle_exception(pc: usize, trap_frame: *mut TrapFrame) { - let insn: usize = *(pc as *const _); - let needs_atomic_emulation = (insn & 0b1111111) == 0b0101111; - - if !needs_atomic_emulation { - extern "C" { - fn ExceptionHandler(tf: *mut TrapFrame); - } - ExceptionHandler(trap_frame); - - return; - } - - let mut frame = [ - 0, - (*trap_frame).ra, - (*trap_frame).sp, - (*trap_frame).gp, - (*trap_frame).tp, - (*trap_frame).t0, - (*trap_frame).t1, - (*trap_frame).t2, - (*trap_frame).s0, - (*trap_frame).s1, - (*trap_frame).a0, - (*trap_frame).a1, - (*trap_frame).a2, - (*trap_frame).a3, - (*trap_frame).a4, - (*trap_frame).a5, - (*trap_frame).a6, - (*trap_frame).a7, - (*trap_frame).s2, - (*trap_frame).s3, - (*trap_frame).s4, - (*trap_frame).s5, - (*trap_frame).s6, - (*trap_frame).s7, - (*trap_frame).s8, - (*trap_frame).s9, - (*trap_frame).s10, - (*trap_frame).s11, - (*trap_frame).t3, - (*trap_frame).t4, - (*trap_frame).t5, - (*trap_frame).t6, - ]; - - riscv_atomic_emulation_trap::atomic_emulation((*trap_frame).pc, &mut frame); - - (*trap_frame).ra = frame[1]; - (*trap_frame).sp = frame[2]; - (*trap_frame).gp = frame[3]; - (*trap_frame).tp = frame[4]; - (*trap_frame).t0 = frame[5]; - (*trap_frame).t1 = frame[6]; - (*trap_frame).t2 = frame[7]; - (*trap_frame).s0 = frame[8]; - (*trap_frame).s1 = frame[9]; - (*trap_frame).a0 = frame[10]; - (*trap_frame).a1 = frame[11]; - (*trap_frame).a2 = frame[12]; - (*trap_frame).a3 = frame[13]; - (*trap_frame).a4 = frame[14]; - (*trap_frame).a5 = frame[15]; - (*trap_frame).a6 = frame[16]; - (*trap_frame).a7 = frame[17]; - (*trap_frame).s2 = frame[18]; - (*trap_frame).s3 = frame[19]; - (*trap_frame).s4 = frame[20]; - (*trap_frame).s5 = frame[21]; - (*trap_frame).s6 = frame[22]; - (*trap_frame).s7 = frame[23]; - (*trap_frame).s8 = frame[24]; - (*trap_frame).s9 = frame[25]; - (*trap_frame).s10 = frame[26]; - (*trap_frame).s11 = frame[27]; - (*trap_frame).t3 = frame[28]; - (*trap_frame).t4 = frame[29]; - (*trap_frame).t5 = frame[30]; - (*trap_frame).t6 = frame[31]; - (*trap_frame).pc = pc + 4; -} - -#[doc(hidden)] -#[no_mangle] -pub fn _setup_interrupts() { - extern "C" { - static _vector_table: *const u32; - } - - unsafe { - // disable all known interrupts - // at least after the 2nd stage bootloader there are some interrupts enabled - // (e.g. UART) - for peripheral_interrupt in 0..255 { - crate::soc::peripherals::Interrupt::try_from(peripheral_interrupt) - .map(|intr| { - #[cfg(multi_core)] - disable(Cpu::AppCpu, intr); - disable(Cpu::ProCpu, intr); - }) - .ok(); - } - - let vec_table = &_vector_table as *const _ as usize; - mtvec::write(vec_table, mtvec::TrapMode::Vectored); - - #[cfg(feature = "vectored")] - crate::interrupt::init_vectoring(); - }; - - #[cfg(plic)] - unsafe { - core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX); - } - - crate::common_init(); -} - -/// Disable the given peripheral interrupt. -pub fn disable(_core: Cpu, interrupt: Interrupt) { - unsafe { - let interrupt_number = interrupt as isize; - let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32; - - // set to 0 to disable the peripheral interrupt - intr_map_base.offset(interrupt_number).write_volatile(0); - } -} - -/// Get status of peripheral interrupts -#[inline] -pub fn get_status(_core: Cpu) -> u128 { - #[cfg(large_intr_status)] - unsafe { - ((*crate::peripherals::INTERRUPT_CORE0::PTR) - .intr_status_reg_0 - .read() - .bits() as u128) - | ((*crate::peripherals::INTERRUPT_CORE0::PTR) - .intr_status_reg_1 - .read() - .bits() as u128) - << 32 - | ((*crate::peripherals::INTERRUPT_CORE0::PTR) - .int_status_reg_2 - .read() - .bits() as u128) - << 64 - } - - #[cfg(not(large_intr_status))] - unsafe { - ((*crate::peripherals::INTERRUPT_CORE0::PTR) - .intr_status_reg_0 - .read() - .bits() as u128) - | ((*crate::peripherals::INTERRUPT_CORE0::PTR) - .intr_status_reg_1 - .read() - .bits() as u128) - << 32 - } -} - -/// Assign a peripheral interrupt to an CPU interrupt. -/// -/// Great care must be taken when using the `vectored` feature (enabled by -/// default). Avoid interrupts 1 - 15 when interrupt vectoring is enabled. -pub unsafe fn map(_core: Cpu, interrupt: Interrupt, which: CpuInterrupt) { - let interrupt_number = interrupt as isize; - let cpu_interrupt_number = which as isize; - let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32; - intr_map_base - .offset(interrupt_number) - .write_volatile(cpu_interrupt_number as u32); -} - -/// Get cpu interrupt assigned to peripheral interrupt -#[inline] -unsafe fn get_assigned_cpu_interrupt(interrupt: Interrupt) -> CpuInterrupt { - let interrupt_number = interrupt as isize; - let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32; - - let cpu_intr = intr_map_base.offset(interrupt_number).read_volatile(); - - core::mem::transmute(cpu_intr) -} - -#[cfg(not(plic))] -mod classic { - use super::{CpuInterrupt, InterruptKind, Priority}; - use crate::Cpu; - - pub(super) const PRIORITY_TO_INTERRUPT: [usize; 15] = - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - - pub(super) const INTERRUPT_TO_PRIORITY: [usize; 15] = - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - - /// Enable a CPU interrupt - pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) { - let cpu_interrupt_number = which as isize; - let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; - intr.cpu_int_enable - .modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits())); - } - - /// Set the interrupt kind (i.e. level or edge) of an CPU interrupt - /// - /// This is safe to call when the `vectored` feature is enabled. The - /// vectored interrupt handler will take care of clearing edge interrupt - /// bits. - pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) { - unsafe { - let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; - let cpu_interrupt_number = which as isize; - - let interrupt_type = match kind { - InterruptKind::Level => 0, - InterruptKind::Edge => 1, - }; - intr.cpu_int_type.modify(|r, w| { - w.bits( - r.bits() & !(1 << cpu_interrupt_number) - | (interrupt_type << cpu_interrupt_number), - ) - }); - } - } - - /// Set the priority level of an CPU interrupt - /// - /// Great care must be taken when using the `vectored` feature (enabled by - /// default). Avoid changing the priority of interrupts 1 - 15 when - /// interrupt vectoring is enabled. - pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) { - let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; - let cpu_interrupt_number = which as isize; - let intr_prio_base = intr.cpu_int_pri_0.as_ptr(); - - intr_prio_base - .offset(cpu_interrupt_number) - .write_volatile(priority as u32); - } - - /// Clear a CPU interrupt - #[inline] - pub fn clear(_core: Cpu, which: CpuInterrupt) { - unsafe { - let cpu_interrupt_number = which as isize; - let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; - intr.cpu_int_clear - .write(|w| w.bits(1 << cpu_interrupt_number)); - } - } - - /// Get interrupt priority - #[inline] - pub(super) unsafe fn get_priority(cpu_interrupt: CpuInterrupt) -> Priority { - let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; - let intr_prio_base = intr.cpu_int_pri_0.as_ptr(); - - let prio = intr_prio_base - .offset(cpu_interrupt as isize) - .read_volatile(); - core::mem::transmute(prio as u8) - } -} - -#[cfg(plic)] -mod plic { - use super::{CpuInterrupt, InterruptKind, Priority}; - use crate::Cpu; - - // don't use interrupts reserved for CLIC (0,3,4,7) - // for some reason also CPU interrupt 8 doesn't work by default since it's - // disabled after reset - so don't use that, too - pub(super) const PRIORITY_TO_INTERRUPT: [usize; 15] = - [1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; - - pub(super) const INTERRUPT_TO_PRIORITY: [usize; 19] = [ - 1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - ]; - - const DR_REG_PLIC_MX_BASE: u32 = 0x20001000; - const PLIC_MXINT_ENABLE_REG: u32 = DR_REG_PLIC_MX_BASE + 0x0; - const PLIC_MXINT_TYPE_REG: u32 = DR_REG_PLIC_MX_BASE + 0x4; - const PLIC_MXINT_CLEAR_REG: u32 = DR_REG_PLIC_MX_BASE + 0x8; - const PLIC_MXINT0_PRI_REG: u32 = DR_REG_PLIC_MX_BASE + 0x10; - - /// Enable a CPU interrupt - pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) { - let cpu_interrupt_number = which as isize; - let mxint_enable = PLIC_MXINT_ENABLE_REG as *mut u32; - unsafe { - mxint_enable.write_volatile(mxint_enable.read_volatile() | 1 << cpu_interrupt_number); - } - } - - /// Set the interrupt kind (i.e. level or edge) of an CPU interrupt - /// - /// This is safe to call when the `vectored` feature is enabled. The - /// vectored interrupt handler will take care of clearing edge interrupt - /// bits. - pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) { - unsafe { - let intr = PLIC_MXINT_TYPE_REG as *mut u32; - let cpu_interrupt_number = which as isize; - - let interrupt_type = match kind { - InterruptKind::Level => 0, - InterruptKind::Edge => 1, - }; - intr.write_volatile( - intr.read_volatile() & !(1 << cpu_interrupt_number) - | (interrupt_type << cpu_interrupt_number), - ); - } - } - - /// Set the priority level of an CPU interrupt - /// - /// Great care must be taken when using the `vectored` feature (enabled by - /// default). Avoid changing the priority of interrupts 1 - 15 when - /// interrupt vectoring is enabled. - pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) { - let plic_mxint_pri_ptr = PLIC_MXINT0_PRI_REG as *mut u32; - - let cpu_interrupt_number = which as isize; - plic_mxint_pri_ptr - .offset(cpu_interrupt_number) - .write_volatile(priority as u32); - } - - /// Clear a CPU interrupt - #[inline] - pub fn clear(_core: Cpu, which: CpuInterrupt) { - unsafe { - let cpu_interrupt_number = which as isize; - let intr = PLIC_MXINT_CLEAR_REG as *mut u32; - intr.write_volatile(1 << cpu_interrupt_number); - } - } - - /// Get interrupt priority - #[inline] - pub(super) unsafe fn get_priority(cpu_interrupt: CpuInterrupt) -> Priority { - let plic_mxint_pri_ptr = PLIC_MXINT0_PRI_REG as *mut u32; - - let cpu_interrupt_number = cpu_interrupt as isize; - let prio = plic_mxint_pri_ptr - .offset(cpu_interrupt_number) - .read_volatile(); - core::mem::transmute(prio as u8) - } -} diff --git a/esp-hal-common/src/interrupt/xtensa.rs b/esp-hal-common/src/interrupt/xtensa.rs deleted file mode 100644 index 9da4268dce5..00000000000 --- a/esp-hal-common/src/interrupt/xtensa.rs +++ /dev/null @@ -1,540 +0,0 @@ -use xtensa_lx::interrupt::{self, InterruptNumber}; -use xtensa_lx_rt::exception::Context; - -use crate::{ - peripherals::{self, Interrupt}, - Cpu, -}; - -/// Enumeration of available CPU interrupts -/// It's possible to create one handler per priority level. (e.g -/// `level1_interrupt`) -#[allow(unused)] -#[derive(Debug, Copy, Clone)] -#[repr(u32)] -pub enum CpuInterrupt { - Interrupt0LevelPriority1 = 0, - Interrupt1LevelPriority1, - Interrupt2LevelPriority1, - Interrupt3LevelPriority1, - Interrupt4LevelPriority1, - Interrupt5LevelPriority1, - Interrupt6Timer0Priority1, - Interrupt7SoftwarePriority1, - Interrupt8LevelPriority1, - Interrupt9LevelPriority1, - Interrupt10EdgePriority1, - Interrupt11ProfilingPriority3, - Interrupt12LevelPriority1, - Interrupt13LevelPriority1, - Interrupt14NmiPriority7, - Interrupt15Timer1Priority3, - Interrupt16Timer2Priority5, - Interrupt17LevelPriority1, - Interrupt18LevelPriority1, - Interrupt19LevelPriority2, - Interrupt20LevelPriority2, - Interrupt21LevelPriority2, - Interrupt22EdgePriority3, - Interrupt23LevelPriority3, - Interrupt24LevelPriority4, - Interrupt25LevelPriority4, - Interrupt26LevelPriority5, - Interrupt27LevelPriority3, - Interrupt28EdgePriority4, - Interrupt29SoftwarePriority3, - Interrupt30EdgePriority4, - Interrupt31EdgePriority5, -} - -/// Assign a peripheral interrupt to an CPU interrupt. -/// -/// Great care **must** be taken when using this function with interrupt -/// vectoring (enabled by default). Avoid the following CPU interrupts: -/// - Interrupt1LevelPriority1 -/// - Interrupt19LevelPriority2 -/// - Interrupt23LevelPriority3 -/// - Interrupt10EdgePriority1 -/// - Interrupt22EdgePriority3 -/// As they are preallocated for interrupt vectoring. -/// -/// Note: this only maps the interrupt to the CPU interrupt. The CPU interrupt -/// still needs to be enabled afterwards -pub unsafe fn map(core: Cpu, interrupt: Interrupt, which: CpuInterrupt) { - let interrupt_number = interrupt as isize; - let cpu_interrupt_number = which as isize; - let intr_map_base = match core { - Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(), - #[cfg(multi_core)] - Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map.as_ptr(), - }; - intr_map_base - .offset(interrupt_number) - .write_volatile(cpu_interrupt_number as u32); -} - -/// Disable the given peripheral interrupt. -pub fn disable(core: Cpu, interrupt: Interrupt) { - unsafe { - let interrupt_number = interrupt as isize; - let intr_map_base = match core { - Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(), - #[cfg(multi_core)] - Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map.as_ptr(), - }; - intr_map_base.offset(interrupt_number).write_volatile(0); - } -} - -/// Clear the given CPU interrupt -pub fn clear(_core: Cpu, which: CpuInterrupt) { - unsafe { - xtensa_lx::interrupt::clear(1 << which as u32); - } -} - -/// Get status of peripheral interrupts -pub fn get_status(core: Cpu) -> u128 { - unsafe { - match core { - Cpu::ProCpu => { - ((*core0_interrupt_peripheral()) - .pro_intr_status_0 - .read() - .bits() as u128) - | ((*core0_interrupt_peripheral()) - .pro_intr_status_1 - .read() - .bits() as u128) - << 32 - | ((*core0_interrupt_peripheral()) - .pro_intr_status_2 - .read() - .bits() as u128) - << 64 - } - #[cfg(multi_core)] - Cpu::AppCpu => { - ((*core1_interrupt_peripheral()) - .app_intr_status_0 - .read() - .bits() as u128) - | ((*core1_interrupt_peripheral()) - .app_intr_status_1 - .read() - .bits() as u128) - << 32 - | ((*core1_interrupt_peripheral()) - .app_intr_status_2 - .read() - .bits() as u128) - << 64 - } - } - } -} - -#[cfg(esp32)] -unsafe fn core0_interrupt_peripheral() -> *const crate::peripherals::dport::RegisterBlock { - crate::peripherals::DPORT::PTR -} - -#[cfg(esp32)] -unsafe fn core1_interrupt_peripheral() -> *const crate::peripherals::dport::RegisterBlock { - crate::peripherals::DPORT::PTR -} - -#[cfg(any(esp32s2, esp32s3))] -unsafe fn core0_interrupt_peripheral() -> *const crate::peripherals::interrupt_core0::RegisterBlock -{ - crate::peripherals::INTERRUPT_CORE0::PTR -} - -#[cfg(esp32s3)] -unsafe fn core1_interrupt_peripheral() -> *const crate::peripherals::interrupt_core1::RegisterBlock -{ - crate::peripherals::INTERRUPT_CORE1::PTR -} - -#[cfg(feature = "vectored")] -pub use vectored::*; - -#[cfg(feature = "vectored")] -mod vectored { - use procmacros::ram; - - use super::*; - use crate::get_core; - - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub enum Error { - InvalidInterrupt, - } - - /// Interrupt priority levels. - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[repr(u8)] - pub enum Priority { - None = 0, - Priority1, - Priority2, - Priority3, - } - - impl Priority { - pub fn max() -> Priority { - Priority::Priority3 - } - - pub fn min() -> Priority { - Priority::Priority1 - } - } - - impl CpuInterrupt { - #[inline] - fn level(&self) -> Priority { - match self { - CpuInterrupt::Interrupt0LevelPriority1 - | CpuInterrupt::Interrupt1LevelPriority1 - | CpuInterrupt::Interrupt2LevelPriority1 - | CpuInterrupt::Interrupt3LevelPriority1 - | CpuInterrupt::Interrupt4LevelPriority1 - | CpuInterrupt::Interrupt5LevelPriority1 - | CpuInterrupt::Interrupt6Timer0Priority1 - | CpuInterrupt::Interrupt7SoftwarePriority1 - | CpuInterrupt::Interrupt8LevelPriority1 - | CpuInterrupt::Interrupt9LevelPriority1 - | CpuInterrupt::Interrupt10EdgePriority1 - | CpuInterrupt::Interrupt12LevelPriority1 - | CpuInterrupt::Interrupt13LevelPriority1 - | CpuInterrupt::Interrupt17LevelPriority1 - | CpuInterrupt::Interrupt18LevelPriority1 => Priority::Priority1, - - CpuInterrupt::Interrupt19LevelPriority2 - | CpuInterrupt::Interrupt20LevelPriority2 - | CpuInterrupt::Interrupt21LevelPriority2 => Priority::Priority2, - - CpuInterrupt::Interrupt11ProfilingPriority3 - | CpuInterrupt::Interrupt15Timer1Priority3 - | CpuInterrupt::Interrupt22EdgePriority3 - | CpuInterrupt::Interrupt27LevelPriority3 - | CpuInterrupt::Interrupt29SoftwarePriority3 - | CpuInterrupt::Interrupt23LevelPriority3 => Priority::Priority3, - - // we direct these to None because we do not support interrupts at this level - // through Rust - CpuInterrupt::Interrupt24LevelPriority4 - | CpuInterrupt::Interrupt25LevelPriority4 - | CpuInterrupt::Interrupt28EdgePriority4 - | CpuInterrupt::Interrupt30EdgePriority4 - | CpuInterrupt::Interrupt31EdgePriority5 - | CpuInterrupt::Interrupt16Timer2Priority5 - | CpuInterrupt::Interrupt26LevelPriority5 - | CpuInterrupt::Interrupt14NmiPriority7 => Priority::None, - } - } - } - - /// Get the interrupts configured for the core - #[inline] - fn get_configured_interrupts(core: Cpu, mut status: u128) -> [u128; 8] { - unsafe { - let intr_map_base = match core { - Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(), - #[cfg(multi_core)] - Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map.as_ptr(), - }; - - let mut levels = [0u128; 8]; - - while status != 0 { - let interrupt_nr = status.trailing_zeros(); - let i = interrupt_nr as isize; - let cpu_interrupt = intr_map_base.offset(i).read_volatile(); - // safety: cast is safe because of repr(u32) - let cpu_interrupt: CpuInterrupt = core::mem::transmute(cpu_interrupt); - let level = cpu_interrupt.level() as u8 as usize; - - levels[level] |= 1 << i; - status &= !(1u128 << interrupt_nr); - } - - levels - } - } - - pub fn enable(interrupt: Interrupt, level: Priority) -> Result<(), Error> { - let cpu_interrupt = - interrupt_level_to_cpu_interrupt(level, chip_specific::interrupt_is_edge(interrupt))?; - - unsafe { - map(get_core(), interrupt, cpu_interrupt); - - xtensa_lx::interrupt::enable_mask( - xtensa_lx::interrupt::get_mask() | 1 << cpu_interrupt as u32, - ); - } - Ok(()) - } - - fn interrupt_level_to_cpu_interrupt( - level: Priority, - is_edge: bool, - ) -> Result { - Ok(if is_edge { - match level { - Priority::None => return Err(Error::InvalidInterrupt), - Priority::Priority1 => CpuInterrupt::Interrupt10EdgePriority1, - Priority::Priority2 => return Err(Error::InvalidInterrupt), - Priority::Priority3 => CpuInterrupt::Interrupt22EdgePriority3, - } - } else { - match level { - Priority::None => return Err(Error::InvalidInterrupt), - Priority::Priority1 => CpuInterrupt::Interrupt1LevelPriority1, - Priority::Priority2 => CpuInterrupt::Interrupt19LevelPriority2, - Priority::Priority3 => CpuInterrupt::Interrupt23LevelPriority3, - } - }) - } - - // TODO use CpuInterrupt::LevelX.mask() // TODO make it const - const CPU_INTERRUPT_LEVELS: [u32; 8] = [ - 0b_0000_0000_0000_0000_0000_0000_0000_0000, // Dummy level 0 - 0b_0000_0000_0000_0110_0011_0111_1111_1111, // Level_1 - 0b_0000_0000_0011_1000_0000_0000_0000_0000, // Level 2 - 0b_0010_1000_1100_0000_1000_1000_0000_0000, // Level 3 - 0b_0101_0011_0000_0000_0000_0000_0000_0000, // Level 4 - 0b_1000_0100_0000_0001_0000_0000_0000_0000, // Level 5 - 0b_0000_0000_0000_0000_0000_0000_0000_0000, // Level 6 - 0b_0000_0000_0000_0000_0100_0000_0000_0000, // Level 7 - ]; - const CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000; - const CPU_INTERRUPT_EDGE: u32 = 0b_0111_0000_0100_0000_0000_1100_1000_0000; - - #[inline] - fn cpu_interrupt_nr_to_cpu_interrupt_handler( - number: u32, - ) -> Option { - use xtensa_lx_rt::*; - // we're fortunate that all esp variants use the same CPU interrupt layout - Some(match number { - 6 => Timer0, - 7 => Software0, - 11 => Profiling, - 14 => NMI, - 15 => Timer1, - 16 => Timer2, - 29 => Software1, - _ => return None, - }) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_1_interrupt(level: u32, save_frame: &mut Context) { - handle_interrupts(level, save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_2_interrupt(level: u32, save_frame: &mut Context) { - handle_interrupts(level, save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_3_interrupt(level: u32, save_frame: &mut Context) { - handle_interrupts(level, save_frame) - } - - #[ram] - unsafe fn handle_interrupts(level: u32, save_frame: &mut Context) { - let cpu_interrupt_mask = - interrupt::get() & interrupt::get_mask() & CPU_INTERRUPT_LEVELS[level as usize]; - - if cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL != 0 { - let cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL; - let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros(); - - if (cpu_interrupt_mask & CPU_INTERRUPT_EDGE) != 0 { - interrupt::clear(1 << cpu_interrupt_nr); - } - if let Some(handler) = cpu_interrupt_nr_to_cpu_interrupt_handler(cpu_interrupt_nr) { - handler(level, save_frame); - } - } else { - if (cpu_interrupt_mask & CPU_INTERRUPT_EDGE) != 0 { - let cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_EDGE; - let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros(); - interrupt::clear(1 << cpu_interrupt_nr); - - // for edge interrupts cannot rely on the interrupt status - // register, therefore call all registered - // handlers for current level - let interrupt_levels = - get_configured_interrupts(crate::get_core(), chip_specific::INTERRUPT_EDGE); - let interrupt_mask = interrupt_levels[level as usize]; - let mut interrupt_mask = interrupt_mask & chip_specific::INTERRUPT_EDGE; - loop { - let interrupt_nr = interrupt_mask.trailing_zeros(); - if let Ok(interrupt) = peripherals::Interrupt::try_from(interrupt_nr as u16) { - handle_interrupt(level, interrupt, save_frame) - } else { - break; - } - interrupt_mask &= !(1u128 << interrupt_nr); - } - } else { - // finally check periperal sources and fire of handlers from pac - // peripheral mapped interrupts are cleared by the peripheral - let status = get_status(crate::get_core()); - let interrupt_levels = get_configured_interrupts(crate::get_core(), status); - let interrupt_mask = status & interrupt_levels[level as usize]; - let interrupt_nr = interrupt_mask.trailing_zeros(); - - // Interrupt::try_from can fail if interrupt already de-asserted: - // silently ignore - if let Ok(interrupt) = peripherals::Interrupt::try_from(interrupt_nr as u16) { - handle_interrupt(level, interrupt, save_frame); - } - } - } - } - - #[ram] - unsafe fn handle_interrupt(level: u32, interrupt: Interrupt, save_frame: &mut Context) { - extern "C" { - // defined in each hal - fn EspDefaultHandler(level: u32, interrupt: Interrupt); - } - - let handler = peripherals::__INTERRUPTS[interrupt.number() as usize]._handler; - if handler as *const _ == EspDefaultHandler as *const unsafe extern "C" fn() { - EspDefaultHandler(level, interrupt); - } else { - let handler: fn(&mut Context) = core::mem::transmute(handler); - handler(save_frame); - } - } - - #[cfg(esp32)] - mod chip_specific { - use super::*; - pub const INTERRUPT_EDGE: u128 = - 0b_0000_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0011_1111_1100_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0000; - #[inline] - pub fn interrupt_is_edge(interrupt: Interrupt) -> bool { - use peripherals::Interrupt::*; - [ - TG0_T0_EDGE, - TG0_T1_EDGE, - TG0_WDT_EDGE, - TG0_LACT_EDGE, - TG1_T0_EDGE, - TG1_T1_EDGE, - TG1_WDT_EDGE, - TG1_LACT_EDGE, - ] - .contains(&interrupt) - } - } - - #[cfg(esp32s2)] - mod chip_specific { - use super::*; - pub const INTERRUPT_EDGE: u128 = - 0b_0000_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0011_1011_1111_1100_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0000; - #[inline] - pub fn interrupt_is_edge(interrupt: Interrupt) -> bool { - use peripherals::Interrupt::*; - [ - TG0_T0_EDGE, - TG0_T1_EDGE, - TG0_WDT_EDGE, - TG0_LACT_EDGE, - TG1_T0_EDGE, - TG1_T1_EDGE, - TG1_WDT_EDGE, - TG1_LACT_EDGE, - SYSTIMER_TARGET0, - SYSTIMER_TARGET1, - SYSTIMER_TARGET2, - ] - .contains(&interrupt) - } - } - - #[cfg(esp32s3)] - mod chip_specific { - use super::*; - pub const INTERRUPT_EDGE: u128 = 0; - #[inline] - pub fn interrupt_is_edge(_interrupt: Interrupt) -> bool { - false - } - } -} - -mod raw { - use super::*; - - extern "C" { - #[cfg(not(feature = "vectored"))] - fn level1_interrupt(save_frame: &mut Context); - #[cfg(not(feature = "vectored"))] - fn level2_interrupt(save_frame: &mut Context); - #[cfg(not(feature = "vectored"))] - fn level3_interrupt(save_frame: &mut Context); - fn level4_interrupt(save_frame: &mut Context); - fn level5_interrupt(save_frame: &mut Context); - fn level6_interrupt(save_frame: &mut Context); - fn level7_interrupt(save_frame: &mut Context); - } - - #[no_mangle] - #[link_section = ".rwtext"] - #[cfg(not(feature = "vectored"))] - unsafe fn __level_1_interrupt(_level: u32, save_frame: &mut Context) { - level1_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - #[cfg(not(feature = "vectored"))] - unsafe fn __level_2_interrupt(_level: u32, save_frame: &mut Context) { - level2_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - #[cfg(not(feature = "vectored"))] - unsafe fn __level_3_interrupt(_level: u32, save_frame: &mut Context) { - level3_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_4_interrupt(_level: u32, save_frame: &mut Context) { - level4_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_5_interrupt(_level: u32, save_frame: &mut Context) { - level5_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_6_interrupt(_level: u32, save_frame: &mut Context) { - level6_interrupt(save_frame) - } - - #[no_mangle] - #[link_section = ".rwtext"] - unsafe fn __level_7_interrupt(_level: u32, save_frame: &mut Context) { - level7_interrupt(save_frame) - } -} diff --git a/esp-hal-common/src/ledc/channel.rs b/esp-hal-common/src/ledc/channel.rs deleted file mode 100644 index 30626607ebf..00000000000 --- a/esp-hal-common/src/ledc/channel.rs +++ /dev/null @@ -1,486 +0,0 @@ -use paste::paste; - -#[cfg(esp32)] -use super::HighSpeed; -use super::{ - timer::{TimerIFace, TimerSpeed}, - LowSpeed, -}; -use crate::{ - gpio::{OutputPin, OutputSignal}, - peripheral::{Peripheral, PeripheralRef}, - peripherals::ledc::RegisterBlock, -}; - -/// Channel errors -#[derive(Debug)] -pub enum Error { - /// Invalid duty % value - Duty, - /// Timer not configured - Timer, - /// Channel not configured - Channel, -} - -/// Channel number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum Number { - Channel0, - Channel1, - Channel2, - Channel3, - Channel4, - Channel5, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Channel6, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Channel7, -} - -/// Channel configuration -pub mod config { - use crate::ledc::timer::{TimerIFace, TimerSpeed}; - - #[derive(Copy, Clone)] - pub enum PinConfig { - PushPull, - OpenDrain, - } - - /// Channel configuration - #[derive(Copy, Clone)] - pub struct Config<'a, S: TimerSpeed> { - pub timer: &'a dyn TimerIFace, - pub duty_pct: u8, - pub pin_config: PinConfig, - } -} - -/// Channel interface -pub trait ChannelIFace<'a, S: TimerSpeed + 'a, O: OutputPin + 'a> -where - Channel<'a, S, O>: ChannelHW, -{ - /// Configure channel - fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>; - - /// Set channel duty HW - fn set_duty(&self, duty_pct: u8) -> Result<(), Error>; -} - -/// Channel HW interface -pub trait ChannelHW { - /// Configure Channel HW except for the duty which is set via - /// [`Self::set_duty_hw`]. - fn configure_hw(&mut self) -> Result<(), Error>; - fn configure_hw_with_pin_config(&mut self, cfg: config::PinConfig) -> Result<(), Error>; - - /// Set channel duty HW - fn set_duty_hw(&self, duty: u32); -} - -/// Channel struct -pub struct Channel<'a, S: TimerSpeed, O: OutputPin> { - ledc: &'a RegisterBlock, - timer: Option<&'a dyn TimerIFace>, - number: Number, - output_pin: PeripheralRef<'a, O>, -} - -impl<'a, S: TimerSpeed, O: OutputPin> Channel<'a, S, O> { - /// Return a new channel - pub fn new(number: Number, output_pin: impl Peripheral

    + 'a) -> Self { - crate::into_ref!(output_pin); - let ledc = unsafe { &*crate::peripherals::LEDC::ptr() }; - Channel { - ledc, - timer: None, - number, - output_pin, - } - } -} - -impl<'a, S: TimerSpeed, O: OutputPin> ChannelIFace<'a, S, O> for Channel<'a, S, O> -where - Channel<'a, S, O>: ChannelHW, -{ - /// Configure channel - fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> { - self.timer = Some(config.timer); - - self.set_duty(config.duty_pct)?; - self.configure_hw_with_pin_config(config.pin_config)?; - - Ok(()) - } - - /// Set duty % of channel - fn set_duty(&self, duty_pct: u8) -> Result<(), Error> { - let duty_exp; - if let Some(timer) = self.timer { - if let Some(timer_duty) = timer.get_duty() { - duty_exp = timer_duty as u32; - } else { - return Err(Error::Timer); - } - } else { - return Err(Error::Channel); - } - - let duty_range = 2u32.pow(duty_exp); - let duty_value = (duty_range * duty_pct as u32) as u32 / 100; - - if duty_pct > 100u8 { - // duty_pct greater than 100% - return Err(Error::Duty); - } - - self.set_duty_hw(duty_value); - - Ok(()) - } -} - -#[cfg(esp32)] -/// Macro to configure channel parameters in hw -macro_rules! set_channel { - ($self: ident, $speed: ident, $num: literal, $timer_number: ident) => {{ - paste! { - $self.ledc.[<$speed sch $num _hpoint>] - .write(|w| unsafe { w.[]().bits(0x0) }); - $self.ledc.[<$speed sch $num _conf0>].modify(|_, w| unsafe { - w.[]() - .set_bit() - .[]() - .bits($timer_number) - }); - } - start_duty_without_fading!($self, $speed, $num); - }}; -} - -#[cfg(not(esp32))] -/// Macro to configure channel parameters in hw -macro_rules! set_channel { - ($self: ident, $speed: ident, $num: literal, $timer_number: ident) => {{ - paste! { - $self.ledc.[] - .write(|w| unsafe { w.[]().bits(0x0) }); - $self.ledc.[].modify(|_, w| unsafe { - w.[]() - .set_bit() - .[]() - .bits($timer_number) - }); - } - start_duty_without_fading!($self, $num); - }}; -} - -#[cfg(esp32)] -/// Macro to start duty cycle, without fading -macro_rules! start_duty_without_fading { - ($self: ident, $speed: ident, $num: literal) => { - paste! { - $self.ledc.[<$speed sch $num _conf1>].write(|w| unsafe { - w.[]() - .set_bit() - .[]() - .set_bit() - .[]() - .bits(0x1) - .[]() - .bits(0x1) - .[]() - .bits(0x0) - }); - } - }; -} - -#[cfg(esp32c6)] -/// Macro to start duty cycle, without fading -macro_rules! start_duty_without_fading { - ($self: ident, $num: literal) => { - paste! { - $self.ledc.[].write(|w| - w.[]() - .set_bit() - ); - $self.ledc.[].write(|w| unsafe { - w.[]() - .set_bit() - .[]() - .bits(0x1) - .[]() - .bits(0x1) - .[]() - .bits(0x0) - }); - } - }; -} - -#[cfg(not(any(esp32, esp32c6)))] -/// Macro to start duty cycle, without fading -macro_rules! start_duty_without_fading { - ($self: ident, $num: literal) => { - paste! { - $self.ledc.[].write(|w| unsafe { - w.[]() - .set_bit() - .[]() - .set_bit() - .[]() - .bits(0x1) - .[]() - .bits(0x1) - .[]() - .bits(0x0) - }); - } - }; -} - -#[cfg(esp32)] -/// Macro to set duty parameters in hw -macro_rules! set_duty { - ($self: ident, $speed: ident, $num: literal, $duty: ident) => {{ - paste! { - $self.ledc - .[<$speed sch $num _duty>] - .write(|w| unsafe { w.[]().bits($duty << 4) }); - } - start_duty_without_fading!($self, $speed, $num); - update_channel!($self, $speed, $num); - }}; -} - -#[cfg(not(esp32))] -/// Macro to set duty parameters in hw -macro_rules! set_duty { - ($self: ident, $speed: ident, $num: literal, $duty: ident) => {{ - paste! { - $self.ledc - .[] - .write(|w| unsafe { w.[]().bits($duty << 4) }); - } - start_duty_without_fading!($self, $num); - update_channel!($self, $speed, $num); - }}; -} - -#[cfg(esp32)] -/// Macro to update channel configuration (only for LowSpeed channels) -macro_rules! update_channel { - ($self: ident, l, $num: literal) => { - paste! { - $self.ledc - .[] - .modify(|_, w| w.[]().set_bit()); - } - }; - ($self: ident, h, $num: literal) => {}; -} - -#[cfg(not(esp32))] -/// Macro to update channel configuration (only for LowSpeed channels) -macro_rules! update_channel { - ($self: ident, l, $num: literal) => { - paste! { - $self.ledc - .[] - .modify(|_, w| w.[]().set_bit()); - } - }; -} - -#[cfg(esp32)] -/// Channel HW interface for HighSpeed channels -impl<'a, O> ChannelHW for Channel<'a, HighSpeed, O> -where - O: OutputPin, -{ - /// Configure Channel HW except for the duty which is set via - /// [`Self::set_duty_hw`]. - fn configure_hw(&mut self) -> Result<(), Error> { - self.configure_hw_with_pin_config(config::PinConfig::PushPull) - } - fn configure_hw_with_pin_config(&mut self, cfg: config::PinConfig) -> Result<(), Error> { - if let Some(timer) = self.timer { - if !timer.is_configured() { - return Err(Error::Timer); - } - - match cfg { - config::PinConfig::PushPull => self.output_pin.set_to_push_pull_output(), - config::PinConfig::OpenDrain => self.output_pin.set_to_open_drain_output(), - }; - - let timer_number = timer.get_number() as u8; - match self.number { - Number::Channel0 => { - set_channel!(self, h, 0, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG0); - } - Number::Channel1 => { - set_channel!(self, h, 1, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG1); - } - Number::Channel2 => { - set_channel!(self, h, 2, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG2); - } - Number::Channel3 => { - set_channel!(self, h, 3, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG3); - } - Number::Channel4 => { - set_channel!(self, h, 4, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG4); - } - Number::Channel5 => { - set_channel!(self, h, 5, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG5); - } - Number::Channel6 => { - set_channel!(self, h, 6, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG6); - } - Number::Channel7 => { - set_channel!(self, h, 7, timer_number); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG7); - } - } - } else { - return Err(Error::Timer); - } - - Ok(()) - } - - /// Set duty in channel HW - fn set_duty_hw(&self, duty: u32) { - match self.number { - Number::Channel0 => set_duty!(self, h, 0, duty), - Number::Channel1 => set_duty!(self, h, 1, duty), - Number::Channel2 => set_duty!(self, h, 2, duty), - Number::Channel3 => set_duty!(self, h, 3, duty), - Number::Channel4 => set_duty!(self, h, 4, duty), - Number::Channel5 => set_duty!(self, h, 5, duty), - Number::Channel6 => set_duty!(self, h, 6, duty), - Number::Channel7 => set_duty!(self, h, 7, duty), - }; - } -} - -/// Channel HW interface for LowSpeed channels -impl<'a, O: OutputPin> ChannelHW for Channel<'a, LowSpeed, O> -where - O: OutputPin, -{ - /// Configure Channel HW - fn configure_hw(&mut self) -> Result<(), Error> { - self.configure_hw_with_pin_config(config::PinConfig::PushPull) - } - fn configure_hw_with_pin_config(&mut self, cfg: config::PinConfig) -> Result<(), Error> { - if let Some(timer) = self.timer { - if !timer.is_configured() { - return Err(Error::Timer); - } - - match cfg { - config::PinConfig::PushPull => { - self.output_pin.set_to_push_pull_output(); - } - config::PinConfig::OpenDrain => { - self.output_pin.set_to_open_drain_output(); - } - } - - let timer_number = timer.get_number() as u8; - match self.number { - Number::Channel0 => { - set_channel!(self, l, 0, timer_number); - update_channel!(self, l, 0); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG0); - } - Number::Channel1 => { - set_channel!(self, l, 1, timer_number); - update_channel!(self, l, 1); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG1); - } - Number::Channel2 => { - set_channel!(self, l, 2, timer_number); - update_channel!(self, l, 2); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG2); - } - Number::Channel3 => { - set_channel!(self, l, 3, timer_number); - update_channel!(self, l, 3); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG3); - } - Number::Channel4 => { - set_channel!(self, l, 4, timer_number); - update_channel!(self, l, 4); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG4); - } - Number::Channel5 => { - set_channel!(self, l, 5, timer_number); - update_channel!(self, l, 5); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG5); - } - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Number::Channel6 => { - set_channel!(self, l, 6, timer_number); - update_channel!(self, l, 6); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG6); - } - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Number::Channel7 => { - set_channel!(self, l, 7, timer_number); - update_channel!(self, l, 7); - self.output_pin - .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG7); - } - } - } else { - return Err(Error::Timer); - } - - Ok(()) - } - - /// Set duty in channel HW - fn set_duty_hw(&self, duty: u32) { - match self.number { - Number::Channel0 => set_duty!(self, l, 0, duty), - Number::Channel1 => set_duty!(self, l, 1, duty), - Number::Channel2 => set_duty!(self, l, 2, duty), - Number::Channel3 => set_duty!(self, l, 3, duty), - Number::Channel4 => set_duty!(self, l, 4, duty), - Number::Channel5 => set_duty!(self, l, 5, duty), - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Number::Channel6 => set_duty!(self, l, 6, duty), - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - Number::Channel7 => set_duty!(self, l, 7, duty), - }; - } -} diff --git a/esp-hal-common/src/ledc/mod.rs b/esp-hal-common/src/ledc/mod.rs deleted file mode 100644 index 9a5ffcc27c0..00000000000 --- a/esp-hal-common/src/ledc/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! LEDC (LED PWM Controller) peripheral control -//! -//! Currently only supports fixed-frequency output. Hardware fade support and -//! interrupts are not currently implemented. High Speed channels are availble -//! for the ESP32 only, while Low Speed channels are available for all supported -//! chips. -//! -//! # LowSpeed Example: -//! -//! The following will configure the Low Speed Channel0 to 24kHz output with -//! 10% duty using the ABPClock -//! -//! ```rust,ignore -//! let mut ledc = LEDC::new(peripherals.LEDC, &clock_control, &mut system.peripheral_clock_control); -//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); -//! -//! let mut lstimer0 = ledc.get_timer::(timer::Number::Timer0); -//! lstimer0 -//! .configure(timer::config::Config { -//! duty: timer::config::Duty::Duty5Bit, -//! clock_source: timer::LSClockSource::APBClk, -//! frequency: 24u32.kHz(), -//! }) -//! .unwrap(); -//! -//! let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); -//! channel0 -//! .configure(channel::config::Config { -//! timer: &lstimer0, -//! duty: 10, -//! }) -//! .unwrap(); -//! ``` -//! -//! # HighSpeed Example (ESP32 only): -//! -//! The following will configure the High Speed Channel0 to 24kHz output with -//! 10% duty using the ABPClock -//! -//! ```rust,ignore -//! let ledc = LEDC::new(peripherals.LEDC, &clock_control, &mut system.peripheral_clock_control); -//! -//! let mut hstimer0 = ledc.get_timer::(timer::Number::Timer0); -//! hstimer0 -//! .configure(timer::config::Config { -//! duty: timer::config::Duty::Duty5Bit, -//! clock_source: timer::HSClockSource::APBClk, -//! frequency: 24u32.kHz(), -//! }) -//! .unwrap(); -//! -//! let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); -//! channel0 -//! .configure(channel::config::Config { -//! timer: &hstimer0, -//! duty: 10, -//! }) -//! .unwrap(); -//! ``` -//! -//! # TODO -//! -//! - Source clock selection -//! - Hardware fade support -//! - Interrupts - -use self::{ - channel::Channel, - timer::{Timer, TimerSpeed}, -}; -use crate::{ - clock::Clocks, - gpio::OutputPin, - peripheral::{Peripheral, PeripheralRef}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -pub mod channel; -pub mod timer; - -/// Global slow clock source -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum LSGlobalClkSource { - APBClk, -} - -/// LEDC (LED PWM Controller) -pub struct LEDC<'d> { - _instance: PeripheralRef<'d, crate::peripherals::LEDC>, - ledc: &'d crate::peripherals::ledc::RegisterBlock, - clock_control_config: &'d Clocks<'d>, -} - -#[cfg(esp32)] -/// Used to specify HighSpeed Timer/Channel -pub struct HighSpeed {} - -/// Used to specify LowSpeed Timer/Channel -pub struct LowSpeed {} - -pub trait Speed {} - -#[cfg(esp32)] -impl Speed for HighSpeed {} - -impl Speed for LowSpeed {} - -impl<'d> LEDC<'d> { - /// Return a new LEDC - pub fn new( - _instance: impl Peripheral

    + 'd, - clock_control_config: &'d Clocks, - system: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(_instance); - system.enable(PeripheralEnable::Ledc); - - let ledc = unsafe { &*crate::peripherals::LEDC::ptr() }; - LEDC { - _instance, - ledc, - clock_control_config, - } - } - - /// Set global slow clock source - #[cfg(esp32)] - pub fn set_global_slow_clock(&mut self, _clock_source: LSGlobalClkSource) { - self.ledc.conf.write(|w| w.apb_clk_sel().set_bit()); - self.ledc.lstimer0_conf.modify(|_, w| w.para_up().set_bit()); - } - - #[cfg(not(esp32))] - /// Set global slow clock source - pub fn set_global_slow_clock(&mut self, clock_source: LSGlobalClkSource) { - #[cfg(esp32c6)] - let pcr = unsafe { &*crate::peripherals::PCR::ptr() }; - - #[cfg(esp32c6)] - pcr.ledc_sclk_conf.write(|w| w.ledc_sclk_en().set_bit()); - - match clock_source { - LSGlobalClkSource::APBClk => { - #[cfg(not(esp32c6))] - self.ledc.conf.write(|w| unsafe { w.apb_clk_sel().bits(1) }); - #[cfg(esp32c6)] - pcr.ledc_sclk_conf - .write(|w| unsafe { w.ledc_sclk_sel().bits(1) }); - } - } - self.ledc.timer0_conf.modify(|_, w| w.para_up().set_bit()); - } - - /// Return a new timer - pub fn get_timer(&self, number: timer::Number) -> Timer { - Timer::new(self.ledc, self.clock_control_config, number) - } - - /// Return a new channel - pub fn get_channel( - &self, - number: channel::Number, - output_pin: impl Peripheral

    + 'd, - ) -> Channel { - Channel::new(number, output_pin) - } -} diff --git a/esp-hal-common/src/ledc/timer.rs b/esp-hal-common/src/ledc/timer.rs deleted file mode 100644 index 3e86587b8ea..00000000000 --- a/esp-hal-common/src/ledc/timer.rs +++ /dev/null @@ -1,437 +0,0 @@ -use fugit::HertzU32; - -#[cfg(esp32)] -use super::HighSpeed; -use super::{LowSpeed, Speed}; -use crate::{clock::Clocks, peripherals::ledc}; - -const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF; - -/// Timer errors -#[derive(Debug)] -pub enum Error { - /// Invalid Divisor - Divisor, -} - -#[cfg(esp32)] -/// Clock source for HS Timers -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum HSClockSource { - APBClk, - // TODO RefTick, -} - -/// Clock source for LS Timers -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum LSClockSource { - APBClk, - // TODO SLOWClk -} - -/// Timer number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum Number { - Timer0, - Timer1, - Timer2, - Timer3, -} - -/// Timer configuration -pub mod config { - use fugit::HertzU32; - - /// Number of bits reserved for duty cycle adjustment - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - pub enum Duty { - Duty1Bit = 1, - Duty2Bit, - Duty3Bit, - Duty4Bit, - Duty5Bit, - Duty6Bit, - Duty7Bit, - Duty8Bit, - Duty9Bit, - Duty10Bit, - Duty11Bit, - Duty12Bit, - Duty13Bit, - Duty14Bit, - #[cfg(esp32)] - Duty15Bit, - #[cfg(esp32)] - Duty16Bit, - #[cfg(esp32)] - Duty17Bit, - #[cfg(esp32)] - Duty18Bit, - #[cfg(esp32)] - Duty19Bit, - #[cfg(esp32)] - Duty20Bit, - } - - /// Timer configuration - #[derive(Copy, Clone)] - pub struct Config { - pub duty: Duty, - pub clock_source: CS, - pub frequency: HertzU32, - } -} - -/// Trait defining the type of timer source -pub trait TimerSpeed: Speed { - type ClockSourceType; -} - -/// Timer source type for LowSpeed timers -impl TimerSpeed for LowSpeed { - type ClockSourceType = LSClockSource; -} - -#[cfg(esp32)] -/// Timer source type for HighSpeed timers -impl TimerSpeed for HighSpeed { - type ClockSourceType = HSClockSource; -} - -/// Interface for Timers -pub trait TimerIFace { - /// Return the frequency of the timer - fn get_freq(&self) -> Option; - - /// Configure the timer - fn configure(&mut self, config: config::Config) -> Result<(), Error>; - - /// Check if the timer has been configured - fn is_configured(&self) -> bool; - - /// Return the duty resolution of the timer - fn get_duty(&self) -> Option; - - /// Return the timer number - fn get_number(&self) -> Number; -} - -/// Interface for HW configuration of timer -pub trait TimerHW { - /// Get the current source timer frequency from the HW - fn get_freq_hw(&self) -> Option; - - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32); - - /// Update the timer in HW - fn update_hw(&self); -} - -/// Timer struct -pub struct Timer<'a, S: TimerSpeed> { - ledc: &'a crate::peripherals::ledc::RegisterBlock, - clock_control_config: &'a Clocks<'a>, - number: Number, - duty: Option, - configured: bool, - use_ref_tick: bool, - clock_source: Option, -} - -impl<'a, S: TimerSpeed> TimerIFace for Timer<'a, S> -where - Timer<'a, S>: TimerHW, -{ - /// Return the frequency of the timer - fn get_freq(&self) -> Option { - self.get_freq_hw() - } - - /// Configure the timer - fn configure(&mut self, config: config::Config) -> Result<(), Error> { - self.duty = Some(config.duty); - self.clock_source = Some(config.clock_source); - - // TODO: we should return some error here if `unwrap()` fails - let src_freq: u32 = self.get_freq().unwrap().to_Hz(); - let precision = 1 << config.duty as u32; - let frequency: u32 = config.frequency.raw(); - - let mut divisor = ((src_freq as u64) << 8) / frequency as u64 / precision as u64; - - if divisor > LEDC_TIMER_DIV_NUM_MAX { - // APB_CLK results in divisor which too high. Try using REF_TICK as clock - // source. - self.use_ref_tick = true; - divisor = ((1_000_000 as u64) << 8) / frequency as u64 / precision as u64; - } - - if divisor >= LEDC_TIMER_DIV_NUM_MAX || divisor < 256 { - return Err(Error::Divisor); - } - - self.configure_hw(divisor as u32); - self.update_hw(); - - self.configured = true; - - Ok(()) - } - - /// Check if the timer has been configured - fn is_configured(&self) -> bool { - self.configured - } - - /// Return the duty resolution of the timer - fn get_duty(&self) -> Option { - self.duty - } - - /// Return the timer number - fn get_number(&self) -> Number { - self.number - } -} - -impl<'a, S: TimerSpeed> Timer<'a, S> { - /// Create a new intance of a timer - pub fn new( - ledc: &'a ledc::RegisterBlock, - clock_control_config: &'a Clocks, - number: Number, - ) -> Self { - Timer { - ledc, - clock_control_config, - number, - duty: None, - configured: false, - use_ref_tick: false, - clock_source: None, - } - } -} - -/// Timer HW implementation for LowSpeed timers -impl<'a> TimerHW for Timer<'a, LowSpeed> { - /// Get the current source timer frequency from the HW - fn get_freq_hw(&self) -> Option { - self.clock_source.map(|cs| match cs { - LSClockSource::APBClk => self.clock_control_config.apb_clock, - }) - } - - #[cfg(esp32)] - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32) { - let duty = self.duty.unwrap() as u8; - let use_apb = !self.use_ref_tick; - - match self.number { - Number::Timer0 => self.ledc.lstimer0_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_apb) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer1 => self.ledc.lstimer1_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_apb) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer2 => self.ledc.lstimer2_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_apb) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer3 => self.ledc.lstimer3_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_apb) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - }; - } - - #[cfg(not(esp32))] - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32) { - let duty = self.duty.unwrap() as u8; - let use_ref_tick = self.use_ref_tick; - - match self.number { - Number::Timer0 => self.ledc.timer0_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_ref_tick) - .rst() - .clear_bit() - .pause() - .clear_bit() - .clk_div() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer1 => self.ledc.timer1_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_ref_tick) - .rst() - .clear_bit() - .pause() - .clear_bit() - .clk_div() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer2 => self.ledc.timer2_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_ref_tick) - .rst() - .clear_bit() - .pause() - .clear_bit() - .clk_div() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer3 => self.ledc.timer3_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(use_ref_tick) - .rst() - .clear_bit() - .pause() - .clear_bit() - .clk_div() - .bits(divisor) - .duty_res() - .bits(duty) - }), - }; - } - - #[cfg(esp32)] - /// Update the timer in HW - fn update_hw(&self) { - match self.number { - Number::Timer0 => self.ledc.lstimer0_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer1 => self.ledc.lstimer1_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer2 => self.ledc.lstimer2_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer3 => self.ledc.lstimer3_conf.modify(|_, w| w.para_up().set_bit()), - }; - } - - #[cfg(not(esp32))] - /// Update the timer in HW - fn update_hw(&self) { - match self.number { - Number::Timer0 => self.ledc.timer0_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer1 => self.ledc.timer1_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer2 => self.ledc.timer2_conf.modify(|_, w| w.para_up().set_bit()), - Number::Timer3 => self.ledc.timer3_conf.modify(|_, w| w.para_up().set_bit()), - }; - } -} - -#[cfg(esp32)] -/// Timer HW implementation for HighSpeed timers -impl<'a> TimerHW for Timer<'a, HighSpeed> { - /// Get the current source timer frequency from the HW - fn get_freq_hw(&self) -> Option { - self.clock_source.map(|cs| match cs { - // TODO RefTick HSClockSource::RefTick => self.clock_control_config.apb_clock, - HSClockSource::APBClk => self.clock_control_config.apb_clock, - }) - } - - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32) { - let duty = self.duty.unwrap() as u8; - let sel_hstimer = self.clock_source == Some(HSClockSource::APBClk); - - match self.number { - Number::Timer0 => self.ledc.hstimer0_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(sel_hstimer) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer1 => self.ledc.hstimer1_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(sel_hstimer) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer2 => self.ledc.hstimer2_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(sel_hstimer) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - Number::Timer3 => self.ledc.hstimer3_conf.modify(|_, w| unsafe { - w.tick_sel() - .bit(sel_hstimer) - .rst() - .clear_bit() - .pause() - .clear_bit() - .div_num() - .bits(divisor) - .duty_res() - .bits(duty) - }), - }; - } - - /// Update the timer in HW - fn update_hw(&self) { - // Nothing to do for HS timers - } -} diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs deleted file mode 100644 index 7febf406a60..00000000000 --- a/esp-hal-common/src/lib.rs +++ /dev/null @@ -1,302 +0,0 @@ -//! `no_std` HAL implementations for the peripherals which are common among -//! Espressif devices. Implements a number of the traits defined by -//! [embedded-hal]. -//! -//! This crate should not be used directly; you should use one of the -//! device-specific HAL crates instead: -//! -//! - [esp32-hal] -//! - [esp32c2-hal] -//! - [esp32c3-hal] -//! - [esp32c6-hal] -//! - [esp32s2-hal] -//! - [esp32s3-hal] -//! -//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ -//! [esp32-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32-hal -//! [esp32c2-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c2-hal -//! [esp32c3-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c3-hal -//! [esp32c6-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32c6-hal -//! [esp32s2-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32s2-hal -//! [esp32s3-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp32s3-hal - -#![no_std] -#![cfg_attr(xtensa, feature(asm_experimental_arch))] -#![cfg_attr( - feature = "async", - allow(incomplete_features), - feature(async_fn_in_trait), - feature(impl_trait_projections) -)] -#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] - -#[cfg(riscv)] -pub use esp_riscv_rt::{self, entry, riscv}; -pub use procmacros as macros; -#[cfg(xtensa)] -pub use xtensa_lx; -#[cfg(xtensa)] -pub use xtensa_lx_rt::{self, entry}; - -#[cfg(adc)] -pub use self::analog::adc::implementation as adc; -#[cfg(dac)] -pub use self::analog::dac::implementation as dac; -#[cfg(gdma)] -pub use self::dma::gdma; -#[cfg(pdma)] -pub use self::dma::pdma; -#[cfg(any(dport, interrupt_core0, interrupt_core1))] -pub use self::interrupt::*; -#[cfg(rmt)] -pub use self::pulse_control::PulseControl; -#[cfg(rng)] -pub use self::rng::Rng; -#[cfg(any(lp_clkrst, rtc_cntl))] -pub use self::rtc_cntl::{Rtc, Rwdt}; -#[cfg(any(esp32, esp32s3))] -pub use self::soc::cpu_control; -#[cfg(efuse)] -pub use self::soc::efuse; -#[cfg(any(spi0, spi1, spi2, spi3))] -pub use self::spi::Spi; -#[cfg(any(timg0, timg1))] -pub use self::timer::Timer; -#[cfg(any(uart0, uart1, uart2))] -pub use self::uart::Uart; -#[cfg(usb_device)] -pub use self::usb_serial_jtag::UsbSerialJtag; -pub use self::{delay::Delay, soc::peripherals}; - -#[cfg(aes)] -pub mod aes; -#[cfg(any(adc, dac))] -pub mod analog; -#[cfg(assist_debug)] -pub mod assist_debug; -pub mod clock; -pub mod delay; -#[cfg(any(gdma, pdma))] -pub mod dma; -#[cfg(feature = "embassy")] -pub mod embassy; -#[cfg(gpio)] -pub mod gpio; -#[cfg(any(i2c0, i2c1))] -pub mod i2c; -#[cfg(any(i2s0, i2s1))] -pub mod i2s; -#[cfg(any(dport, interrupt_core0, interrupt_core1))] -pub mod interrupt; -#[cfg(ledc)] -pub mod ledc; -#[cfg(any(mcpwm0, mcpwm1))] -pub mod mcpwm; -#[cfg(usb0)] -pub mod otg_fs; -#[cfg(pcnt)] -pub mod pcnt; -pub mod peripheral; -pub mod prelude; -#[cfg(rmt)] -pub mod pulse_control; -#[cfg(radio)] -pub mod radio; -pub mod reset; -#[cfg(rng)] -pub mod rng; -pub mod rom; -#[cfg(rsa)] -pub mod rsa; -#[cfg(any(lp_clkrst, rtc_cntl))] -pub mod rtc_cntl; -#[cfg(sha)] -pub mod sha; -pub mod soc; -#[cfg(any(spi0, spi1, spi2, spi3))] -pub mod spi; -#[cfg(any(dport, pcr, system))] -pub mod system; -#[cfg(systimer)] -pub mod systimer; -#[cfg(any(timg0, timg1))] -pub mod timer; -#[cfg(any(twai0, twai1))] -pub mod twai; -#[cfg(any(uart0, uart1, uart2))] -pub mod uart; -#[cfg(usb_device)] -pub mod usb_serial_jtag; - -/// State of the CPU saved when entering exception or interrupt -pub mod trapframe { - #[cfg(riscv)] - pub use esp_riscv_rt::TrapFrame; - #[cfg(xtensa)] - pub use xtensa_lx_rt::exception::Context as TrapFrame; -} - -#[no_mangle] -extern "C" fn EspDefaultHandler(_level: u32, _interrupt: peripherals::Interrupt) {} - -#[cfg(xtensa)] -#[no_mangle] -extern "C" fn DefaultHandler() {} - -#[cfg(esp32c6)] -pub fn disable_apm_filter() { - unsafe { - (&*esp32c6::LP_APM::PTR).func_ctrl.write(|w| w.bits(0)); - (&*esp32c6::LP_APM0::PTR).func_ctrl.write(|w| w.bits(0)); - (&*esp32c6::HP_APM::PTR).func_ctrl.write(|w| w.bits(0)); - } -} - -#[doc(hidden)] -pub fn common_init() { - #[cfg(psram)] - soc::psram::init_psram(); -} - -/// Enumeration of CPU cores -/// The actual number of available cores depends on the target. -pub enum Cpu { - /// The first core - ProCpu = 0, - /// The second core - #[cfg(multi_core)] - AppCpu, -} - -pub fn get_core() -> Cpu { - #[cfg(all(xtensa, multi_core))] - match ((xtensa_lx::get_processor_id() >> 13) & 1) != 0 { - false => Cpu::ProCpu, - true => Cpu::AppCpu, - } - - // #[cfg(all(riscv, multi_core))] - // TODO get hart_id - - // single core always has ProCpu only - #[cfg(single_core)] - Cpu::ProCpu -} - -mod critical_section_impl { - struct CriticalSection; - - critical_section::set_impl!(CriticalSection); - - #[cfg(xtensa)] - mod xtensa { - unsafe impl critical_section::Impl for super::CriticalSection { - unsafe fn acquire() -> critical_section::RawRestoreState { - let tkn: critical_section::RawRestoreState; - core::arch::asm!("rsil {0}, 5", out(reg) tkn); - #[cfg(multi_core)] - { - let guard = super::multicore::MULTICORE_LOCK.lock(); - core::mem::forget(guard); // forget it so drop doesn't run - } - tkn - } - - unsafe fn release(token: critical_section::RawRestoreState) { - if token != 0 { - #[cfg(multi_core)] - { - debug_assert!(super::multicore::MULTICORE_LOCK.is_owned_by_current_thread()); - // safety: we logically own the mutex from acquire() - super::multicore::MULTICORE_LOCK.force_unlock(); - } - core::arch::asm!( - "wsr.ps {0}", - "rsync", in(reg) token) - } - } - } - } - - #[cfg(riscv)] - mod riscv { - use esp_riscv_rt::riscv; - - unsafe impl critical_section::Impl for super::CriticalSection { - unsafe fn acquire() -> critical_section::RawRestoreState { - let mut mstatus = 0u32; - core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus); - let interrupts_active = (mstatus & 0b1000) != 0; - #[cfg(multi_core)] - { - let guard = multicore::MULTICORE_LOCK.lock(); - core::mem::forget(guard); // forget it so drop doesn't run - } - - interrupts_active as _ - } - - unsafe fn release(token: critical_section::RawRestoreState) { - if token != 0 { - #[cfg(multi_core)] - { - debug_assert!(multicore::MULTICORE_LOCK.is_owned_by_current_thread()); - // safety: we logically own the mutex from acquire() - multicore::MULTICORE_LOCK.force_unlock(); - } - riscv::interrupt::enable(); - } - } - } - } - - #[cfg(multi_core)] - mod multicore { - use core::sync::atomic::{AtomicBool, Ordering}; - - use lock_api::{GetThreadId, GuardSend, RawMutex}; - - use crate::get_core; - - /// Reentrant Mutex - /// - /// Currently implemented using an atomic spin lock. - /// In the future we can optimize this raw mutex to use some hardware - /// features. - pub(crate) static MULTICORE_LOCK: lock_api::ReentrantMutex = - lock_api::ReentrantMutex::const_new(RawSpinlock::INIT, RawThreadId::INIT, ()); - - pub(crate) struct RawThreadId; - - unsafe impl lock_api::GetThreadId for RawThreadId { - const INIT: Self = RawThreadId; - - fn nonzero_thread_id(&self) -> core::num::NonZeroUsize { - core::num::NonZeroUsize::new((get_core() as usize) + 1).unwrap() - } - } - - pub(crate) struct RawSpinlock(AtomicBool); - - unsafe impl lock_api::RawMutex for RawSpinlock { - const INIT: RawSpinlock = RawSpinlock(AtomicBool::new(false)); - - // A spinlock guard can be sent to another thread and unlocked there - type GuardMarker = GuardSend; - - fn lock(&self) { - while !self.try_lock() {} - } - - fn try_lock(&self) -> bool { - self.0 - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - } - - unsafe fn unlock(&self) { - self.0.store(false, Ordering::Release); - } - } - } -} diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs deleted file mode 100644 index e43b633f885..00000000000 --- a/esp-hal-common/src/mcpwm/mod.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! MCPWM (Motor Control Pulse Width Modulator) peripheral -//! -//! # Peripheral capabilities: -//! * PWM Timers 0, 1 and 2 -//! * Every PWM timer has a dedicated 8-bit clock prescaler. -//! * The 16-bit counter in the PWM timer can work in count-up mode, -//! count-down mode or count-up-down mode. -//! * A hardware sync or software sync can trigger a reload on the PWM timer -//! with a phase register (Not yet implemented) -//! * PWM Operators 0, 1 and 2 -//! * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work -//! independently, in symmetric and asymmetric configuration. -//! * Software, asynchronously override control of PWM signals. -//! * Configurable dead-time on rising and falling edges; each set up -//! independently. (Not yet implemented) -//! * All events can trigger CPU interrupts. (Not yet implemented) -//! * Modulating of PWM output by high-frequency carrier signals, useful -//! when gate drivers are insulated with a transformer. (Not yet -//! implemented) -//! * Period, time stamps and important control registers have shadow -//! registers with flexible updating methods. -//! * Fault Detection Module (Not yet implemented) -//! * Capture Module (Not yet implemented) -//! -//! # Example -//! Uses timer0 and operator0 of the MCPWM0 peripheral to output a 50% duty -//! signal at 20 kHz. The signal will be output to the pin assigned to `pin`. -//! -//! ``` -//! # use esp_hal_common::{mcpwm, prelude::*}; -//! use mcpwm::{operator::PwmPinConfig, timer::PwmWorkingMode, PeripheralClockConfig, MCPWM}; -//! -//! // initialize peripheral -//! let clock_cfg = PeripheralClockConfig::with_frequency(&clocks, 40u32.MHz()).unwrap(); -//! let mut mcpwm = MCPWM::new( -//! peripherals.PWM0, -//! clock_cfg, -//! &mut system.peripheral_clock_control, -//! ); -//! -//! // connect operator0 to timer0 -//! mcpwm.operator0.set_timer(&mcpwm.timer0); -//! // connect operator0 to pin -//! let mut pwm_pin = mcpwm -//! .operator0 -//! .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH); -//! -//! // start timer with timestamp values in the range of 0..=99 and a frequency of 20 kHz -//! let timer_clock_cfg = clock_cfg -//! .timer_clock_with_frequency(99, PwmWorkingMode::Increase, 20u32.kHz()) -//! .unwrap(); -//! mcpwm.timer0.start(timer_clock_cfg); -//! -//! // pin will be high 50% of the time -//! pwm_pin.set_timestamp(50); -//! ``` - -#![deny(missing_docs)] - -use core::{marker::PhantomData, ops::Deref}; - -use fugit::HertzU32; -use operator::Operator; -use timer::Timer; - -use crate::{ - clock::Clocks, - gpio::OutputSignal, - peripheral::{Peripheral, PeripheralRef}, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -/// MCPWM operators -pub mod operator; -/// MCPWM timers -pub mod timer; - -type RegisterBlock = crate::peripherals::mcpwm0::RegisterBlock; - -/// The MCPWM peripheral -#[non_exhaustive] -pub struct MCPWM<'d, PWM> { - _inner: PeripheralRef<'d, PWM>, - /// Timer0 - pub timer0: Timer<0, PWM>, - /// Timer1 - pub timer1: Timer<1, PWM>, - /// Timer2 - pub timer2: Timer<2, PWM>, - /// Operator0 - pub operator0: Operator<0, PWM>, - /// Operator1 - pub operator1: Operator<1, PWM>, - /// Operator2 - pub operator2: Operator<2, PWM>, -} - -impl<'d, PWM: PwmPeripheral> MCPWM<'d, PWM> { - /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)` - // clocks.crypto_pwm_clock normally is 160 MHz - pub fn new( - peripheral: impl Peripheral

    + 'd, - peripheral_clock: PeripheralClockConfig, - system: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(peripheral); - - PWM::enable(system); - - #[cfg(not(esp32c6))] - { - // set prescaler - peripheral - .clk_cfg - .write(|w| w.clk_prescale().variant(peripheral_clock.prescaler)); - - // enable clock - peripheral.clk.write(|w| w.en().set_bit()); - } - - #[cfg(esp32c6)] - { - unsafe { &*crate::peripherals::PCR::PTR } - .pwm_clk_conf - .modify(|_, w| unsafe { - w.pwm_div_num() - .variant(peripheral_clock.prescaler) - .pwm_clkm_en() - .set_bit() - .pwm_clkm_sel() - .bits(1) - }); - - // TODO: Add other clock sources - } - - Self { - _inner: peripheral, - timer0: Timer::new(), - timer1: Timer::new(), - timer2: Timer::new(), - operator0: Operator::new(), - operator1: Operator::new(), - operator2: Operator::new(), - } - } -} - -/// Clock configuration of the MCPWM peripheral -#[derive(Copy, Clone)] -pub struct PeripheralClockConfig<'a> { - frequency: HertzU32, - prescaler: u8, - phantom: PhantomData<&'a Clocks<'a>>, -} - -impl<'a> PeripheralClockConfig<'a> { - /// Get a clock configuration with the given prescaler. - /// - /// With standard system clock configurations the input clock to the MCPWM - /// peripheral is `160 MHz`. - /// - /// The peripheral clock frequency is calculated as: - /// `peripheral_clock = input_clock / (prescaler + 1)` - pub fn with_prescaler(clocks: &'a Clocks, prescaler: u8) -> Self { - #[cfg(esp32)] - let source_clock = clocks.pwm_clock; - #[cfg(esp32c6)] - let source_clock = clocks.crypto_clock; - #[cfg(esp32s3)] - let source_clock = clocks.crypto_pwm_clock; - - Self { - frequency: source_clock / (prescaler as u32 + 1), - prescaler, - phantom: PhantomData, - } - } - - /// Get a clock configuration with the given frequency. - /// - /// ### Note: - /// This will try to select an appropriate prescaler for the - /// [`PeripheralClockConfig::with_prescaler`] method. - /// If the calculated prescaler is not in the range `0..u8::MAX` - /// [`FrequencyError`] will be returned. - /// - /// With standard system clock configurations the input clock to the MCPWM - /// peripheral is `160 MHz`. - /// - /// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ..., - /// `160 Mhz / 256`) are representable exactly. Other target frequencies - /// will be rounded up to the next divisor. - pub fn with_frequency( - clocks: &'a Clocks, - target_freq: HertzU32, - ) -> Result { - #[cfg(esp32)] - let source_clock = clocks.pwm_clock; - #[cfg(esp32c6)] - let source_clock = clocks.crypto_clock; - #[cfg(esp32s3)] - let source_clock = clocks.crypto_pwm_clock; - - if target_freq.raw() == 0 || target_freq > source_clock { - return Err(FrequencyError); - } - - let prescaler = source_clock / target_freq - 1; - if prescaler > u8::MAX as u32 { - return Err(FrequencyError); - } - - Ok(Self::with_prescaler(clocks, prescaler as u8)) - } - - /// Get the peripheral clock frequency. - /// - /// ### Note: - /// The actual value is rounded down to the nearest `u32` value - pub fn frequency(&self) -> HertzU32 { - self.frequency - } - - /// Get a timer clock configuration with the given prescaler. - /// - /// The resulting timer frequency depends of the chosen - /// [`timer::PwmWorkingMode`]. - /// - /// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease` - /// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)` - /// #### `PwmWorkingMode::UpDown` - /// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)` - pub fn timer_clock_with_prescaler( - &self, - period: u16, - mode: timer::PwmWorkingMode, - prescaler: u8, - ) -> timer::TimerClockConfig<'a> { - timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler) - } - - /// Get a timer clock configuration with the given frequency. - /// - /// ### Note: - /// This will try to select an appropriate prescaler for the timer. - /// If the calculated prescaler is not in the range `0..u8::MAX` - /// [`FrequencyError`] will be returned. - /// - /// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the - /// frequency is calculated. - pub fn timer_clock_with_frequency( - &self, - period: u16, - mode: timer::PwmWorkingMode, - target_freq: HertzU32, - ) -> Result, FrequencyError> { - timer::TimerClockConfig::with_frequency(self, period, mode, target_freq) - } -} - -/// Target frequency could not be set. -/// Check how the frequency is calculated in the corresponding method docs. -#[derive(Copy, Clone, Debug)] -pub struct FrequencyError; - -/// A MCPWM peripheral -pub unsafe trait PwmPeripheral: Deref { - /// Enable peripheral - fn enable(system: &mut PeripheralClockControl); - /// Get a pointer to the peripheral RegisterBlock - fn block() -> *const RegisterBlock; - /// Get operator GPIO mux output signal - fn output_signal() -> OutputSignal; -} - -#[cfg(mcpwm0)] -unsafe impl PwmPeripheral for crate::peripherals::MCPWM0 { - fn enable(system: &mut PeripheralClockControl) { - system.enable(PeripheralEnable::Mcpwm0) - } - - fn block() -> *const RegisterBlock { - Self::PTR - } - - fn output_signal() -> OutputSignal { - match (OP, IS_A) { - (0, true) => OutputSignal::PWM0_0A, - (1, true) => OutputSignal::PWM0_1A, - (2, true) => OutputSignal::PWM0_2A, - (0, false) => OutputSignal::PWM0_0B, - (1, false) => OutputSignal::PWM0_1B, - (2, false) => OutputSignal::PWM0_2B, - _ => unreachable!(), - } - } -} - -#[cfg(mcpwm1)] -unsafe impl PwmPeripheral for crate::peripherals::MCPWM1 { - fn enable(system: &mut PeripheralClockControl) { - system.enable(PeripheralEnable::Mcpwm1) - } - - fn block() -> *const RegisterBlock { - Self::PTR - } - - fn output_signal() -> OutputSignal { - match (OP, IS_A) { - (0, true) => OutputSignal::PWM1_0A, - (1, true) => OutputSignal::PWM1_1A, - (2, true) => OutputSignal::PWM1_2A, - (0, false) => OutputSignal::PWM1_0B, - (1, false) => OutputSignal::PWM1_1B, - (2, false) => OutputSignal::PWM1_2B, - _ => unreachable!(), - } - } -} diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs deleted file mode 100644 index e0541caed90..00000000000 --- a/esp-hal-common/src/mcpwm/operator.rs +++ /dev/null @@ -1,438 +0,0 @@ -use core::marker::PhantomData; - -use crate::{ - gpio::OutputPin, - mcpwm::{timer::Timer, PwmPeripheral}, - peripheral::{Peripheral, PeripheralRef}, -}; - -/// A MCPWM operator -/// -/// The PWM Operator submodule has the following functions: -/// * Generates a PWM signal pair, based on timing references obtained from the -/// corresponding PWM timer. -/// * Each signal out of the PWM signal pair includes a specific pattern of dead -/// time. (Not yet implemented) -/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet -/// implemented) -/// * Handles response under fault conditions. (Not yet implemented) -pub struct Operator { - phantom: PhantomData, -} - -impl Operator { - pub(super) fn new() -> Self { - // Side note: - // It would have been nice to deselect any timer reference on peripheral - // initialization. - // However experimentation (ESP32-S3) showed that writing `3` to timersel - // will not disable the timer reference but instead act as though `2` was - // written. - Operator { - phantom: PhantomData, - } - } - - /// Select a [`Timer`] to be the timing reference for this operator - /// - /// ### Note: - /// By default TIMER0 is used - pub fn set_timer(&mut self, timer: &Timer) { - let _ = timer; - // SAFETY: - // We only write to our OPERATORx_TIMERSEL register - let block = unsafe { &*PWM::block() }; - block.operator_timersel.modify(|_, w| match OP { - 0 => w.operator0_timersel().variant(TIM), - 1 => w.operator1_timersel().variant(TIM), - 2 => w.operator2_timersel().variant(TIM), - _ => { - unreachable!() - } - }); - } - - /// Use the A output with the given pin and configuration - pub fn with_pin_a<'d, Pin: OutputPin>( - self, - pin: impl Peripheral

    + 'd, - config: PwmPinConfig, - ) -> PwmPin<'d, Pin, PWM, OP, true> { - PwmPin::new(pin, config) - } - - /// Use the B output with the given pin and configuration - pub fn with_pin_b<'d, Pin: OutputPin>( - self, - pin: impl Peripheral

    + 'd, - config: PwmPinConfig, - ) -> PwmPin<'d, Pin, PWM, OP, false> { - PwmPin::new(pin, config) - } - - /// Use both the A and the B output with the given pins and configurations - pub fn with_pins<'d, PinA: OutputPin, PinB: OutputPin>( - self, - pin_a: impl Peripheral

    + 'd, - config_a: PwmPinConfig, - pin_b: impl Peripheral

    + 'd, - config_b: PwmPinConfig, - ) -> ( - PwmPin<'d, PinA, PWM, OP, true>, - PwmPin<'d, PinB, PWM, OP, false>, - ) { - (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b)) - } -} - -/// Configuration describing how the operator generates a signal on a connected -/// pin -pub struct PwmPinConfig { - actions: PwmActions, - update_method: PwmUpdateMethod, -} - -impl PwmPinConfig { - /// A configuration using [`PwmActions::UP_ACTIVE_HIGH`] and - /// [`PwmUpdateMethod::SYNC_ON_ZERO`] - pub const UP_ACTIVE_HIGH: Self = - Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO); - /// A configuration using [`PwmActions::UP_DOWN_ACTIVE_HIGH`] and - /// [`PwmUpdateMethod::SYNC_ON_ZERO`] - pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new( - PwmActions::UP_DOWN_ACTIVE_HIGH, - PwmUpdateMethod::SYNC_ON_ZERO, - ); - - /// Get a configuration using the given `PwmActions` and `PwmUpdateMethod` - pub const fn new(actions: PwmActions, update_method: PwmUpdateMethod) -> Self { - PwmPinConfig { - actions, - update_method, - } - } -} - -/// A pin driven by an MCPWM operator -pub struct PwmPin<'d, Pin, PWM, const OP: u8, const IS_A: bool> { - _pin: PeripheralRef<'d, Pin>, - phantom: PhantomData, -} - -impl<'d, Pin: OutputPin, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> - PwmPin<'d, Pin, PWM, OP, IS_A> -{ - fn new(pin: impl Peripheral

    + 'd, config: PwmPinConfig) -> Self { - crate::into_ref!(pin); - let output_signal = PWM::output_signal::(); - pin.enable_output(true) - .connect_peripheral_to_output(output_signal); - let mut pin = PwmPin { - _pin: pin, - phantom: PhantomData, - }; - pin.set_actions(config.actions); - pin.set_update_method(config.update_method); - pin - } - - /// Configure what actions should be taken on timing events - pub fn set_actions(&mut self, value: PwmActions) { - // SAFETY: - // We only write to our GENx_x register - let block = unsafe { &*PWM::block() }; - - let bits = value.0; - - // SAFETY: - // `bits` is a valid bit pattern - unsafe { - match (OP, IS_A) { - (0, true) => block.gen0_a.write(|w| w.bits(bits)), - (1, true) => block.gen1_a.write(|w| w.bits(bits)), - (2, true) => block.gen2_a.write(|w| w.bits(bits)), - (0, false) => block.gen0_b.write(|w| w.bits(bits)), - (1, false) => block.gen1_b.write(|w| w.bits(bits)), - (2, false) => block.gen2_b.write(|w| w.bits(bits)), - _ => unreachable!(), - } - } - } - - /// Set how a new timestamp syncs with the timer - #[cfg(esp32)] - pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { - // SAFETY: - // We only write to our GENx_x_UPMETHOD register - let block = unsafe { &*PWM::block() }; - let bits = update_method.0; - match (OP, IS_A) { - (0, true) => block - .gen0_stmp_cfg - .modify(|_, w| w.gen0_a_upmethod().variant(bits)), - (1, true) => block - .gen1_stmp_cfg - .modify(|_, w| w.gen1_a_upmethod().variant(bits)), - (2, true) => block - .gen2_stmp_cfg - .modify(|_, w| w.gen2_a_upmethod().variant(bits)), - (0, false) => block - .gen0_stmp_cfg - .modify(|_, w| w.gen0_b_upmethod().variant(bits)), - (1, false) => block - .gen1_stmp_cfg - .modify(|_, w| w.gen1_b_upmethod().variant(bits)), - (2, false) => block - .gen2_stmp_cfg - .modify(|_, w| w.gen2_b_upmethod().variant(bits)), - _ => { - unreachable!() - } - } - } - - /// Set how a new timestamp syncs with the timer - #[cfg(esp32s3)] - pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { - // SAFETY: - // We only write to our GENx_x_UPMETHOD register - let block = unsafe { &*PWM::block() }; - let bits = update_method.0; - match (OP, IS_A) { - (0, true) => block - .cmpr0_cfg - .modify(|_, w| w.cmpr0_a_upmethod().variant(bits)), - (1, true) => block - .cmpr1_cfg - .modify(|_, w| w.cmpr1_a_upmethod().variant(bits)), - (2, true) => block - .cmpr2_cfg - .modify(|_, w| w.cmpr2_a_upmethod().variant(bits)), - (0, false) => block - .cmpr0_cfg - .modify(|_, w| w.cmpr0_b_upmethod().variant(bits)), - (1, false) => block - .cmpr1_cfg - .modify(|_, w| w.cmpr1_b_upmethod().variant(bits)), - (2, false) => block - .cmpr2_cfg - .modify(|_, w| w.cmpr2_b_upmethod().variant(bits)), - _ => { - unreachable!() - } - } - } - - /// Set how a new timestamp syncs with the timer - #[cfg(esp32c6)] - pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { - // SAFETY: - // We only write to our GENx_x_UPMETHOD register - let block = unsafe { &*PWM::block() }; - let bits = update_method.0; - match (OP, IS_A) { - (0, true) => block - .gen0_stmp_cfg - .modify(|_, w| w.cmpr0_a_upmethod().variant(bits)), - (1, true) => block - .gen1_stmp_cfg - .modify(|_, w| w.cmpr1_a_upmethod().variant(bits)), - (2, true) => block - .gen2_stmp_cfg - .modify(|_, w| w.cmpr2_a_upmethod().variant(bits)), - (0, false) => block - .gen0_stmp_cfg - .modify(|_, w| w.cmpr0_b_upmethod().variant(bits)), - (1, false) => block - .gen1_stmp_cfg - .modify(|_, w| w.cmpr1_b_upmethod().variant(bits)), - (2, false) => block - .gen2_stmp_cfg - .modify(|_, w| w.cmpr2_b_upmethod().variant(bits)), - _ => { - unreachable!() - } - } - } - - /// Write a new timestamp. - /// The written value will take effect according to the set - /// [`PwmUpdateMethod`]. - #[cfg(esp32)] - pub fn set_timestamp(&mut self, value: u16) { - // SAFETY: - // We only write to our GENx_TSTMP_x register - let block = unsafe { &*PWM::block() }; - match (OP, IS_A) { - (0, true) => block.gen0_tstmp_a.write(|w| w.gen0_a().variant(value)), - (1, true) => block.gen1_tstmp_a.write(|w| w.gen1_a().variant(value)), - (2, true) => block.gen2_tstmp_a.write(|w| w.gen2_a().variant(value)), - (0, false) => block.gen0_tstmp_b.write(|w| w.gen0_b().variant(value)), - (1, false) => block.gen1_tstmp_b.write(|w| w.gen1_b().variant(value)), - (2, false) => block.gen2_tstmp_b.write(|w| w.gen2_b().variant(value)), - _ => { - unreachable!() - } - } - } - - /// Write a new timestamp. - /// The written value will take effect according to the set - /// [`PwmUpdateMethod`]. - #[cfg(esp32s3)] - pub fn set_timestamp(&mut self, value: u16) { - // SAFETY: - // We only write to our CMPRx_VALUEx register - let block = unsafe { &*PWM::block() }; - match (OP, IS_A) { - (0, true) => block.cmpr0_value0.write(|w| w.cmpr0_a().variant(value)), - (1, true) => block.cmpr1_value0.write(|w| w.cmpr1_a().variant(value)), - (2, true) => block.cmpr2_value0.write(|w| w.cmpr2_a().variant(value)), - (0, false) => block.cmpr0_value1.write(|w| w.cmpr0_b().variant(value)), - (1, false) => block.cmpr1_value1.write(|w| w.cmpr1_b().variant(value)), - (2, false) => block.cmpr2_value1.write(|w| w.cmpr2_b().variant(value)), - _ => { - unreachable!() - } - } - } - - /// Write a new timestamp. - /// The written value will take effect according to the set - /// [`PwmUpdateMethod`]. - #[cfg(esp32c6)] - pub fn set_timestamp(&mut self, value: u16) { - // SAFETY: - // We only write to our GENx_TSTMP_x register - let block = unsafe { &*PWM::block() }; - match (OP, IS_A) { - (0, true) => block.gen0_tstmp_a.write(|w| w.cmpr0_a().variant(value)), - (1, true) => block.gen1_tstmp_a.write(|w| w.cmpr1_a().variant(value)), - (2, true) => block.gen2_tstmp_a.write(|w| w.cmpr2_a().variant(value)), - (0, false) => block.gen0_tstmp_b.write(|w| w.cmpr0_b().variant(value)), - (1, false) => block.gen1_tstmp_b.write(|w| w.cmpr1_b().variant(value)), - (2, false) => block.gen2_tstmp_b.write(|w| w.cmpr2_b().variant(value)), - _ => { - unreachable!() - } - } - } -} - -/// An action the operator applies to an output -#[non_exhaustive] -#[repr(u32)] -pub enum UpdateAction { - /// Clear the output by setting it to a low level. - SetLow = 1, - /// Set the to a high level. - SetHigh = 2, - /// Change the current output level to the opposite value. - /// If it is currently pulled high, pull it low, or vice versa. - Toggle = 3, -} - -/// Settings for what actions should be taken on timing events -/// -/// ### Note: -/// The hardware supports using a timestamp A event to trigger an action on -/// output B or vice versa. For clearer ownership semantics this HAL does not -/// support such configurations. -pub struct PwmActions(u32); - -impl PwmActions { - /// Using this setting together with a timer configured with - /// [`PwmWorkingMode::Increase`](super::timer::PwmWorkingMode::Increase) - /// will set the output high for a duration proportional to the set - /// timestamp. - pub const UP_ACTIVE_HIGH: Self = Self::empty() - .on_up_counting_timer_equals_zero(UpdateAction::SetHigh) - .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow); - - /// Using this setting together with a timer configured with - /// [`PwmWorkingMode::UpDown`](super::timer::PwmWorkingMode::UpDown) will - /// set the output high for a duration proportional to the set - /// timestamp. - pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty() - .on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh) - .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow); - - /// `PwmActions` with no `UpdateAction`s set - pub const fn empty() -> Self { - PwmActions(0) - } - - /// Choose an `UpdateAction` for an `UTEZ` event - pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self { - self.with_value_at_offset(action as u32, 0) - } - - /// Choose an `UpdateAction` for an `UTEP` event - pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self { - self.with_value_at_offset(action as u32, 2) - } - - /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event - pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self { - match IS_A { - true => self.with_value_at_offset(action as u32, 4), - false => self.with_value_at_offset(action as u32, 6), - } - } - - /// Choose an `UpdateAction` for an `DTEZ` event - pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self { - self.with_value_at_offset(action as u32, 12) - } - - /// Choose an `UpdateAction` for an `DTEP` event - pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self { - self.with_value_at_offset(action as u32, 14) - } - - /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event - pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self { - match IS_A { - true => self.with_value_at_offset(action as u32, 16), - false => self.with_value_at_offset(action as u32, 18), - } - } - - const fn with_value_at_offset(self, value: u32, offset: u32) -> Self { - let mask = !(0b11 << offset); - let value = (self.0 & mask) | (value << offset); - PwmActions(value) - } -} - -/// Settings for when [`PwmPin::set_timestamp`] takes effect -/// -/// Multiple syncing triggers can be set. -pub struct PwmUpdateMethod(u8); - -impl PwmUpdateMethod { - /// New timestamp will be applied immediately - pub const SYNC_IMMEDIATLY: Self = Self::empty(); - /// New timestamp will be applied when timer is equal to zero - pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero(); - /// New timestamp will be applied when timer is equal to period - pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period(); - - /// `PwmUpdateMethod` with no sync triggers. - /// Corresponds to syncing immediately - pub const fn empty() -> Self { - PwmUpdateMethod(0) - } - - /// Enable syncing new timestamp values when timer is equal to zero - pub const fn sync_on_timer_equals_zero(mut self) -> Self { - self.0 |= 0b0001; - self - } - - /// Enable syncing new timestamp values when timer is equal to period - pub const fn sync_on_timer_equals_period(mut self) -> Self { - self.0 |= 0b0010; - self - } -} diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs deleted file mode 100644 index ccdcc328044..00000000000 --- a/esp-hal-common/src/mcpwm/timer.rs +++ /dev/null @@ -1,290 +0,0 @@ -use core::marker::PhantomData; - -use fugit::HertzU32; - -use crate::{ - clock::Clocks, - mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral}, - peripherals::mcpwm0::{TIMER0_CFG0, TIMER0_CFG1}, -}; - -/// A MCPWM timer -/// -/// Every timer of a particular [`MCPWM`](super::MCPWM) peripheral can be used -/// as a timing reference for every -/// [`Operator`](super::operator::Operator) of that peripheral -pub struct Timer { - pub(super) phantom: PhantomData, -} - -impl Timer { - pub(super) fn new() -> Self { - Timer { - phantom: PhantomData, - } - } - - /// Apply the given timer configuration. - /// - /// ### Note: - /// The prescalar and period configuration will be applied immediately and - /// before setting the [`PwmWorkingMode`]. - /// If the timer is already running you might want to call [`Timer::stop`] - /// and/or [`Timer::set_counter`] first - /// (if the new period is larger than the current counter value this will - /// cause weird behavior). - /// - /// The hardware supports writing these settings in sync with certain timer - /// events but this HAL does not expose these for now. - pub fn start(&mut self, timer_config: TimerClockConfig) { - // write prescaler and period with immediate update method - self.cfg0().write(|w| { - w.timer0_prescale() - .variant(timer_config.prescaler) - .timer0_period() - .variant(timer_config.period) - .timer0_period_upmethod() - .variant(0) - }); - - // set timer to continuously run and set the timer working mode - self.cfg1().write(|w| { - w.timer0_start() - .variant(2) - .timer0_mod() - .variant(timer_config.mode as u8) - }); - } - - /// Stop the timer in its current state - pub fn stop(&mut self) { - // freeze the timer - self.cfg1().write(|w| w.timer0_mod().variant(0)); - } - - /// Set the timer counter to the provided value - pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) { - // SAFETY: - // We only write to our TIMERx_SYNC register - let block = unsafe { &*PWM::block() }; - - match TIM { - 0 => { - let sw = block.timer0_sync.read().sw().bit_is_set(); - block.timer0_sync.write(|w| { - w.timer0_phase_direction() - .variant(direction as u8 != 0) - .timer0_phase() - .variant(phase) - .sw() - .variant(!sw) - }); - } - 1 => { - let sw = block.timer1_sync.read().sw().bit_is_set(); - block.timer1_sync.write(|w| { - w.timer1_phase_direction() - .variant(direction as u8 != 0) - .timer1_phase() - .variant(phase) - .sw() - .variant(!sw) - }); - } - 2 => { - let sw = block.timer2_sync.read().sw().bit_is_set(); - block.timer2_sync.write(|w| { - w.timer2_phase_direction() - .variant(direction as u8 != 0) - .timer2_phase() - .variant(phase) - .sw() - .variant(!sw) - }); - } - _ => unreachable!(), - } - } - - /// Read the counter value and counter direction of the timer - pub fn status(&self) -> (u16, CounterDirection) { - // SAFETY: - // We only read from our TIMERx_STATUS register - let block = unsafe { &*PWM::block() }; - - match TIM { - 0 => { - let reg = block.timer0_status.read(); - ( - reg.timer0_value().bits(), - reg.timer0_direction().bit_is_set().into(), - ) - } - 1 => { - let reg = block.timer1_status.read(); - ( - reg.timer1_value().bits(), - reg.timer1_direction().bit_is_set().into(), - ) - } - 2 => { - let reg = block.timer2_status.read(); - ( - reg.timer2_value().bits(), - reg.timer2_direction().bit_is_set().into(), - ) - } - _ => unreachable!(), - } - } - - fn cfg0(&mut self) -> &TIMER0_CFG0 { - // SAFETY: - // We only grant access to our CFG0 register with the lifetime of &mut self - let block = unsafe { &*PWM::block() }; - - // SAFETY: - // The CFG0 registers are identical for all timers so we can pretend they're - // TIMER0_CFG0 - match TIM { - 0 => &block.timer0_cfg0, - 1 => unsafe { &*(&block.timer1_cfg0 as *const _ as *const _) }, - 2 => unsafe { &*(&block.timer2_cfg0 as *const _ as *const _) }, - _ => unreachable!(), - } - } - - fn cfg1(&mut self) -> &TIMER0_CFG1 { - // SAFETY: - // We only grant access to our CFG1 register with the lifetime of &mut self - let block = unsafe { &*PWM::block() }; - - // SAFETY: - // The CFG1 registers are identical for all timers so we can pretend they're - // TIMER0_CFG1 - match TIM { - 0 => &block.timer0_cfg1, - 1 => unsafe { &*(&block.timer1_cfg1 as *const _ as *const _) }, - 2 => unsafe { &*(&block.timer2_cfg1 as *const _ as *const _) }, - _ => unreachable!(), - } - } -} - -/// Clock configuration of a MCPWM timer -/// -/// Use [`PeripheralClockConfig::timer_clock_with_prescaler`](super::PeripheralClockConfig::timer_clock_with_prescaler) or -/// [`PeripheralClockConfig::timer_clock_with_frequency`](super::PeripheralClockConfig::timer_clock_with_frequency) to it. -#[derive(Copy, Clone)] -pub struct TimerClockConfig<'a> { - frequency: HertzU32, - period: u16, - prescaler: u8, - mode: PwmWorkingMode, - phantom: PhantomData<&'a Clocks<'a>>, -} - -impl<'a> TimerClockConfig<'a> { - pub(super) fn with_prescaler( - clock: &PeripheralClockConfig<'a>, - period: u16, - mode: PwmWorkingMode, - prescaler: u8, - ) -> Self { - let cycle_period = match mode { - PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1, - // The reference manual seems to provide an incorrect formula for UpDown - PwmWorkingMode::UpDown => period as u32 * 2, - }; - let frequency = clock.frequency / (prescaler as u32 + 1) / cycle_period; - - TimerClockConfig { - frequency, - prescaler, - period, - mode, - phantom: PhantomData, - } - } - - pub(super) fn with_frequency( - clock: &PeripheralClockConfig<'a>, - period: u16, - mode: PwmWorkingMode, - target_freq: HertzU32, - ) -> Result { - let cycle_period = match mode { - PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1, - // The reference manual seems to provide an incorrect formula for UpDown - PwmWorkingMode::UpDown => period as u32 * 2, - }; - let target_timer_frequency = target_freq - .raw() - .checked_mul(cycle_period) - .ok_or(FrequencyError)?; - if target_timer_frequency == 0 || target_freq > clock.frequency { - return Err(FrequencyError); - } - let prescaler = clock.frequency.raw() / target_timer_frequency - 1; - if prescaler > u8::MAX as u32 { - return Err(FrequencyError); - } - let frequency = clock.frequency / (prescaler + 1) / cycle_period; - - Ok(TimerClockConfig { - frequency, - prescaler: prescaler as u8, - period, - mode, - phantom: PhantomData, - }) - } - - /// Get the timer clock frequency. - /// - /// ### Note: - /// The actual value is rounded down to the nearest `u32` value - pub fn frequency(&self) -> HertzU32 { - self.frequency - } -} - -/// PWM working mode -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum PwmWorkingMode { - /// In this mode, the PWM timer increments from zero until reaching the - /// value configured in the period field. Once done, the PWM timer - /// returns to zero and starts increasing again. PWM period is equal to the - /// value of the period field + 1. - Increase = 1, - /// The PWM timer decrements to zero, starting from the value configured in - /// the period field. After reaching zero, it is set back to the period - /// value. Then it starts to decrement again. In this case, the PWM period - /// is also equal to the value of period field + 1. - Decrease = 2, - /// This is a combination of the two modes mentioned above. The PWM timer - /// starts increasing from zero until the period value is reached. Then, - /// the timer decreases back to zero. This pattern is then repeated. The - /// PWM period is the result of the value of the period field × 2. - UpDown = 3, -} - -/// The direction the timer counter is changing -#[derive(Debug)] -#[repr(u8)] -pub enum CounterDirection { - /// The timer counter is increasing - Increasing = 0, - /// The timer counter is decreasing - Decreasing = 1, -} - -impl From for CounterDirection { - fn from(bit: bool) -> Self { - match bit { - false => CounterDirection::Increasing, - true => CounterDirection::Decreasing, - } - } -} diff --git a/esp-hal-common/src/otg_fs.rs b/esp-hal-common/src/otg_fs.rs deleted file mode 100644 index 740d6819a70..00000000000 --- a/esp-hal-common/src/otg_fs.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! USB OTG full-speed peripheral - -pub use esp_synopsys_usb_otg::UsbBus; -use esp_synopsys_usb_otg::UsbPeripheral; - -use crate::{ - gpio::InputSignal, - peripheral::{Peripheral, PeripheralRef}, - peripherals, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -#[doc(hidden)] -pub trait UsbSel {} - -#[doc(hidden)] -pub trait UsbDp {} - -#[doc(hidden)] -pub trait UsbDm {} - -pub struct USB<'d, S, P, M> -where - S: UsbSel + Send + Sync, - P: UsbDp + Send + Sync, - M: UsbDm + Send + Sync, -{ - _usb0: PeripheralRef<'d, peripherals::USB0>, - _usb_sel: PeripheralRef<'d, S>, - _usb_dp: PeripheralRef<'d, P>, - _usb_dm: PeripheralRef<'d, M>, -} - -impl<'d, S, P, M> USB<'d, S, P, M> -where - S: UsbSel + Send + Sync, - P: UsbDp + Send + Sync, - M: UsbDm + Send + Sync, -{ - pub fn new( - usb0: impl Peripheral

    + 'd, - usb_sel: impl Peripheral

    + 'd, - usb_dp: impl Peripheral

    + 'd, - usb_dm: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(usb_sel, usb_dp, usb_dm); - peripheral_clock_control.enable(PeripheralEnable::Usb); - Self { - _usb0: usb0.into_ref(), - _usb_sel: usb_sel, - _usb_dp: usb_dp, - _usb_dm: usb_dm, - } - } -} - -unsafe impl<'d, S, P, M> Sync for USB<'d, S, P, M> -where - S: UsbSel + Send + Sync, - P: UsbDp + Send + Sync, - M: UsbDm + Send + Sync, -{ -} - -unsafe impl<'d, S, P, M> UsbPeripheral for USB<'d, S, P, M> -where - S: UsbSel + Send + Sync, - P: UsbDp + Send + Sync, - M: UsbDm + Send + Sync, -{ - const REGISTERS: *const () = peripherals::USB0::ptr() as *const (); - - const HIGH_SPEED: bool = false; - const FIFO_DEPTH_WORDS: usize = 256; - const ENDPOINT_COUNT: usize = 5; - - fn enable() { - unsafe { - let usb_wrap = &*peripherals::USB_WRAP::PTR; - usb_wrap.otg_conf.modify(|_, w| { - w.usb_pad_enable() - .set_bit() - .phy_sel() - .clear_bit() - .clk_en() - .set_bit() - .ahb_clk_force_on() - .set_bit() - .phy_clk_force_on() - .set_bit() - }); - - #[cfg(esp32s3)] - { - let rtc = &*peripherals::RTC_CNTL::PTR; - rtc.usb_conf - .modify(|_, w| w.sw_hw_usb_phy_sel().set_bit().sw_usb_phy_sel().set_bit()); - } - - crate::gpio::connect_high_to_peripheral(InputSignal::USB_OTG_IDDIG); // connected connector is mini-B side - crate::gpio::connect_high_to_peripheral(InputSignal::USB_SRP_BVALID); // HIGH to force USB device mode - crate::gpio::connect_high_to_peripheral(InputSignal::USB_OTG_VBUSVALID); // receiving a valid Vbus from device - crate::gpio::connect_low_to_peripheral(InputSignal::USB_OTG_AVALID); - - usb_wrap.otg_conf.modify(|_, w| { - w.pad_pull_override() - .set_bit() - .dp_pullup() - .set_bit() - .dp_pulldown() - .clear_bit() - .dm_pullup() - .clear_bit() - .dm_pulldown() - .clear_bit() - }); - } - } - - fn ahb_frequency_hz(&self) -> u32 { - // unused - 80_000_000 - } -} diff --git a/esp-hal-common/src/pcnt/channel.rs b/esp-hal-common/src/pcnt/channel.rs deleted file mode 100644 index e7583c125ec..00000000000 --- a/esp-hal-common/src/pcnt/channel.rs +++ /dev/null @@ -1,240 +0,0 @@ -use super::unit; -use crate::{ - gpio::{InputPin, InputSignal, ONE_INPUT, ZERO_INPUT}, - peripheral::Peripheral, - peripherals::GPIO, -}; - -/// Channel number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum Number { - Channel0, - Channel1, -} - -/// PCNT channel action on signal edge -#[derive(Debug, Copy, Clone, Default)] -pub enum EdgeMode { - /// Hold current count value - Hold = 0, - /// Increase count value - #[default] - Increment = 1, - /// Decrease count value - Decrement = 2, -} - -/// PCNT channel action on control level -#[derive(Debug, Copy, Clone, Default)] -pub enum CtrlMode { - /// Keep current count mode - Keep = 0, - /// Invert current count mode (increase -> decrease, decrease -> increase) - #[default] - Reverse = 1, - /// Hold current count value - Disable = 2, -} - -/// Pulse Counter configuration for a single channel -#[derive(Debug, Copy, Clone, Default)] -pub struct Config { - /// PCNT low control mode - pub lctrl_mode: CtrlMode, - /// PCNT high control mode - pub hctrl_mode: CtrlMode, - /// PCNT signal positive edge count mode - pub pos_edge: EdgeMode, - /// PCNT signal negative edge count mode - pub neg_edge: EdgeMode, - pub invert_ctrl: bool, - pub invert_sig: bool, -} - -/// PcntPin can be always high, always low, or an actual pin -#[derive(Clone, Copy)] -pub struct PcntSource { - source: u8, -} - -impl PcntSource { - pub fn from_pin<'a, P: InputPin>(pin: impl Peripheral

    + 'a) -> Self { - crate::into_ref!(pin); - Self { - source: pin.number(), - } - } - pub fn always_high() -> Self { - Self { source: ONE_INPUT } - } - pub fn always_low() -> Self { - Self { source: ZERO_INPUT } - } -} - -pub struct Channel { - unit: unit::Number, - channel: Number, -} - -impl Channel { - /// return a new Channel - pub(super) fn new(unit: unit::Number, channel: Number) -> Self { - Self { unit, channel } - } - - /// Configure the channel - pub fn configure(&mut self, ctrl_signal: PcntSource, edge_signal: PcntSource, config: Config) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - let conf0 = match self.unit { - unit::Number::Unit0 => &pcnt.u0_conf0, - unit::Number::Unit1 => &pcnt.u1_conf0, - unit::Number::Unit2 => &pcnt.u2_conf0, - unit::Number::Unit3 => &pcnt.u3_conf0, - #[cfg(esp32)] - unit::Number::Unit4 => &pcnt.u4_conf0, - #[cfg(esp32)] - unit::Number::Unit5 => &pcnt.u5_conf0, - #[cfg(esp32)] - unit::Number::Unit6 => &pcnt.u6_conf0, - #[cfg(esp32)] - unit::Number::Unit7 => &pcnt.u7_conf0, - }; - match self.channel { - Number::Channel0 => { - conf0.modify(|_, w| unsafe { - w.ch0_hctrl_mode() - .bits(config.hctrl_mode as u8) - .ch0_lctrl_mode() - .bits(config.lctrl_mode as u8) - .ch0_neg_mode() - .bits(config.neg_edge as u8) - .ch0_pos_mode() - .bits(config.pos_edge as u8) - }); - } - Number::Channel1 => { - conf0.modify(|_, w| unsafe { - w.ch1_hctrl_mode() - .bits(config.hctrl_mode as u8) - .ch1_lctrl_mode() - .bits(config.lctrl_mode as u8) - .ch1_neg_mode() - .bits(config.neg_edge as u8) - .ch1_pos_mode() - .bits(config.pos_edge as u8) - }); - } - } - self.set_ctrl_signal(ctrl_signal, config.invert_ctrl); - self.set_edge_signal(edge_signal, config.invert_sig); - } - - /// Set the control signal (pin/high/low) for this channel - pub fn set_ctrl_signal(&self, source: PcntSource, invert: bool) -> &Self { - let signal = match self.unit { - unit::Number::Unit0 => match self.channel { - Number::Channel0 => InputSignal::PCNT0_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT0_CTRL_CH1, - }, - unit::Number::Unit1 => match self.channel { - Number::Channel0 => InputSignal::PCNT1_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT1_CTRL_CH1, - }, - unit::Number::Unit2 => match self.channel { - Number::Channel0 => InputSignal::PCNT2_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT2_CTRL_CH1, - }, - unit::Number::Unit3 => match self.channel { - Number::Channel0 => InputSignal::PCNT3_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT3_CTRL_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit4 => match self.channel { - Number::Channel0 => InputSignal::PCNT4_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT4_CTRL_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit5 => match self.channel { - Number::Channel0 => InputSignal::PCNT5_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT5_CTRL_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit6 => match self.channel { - Number::Channel0 => InputSignal::PCNT6_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT6_CTRL_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit7 => match self.channel { - Number::Channel0 => InputSignal::PCNT7_CTRL_CH0, - Number::Channel1 => InputSignal::PCNT7_CTRL_CH1, - }, - }; - - if (signal as usize) <= crate::gpio::INPUT_SIGNAL_MAX as usize { - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(invert) - .in_sel() - .bits(source.source) - }); - } - self - } - - /// Set the edge signal (pin/high/low) for this channel - pub fn set_edge_signal(&self, source: PcntSource, invert: bool) -> &Self { - let signal = match self.unit { - unit::Number::Unit0 => match self.channel { - Number::Channel0 => InputSignal::PCNT0_SIG_CH0, - Number::Channel1 => InputSignal::PCNT0_SIG_CH1, - }, - unit::Number::Unit1 => match self.channel { - Number::Channel0 => InputSignal::PCNT1_SIG_CH0, - Number::Channel1 => InputSignal::PCNT1_SIG_CH1, - }, - unit::Number::Unit2 => match self.channel { - Number::Channel0 => InputSignal::PCNT2_SIG_CH0, - Number::Channel1 => InputSignal::PCNT2_SIG_CH1, - }, - unit::Number::Unit3 => match self.channel { - Number::Channel0 => InputSignal::PCNT3_SIG_CH0, - Number::Channel1 => InputSignal::PCNT3_SIG_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit4 => match self.channel { - Number::Channel0 => InputSignal::PCNT4_SIG_CH0, - Number::Channel1 => InputSignal::PCNT4_SIG_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit5 => match self.channel { - Number::Channel0 => InputSignal::PCNT5_SIG_CH0, - Number::Channel1 => InputSignal::PCNT5_SIG_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit6 => match self.channel { - Number::Channel0 => InputSignal::PCNT6_SIG_CH0, - Number::Channel1 => InputSignal::PCNT6_SIG_CH1, - }, - #[cfg(esp32)] - unit::Number::Unit7 => match self.channel { - Number::Channel0 => InputSignal::PCNT7_SIG_CH0, - Number::Channel1 => InputSignal::PCNT7_SIG_CH1, - }, - }; - - if (signal as usize) <= crate::gpio::INPUT_SIGNAL_MAX as usize { - unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe { - w.sel() - .set_bit() - .in_inv_sel() - .bit(invert) - .in_sel() - .bits(source.source) - }); - } - self - } -} diff --git a/esp-hal-common/src/pcnt/mod.rs b/esp-hal-common/src/pcnt/mod.rs deleted file mode 100644 index 28d27cffe43..00000000000 --- a/esp-hal-common/src/pcnt/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -use self::unit::Unit; -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - system::PeripheralClockControl, -}; - -pub mod channel; -pub mod unit; - -pub struct PCNT<'d> { - _instance: PeripheralRef<'d, crate::peripherals::PCNT>, -} - -impl<'d> PCNT<'d> { - /// Return a new PCNT - pub fn new( - _instance: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(_instance); - // Enable the PCNT peripherals clock in the system peripheral - peripheral_clock_control.enable(crate::system::Peripheral::Pcnt); - PCNT { _instance } - } - - /// Return a unit - pub fn get_unit(&self, number: unit::Number) -> Unit { - Unit::new(number) - } -} diff --git a/esp-hal-common/src/pcnt/unit.rs b/esp-hal-common/src/pcnt/unit.rs deleted file mode 100644 index 9703d9caa6f..00000000000 --- a/esp-hal-common/src/pcnt/unit.rs +++ /dev/null @@ -1,392 +0,0 @@ -use critical_section::CriticalSection; - -use super::channel; - -/// Unit number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum Number { - Unit0, - Unit1, - Unit2, - Unit3, - #[cfg(esp32)] - Unit4, - #[cfg(esp32)] - Unit5, - #[cfg(esp32)] - Unit6, - #[cfg(esp32)] - Unit7, -} - -/// Unit errors -#[derive(Debug)] -pub enum Error { - /// Invalid filter threshold value - InvalidFilterThresh, - /// Invalid low limit - must be < 0 - InvalidLowLimit, - /// Invalid high limit - must be > 0 - InvalidHighLimit, -} - -/// the current status of the counter. -#[derive(Copy, Clone, Debug, Default)] -pub enum ZeroMode { - /// pulse counter decreases from positive to 0. - #[default] - PosZero = 0, - /// pulse counter increases from negative to 0 - NegZero = 1, - /// pulse counter is negative (not implemented?) - Negitive = 2, - /// pulse counter is positive (not implemented?) - Positive = 3, -} - -impl From for ZeroMode { - fn from(value: u8) -> Self { - match value { - 0 => Self::PosZero, - 1 => Self::NegZero, - 2 => Self::Negitive, - 3 => Self::Positive, - _ => unreachable!(), // TODO: is this good enoough? should we use some default? - } - } -} - -// Events -#[derive(Copy, Clone, Debug, Default)] -pub struct Events { - pub low_limit: bool, - pub high_limit: bool, - pub thresh0: bool, - pub thresh1: bool, - pub zero: bool, -} - -/// Unit configuration -#[derive(Copy, Clone, Default)] -pub struct Config { - pub low_limit: i16, - pub high_limit: i16, - pub thresh0: i16, - pub thresh1: i16, - pub filter: Option, -} - -pub struct Unit { - number: Number, -} - -impl Unit { - /// return a new Unit - pub(super) fn new(number: Number) -> Self { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - let conf0 = match number { - Number::Unit0 => &pcnt.u0_conf0, - Number::Unit1 => &pcnt.u1_conf0, - Number::Unit2 => &pcnt.u2_conf0, - Number::Unit3 => &pcnt.u3_conf0, - #[cfg(esp32)] - Number::Unit4 => &pcnt.u4_conf0, - #[cfg(esp32)] - Number::Unit5 => &pcnt.u5_conf0, - #[cfg(esp32)] - Number::Unit6 => &pcnt.u6_conf0, - #[cfg(esp32)] - Number::Unit7 => &pcnt.u7_conf0, - }; - // disable filter and all events - conf0.modify(|_, w| unsafe { - w.filter_en() - .clear_bit() - .filter_thres() - .bits(0) - .thr_l_lim_en() - .clear_bit() - .thr_h_lim_en() - .clear_bit() - .thr_thres0_en() - .clear_bit() - .thr_thres1_en() - .clear_bit() - .thr_zero_en() - .clear_bit() - }); - Self { number } - } - - pub fn configure(&mut self, config: Config) -> Result<(), Error> { - // low limit must be >= or the limit is -32768 and when thats - // hit the event status claims it was the high limit. - // tested on an esp32s3 - if config.low_limit >= 0 { - return Err(Error::InvalidLowLimit); - } - if config.high_limit <= 0 { - return Err(Error::InvalidHighLimit); - } - let (filter_en, filter) = match config.filter { - Some(filter) => (true, filter), - None => (false, 0), - }; - // filter must be less than 1024 - if filter > 1023 { - return Err(Error::InvalidFilterThresh); - } - - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - let (conf0, conf1, conf2) = match self.number { - Number::Unit0 => (&pcnt.u0_conf0, &pcnt.u0_conf1, &pcnt.u0_conf2), - Number::Unit1 => (&pcnt.u1_conf0, &pcnt.u1_conf1, &pcnt.u1_conf2), - Number::Unit2 => (&pcnt.u2_conf0, &pcnt.u2_conf1, &pcnt.u2_conf2), - Number::Unit3 => (&pcnt.u3_conf0, &pcnt.u3_conf1, &pcnt.u3_conf2), - #[cfg(esp32)] - Number::Unit4 => (&pcnt.u4_conf0, &pcnt.u4_conf1, &pcnt.u4_conf2), - #[cfg(esp32)] - Number::Unit5 => (&pcnt.u5_conf0, &pcnt.u5_conf1, &pcnt.u5_conf2), - #[cfg(esp32)] - Number::Unit6 => (&pcnt.u6_conf0, &pcnt.u6_conf1, &pcnt.u6_conf2), - #[cfg(esp32)] - Number::Unit7 => (&pcnt.u7_conf0, &pcnt.u7_conf1, &pcnt.u7_conf2), - }; - conf2.write(|w| unsafe { - w.cnt_l_lim() - .bits(config.low_limit as u16) - .cnt_h_lim() - .bits(config.high_limit as u16) - }); - conf1.write(|w| unsafe { - w.cnt_thres0() - .bits(config.thresh0 as u16) - .cnt_thres1() - .bits(config.thresh1 as u16) - }); - conf0.modify(|_, w| unsafe { w.filter_thres().bits(filter).filter_en().bit(filter_en) }); - self.pause(); - self.clear(); - Ok(()) - } - - pub fn get_channel(&self, number: channel::Number) -> super::channel::Channel { - super::channel::Channel::new(self.number, number) - } - - pub fn clear(&self) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| { - match self.number { - Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u0().set_bit()), - Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u1().set_bit()), - Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u2().set_bit()), - Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u3().set_bit()), - #[cfg(esp32)] - Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u4().set_bit()), - #[cfg(esp32)] - Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u5().set_bit()), - #[cfg(esp32)] - Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u6().set_bit()), - #[cfg(esp32)] - Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u7().set_bit()), - } - // TODO: does this need a delay? (liebman / Jan 2 2023) - match self.number { - Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u0().clear_bit()), - Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u1().clear_bit()), - Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u2().clear_bit()), - Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u3().clear_bit()), - #[cfg(esp32)] - Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u4().clear_bit()), - #[cfg(esp32)] - Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u5().clear_bit()), - #[cfg(esp32)] - Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u6().clear_bit()), - #[cfg(esp32)] - Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u7().clear_bit()), - } - }); - } - - /// Pause the counter - pub fn pause(&self) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| match self.number { - Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u0().set_bit()), - Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u1().set_bit()), - Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u2().set_bit()), - Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u3().set_bit()), - #[cfg(esp32)] - Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u4().set_bit()), - #[cfg(esp32)] - Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u5().set_bit()), - #[cfg(esp32)] - Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u6().set_bit()), - #[cfg(esp32)] - Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u7().set_bit()), - }); - } - - /// Resume the counter - pub fn resume(&self) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| match self.number { - Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u0().clear_bit()), - Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u1().clear_bit()), - Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u2().clear_bit()), - Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u3().clear_bit()), - #[cfg(esp32)] - Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u4().clear_bit()), - #[cfg(esp32)] - Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u5().clear_bit()), - #[cfg(esp32)] - Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u6().clear_bit()), - #[cfg(esp32)] - Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u7().clear_bit()), - }); - } - - /// Enable which events generate interrupts on this unit. - pub fn events(&self, events: Events) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - let conf0 = match self.number { - Number::Unit0 => &pcnt.u0_conf0, - Number::Unit1 => &pcnt.u1_conf0, - Number::Unit2 => &pcnt.u2_conf0, - Number::Unit3 => &pcnt.u3_conf0, - #[cfg(esp32)] - Number::Unit4 => &pcnt.u4_conf0, - #[cfg(esp32)] - Number::Unit5 => &pcnt.u5_conf0, - #[cfg(esp32)] - Number::Unit6 => &pcnt.u6_conf0, - #[cfg(esp32)] - Number::Unit7 => &pcnt.u7_conf0, - }; - conf0.modify(|_, w| { - w.thr_l_lim_en() - .bit(events.low_limit) - .thr_h_lim_en() - .bit(events.high_limit) - .thr_thres0_en() - .bit(events.thresh0) - .thr_thres1_en() - .bit(events.thresh1) - .thr_zero_en() - .bit(events.zero) - }); - } - - /// Get the latest events for this unit. - pub fn get_events(&self) -> Events { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - let status = pcnt.u_status[self.number as usize].read(); - - Events { - low_limit: status.l_lim().bit(), - high_limit: status.h_lim().bit(), - thresh0: status.thres0().bit(), - thresh1: status.thres1().bit(), - zero: status.zero().bit(), - } - } - - /// Get the mode of the last zero crossing - pub fn get_zero_mode(&self) -> ZeroMode { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - pcnt.u_status[self.number as usize] - .read() - .zero_mode() - .bits() - .into() - } - - /// Enable interrupts for this unit. - pub fn listen(&self) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| { - pcnt.int_ena.modify(|_, w| match self.number { - Number::Unit0 => w.cnt_thr_event_u0().set_bit(), - Number::Unit1 => w.cnt_thr_event_u1().set_bit(), - Number::Unit2 => w.cnt_thr_event_u2().set_bit(), - Number::Unit3 => w.cnt_thr_event_u3().set_bit(), - #[cfg(esp32)] - Number::Unit4 => w.cnt_thr_event_u4().set_bit(), - #[cfg(esp32)] - Number::Unit5 => w.cnt_thr_event_u5().set_bit(), - #[cfg(esp32)] - Number::Unit6 => w.cnt_thr_event_u6().set_bit(), - #[cfg(esp32)] - Number::Unit7 => w.cnt_thr_event_u7().set_bit(), - }) - }); - } - - /// Disable interrupts for this unit. - pub fn unlisten(&self, _cs: CriticalSection) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| { - pcnt.int_ena.write(|w| match self.number { - Number::Unit0 => w.cnt_thr_event_u0().clear_bit(), - Number::Unit1 => w.cnt_thr_event_u1().clear_bit(), - Number::Unit2 => w.cnt_thr_event_u2().clear_bit(), - Number::Unit3 => w.cnt_thr_event_u3().clear_bit(), - #[cfg(esp32)] - Number::Unit4 => w.cnt_thr_event_u4().clear_bit(), - #[cfg(esp32)] - Number::Unit5 => w.cnt_thr_event_u5().clear_bit(), - #[cfg(esp32)] - Number::Unit6 => w.cnt_thr_event_u6().clear_bit(), - #[cfg(esp32)] - Number::Unit7 => w.cnt_thr_event_u7().clear_bit(), - }) - }); - } - - /// Returns true if an interrupt is active for this unit. - pub fn interrupt_set(&self) -> bool { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - match self.number { - Number::Unit0 => pcnt.int_st.read().cnt_thr_event_u0().bit(), - Number::Unit1 => pcnt.int_st.read().cnt_thr_event_u1().bit(), - Number::Unit2 => pcnt.int_st.read().cnt_thr_event_u2().bit(), - Number::Unit3 => pcnt.int_st.read().cnt_thr_event_u3().bit(), - #[cfg(esp32)] - Number::Unit4 => pcnt.int_st.read().cnt_thr_event_u4().bit(), - #[cfg(esp32)] - Number::Unit5 => pcnt.int_st.read().cnt_thr_event_u5().bit(), - #[cfg(esp32)] - Number::Unit6 => pcnt.int_st.read().cnt_thr_event_u6().bit(), - #[cfg(esp32)] - Number::Unit7 => pcnt.int_st.read().cnt_thr_event_u7().bit(), - } - } - - /// Clear the interrupt bit for this unit. - pub fn reset_interrupt(&self) { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - critical_section::with(|_cs| { - pcnt.int_clr.write(|w| match self.number { - Number::Unit0 => w.cnt_thr_event_u0().set_bit(), - Number::Unit1 => w.cnt_thr_event_u1().set_bit(), - Number::Unit2 => w.cnt_thr_event_u2().set_bit(), - Number::Unit3 => w.cnt_thr_event_u3().set_bit(), - #[cfg(esp32)] - Number::Unit4 => w.cnt_thr_event_u4().set_bit(), - #[cfg(esp32)] - Number::Unit5 => w.cnt_thr_event_u5().set_bit(), - #[cfg(esp32)] - Number::Unit6 => w.cnt_thr_event_u6().set_bit(), - #[cfg(esp32)] - Number::Unit7 => w.cnt_thr_event_u7().set_bit(), - }) - }); - } - - /// Get the current counter value. - pub fn get_value(&self) -> i16 { - let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() }; - pcnt.u_cnt[self.number as usize].read().cnt().bits() as i16 - } -} diff --git a/esp-hal-common/src/peripheral.rs b/esp-hal-common/src/peripheral.rs deleted file mode 100644 index 38d63677e90..00000000000 --- a/esp-hal-common/src/peripheral.rs +++ /dev/null @@ -1,347 +0,0 @@ -use core::{ - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -/// An exclusive reference to a peripheral. -/// -/// This is functionally the same as a `&'a mut T`. The reason for having a -/// dedicated struct is memory efficiency: -/// -/// Peripheral singletons are typically either zero-sized (for concrete -/// peripehrals like `PA9` or `Spi4`) or very small (for example `AnyPin` which -/// is 1 byte). However `&mut T` is always 4 bytes for 32-bit targets, even if T -/// is zero-sized. PeripheralRef stores a copy of `T` instead, so it's the same -/// size. -/// -/// but it is the size of `T` not the size -/// of a pointer. This is useful if T is a zero sized type. -pub struct PeripheralRef<'a, T> { - inner: T, - _lifetime: PhantomData<&'a mut T>, -} - -impl<'a, T> PeripheralRef<'a, T> { - #[inline] - pub fn new(inner: T) -> Self { - Self { - inner, - _lifetime: PhantomData, - } - } - - /// Unsafely clone (duplicate) a peripheral singleton. - /// - /// # Safety - /// - /// This returns an owned clone of the peripheral. You must manually ensure - /// only one copy of the peripheral is in use at a time. For example, don't - /// create two SPI drivers on `SPI1`, because they will "fight" each other. - /// - /// You should strongly prefer using `reborrow()` instead. It returns a - /// `PeripheralRef` that borrows `self`, which allows the borrow checker - /// to enforce this at compile time. - pub unsafe fn clone_unchecked(&mut self) -> PeripheralRef<'a, T> - where - T: Peripheral

    , - { - PeripheralRef::new(self.inner.clone_unchecked()) - } - - /// Reborrow into a "child" PeripheralRef. - /// - /// `self` will stay borrowed until the child PeripheralRef is dropped. - pub fn reborrow(&mut self) -> PeripheralRef<'_, T> - where - T: Peripheral

    , - { - // safety: we're returning the clone inside a new PeripheralRef that borrows - // self, so user code can't use both at the same time. - PeripheralRef::new(unsafe { self.inner.clone_unchecked() }) - } - - /// Map the inner peripheral using `Into`. - /// - /// This converts from `PeripheralRef<'a, T>` to `PeripheralRef<'a, U>`, - /// using an `Into` impl to convert from `T` to `U`. - /// - /// For example, this can be useful to degrade GPIO pins: converting from - /// PeripheralRef<'a, PB11>` to `PeripheralRef<'a, AnyPin>`. - #[inline] - pub fn map_into(self) -> PeripheralRef<'a, U> - where - T: Into, - { - PeripheralRef { - inner: self.inner.into(), - _lifetime: PhantomData, - } - } -} - -impl<'a, T> Deref for PeripheralRef<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl<'a, T> DerefMut for PeripheralRef<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -/// Trait for any type that can be used as a peripheral of type `P`. -/// -/// This is used in driver constructors, to allow passing either owned -/// peripherals (e.g. `TWISPI0`), or borrowed peripherals (e.g. `&mut TWISPI0`). -/// -/// For example, if you have a driver with a constructor like this: -/// -/// ```ignore -/// impl<'d, T: Instance> Twim<'d, T> { -/// pub fn new( -/// twim: impl Peripheral

    + 'd, -/// irq: impl Peripheral

    + 'd, -/// sda: impl Peripheral

    + 'd, -/// scl: impl Peripheral

    + 'd, -/// config: Config, -/// ) -> Self { .. } -/// } -/// ``` -/// -/// You may call it with owned peripherals, which yields an instance that can -/// live forever (`'static`): -/// -/// ```ignore -/// let mut twi: Twim<'static, ...> = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); -/// ``` -/// -/// Or you may call it with borrowed peripherals, which yields an instance that -/// can only live for as long as the borrows last: -/// -/// ```ignore -/// let mut twi: Twim<'_, ...> = Twim::new(&mut p.TWISPI0, &mut irq, &mut p.P0_03, &mut p.P0_04, config); -/// ``` -/// -/// # Implementation details, for HAL authors -/// -/// When writing a HAL, the intended way to use this trait is to take `impl -/// Peripheral

    ` in the HAL's public API (such as driver constructors), -/// calling `.into_ref()` to obtain a `PeripheralRef`, and storing that in the -/// driver struct. -/// -/// `.into_ref()` on an owned `T` yields a `PeripheralRef<'static, T>`. -/// `.into_ref()` on an `&'a mut T` yields a `PeripheralRef<'a, T>`. -pub trait Peripheral: Sized + sealed::Sealed { - /// Peripheral singleton type - type P; - - /// Unsafely clone (duplicate) a peripheral singleton. - /// - /// # Safety - /// - /// This returns an owned clone of the peripheral. You must manually ensure - /// only one copy of the peripheral is in use at a time. For example, don't - /// create two SPI drivers on `SPI1`, because they will "fight" each other. - /// - /// You should strongly prefer using `into_ref()` instead. It returns a - /// `PeripheralRef`, which allows the borrow checker to enforce this at - /// compile time. - unsafe fn clone_unchecked(&mut self) -> Self::P; - - /// Convert a value into a `PeripheralRef`. - /// - /// When called on an owned `T`, yields a `PeripheralRef<'static, T>`. - /// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`. - #[inline] - fn into_ref<'a>(mut self) -> PeripheralRef<'a, Self::P> - where - Self: 'a, - { - PeripheralRef::new(unsafe { self.clone_unchecked() }) - } -} - -impl Peripheral for &mut T -where - T: Peripheral

    , -{ - type P = T; - - unsafe fn clone_unchecked(&mut self) -> Self::P { - T::clone_unchecked(self) - } -} - -impl sealed::Sealed for &mut T where T: sealed::Sealed {} - -pub(crate) mod sealed { - pub trait Sealed {} -} - -mod peripheral_macros { - #[macro_export] - macro_rules! peripherals { - ($($(#[$cfg:meta])? $name:ident => $from_pac:tt),*$(,)?) => { - - /// Contains the generated peripherals which implement [`Peripheral`] - mod peripherals { - pub use super::pac::*; - $( - crate::create_peripheral!($(#[$cfg])? $name => $from_pac); - )* - } - - #[allow(non_snake_case)] - pub struct Peripherals { - $( - $(#[$cfg])? - pub $name: peripherals::$name, - )* - } - - impl Peripherals { - /// Returns all the peripherals *once* - #[inline] - pub fn take() -> Self { - - #[no_mangle] - static mut _ESP_HAL_DEVICE_PERIPHERALS: bool = false; - - critical_section::with(|_| unsafe { - if _ESP_HAL_DEVICE_PERIPHERALS { - panic!("init called more than once!") - } - _ESP_HAL_DEVICE_PERIPHERALS = true; - Self::steal() - }) - } - } - - impl Peripherals { - /// Unsafely create an instance of this peripheral out of thin air. - /// - /// # Safety - /// - /// You must ensure that you're only using one instance of this type at a time. - #[inline] - pub unsafe fn steal() -> Self { - Self { - $( - $(#[$cfg])? - $name: peripherals::$name::steal(), - )* - } - } - } - - // expose the new structs - $( - pub use peripherals::$name; - )* - } - } - - #[macro_export] - macro_rules! into_ref { - ($($name:ident),*) => { - $( - #[allow(unused_mut)] - let mut $name = $name.into_ref(); - )* - } - } - - #[macro_export] - macro_rules! create_peripheral { - ($(#[$cfg:meta])? $name:ident => true) => { - $(#[$cfg])? - #[derive(Debug)] - #[allow(non_camel_case_types)] - pub struct $name { _inner: () } - - $(#[$cfg])? - impl $name { - /// Unsafely create an instance of this peripheral out of thin air. - /// - /// # Safety - /// - /// You must ensure that you're only using one instance of this type at a time. - #[inline] - pub unsafe fn steal() -> Self { - Self { _inner: () } - } - - #[doc = r"Pointer to the register block"] - pub const PTR: *const ::Target = super::pac::$name::PTR; - - #[doc = r"Return the pointer to the register block"] - #[inline(always)] - pub const fn ptr() -> *const ::Target { - super::pac::$name::PTR - } - } - - impl core::ops::Deref for $name { - type Target = ::Target; - - fn deref(&self) -> &Self::Target { - unsafe { &*Self::PTR } - } - } - - impl core::ops::DerefMut for $name { - - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *(Self::PTR as *mut _) } - } - } - - impl crate::peripheral::Peripheral for $name { - type P = $name; - - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - Self::steal() - } - } - - impl crate::peripheral::sealed::Sealed for $name {} - }; - ($(#[$cfg:meta])? $name:ident => false) => { - $(#[$cfg])? - #[derive(Debug)] - #[allow(non_camel_case_types)] - pub struct $name { _inner: () } - - $(#[$cfg])? - impl $name { - /// Unsafely create an instance of this peripheral out of thin air. - /// - /// # Safety - /// - /// You must ensure that you're only using one instance of this type at a time. - #[inline] - pub unsafe fn steal() -> Self { - Self { _inner: () } - } - } - - impl crate::peripheral::Peripheral for $name { - type P = $name; - - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - Self::steal() - } - } - - impl crate::peripheral::sealed::Sealed for $name {} - } - } -} diff --git a/esp-hal-common/src/prelude.rs b/esp-hal-common/src/prelude.rs deleted file mode 100644 index 75974b15c27..00000000000 --- a/esp-hal-common/src/prelude.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! The prelude -//! -//! Re-exports all traits required for interacting with the various peripheral -//! drivers implemented in this crate. - -pub use embedded_dma::{ - ReadBuffer as _embedded_dma_ReadBuffer, - ReadTarget as _embedded_dma_ReadTarget, - Word as _embedded_dma_Word, - WriteBuffer as _embedded_dma_WriteBuffer, - WriteTarget as _embedded_dma_WriteTarget, -}; -pub use embedded_hal::{ - digital::v2::{ - InputPin as _embedded_hal_digital_v2_InputPin, - OutputPin as _embedded_hal_digital_v2_OutputPin, - StatefulOutputPin as _embedded_hal_digital_v2_StatefulOutputPin, - ToggleableOutputPin as _embedded_hal_digital_v2_ToggleableOutputPin, - }, - prelude::*, -}; -#[cfg(feature = "async")] -pub use embedded_hal_async::{ - delay::DelayUs as _embedded_hal_async_delay_DelayUs, - digital::Wait as _embedded_hal_async_digital_Wait, - i2c::I2c as _embedded_hal_async_i2c_I2c, - spi::SpiBus as _embedded_hal_spi_SpiBus, - spi::SpiBusFlush as _embedded_hal_spi_SpiBusFlush, - spi::SpiBusRead as _embedded_hal_spi_SpiBusRead, - spi::SpiBusWrite as _embedded_hal_spi_SpiBusWrite, - spi::SpiDevice as _embedded_hal_spi_SpiDevice, -}; -pub use fugit::{ - ExtU32 as _fugit_ExtU32, - ExtU64 as _fugit_ExtU64, - RateExtU32 as _fugit_RateExtU32, - RateExtU64 as _fugit_RateExtU64, -}; -pub use nb; - -#[cfg(any(esp32c2, esp32c3, esp32c6))] -pub use crate::analog::SarAdcExt as _esp_hal_analog_SarAdcExt; -#[cfg(sens)] -pub use crate::analog::SensExt as _esp_hal_analog_SensExt; -#[cfg(any(gdma, pdma))] -pub use crate::dma::{ - DmaTransfer as _esp_hal_dma_DmaTransfer, - DmaTransferRxTx as _esp_hal_dma_DmaTransferRxTx, -}; -#[cfg(gpio)] -pub use crate::gpio::{ - InputPin as _esp_hal_gpio_InputPin, - OutputPin as _esp_hal_gpio_OutputPin, - Pin as _esp_hal_gpio_Pin, -}; -#[cfg(any(i2c0, i2c1))] -pub use crate::i2c::Instance as _esp_hal_i2c_Instance; -#[cfg(ledc)] -pub use crate::ledc::{ - channel::{ - ChannelHW as _esp_hal_ledc_channel_ChannelHW, - ChannelIFace as _esp_hal_ledc_channel_ChannelIFace, - }, - timer::{TimerHW as _esp_hal_ledc_timer_TimerHW, TimerIFace as _esp_hal_ledc_timer_TimerIFace}, -}; -#[cfg(rmt)] -pub use crate::pulse_control::{ - ConfiguredChannel as _esp_hal_pulse_control_ConfiguredChannel, - OutputChannel as _esp_hal_pulse_control_OutputChannel, -}; -#[cfg(radio)] -pub use crate::radio::RadioExt as _esp_hal_RadioExt; -#[cfg(any(esp32, esp32s2))] -pub use crate::spi::dma::WithDmaSpi3 as _esp_hal_spi_dma_WithDmaSpi3; -#[cfg(any(spi0, spi1, spi2, spi3))] -pub use crate::spi::{ - dma::WithDmaSpi2 as _esp_hal_spi_dma_WithDmaSpi2, - Instance as _esp_hal_spi_Instance, - InstanceDma as _esp_hal_spi_InstanceDma, -}; -#[cfg(any(dport, pcr, system))] -pub use crate::system::SystemExt as _esp_hal_system_SystemExt; -#[cfg(any(timg0, timg1))] -pub use crate::timer::{ - Instance as _esp_hal_timer_Instance, - TimerGroupInstance as _esp_hal_timer_TimerGroupInstance, -}; -#[cfg(any(uart0, uart1, uart2))] -pub use crate::uart::{Instance as _esp_hal_uart_Instance, UartPins as _esp_hal_uart_UartPins}; -pub use crate::{clock::Clock as _esp_hal_clock_Clock, entry, macros::*}; - -/// All traits required for using the 1.0.0-alpha.x release of embedded-hal -#[cfg(feature = "eh1")] -pub mod eh1 { - #[cfg(any(twai0, twai1))] - pub use embedded_can::{ - blocking::Can as _embedded_can_blocking_Can, - nb::Can as _embedded_can_nb_Can, - Error as _embedded_can_Error, - Frame as _embedded_can_Frame, - }; - pub use embedded_hal_1::{ - delay::DelayUs as _embedded_hal_delay_blocking_DelayUs, - digital::{ - InputPin as _embedded_hal_digital_blocking_InputPin, - OutputPin as _embedded_hal_digital_blocking_OutputPin, - StatefulOutputPin as _embedded_hal_digital_blocking_StatefulOutputPin, - ToggleableOutputPin as _embedded_hal_digital_blocking_ToggleableOutputPin, - }, - i2c::I2c as _embedded_hal_i2c_blocking_I2c, - spi::{ - SpiBus as _embedded_hal_spi_blocking_SpiBus, - SpiBusFlush as _embedded_hal_spi_blocking_SpiBusFlush, - SpiBusRead as _embedded_hal_spi_blocking_SpiBusRead, - SpiBusWrite as _embedded_hal_spi_blocking_SpiBusWrite, - }, - }; - pub use embedded_hal_nb::{ - serial::{Read as _embedded_hal_nb_serial_Read, Write as _embedded_hal_nb_serial_Write}, - spi::FullDuplex as _embedded_hal_nb_spi_FullDuplex, - }; - - pub use super::*; -} diff --git a/esp-hal-common/src/pulse_control.rs b/esp-hal-common/src/pulse_control.rs deleted file mode 100644 index c4256214c7c..00000000000 --- a/esp-hal-common/src/pulse_control.rs +++ /dev/null @@ -1,1086 +0,0 @@ -//! # Remote Control Peripheral (RMT) -//! -//! ### Summary -//! The ESP32 variants include a remote control peripheral (RMT) that -//! is designed to handle infrared remote control signals. For that -//! purpose, it can convert bitstreams of data (from the RAM) into -//! pulse codes and even modulate those codes into a carrier wave. -//! -//! It can also convert received pulse codes (again, with carrier -//! wave support) into data bits. -//! -//! A secondary use case for this peripheral is to drive RGB(W) LEDs -//! that bear an internal IC and use a pulse code protocol. -//! -//! ### Channels -//! The RMT peripheral has the following channels available -//! on individual chips: -//! -//! * The **ESP32** has 8 channels, each of them can be either receiver or -//! transmitter -//! * The **ESP32-C3** has 4 channels, `Channel0` and `Channel1` hardcoded for -//! transmitting signals and `Channel2` and `Channel3` hardcoded for receiving -//! signals. -//! * The **ESP32-C6** has 4 channels, `Channel0` and `Channel1` hardcoded for -//! transmitting signals and `Channel2` and `Channel3` hardcoded for receiving -//! signals. -//! * The **ESP32-S2** has 4 channels, each of them can be either receiver or -//! transmitter. -//! * The **ESP32-S3** has 8 channels, `Channel0`-`Channel3` hardcdoded for -//! transmitting signals and `Channel4`-`Channel7` hardcoded for receiving -//! signals. -//! -//! ### Implementation State -//! * FIFO mode is not supported (there appear to be some issues with FIFO mode -//! in some variants and for consistency all variants therefore we use -//! NON-FIFO mode everywhere) -//! * Non-blocking mode is currently not supported! -//! * Input channels are currently not supported! -//! -//! ### Example (for ESP32-C3) -//! ``` -//! let mut peripherals = peripherals::Peripherals::take(); -//! -//! // Configure RMT peripheral globally -//! let pulse = PulseControl::new( -//! peripherals.RMT, -//! &mut peripherals.SYSTEM, -//! ClockSource::APB, -//! 0, // Integer part of the RMT-wide clock divider -//! 0, // Numerator part of the RMT-wide clock divider -//! 0, // Denominator part of the RMT-wide clock divider -//! ) -//! .unwrap(); -//! -//! // Get reference to channel -//! let mut rmt_channel0 = pulse.channel0; -//! -//! // Set up channel -//! rmt_channel0 -//! .set_idle_output_level(false) -//! .set_carrier_modulation(false) -//! .set_channel_divider(1) -//! .set_idle_output(true); -//! -//! // Assign GPIO pin where pulses should be sent to -//! let mut rmt_channel0 = rmt_channel0.assign_pin(io.pins.gpio8); -//! -//! // Create pulse sequence -//! let mut seq = [PulseCode { -//! level1: true, -//! length1: 10u32.nanos(), -//! level2: false, -//! length2: 90u32.nanos(), -//! }; 288]; -//! -//! // Send sequence -//! rmt_channel0 -//! .send_pulse_sequence(RepeatMode::SingleShot, &seq) -//! .unwrap(); -//! ``` - -#![deny(missing_docs)] - -use core::slice::Iter; - -use fugit::NanosDurationU32; -pub use paste::paste; - -#[cfg(esp32c6)] -use crate::peripherals::PCR; -use crate::{ - gpio::{OutputPin, OutputSignal}, - peripheral::{Peripheral, PeripheralRef}, - peripherals::RMT, - system::PeripheralClockControl, -}; - -/// Errors that can occur when the peripheral is configured -#[derive(Debug)] -pub enum SetupError { - /// The global configuration for the RMT peripheral is invalid - /// (e.g. the fractional parameters are outOfBound) - InvalidGlobalConfig, -} - -/// Errors that can occur during a transmission attempt -#[derive(Debug)] -pub enum TransmissionError { - /// Generic Transmission Error - Failure(bool, bool, bool, bool), - /// The maximum number of transmissions (`=(2^10)-1`) was exceeded - RepetitionOverflow, - /// The `RepeatNtimes` and `Forever` modesl are only feasible if the - /// sequence fits into the RAM in one go. If the sequence has > 48 - /// elements, the `RepeatNtimes` and `Forever` modes cannot be used. - IncompatibleRepeatMode, -} - -/// Specifies the mode with which pulses are sent out in transmitter channels -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum RepeatMode { - /// Send sequence once - SingleShot, - /// Send sequence N times (`N < (2^10)`) - #[cfg(not(esp32))] - RepeatNtimes(u16), - /// Repeat sequence until stopped by additional function call - Forever, -} - -/// Specify the clock source for the RMT peripheral -#[cfg(any(esp32c3, esp32c6, esp32s3))] -#[derive(Debug, Copy, Clone)] -pub enum ClockSource { - /// Application-level clock - APB = 1, - /// 20 MHz internal oscillator - RTC20M = 2, - /// External clock source - XTAL = 3, -} - -/// Specify the clock source for the RMT peripheral on the ESP32 and ESP32-S3 -/// variants -#[cfg(any(esp32s2, esp32))] -#[derive(Debug, Copy, Clone)] -pub enum ClockSource { - /// Reference Tick (usually configured to 1 us) - RefTick = 0, - /// Application-level clock - APB = 1, -} - -// Specifies how many entries we can store in the RAM section that is allocated -// to the RMT channel -#[cfg(any(esp32s2, esp32))] -const CHANNEL_RAM_SIZE: u8 = 64; -#[cfg(any(esp32c3, esp32c6, esp32s3))] -const CHANNEL_RAM_SIZE: u8 = 48; - -// Specifies where the RMT RAM section starts for the particular ESP32 variant -#[cfg(esp32s2)] -const RMT_RAM_START: usize = 0x3f416400; -#[cfg(esp32c3)] -const RMT_RAM_START: usize = 0x60016400; -#[cfg(esp32c6)] -const RMT_RAM_START: usize = 0x60006400; -#[cfg(esp32)] -const RMT_RAM_START: usize = 0x3ff56800; -#[cfg(esp32s3)] -const RMT_RAM_START: usize = 0x60016800; - -/// Object representing the state of one pulse code per ESP32-C3 TRM -/// -/// Allows for the assignment of two levels and their lenghts -#[derive(Clone, Copy, Debug)] -pub struct PulseCode { - /// Logical output level in the first pulse code interval - pub level1: bool, - /// Length of the first pulse code interval (in clock cycles) - pub length1: NanosDurationU32, - /// Logical output level in the second pulse code interval - pub level2: bool, - /// Length of the second pulse code interval (in clock cycles) - pub length2: NanosDurationU32, -} - -/// Convert a pulse code structure into a u32 value that can be written -/// into the data registers -impl From for u32 { - #[inline(always)] - fn from(p: PulseCode) -> u32 { - // The Pulse Code format in the RAM appears to be - // little-endian - - // The length1 value resides in bits [14:0] - let mut entry: u32 = p.length1.ticks() as u32; - - // If level1 is high, set bit 15, otherwise clear it - if p.level1 { - entry |= 1 << 15; - } else { - entry &= !(1 << 15); - } - - // If level2 is high, set bit 31, otherwise clear it - if p.level2 { - entry |= 1 << 31; - } else { - entry &= !(1 << 31); - } - - // The length2 value resides in bits [30:16] - entry |= (p.length2.ticks() as u32) << 16; - - entry - } -} - -/// Functionality that every OutputChannel must support -pub trait OutputChannel { - /// Output channel type - type ConfiguredChannel<'d, P> - where - P: OutputPin + 'd; - - /// Set the logical level that the connected pin is pulled to - /// while the channel is idle - fn set_idle_output_level(&mut self, level: bool) -> &mut Self; - - /// Enable/Disable the output while the channel is idle - fn set_idle_output(&mut self, state: bool) -> &mut Self; - - /// Set channel clock divider value - fn set_channel_divider(&mut self, divider: u8) -> &mut Self; - - /// Enable/Disable carrier modulation - fn set_carrier_modulation(&mut self, state: bool) -> &mut Self; - - /// Set the clock source (for the ESP32-S2 abd ESP32 this can be done on a - /// channel level) - #[cfg(any(esp32s2, esp32))] - fn set_clock_source(&mut self, source: ClockSource) -> &mut Self; - - /// Assign a pin that should be driven by this channel - fn assign_pin<'d, P: OutputPin>( - self, - pin: impl Peripheral

    + 'd, - ) -> Self::ConfiguredChannel<'d, P>; -} - -/// Functionality that is allowed only on `ConfiguredChannel` -pub trait ConfiguredChannel { - /// Send a pulse sequence in a blocking fashion - fn send_pulse_sequence( - &mut self, - repeat_mode: RepeatMode, - sequence: &[PulseCode; N], - ) -> Result<(), TransmissionError>; - - /// Send a raw pulse sequence in a blocking fashion - /// - /// In this function we expect the `sequence` elements to be already - /// in the correct u32 format that is understood by the RMT. - /// Please refer to the reference manual or use the variant which - /// accepts `PulseCode` objects instead. - fn send_pulse_sequence_raw( - &mut self, - repeat_mode: RepeatMode, - sequence: &[u32; N], - ) -> Result<(), TransmissionError>; - - /// Stop any ongoing (repetitive) transmission - /// - /// This function needs to be called to stop sending when - /// previously a sequence was sent with `RepeatMode::Forever`. - fn stop_transmission(&self); -} - -macro_rules! channel_instance { - ($num:literal, $cxi:ident, $output_signal:path - ) => { - /// RX/TX Input/Output Channel - pub struct $cxi { - mem_offset: usize, - } - impl $cxi { - /// Create a new channel instance - pub fn new() -> Self { - let mut channel = $cxi { mem_offset: 0 }; - - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - // Apply default configuration - unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| unsafe { - // Configure memory block size - w.mem_size() - .bits(1) - }); - } - else { - conf0!($num).modify(|_, w| unsafe { - // Configure memory block size - w.mem_size() - .bits(1) - }); - conf1!($num).modify(|_, w| - // Configure memory block size - w.mem_owner() - .clear_bit() - ); - - } - }; - - #[cfg(esp32)] - conf0!($num).modify(|_, w| - // Enable clock - w.clk_en() - .set_bit() - // Disable forced power down of the peripheral (just to be sure) - .mem_pd() - .clear_bit() - ); - - channel.set_carrier_modulation(false); - channel.set_idle_output_level(false); - channel.set_idle_output(false); - channel.set_channel_divider(1); - - channel - } - - /// Write a sequence of pulse codes into the RMT fifo buffer - #[inline(always)] - fn write_sequence( - &mut self, - seq_iter: &mut Iter, - max_inserted_elements: u8, - ){ - for _ in 0..max_inserted_elements { - match seq_iter.next() { - None => { - break; - } - Some(pulse) => self.load_fifo(*pulse), - } - } - } - - #[inline(always)] - fn load_fifo(&mut self, value: u32) { - let base_ptr: usize = RMT_RAM_START + ($num * CHANNEL_RAM_SIZE as usize * 4); - let ram_ptr = (base_ptr + self.mem_offset) as *mut u32; - unsafe { - ram_ptr.write_volatile(value); - } - - self.mem_offset += 4; - if self.mem_offset >= CHANNEL_RAM_SIZE as usize * 4 { - self.mem_offset = 0; - } - } - - #[inline(always)] - fn reset_fifo(&mut self) { - self.mem_offset = 0; - } - } - - paste!( - #[doc = "Wrapper for`" $cxi "` object."] - pub struct []<'d, P> { - channel: $cxi, - _pin: PeripheralRef<'d, P> - } - - impl<'d, P: OutputPin> ConfiguredChannel for []<'d, P> { - /// Send a pulse sequence in a blocking fashion - fn send_pulse_sequence( - &mut self, - repeat_mode: RepeatMode, - sequence: &[PulseCode; N], - ) -> Result<(), TransmissionError> { - let precomputed_sequence = sequence.map(|x| u32::from(x)); - - self.send_pulse_sequence_raw(repeat_mode, &precomputed_sequence) - } - - /// Send a raw pulse sequence in a blocking fashion - /// - /// In this function we expect the `sequence` elements to be already - /// in the correct u32 format that is understood by the RMT. - /// Please refer to the reference manual or use the variant which - /// accepts `PulseCode` objects instead. - /// - /// We expect that the end marker is already part of the provided - /// sequence and to be provided in all modes! - fn send_pulse_sequence_raw( - &mut self, - repeat_mode: RepeatMode, - sequence: &[u32; N], - ) -> Result<(), TransmissionError> { - // Check for any configuration error states - match repeat_mode { - #[cfg(not(esp32))] - RepeatMode::RepeatNtimes(val) => { - if val >= 1024 { - return Err(TransmissionError::RepetitionOverflow); - } - if sequence.len() > CHANNEL_RAM_SIZE as usize { - return Err(TransmissionError::IncompatibleRepeatMode); - } - } - RepeatMode::Forever => { - if sequence.len() > CHANNEL_RAM_SIZE as usize { - return Err(TransmissionError::IncompatibleRepeatMode); - } - } - _ => (), - }; - - // Depending on the variant, other registers have to be used here - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - let conf_reg = & conf1!($num); - } else { - let conf_reg = & unsafe{ &*RMT::PTR }.ch_tx_conf0[$num]; - } - } - - // The ESP32 does not support loop/count modes, as such we have to - // only configure a subset of registers - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // Configure counting mode and repetitions - unsafe { &*RMT::PTR }.ch_tx_lim[$num].modify(|_, w| unsafe { - // Set the interrupt threshold for sent pulse codes to - // half the size of the RAM in case we use wrap mode - w.tx_lim() - .bits(CHANNEL_RAM_SIZE as u16 /2) - }); - } else { - // Extract repetition value - let mut reps = 0; - if let RepeatMode::RepeatNtimes(val) = repeat_mode { - reps = val; - } - - // Configure counting mode and repetitions - unsafe { &*RMT::PTR }.ch_tx_lim[$num].modify(|_, w| unsafe { - // Set number of repetitions - w.tx_loop_num() - .bits(reps) - // Enable loop counting - .tx_loop_cnt_en() - .bit(reps != 0) - // Reset any pre-existing counting value - .loop_count_reset() - .set_bit() - // Set the interrupt threshold for sent pulse codes to 24 - // (= half the size of the RAM) in case we use wrap mode - .tx_lim() - .bits(CHANNEL_RAM_SIZE as u16/2) - }); - } - } - - #[cfg(any(esp32c3, esp32c6, esp32s3))] - conf_reg.modify(|_, w| { - // Set config update bit - w.conf_update().set_bit() - }); - - // Setup configuration - conf_reg.modify(|_, w| { - // Set configure continuous - // (also reset FIFO buffer pointers) - w.tx_conti_mode() - .bit(repeat_mode != RepeatMode::SingleShot) - .mem_rd_rst() - .set_bit() - .apb_mem_rst() - .set_bit() - }); - - self.channel.reset_fifo(); - let mut sequence_iter = sequence.iter(); - - // We have to differentiate here if we can fit the whole sequence - // in the RAM in one go or if we have to use the wrap mode to split - // the sequence into chuncks. - if sequence.len() >= CHANNEL_RAM_SIZE as usize { - // Write the first 48 entries - self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE); - } else { - // Write whole sequence to FIFO RAM - self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE); - } - - // Clear the relevant interrupts - // - // (since this is a write-through register, we can do this - // safely for multiple separate channel instances without - // having concurrency issues) - // Depending on the variant, other registers have to be used here - cfg_if::cfg_if! { - if #[cfg(esp32)] { - unsafe { &*RMT::PTR }.int_clr.write(|w| { - // The ESP32 variant does not have the loop functionality - paste!( - w.[]() - .set_bit() - .[]() - .set_bit() - .[]() - .set_bit() - ) - }); - } else if #[cfg(esp32s2)] { - unsafe { &*RMT::PTR }.int_clr.write(|w| { - paste!( - w.[]() - .set_bit() - .[]() - .set_bit() - .[]() - .set_bit() - .[]() - .set_bit() - ) - }); - } else { - unsafe { &*RMT::PTR }.int_clr.write(|w| { - paste!( - w.[]() - .set_bit() - .[]() - .set_bit() - .[]() - .set_bit() - .[]() - .set_bit() - ) - }); - } - } - - // always enable tx wrap - #[cfg(any(esp32c3, esp32c6, esp32s3))] - unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| { - w.mem_tx_wrap_en() - .set_bit() - }); - - // apply configuration updates - #[cfg(any(esp32c3, esp32c6, esp32s3))] - unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| { - w.conf_update() - .set_bit() - }); - - // Depending on the variant, other registers have to be used here - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - conf1!($num).modify(|_, w| w.tx_start().set_bit()); - } else { - unsafe{ &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| w.tx_start().set_bit()); - } - } - - // If we're in forever mode, we return right away, otherwise we wait - // for completion - if repeat_mode != RepeatMode::Forever { - // Wait for interrupt being raised, either completion or error - loop { - let interrupts = unsafe { &*RMT::PTR }.int_raw.read(); - - match ( - unsafe { interrupts.ch_tx_end_int_raw($num).bit() }, - // The ESP32 variant does not support the loop functionality - #[cfg(not(esp32))] - unsafe {interrupts.ch_tx_loop_int_raw($num).bit()}, - #[cfg(esp32)] - false, - // The C3/S3 have a slightly different interrupt naming scheme - #[cfg(any(esp32, esp32s2))] - unsafe { interrupts.ch_err_int_raw($num).bit() }, - #[cfg(any(esp32c3, esp32c6, esp32s3))] - unsafe { interrupts.ch_tx_err_int_raw($num).bit() }, - unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() }, - ) { - // SingleShot completed and no error -> success - (true, false, false, _) => break, - // Sequence completed and no error -> success - (false, true, false, _) => { - // Stop transmitting (only necessary in sequence case) - self.stop_transmission(); - break; - } - // Refill the buffer - (false, false, false, true) => { - self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE / 2); - - // Clear the threshold interrupt (write-through) - unsafe { &*RMT::PTR }.int_clr.write(|w| { - paste!(w.[]().set_bit()) - }); - } - // Neither completed nor error -> continue busy waiting - (false, false, false, false) => (), - // Anything else constitutes an error state - _ => { - return Err(TransmissionError::Failure( - unsafe { interrupts.ch_tx_end_int_raw($num).bit() }, - // The ESP32 variant does not support the loop functionality - #[cfg(not(esp32))] - unsafe {interrupts.ch_tx_loop_int_raw($num).bit()}, - #[cfg(esp32)] - false, - // The C3/S3 have a slightly different interrupt naming scheme - #[cfg(any(esp32, esp32s2))] - unsafe { interrupts.ch_err_int_raw($num).bit() }, - #[cfg(any(esp32c3, esp32c6, esp32s3))] - unsafe { interrupts.ch_tx_err_int_raw($num).bit() }, - unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() }, - )) - } - } - } - } - - Ok(()) - } - - /// Stop any ongoing (repetitive) transmission - /// - /// This function needs to be called to stop sending when - /// previously a sequence was sent with `RepeatMode::Forever`. - fn stop_transmission(&self) { - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - unsafe { &*RMT::PTR } - .ch_tx_conf0[$num] - .modify(|_, w| w.tx_stop().set_bit()); - } - else if #[cfg(esp32s2)] { - conf1!($num) - .modify(|_, w| w.tx_stop().set_bit()); - } - // The ESP32 variant does not have any way to stop a - // transmission once it has been started! - }; - } - } - - ); - }; -} - -macro_rules! output_channel { - ($num:literal, $cxi:ident, $output_signal:path - ) => { - paste!( - - impl OutputChannel for $cxi { - - type ConfiguredChannel<'d, P> = []<'d, P> - where P: OutputPin + 'd; - - /// Set the logical level that the connected pin is pulled to - /// while the channel is idle - #[inline(always)] - fn set_idle_output_level(&mut self, level: bool) -> &mut Self { - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - unsafe { &*RMT::PTR } - .ch_tx_conf0[$num] - .modify(|_, w| w.idle_out_lv().bit(level)); - } - else { - conf1!($num) - .modify(|_, w| w.idle_out_lv().bit(level)); - } - }; - self - } - - /// Enable/Disable the output while the channel is idle - #[inline(always)] - fn set_idle_output(&mut self, state: bool) -> &mut Self { - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - unsafe { &*RMT::PTR } - .ch_tx_conf0[$num] - .modify(|_, w| w.idle_out_en().bit(state)); - } - else { - conf1!($num) - .modify(|_, w| w.idle_out_en().bit(state)); - } - }; - self - } - - /// Set channel clock divider value - #[inline(always)] - fn set_channel_divider(&mut self, divider: u8) -> &mut Self { - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - unsafe { &*RMT::PTR } - .ch_tx_conf0[$num] - .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); - } - else { - conf0!($num) - .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); - } - }; - self - } - - /// Enable/Disable carrier modulation - #[inline(always)] - fn set_carrier_modulation(&mut self, state: bool) -> &mut Self { - cfg_if::cfg_if! { - if #[cfg(any(esp32c3, esp32c6, esp32s3))] { - unsafe { &*RMT::PTR } - .ch_tx_conf0[$num] - .modify(|_, w| w.carrier_en().bit(state)); - } - else { - conf0!($num) - .modify(|_, w| w.carrier_en().bit(state)); - } - }; - self - } - - /// Set the clock source (for the ESP32-S2 and ESP32 this can be done on a - /// channel level) - #[cfg(any(esp32s2, esp32))] - #[inline(always)] - fn set_clock_source(&mut self, source: ClockSource) -> &mut Self { - let bit_value = match source { - ClockSource::RefTick => false, - ClockSource::APB => true, - }; - - conf1!($num) - .modify(|_, w| w.ref_always_on().bit(bit_value)); - self - } - - /// Assign a pin that should be driven by this channel - fn assign_pin<'d, RmtPin: OutputPin >( - self, - pin: impl Peripheral

    + 'd - ) -> []<'d, RmtPin> { - crate::into_ref!(pin); - // Configure Pin as output anc connect to signal - pin.set_to_push_pull_output() - .connect_peripheral_to_output($output_signal); - - [] { - channel: self, - _pin: pin - } - } - } - ); - }; -} - -#[cfg(esp32)] -macro_rules! conf0 { - ($channel: literal) => { - match $channel { - 0 => &unsafe { &*RMT::PTR }.ch0conf0, - 1 => &unsafe { &*RMT::PTR }.ch1conf0, - 2 => &unsafe { &*RMT::PTR }.ch2conf0, - 3 => &unsafe { &*RMT::PTR }.ch3conf0, - 4 => &unsafe { &*RMT::PTR }.ch4conf0, - 5 => &unsafe { &*RMT::PTR }.ch5conf0, - 6 => &unsafe { &*RMT::PTR }.ch6conf0, - 7 => &unsafe { &*RMT::PTR }.ch7conf0, - _ => panic!("Attempted access to non-existing channel!"), - } - }; -} - -#[cfg(esp32)] -macro_rules! conf1 { - ($channel: literal) => { - match $channel { - 0 => &unsafe { &*RMT::PTR }.ch0conf1, - 1 => &unsafe { &*RMT::PTR }.ch1conf1, - 2 => &unsafe { &*RMT::PTR }.ch2conf1, - 3 => &unsafe { &*RMT::PTR }.ch3conf1, - 4 => &unsafe { &*RMT::PTR }.ch4conf1, - 5 => &unsafe { &*RMT::PTR }.ch5conf1, - 6 => &unsafe { &*RMT::PTR }.ch6conf1, - 7 => &unsafe { &*RMT::PTR }.ch7conf1, - _ => panic!("Attempted access to non-existing channel!"), - } - }; -} - -#[cfg(esp32s2)] -macro_rules! conf0 { - ($channel: literal) => { - match $channel { - 0 => &unsafe { &*RMT::PTR }.ch0conf0, - 1 => &unsafe { &*RMT::PTR }.ch1conf0, - 2 => &unsafe { &*RMT::PTR }.ch2conf0, - 3 => &unsafe { &*RMT::PTR }.ch3conf0, - _ => panic!("Attempted access to non-existing channel!"), - } - }; -} - -#[cfg(esp32s2)] -macro_rules! conf1 { - ($channel: literal) => { - match $channel { - 0 => &unsafe { &*RMT::PTR }.ch0conf1, - 1 => &unsafe { &*RMT::PTR }.ch1conf1, - 2 => &unsafe { &*RMT::PTR }.ch2conf1, - 3 => &unsafe { &*RMT::PTR }.ch3conf1, - _ => panic!("Attempted access to non-existing channel!"), - } - }; -} - -macro_rules! rmt { - ( - $global_conf_reg:ident, - $( - ($num:literal, $cxi:ident, $obj_name:ident, $output_signal:path), - )+ - ) - => { - /// RMT peripheral (RMT) - pub struct PulseControl<'d> { - /// The underlying register block - reg: PeripheralRef<'d, RMT>, - $( - /// RMT channel $cxi - pub $obj_name: $cxi, - )+ - } - - impl<'d> PulseControl<'d> { - /// Create a new pulse controller instance - #[cfg(any(esp32c3, esp32c6, esp32s3))] - pub fn new( - instance: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - clk_source: ClockSource, - div_abs: u8, - div_frac_a: u8, - div_frac_b: u8, - ) -> Result { - - crate::into_ref!(instance); - let pc = PulseControl { - reg: instance, - $( - $obj_name: $cxi::new(), - )+ - }; - - pc.enable_peripheral(peripheral_clock_control); - pc.config_global(clk_source, div_abs, div_frac_a, div_frac_b)?; - - Ok(pc) - } - - /// Create a new pulse controller instance - #[cfg(any(esp32, esp32s2))] - pub fn new( - instance: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Result { - - crate::into_ref!(instance); - let pc = PulseControl { - reg: instance, - $( - $obj_name: $cxi::new(), - )+ - }; - - pc.enable_peripheral(peripheral_clock_control); - pc.config_global()?; - - Ok(pc) - } - - // Enable the RMT peripherals clock in the system peripheral - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Rmt); - } - - /// Assign the global (peripheral-wide) configuration. This - /// is mostly the divider setup and the clock source selection - /// - /// The dividing factor for the source - /// clock is calculated as follows: - /// - /// divider = absolute_part + 1 + (fractional_part_a / fractional_part_b) - #[cfg(any(esp32c3, esp32c6, esp32s3))] - fn config_global( - &self, - clk_source: ClockSource, - div_abs: u8, - div_frac_a: u8, - div_frac_b: u8, - ) -> Result<(), SetupError> { - // Before assigning, confirm that the fractional parameters for - // the divider are within bounds - if div_frac_a > 64 || div_frac_b > 64 { - return Err(SetupError::InvalidGlobalConfig); - } - - // TODO: Confirm that the selected clock source is enabled in the - // system / rtc_cntl peripheral? Particularly relevant for clock sources - // other than APB_CLK, this needs to be revisited once #24 and $44 have been - // addressed! - - // Configure peripheral - - #[cfg(esp32c6)] - let pcr = unsafe { &*PCR::ptr() }; - - #[cfg(esp32c6)] - pcr.rmt_sclk_conf.write(|w| w.sclk_en().set_bit()); - - - self.reg.sys_conf.modify(|_, w| - // Enable clock - w.clk_en() - .set_bit() - // Force Clock on - .mem_clk_force_on() - .set_bit() - // Enable Source clock - .sclk_active() - .set_bit() - // Disable forced power down of the peripheral (just to be sure) - .mem_force_pd() - .clear_bit() - // Disable FIFO mode - .apb_fifo_mask() - .set_bit()); - // Select clock source - #[cfg(not(esp32c6))] - self.reg.sys_conf.modify(|_, w| unsafe { - w.sclk_sel() - .bits(clk_source as u8) - // Set absolute part of divider - .sclk_div_num() - .bits(div_abs) - // Set fractional parts of divider to 0 - .sclk_div_a() - .bits(div_frac_a) - .sclk_div_b() - .bits(div_frac_b) }); - #[cfg(esp32c6)] - pcr.rmt_sclk_conf.modify(|_,w| unsafe { - w.sclk_sel() - .bits(clk_source as u8) - // Set absolute part of divider - .sclk_div_num() - .bits(div_abs) - // Set fractional parts of divider to 0 - .sclk_div_a() - .bits(div_frac_a) - .sclk_div_b() - .bits(div_frac_b) - }); - - // Disable all interrupts - self.reg.int_ena.write(|w| unsafe { w.bits(0) }); - - // Clear all interrupts - self.reg.int_clr.write(|w| unsafe { w.bits(0xffffffff) }); - - Ok(()) - } - - /// Assign the global (peripheral-wide) configuration. - #[cfg(any(esp32s2, esp32))] - fn config_global(&self) -> Result<(), SetupError> { - // TODO: Confirm that the selected clock source is enabled in the - // system / rtc_cntl peripheral? Particularly relevant for clock sources - // other than APB_CLK, this needs to be revisited once #24 and $44 have been - // addressed! - - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // Configure peripheral - self.reg.apb_conf.modify(|_, w| - // Disable FIFO mode - w.apb_fifo_mask() - .set_bit() - // Enable wrap mode (globally for the ESP32 and ESP32-S2 variants) - .mem_tx_wrap_en() - .set_bit() - ); - } - else { - // Configure peripheral - self.reg.apb_conf.modify(|_, w| - // Enable clock - w.clk_en() - .set_bit() - // Force Clock on - .mem_clk_force_on() - .set_bit() - // Disable forced power down of the peripheral (just to be sure) - .mem_force_pd() - .clear_bit() - // Disable FIFO mode - .apb_fifo_mask() - .set_bit() - // Enable wrap mode (globally for the ESP32 and ESP32-S2 variants) - .mem_tx_wrap_en() - .set_bit() - ); - } - }; - - // Disable all interrupts - self.reg.int_ena.write(|w| unsafe { w.bits(0) }); - - // Clear all interrupts - self.reg.int_clr.write(|w| unsafe { w.bits(0) }); - - Ok(()) - } - } - $( - channel_instance!($num, $cxi, $output_signal); - output_channel!($num, $cxi, $output_signal); - )+ - }; -} - -#[cfg(any(esp32c3, esp32c6))] -rmt!( - sys_conf, - (0, Channel0, channel0, OutputSignal::RMT_SIG_0), - (1, Channel1, channel1, OutputSignal::RMT_SIG_1), -); - -#[cfg(esp32s2)] -rmt!( - apb_conf, - (0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0), - (1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1), - (2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2), - (3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3), -); - -#[cfg(esp32)] -rmt!( - apb_conf, - (0, Channel0, channel0, OutputSignal::RMT_SIG_0), - (1, Channel1, channel1, OutputSignal::RMT_SIG_1), - (2, Channel2, channel2, OutputSignal::RMT_SIG_2), - (3, Channel3, channel3, OutputSignal::RMT_SIG_3), - (4, Channel4, channel4, OutputSignal::RMT_SIG_4), - (5, Channel5, channel5, OutputSignal::RMT_SIG_5), - (6, Channel6, channel6, OutputSignal::RMT_SIG_6), - (7, Channel7, channel7, OutputSignal::RMT_SIG_7), -); - -#[cfg(esp32s3)] -rmt!( - sys_conf, - (0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0), - (1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1), - (2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2), - (3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3), -); diff --git a/esp-hal-common/src/radio.rs b/esp-hal-common/src/radio.rs deleted file mode 100644 index aff4209a1b9..00000000000 --- a/esp-hal-common/src/radio.rs +++ /dev/null @@ -1,102 +0,0 @@ -pub trait RadioExt { - type Components; - - fn split(self) -> Self::Components; -} - -/// WiFi radio -pub struct Wifi { - _private: (), -} - -/// Bluetooth radio -pub struct Bluetooth { - _private: (), -} - -/// IEEE 802.15.4 Low rate wireless personal area radio -pub struct LowRate { - _private: (), -} - -impl Wifi { - pub const unsafe fn steal() -> Self { - Self { _private: () } - } -} - -impl crate::peripheral::Peripheral for Wifi { - type P = Self; - - unsafe fn clone_unchecked(&mut self) -> Self::P { - Self::steal() - } -} - -impl crate::peripheral::sealed::Sealed for Wifi {} - -impl Bluetooth { - pub const unsafe fn steal() -> Self { - Self { _private: () } - } -} - -impl crate::peripheral::Peripheral for Bluetooth { - type P = Self; - - unsafe fn clone_unchecked(&mut self) -> Self::P { - Self::steal() - } -} - -impl crate::peripheral::sealed::Sealed for Bluetooth {} - -impl LowRate { - pub const unsafe fn steal() -> Self { - Self { _private: () } - } -} - -impl crate::peripheral::Peripheral for LowRate { - type P = Self; - - unsafe fn clone_unchecked(&mut self) -> Self::P { - Self::steal() - } -} - -impl crate::peripheral::sealed::Sealed for LowRate {} - -cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32c2, esp32c3, esp32s3))] { - impl RadioExt for crate::peripherals::RADIO { - type Components = (Wifi, Bluetooth); - - fn split(self) -> Self::Components { - unsafe { - (Wifi::steal(), Bluetooth::steal()) - } - } - } - } else if #[cfg(esp32c6)] { - impl RadioExt for crate::peripherals::RADIO { - type Components = (Wifi, Bluetooth, LowRate); - - fn split(self) -> Self::Components { - unsafe { - (Wifi::steal(), Bluetooth::steal(), LowRate::steal()) - } - } - } - } else if #[cfg(esp32s2)] { - impl RadioExt for crate::peripherals::RADIO { - type Components = Wifi; - - fn split(self) -> Self::Components { - unsafe { - Wifi::steal() - } - } - } - } -} diff --git a/esp-hal-common/src/reset.rs b/esp-hal-common/src/reset.rs deleted file mode 100644 index b87720b3ff8..00000000000 --- a/esp-hal-common/src/reset.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::rtc_cntl::SocResetReason; - -pub enum SleepSource { - /// In case of deep sleep, reset was not caused by exit from deep sleep - Undefined = 0, - /// Not a wakeup cause, used to disable all wakeup sources with - /// esp_sleep_disable_wakeup_source - All, - /// Wakeup caused by external signal using RTC_IO - Ext0, - /// Wakeup caused by external signal using RTC_CNTL - Ext1, - /// Wakeup caused by timer - Timer, - /// Wakeup caused by touchpad - TouchPad, - /// Wakeup caused by ULP program - Ulp, - /// Wakeup caused by GPIO (light sleep only on ESP32, S2 and S3) - Gpio, - /// Wakeup caused by UART (light sleep only) - Uart, - /// Wakeup caused by WIFI (light sleep only) - Wifi, - /// Wakeup caused by COCPU int - Cocpu, - /// Wakeup caused by COCPU crash - CocpuTrapTrig, - /// Wakeup caused by BT (light sleep only) - BT, -} - -bitflags::bitflags! { - #[allow(unused)] - pub(crate) struct WakeupReason: u32 { - const NoSleep = 0; - #[cfg(pm_support_ext0_wakeup)] - /// EXT0 GPIO wakeup - const ExtEvent0Trig = 1 << 0; - #[cfg(pm_support_ext1_wakeup)] - /// EXT1 GPIO wakeup - const ExtEvent1Trig = 1 << 1; - /// GPIO wakeup (light sleep only) - const GpioTrigEn = 1 << 2; - /// Timer wakeup - const TimerTrigEn = 1 << 3; - #[cfg(pm_support_wifi_wakeup)] - /// MAC wakeup (light sleep only) - const WifiTrigEn = 1 << 5; - /// UART0 wakeup (light sleep only) - const Uart0TrigEn = 1 << 6; - /// UART1 wakeup (light sleep only) - const Uart1TrigEn = 1 << 7; - #[cfg(pm_support_touch_sensor_wakeup)] - /// Touch wakeup - const TouchTrigEn = 1 << 8; - #[cfg(ulp_supported)] - /// ULP wakeup - const UlpTrigEn = 1 << 9; - #[cfg(pm_support_bt_wakeup)] - /// BT wakeup (light sleep only) - const BtTrigEn = 1 << 10; - #[cfg(riscv_coproc_supported)] - const CocpuTrigEn = 1 << 11; - #[cfg(riscv_coproc_supported)] - const CocpuTrapTrigEn = 1 << 13; - } -} - -pub fn software_reset() { - unsafe { crate::rtc_cntl::software_reset() } -} -pub fn software_reset_cpu() { - unsafe { crate::rtc_cntl::software_reset_cpu() } -} - -pub fn get_reset_reason() -> Option { - crate::rtc_cntl::get_reset_reason(crate::get_core()) -} - -pub fn get_wakeup_cause() -> SleepSource { - crate::rtc_cntl::get_wakeup_cause() -} diff --git a/esp-hal-common/src/rng.rs b/esp-hal-common/src/rng.rs deleted file mode 100644 index 64e2eeb9c31..00000000000 --- a/esp-hal-common/src/rng.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Random number generator driver - -use core::convert::Infallible; - -use embedded_hal::blocking::rng::Read; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::RNG, -}; - -/// Random Number Generator -/// -/// It should be noted that there are certain pre-conditions which must be met -/// in order for the RNG to produce *true* random numbers. The hardware RNG -/// produces true random numbers under any of the following conditions: -/// -/// - RF subsystem is enabled (i.e. Wi-Fi or Bluetooth are enabled). -/// - An internal entropy source has been enabled by calling -/// `bootloader_random_enable()` and not yet disabled by calling -/// `bootloader_random_disable()`. -/// - While the ESP-IDF Second stage bootloader is running. This is because the -/// default ESP-IDF bootloader implementation calls -/// `bootloader_random_enable()` when the bootloader starts, and -/// `bootloader_random_disable()` before executing the app. -/// -/// When any of these conditions are true, samples of physical noise are -/// continuously mixed into the internal hardware RNG state to provide entropy. -/// If none of the above conditions are true, the output of the RNG should be -/// considered pseudo-random only. -/// -/// For more information, please refer to the ESP-IDF documentation: -/// -pub struct Rng<'d> { - rng: PeripheralRef<'d, RNG>, -} - -impl<'d> Rng<'d> { - /// Create a new random number generator instance - pub fn new(rng: impl Peripheral

    + 'd) -> Self { - crate::into_ref!(rng); - - Self { rng } - } - - #[inline] - /// Reads currently available `u32` integer from `RNG` - pub fn random(&mut self) -> u32 { - self.rng.data.read().bits() - } -} - -impl Read for Rng<'_> { - type Error = Infallible; - - fn read(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> { - for chunk in buffer.chunks_mut(4) { - let bytes = self.random().to_le_bytes(); - chunk.copy_from_slice(&bytes[..chunk.len()]); - } - - Ok(()) - } -} diff --git a/esp-hal-common/src/rom.rs b/esp-hal-common/src/rom.rs deleted file mode 100644 index f4549cdbc8c..00000000000 --- a/esp-hal-common/src/rom.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub use paste::paste; - -#[allow(unused)] -extern "C" { - pub(crate) fn rom_i2c_writeReg(block: u32, block_hostid: u32, reg_add: u32, indata: u32); - - pub(crate) fn rom_i2c_writeReg_Mask( - block: u32, - block_hostid: u32, - reg_add: u32, - reg_add_msb: u32, - reg_add_lsb: u32, - indata: u32, - ); -} - -#[macro_export] -macro_rules! regi2c_write { - ( $block: ident, $reg_add: ident, $indata: expr ) => { - paste! { - rom_i2c_writeReg($block, - [<$block _HOSTID>], - $reg_add, - $indata - ); - } - }; -} - -#[macro_export] -macro_rules! regi2c_write_mask { - ( $block: ident, $reg_add: ident, $indata: expr ) => { - paste! { - rom_i2c_writeReg_Mask($block, - [<$block _HOSTID>], - $reg_add, - [<$reg_add _MSB>], - [<$reg_add _LSB>], - $indata - ); - } - }; -} diff --git a/esp-hal-common/src/rsa/esp32.rs b/esp-hal-common/src/rsa/esp32.rs deleted file mode 100644 index b8c74a1676d..00000000000 --- a/esp-hal-common/src/rsa/esp32.rs +++ /dev/null @@ -1,211 +0,0 @@ -use core::{ - convert::Infallible, - marker::PhantomData, - ptr::{copy_nonoverlapping, write_bytes}, -}; - -use crate::rsa::{ - implement_op, - Multi, - Rsa, - RsaMode, - RsaModularExponentiation, - RsaModularMultiplication, - RsaMultiplication, -}; - -impl<'d> Rsa<'d> { - /// After the RSA Accelerator is released from reset, the memory blocks - /// needs to be initialized, only after that peripheral should be used. - /// This function would return without an error if the memory is initialized - pub fn ready(&mut self) -> nb::Result<(), Infallible> { - if self.rsa.clean.read().clean().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - Ok(()) - } - - pub(super) fn write_multi_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.rsa.mult_mode, mode as u32); - } - - pub(super) fn write_modexp_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.rsa.modexp_mode, mode); - } - - pub(super) fn write_modexp_start(&mut self) { - self.rsa.modexp_start.write(|w| w.modexp_start().set_bit()); - } - - pub(super) fn write_multi_start(&mut self) { - self.rsa.mult_start.write(|w| w.mult_start().set_bit()); - } - - pub(super) fn clear_interrupt(&mut self) { - self.rsa.interrupt.write(|w| w.interrupt().set_bit()); - } - - pub(super) fn is_idle(&mut self) -> bool { - self.rsa.interrupt.read().bits() == 1 - } - - unsafe fn write_multi_operand_a(&mut self, operand_a: &[u8; N]) { - copy_nonoverlapping( - operand_a.as_ptr(), - self.rsa.x_mem.as_mut_ptr() as *mut u8, - N, - ); - write_bytes(self.rsa.x_mem.as_mut_ptr().add(N), 0, N); - } - - unsafe fn write_multi_operand_b(&mut self, operand_b: &[u8; N]) { - write_bytes(self.rsa.z_mem.as_mut_ptr(), 0, N); - copy_nonoverlapping( - operand_b.as_ptr(), - self.rsa.z_mem.as_mut_ptr().add(N) as *mut u8, - N, - ); - } -} - -pub mod operand_sizes { - //! Marker types for the operand sizes - use paste::paste; - - use super::{implement_op, Multi, RsaMode}; - - implement_op!( - (512, multi), - (1024, multi), - (1536, multi), - (2048, multi), - (2560), - (3072), - (3584), - (4096) - ); -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaMultiplication`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 24.3.2 in the - /// - pub fn new(rsa: &'a mut Rsa<'d>, modulus: &T::InputType, m_prime: u32) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_modulus(modulus); - } - rsa.write_mprime(m_prime); - - Self { - rsa, - phantom: PhantomData, - } - } - - fn set_mode(rsa: &mut Rsa) { - rsa.write_multi_mode((N / 64 - 1) as u32) - } - - /// Starts the first step of modular multiplication operation. `r` could be - /// calculated using `2 ^ ( bitlength * 2 ) mod modulus`, - /// for more information check 24.3.2 in the - /// - pub fn start_step1(&mut self, operand_a: &T::InputType, r: &T::InputType) { - unsafe { - self.rsa.write_operand_a(operand_a); - self.rsa.write_r(r); - } - self.set_start(); - } - - /// Starts the second step of modular multiplication operation. - /// This is a non blocking function that returns without an error if - /// operation is completed successfully. `start_step1` must be called - /// before calling this function. - pub fn start_step2(&mut self, operand_b: &T::InputType) -> nb::Result<(), Infallible> { - if !self.rsa.is_idle() { - return Err(nb::Error::WouldBlock); - } - self.rsa.clear_interrupt(); - unsafe { - self.rsa.write_operand_a(operand_b); - } - self.set_start(); - Ok(()) - } - - fn set_start(&mut self) { - self.rsa.write_multi_start(); - } -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularExponentiation<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaModularExponentiation`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 24.3.2 in the - /// - pub fn new( - rsa: &'a mut Rsa<'d>, - exponent: &T::InputType, - modulus: &T::InputType, - m_prime: u32, - ) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_operand_b(exponent); - rsa.write_modulus(modulus); - } - rsa.write_mprime(m_prime); - Self { - rsa, - phantom: PhantomData, - } - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_modexp_mode((N / 64 - 1) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_modexp_start(); - } -} - -impl<'a, 'd, T: RsaMode + Multi, const N: usize> RsaMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaMultiplication`. - pub fn new(rsa: &'a mut Rsa<'d>) -> Self { - Self::set_mode(rsa); - Self { - rsa, - phantom: PhantomData, - } - } - - /// Starts the multiplication operation. - pub fn start_multiplication(&mut self, operand_a: &T::InputType, operand_b: &T::InputType) { - unsafe { - self.rsa.write_multi_operand_a(operand_a); - self.rsa.write_multi_operand_b(operand_b); - } - self.set_start(); - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_multi_mode((N / 32 - 1 + 8) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_multi_start(); - } -} diff --git a/esp-hal-common/src/rsa/esp32cX.rs b/esp-hal-common/src/rsa/esp32cX.rs deleted file mode 100644 index 058d4dff40f..00000000000 --- a/esp-hal-common/src/rsa/esp32cX.rs +++ /dev/null @@ -1,347 +0,0 @@ -use core::{convert::Infallible, marker::PhantomData, ptr::copy_nonoverlapping}; - -use crate::rsa::{ - implement_op, - Multi, - Rsa, - RsaMode, - RsaModularExponentiation, - RsaModularMultiplication, - RsaMultiplication, -}; - -impl<'d> Rsa<'d> { - /// After the RSA Accelerator is released from reset, the memory blocks - /// needs to be initialized, only after that peripheral should be used. - /// This function would return without an error if the memory is initialized - pub fn ready(&mut self) -> nb::Result<(), Infallible> { - if self.rsa.query_clean.read().query_clean().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - Ok(()) - } - - /// Enables/disables rsa interrupt, when enabled rsa perpheral would - /// generate an interrupt when a operation is finished. - pub fn enable_disable_interrupt(&mut self, enable: bool) { - match enable { - true => self.rsa.int_ena.write(|w| w.int_ena().set_bit()), - false => self.rsa.int_ena.write(|w| w.int_ena().clear_bit()), - } - } - - fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.rsa.mode, mode as u32); - } - - /// Enables/disables search acceleration, when enabled it would increases - /// the performance of modular exponentiation by discarding the - /// exponent's bits before the most significant set bit. Note: this might - /// affect the security, for more info refer 18.3.4 of - pub fn enable_disable_search_acceleration(&mut self, enable: bool) { - match enable { - true => self - .rsa - .search_enable - .write(|w| w.search_enable().set_bit()), - false => self - .rsa - .search_enable - .write(|w| w.search_enable().clear_bit()), - } - } - - pub(super) fn is_search_enabled(&mut self) -> bool { - self.rsa.search_enable.read().search_enable().bit_is_set() - } - - pub(super) fn write_search_position(&mut self, search_position: u32) { - Self::write_to_register(&mut self.rsa.search_pos, search_position); - } - - /// Enables/disables constant time acceleration, when enabled it would - /// increases the performance of modular exponentiation by simplifying - /// the calculation concerning the 0 bits of the exponent i.e. lesser the - /// hamming weight, greater the performance. Note : this might affect - /// the security, for more info refer 18.3.4 of - pub fn enable_disable_constant_time_acceleration(&mut self, enable: bool) { - match enable { - true => self - .rsa - .constant_time - .write(|w| w.constant_time().clear_bit()), - false => self - .rsa - .constant_time - .write(|w| w.constant_time().set_bit()), - } - } - - pub(super) fn write_modexp_start(&mut self) { - self.rsa - .set_start_modexp - .write(|w| w.set_start_modexp().set_bit()); - } - - pub(super) fn write_multi_start(&mut self) { - self.rsa - .set_start_mult - .write(|w| w.set_start_mult().set_bit()); - } - - fn write_modmulti_start(&mut self) { - self.rsa - .set_start_modmult - .write(|w| w.set_start_modmult().set_bit()); - } - - pub(super) fn clear_interrupt(&mut self) { - self.rsa.int_clr.write(|w| w.clear_interrupt().set_bit()); - } - - pub(super) fn is_idle(&mut self) -> bool { - self.rsa.query_idle.read().query_idle().bit_is_set() - } - - unsafe fn write_multi_operand_b(&mut self, operand_b: &[u8; N]) { - copy_nonoverlapping( - operand_b.as_ptr(), - self.rsa.z_mem.as_mut_ptr().add(N) as *mut u8, - N, - ); - } -} - -pub mod operand_sizes { - //! Marker types for the operand sizes - use paste::paste; - - use super::{implement_op, Multi, RsaMode}; - - implement_op!( - (32, multi), - (64, multi), - (96, multi), - (128, multi), - (160, multi), - (192, multi), - (224, multi), - (256, multi), - (288, multi), - (320, multi), - (352, multi), - (384, multi), - (416, multi), - (448, multi), - (480, multi), - (512, multi), - (544, multi), - (576, multi), - (608, multi), - (640, multi), - (672, multi), - (704, multi), - (736, multi), - (768, multi), - (800, multi), - (832, multi), - (864, multi), - (896, multi), - (928, multi), - (960, multi), - (992, multi), - (1024, multi), - (1056, multi), - (1088, multi), - (1120, multi), - (1152, multi), - (1184, multi), - (1216, multi), - (1248, multi), - (1280, multi), - (1312, multi), - (1344, multi), - (1376, multi), - (1408, multi), - (1440, multi), - (1472, multi), - (1504, multi), - (1536, multi), - (1568), - (1600), - (1632), - (1664), - (1696), - (1728), - (1760), - (1792), - (1824), - (1856), - (1888), - (1920), - (1952), - (1984), - (2016), - (2048), - (2080), - (2112), - (2144), - (2176), - (2208), - (2240), - (2272), - (2304), - (2336), - (2368), - (2400), - (2432), - (2464), - (2496), - (2528), - (2560), - (2592), - (2624), - (2656), - (2688), - (2720), - (2752), - (2784), - (2816), - (2848), - (2880), - (2912), - (2944), - (2976), - (3008), - (3040), - (3072) - ); -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularExponentiation<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaModularExponentiation`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 19.3.1 in the - /// - pub fn new( - rsa: &'a mut Rsa<'d>, - exponent: &T::InputType, - modulus: &T::InputType, - m_prime: u32, - ) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_operand_b(exponent); - rsa.write_modulus(modulus); - } - rsa.write_mprime(m_prime); - if rsa.is_search_enabled() { - rsa.write_search_position(Self::find_search_pos(exponent)); - } - Self { - rsa, - phantom: PhantomData, - } - } - - fn find_search_pos(exponent: &T::InputType) -> u32 { - for (i, byte) in exponent.iter().rev().enumerate() { - if *byte == 0 { - continue; - } - return (exponent.len() * 8) as u32 - (byte.leading_zeros() + i as u32 * 8) - 1; - } - 0 - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 4 - 1) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_modexp_start(); - } -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - fn write_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 4 - 1) as u32) - } - - /// Creates an Instance of `RsaModularMultiplication`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 19.3.1 in the - /// - pub fn new( - rsa: &'a mut Rsa<'d>, - operand_a: &T::InputType, - operand_b: &T::InputType, - modulus: &T::InputType, - m_prime: u32, - ) -> Self { - Self::write_mode(rsa); - rsa.write_mprime(m_prime); - unsafe { - rsa.write_modulus(modulus); - rsa.write_operand_a(operand_a); - rsa.write_operand_b(operand_b); - } - Self { - rsa, - phantom: PhantomData, - } - } - - /// Starts the modular multiplication operation. `r` could be calculated - /// using `2 ^ ( bitlength * 2 ) mod modulus`, for more information - /// check 19.3.1 in the - pub fn start_modular_multiplication(&mut self, r: &T::InputType) { - unsafe { - self.rsa.write_r(r); - } - self.set_start(); - } - - fn set_start(&mut self) { - self.rsa.write_modmulti_start(); - } -} - -impl<'a, 'd, T: RsaMode + Multi, const N: usize> RsaMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaMultiplication`. - pub fn new(rsa: &'a mut Rsa<'d>, operand_a: &T::InputType) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_operand_a(operand_a); - } - Self { - rsa, - phantom: PhantomData, - } - } - - /// Starts the multiplication operation. - pub fn start_multiplication(&mut self, operand_b: &T::InputType) { - unsafe { - self.rsa.write_multi_operand_b(operand_b); - } - self.set_start(); - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 2 - 1) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_multi_start(); - } -} diff --git a/esp-hal-common/src/rsa/esp32sX.rs b/esp-hal-common/src/rsa/esp32sX.rs deleted file mode 100644 index 3589d287a74..00000000000 --- a/esp-hal-common/src/rsa/esp32sX.rs +++ /dev/null @@ -1,386 +0,0 @@ -use core::{convert::Infallible, marker::PhantomData, ptr::copy_nonoverlapping}; - -use crate::rsa::{ - implement_op, - Multi, - Rsa, - RsaMode, - RsaModularExponentiation, - RsaModularMultiplication, - RsaMultiplication, -}; - -impl<'d> Rsa<'d> { - /// After the RSA Accelerator is released from reset, the memory blocks - /// needs to be initialized, only after that peripheral should be used. - /// This function would return without an error if the memory is initialized - pub fn ready(&mut self) -> nb::Result<(), Infallible> { - if self.rsa.clean.read().clean().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - Ok(()) - } - - /// Enables/disables rsa interrupt, when enabled rsa perpheral would - /// generate an interrupt when a operation is finished. - pub fn enable_disable_interrupt(&mut self, enable: bool) { - match enable { - true => self - .rsa - .interrupt_ena - .write(|w| w.interrupt_ena().set_bit()), - false => self - .rsa - .interrupt_ena - .write(|w| w.interrupt_ena().clear_bit()), - } - } - - fn write_mode(&mut self, mode: u32) { - Self::write_to_register(&mut self.rsa.mode, mode as u32); - } - - /// Enables/disables search acceleration, when enabled it would increases - /// the performance of modular exponentiation by discarding the - /// exponent's bits before the most significant set bit. Note: this might - /// affect the security, for more info refer 18.3.4 of - pub fn enable_disable_search_acceleration(&mut self, enable: bool) { - match enable { - true => self - .rsa - .search_enable - .write(|w| w.search_enable().set_bit()), - false => self - .rsa - .search_enable - .write(|w| w.search_enable().clear_bit()), - } - } - - pub(super) fn is_search_enabled(&mut self) -> bool { - self.rsa.search_enable.read().search_enable().bit_is_set() - } - - pub(super) fn write_search_position(&mut self, search_position: u32) { - Self::write_to_register(&mut self.rsa.search_pos, search_position); - } - - /// Enables/disables constant time acceleration, when enabled it would - /// increases the performance of modular exponentiation by simplifying - /// the calculation concerning the 0 bits of the exponent i.e. lesser the - /// hamming weight, greater the performance. Note : this might affect - /// the security, for more info refer 18.3.4 of - pub fn enable_disable_constant_time_acceleration(&mut self, enable: bool) { - match enable { - true => self - .rsa - .constant_time - .write(|w| w.constant_time().clear_bit()), - false => self - .rsa - .constant_time - .write(|w| w.constant_time().set_bit()), - } - } - - pub(super) fn write_modexp_start(&mut self) { - self.rsa.modexp_start.write(|w| w.modexp_start().set_bit()); - } - - pub(super) fn write_multi_start(&mut self) { - self.rsa.mult_start.write(|w| w.mult_start().set_bit()); - } - - fn write_modmulti_start(&mut self) { - self.rsa - .modmult_start - .write(|w| w.modmult_start().set_bit()); - } - - pub(super) fn clear_interrupt(&mut self) { - self.rsa - .clear_interrupt - .write(|w| w.clear_interrupt().set_bit()); - } - - pub(super) fn is_idle(&mut self) -> bool { - self.rsa.idle.read().idle().bit_is_set() - } - - unsafe fn write_multi_operand_b(&mut self, operand_b: &[u8; N]) { - copy_nonoverlapping( - operand_b.as_ptr(), - self.rsa.z_mem.as_mut_ptr().add(N) as *mut u8, - N, - ); - } -} - -pub mod operand_sizes { - //! Marker types for the operand sizes - use paste::paste; - - use super::{implement_op, Multi, RsaMode}; - - implement_op!( - (32, multi), - (64, multi), - (96, multi), - (128, multi), - (160, multi), - (192, multi), - (224, multi), - (256, multi), - (288, multi), - (320, multi), - (352, multi), - (384, multi), - (416, multi), - (448, multi), - (480, multi), - (512, multi), - (544, multi), - (576, multi), - (608, multi), - (640, multi), - (672, multi), - (704, multi), - (736, multi), - (768, multi), - (800, multi), - (832, multi), - (864, multi), - (896, multi), - (928, multi), - (960, multi), - (992, multi), - (1024, multi), - (1056, multi), - (1088, multi), - (1120, multi), - (1152, multi), - (1184, multi), - (1216, multi), - (1248, multi), - (1280, multi), - (1312, multi), - (1344, multi), - (1376, multi), - (1408, multi), - (1440, multi), - (1472, multi), - (1504, multi), - (1536, multi), - (1568, multi), - (1600, multi), - (1632, multi), - (1664, multi), - (1696, multi), - (1728, multi), - (1760, multi), - (1792, multi), - (1824, multi), - (1856, multi), - (1888, multi), - (1920, multi), - (1952, multi), - (1984, multi), - (2016, multi), - (2048, multi) - ); - - implement_op!( - (2080), - (2112), - (2144), - (2176), - (2208), - (2240), - (2272), - (2304), - (2336), - (2368), - (2400), - (2432), - (2464), - (2496), - (2528), - (2560), - (2592), - (2624), - (2656), - (2688), - (2720), - (2752), - (2784), - (2816), - (2848), - (2880), - (2912), - (2944), - (2976), - (3008), - (3040), - (3072), - (3104), - (3136), - (3168), - (3200), - (3232), - (3264), - (3296), - (3328), - (3360), - (3392), - (3424), - (3456), - (3488), - (3520), - (3552), - (3584), - (3616), - (3648), - (3680), - (3712), - (3744), - (3776), - (3808), - (3840), - (3872), - (3904), - (3936), - (3968), - (4000), - (4032), - (4064), - (4096) - ); -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularExponentiation<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaModularExponentiation`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 19.3.1 in the - /// - pub fn new( - rsa: &'a mut Rsa<'d>, - exponent: &T::InputType, - modulus: &T::InputType, - m_prime: u32, - ) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_operand_b(exponent); - rsa.write_modulus(modulus); - } - rsa.write_mprime(m_prime); - if rsa.is_search_enabled() { - rsa.write_search_position(Self::find_search_pos(exponent)); - } - Self { - rsa, - phantom: PhantomData, - } - } - - fn find_search_pos(exponent: &T::InputType) -> u32 { - for (i, byte) in exponent.iter().rev().enumerate() { - if *byte == 0 { - continue; - } - return (exponent.len() * 8) as u32 - (byte.leading_zeros() + i as u32 * 8) - 1; - } - 0 - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 4 - 1) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_modexp_start(); - } -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaModularMultiplication`. - /// `m_prime` could be calculated using `-(modular multiplicative inverse of - /// modulus) mod 2^32`, for more information check 19.3.1 in the - /// - pub fn new( - rsa: &'a mut Rsa<'d>, - operand_a: &T::InputType, - operand_b: &T::InputType, - modulus: &T::InputType, - m_prime: u32, - ) -> Self { - Self::write_mode(rsa); - rsa.write_mprime(m_prime); - unsafe { - rsa.write_modulus(modulus); - rsa.write_operand_a(operand_a); - rsa.write_operand_b(operand_b); - } - Self { - rsa, - phantom: PhantomData, - } - } - - fn write_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 4 - 1) as u32) - } - - /// Starts the modular multiplication operation. `r` could be calculated - /// using `2 ^ ( bitlength * 2 ) mod modulus`, for more information - /// check 19.3.1 in the - pub fn start_modular_multiplication(&mut self, r: &T::InputType) { - unsafe { - self.rsa.write_r(r); - } - self.set_start(); - } - - fn set_start(&mut self) { - self.rsa.write_modmulti_start(); - } -} - -impl<'a, 'd, T: RsaMode + Multi, const N: usize> RsaMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Creates an Instance of `RsaMultiplication`. - pub fn new(rsa: &'a mut Rsa<'d>, operand_a: &T::InputType) -> Self { - Self::set_mode(rsa); - unsafe { - rsa.write_operand_a(operand_a); - } - Self { - rsa, - phantom: PhantomData, - } - } - - /// Starts the multiplication operation. - pub fn start_multiplication(&mut self, operand_b: &T::InputType) { - unsafe { - self.rsa.write_multi_operand_b(operand_b); - } - self.set_start(); - } - - pub(super) fn set_mode(rsa: &mut Rsa) { - rsa.write_mode((N / 2 - 1) as u32) - } - - pub(super) fn set_start(&mut self) { - self.rsa.write_multi_start(); - } -} diff --git a/esp-hal-common/src/rsa/mod.rs b/esp-hal-common/src/rsa/mod.rs deleted file mode 100644 index b0267373835..00000000000 --- a/esp-hal-common/src/rsa/mod.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! RSA Accelerator support. -//! -//! This module provides functions and structs for multi precision arithmetic -//! operations used in RSA asym-metric cipher algorithms -//! -//! ### Features -//! The RSA peripheral supports maximum operand of the following sizes for each -//! individual chips: -//! -//! | Feature | ESP32| ESP32-C3| ESP32-C6| ESP32-S2| ESP32-S3| -//! |---------------------- |------|---------|---------|---------|---------| -//! |modular exponentiation |4096 |3072 |3072 |4096 |4096 | -//! |modular multiplication |4096 |3072 |3072 |4096 |4096 | -//! |multiplication |2048 |1536 |1536 |2048 |2048 | - -use core::{convert::Infallible, marker::PhantomData, ptr::copy_nonoverlapping}; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::{ - generic::{Reg, RegisterSpec, Resettable, Writable}, - RSA, - }, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, -}; - -#[cfg_attr(esp32s2, path = "esp32sX.rs")] -#[cfg_attr(esp32s3, path = "esp32sX.rs")] -#[cfg_attr(esp32c3, path = "esp32cX.rs")] -#[cfg_attr(esp32c6, path = "esp32cX.rs")] -#[cfg_attr(esp32, path = "esp32.rs")] -mod rsa_spec_impl; - -pub use rsa_spec_impl::operand_sizes; - -/// RSA peripheral container -pub struct Rsa<'d> { - rsa: PeripheralRef<'d, RSA>, -} - -impl<'d> Rsa<'d> { - pub fn new( - rsa: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(rsa); - let mut ret = Self { rsa }; - ret.init(peripheral_clock_control); - ret - } - - fn init(&mut self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(PeripheralEnable::Rsa); - } - - unsafe fn write_operand_b(&mut self, operand_b: &[u8; N]) { - copy_nonoverlapping( - operand_b.as_ptr(), - self.rsa.y_mem.as_mut_ptr() as *mut u8, - N, - ); - } - - unsafe fn write_modulus(&mut self, modulus: &[u8; N]) { - copy_nonoverlapping(modulus.as_ptr(), self.rsa.m_mem.as_mut_ptr() as *mut u8, N); - } - - fn write_mprime(&mut self, m_prime: u32) { - Self::write_to_register(&mut self.rsa.m_prime, m_prime); - } - - unsafe fn write_operand_a(&mut self, operand_a: &[u8; N]) { - copy_nonoverlapping( - operand_a.as_ptr(), - self.rsa.x_mem.as_mut_ptr() as *mut u8, - N, - ); - } - - unsafe fn write_r(&mut self, r: &[u8; N]) { - copy_nonoverlapping(r.as_ptr(), self.rsa.z_mem.as_mut_ptr() as *mut u8, N); - } - - unsafe fn read_out(&mut self, outbuf: &mut [u8; N]) { - copy_nonoverlapping(self.rsa.z_mem.as_ptr() as *const u8, outbuf.as_mut_ptr(), N); - } - - fn write_to_register(reg: &mut Reg, data: u32) - where - T: RegisterSpec + Resettable + Writable, - { - reg.write(|w| unsafe { w.bits(data) }); - } -} - -mod sealed { - pub trait RsaMode { - type InputType; - } - pub trait Multi: RsaMode { - type OutputType; - } -} - -pub(self) use sealed::*; - -macro_rules! implement_op { - - (($x:literal, multi)) => { - paste! {pub struct [];} - paste! { - impl Multi for [] { - type OutputType = [u8; $x*2 / 8]; - }} - paste!{ - impl RsaMode for [] { - type InputType = [u8; $x / 8]; - }} - }; - - (($x:literal)) => { - paste! {pub struct [];} - paste!{ - impl RsaMode for [] { - type InputType = [u8; $x / 8]; - }} - }; - - ($x:tt, $($y:tt),+) => { - implement_op!($x); - implement_op!($($y),+); - }; -} - -pub(self) use implement_op; - -/// Support for RSA peripheral's modular exponentiation feature that could be -/// used to find the `(base ^ exponent) mod modulus`. -/// -/// Each operand is a little endian byte array of the same size -pub struct RsaModularExponentiation<'a, 'd, T: RsaMode> { - rsa: &'a mut Rsa<'d>, - phantom: PhantomData, -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularExponentiation<'a, 'd, T> -where - T: RsaMode, -{ - /// starts the modular exponentiation operation. `r` could be calculated - /// using `2 ^ ( bitlength * 2 ) mod modulus`, for more information - /// check 24.3.2 in the - pub fn start_exponentiation(&mut self, base: &T::InputType, r: &T::InputType) { - unsafe { - self.rsa.write_operand_a(base); - self.rsa.write_r(r); - } - self.set_start(); - } - - /// reads the result to the given buffer. - /// This is a non blocking function that returns without an error if - /// operation is completed successfully. `start_exponentiation` must be - /// called before calling this function. - pub fn read_results(&mut self, outbuf: &mut T::InputType) -> nb::Result<(), Infallible> { - if !self.rsa.is_idle() { - return Err(nb::Error::WouldBlock); - } - unsafe { - self.rsa.read_out(outbuf); - } - self.rsa.clear_interrupt(); - Ok(()) - } -} - -/// Support for RSA peripheral's modular multiplication feature that could be -/// used to find the `(operand a * operand b) mod modulus`. -/// -/// Each operand is a little endian byte array of the same size -pub struct RsaModularMultiplication<'a, 'd, T: RsaMode> { - rsa: &'a mut Rsa<'d>, - phantom: PhantomData, -} - -impl<'a, 'd, T: RsaMode, const N: usize> RsaModularMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Reads the result to the given buffer. - /// This is a non blocking function that returns without an error if - /// operation is completed successfully. - pub fn read_results(&mut self, outbuf: &mut T::InputType) -> nb::Result<(), Infallible> { - if !self.rsa.is_idle() { - return Err(nb::Error::WouldBlock); - } - unsafe { - self.rsa.read_out(outbuf); - } - self.rsa.clear_interrupt(); - Ok(()) - } -} - -/// Support for RSA peripheral's large number multiplication feature that could -/// be used to find the `operand a * operand b`. -/// -/// Each operand is a little endian byte array of the same size -pub struct RsaMultiplication<'a, 'd, T: RsaMode + Multi> { - rsa: &'a mut Rsa<'d>, - phantom: PhantomData, -} - -impl<'a, 'd, T: RsaMode + Multi, const N: usize> RsaMultiplication<'a, 'd, T> -where - T: RsaMode, -{ - /// Reads the result to the given buffer. - /// This is a non blocking function that returns without an error if - /// operation is completed successfully. `start_multiplication` must be - /// called before calling this function. - pub fn read_results<'b, const O: usize>( - &mut self, - outbuf: &mut T::OutputType, - ) -> nb::Result<(), Infallible> - where - T: Multi, - { - if !self.rsa.is_idle() { - return Err(nb::Error::WouldBlock); - } - unsafe { - self.rsa.read_out(outbuf); - } - self.rsa.clear_interrupt(); - Ok(()) - } -} diff --git a/esp-hal-common/src/rtc_cntl/mod.rs b/esp-hal-common/src/rtc_cntl/mod.rs deleted file mode 100644 index 485177adeb5..00000000000 --- a/esp-hal-common/src/rtc_cntl/mod.rs +++ /dev/null @@ -1,830 +0,0 @@ -use embedded_hal::watchdog::{Watchdog, WatchdogDisable, WatchdogEnable}; -#[cfg(not(esp32c6))] -use fugit::HertzU32; -use fugit::MicrosDurationU64; - -pub use self::rtc::SocResetReason; -#[cfg(not(esp32c6))] -use crate::clock::{Clock, XtalClock}; -#[cfg(not(esp32))] -use crate::efuse::Efuse; -#[cfg(esp32c6)] -use crate::peripherals::LP_WDT; -#[cfg(not(esp32c6))] -use crate::peripherals::{RTC_CNTL, TIMG0}; -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - reset::{SleepSource, WakeupReason}, - Cpu, -}; - -#[cfg(esp32c6)] -type RtcCntl = crate::peripherals::LP_CLKRST; -#[cfg(not(esp32c6))] -type RtcCntl = crate::peripherals::RTC_CNTL; - -#[cfg_attr(esp32, path = "rtc/esp32.rs")] -#[cfg_attr(esp32c2, path = "rtc/esp32c2.rs")] -#[cfg_attr(esp32c3, path = "rtc/esp32c3.rs")] -#[cfg_attr(esp32c6, path = "rtc/esp32c6.rs")] -#[cfg_attr(esp32s2, path = "rtc/esp32s2.rs")] -#[cfg_attr(esp32s3, path = "rtc/esp32s3.rs")] -mod rtc; - -#[cfg(esp32c6)] -pub use rtc::RtcClock; - -extern "C" { - #[allow(dead_code)] - fn ets_delay_us(us: u32); - fn rtc_get_reset_reason(cpu_num: u32) -> u32; - pub fn software_reset_cpu(); - pub fn software_reset(); -} - -#[cfg(not(esp32c6))] -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -/// RTC SLOW_CLK frequency values -pub(crate) enum RtcFastClock { - /// Main XTAL, divided by 4 - RtcFastClockXtalD4 = 0, - /// Internal fast RC oscillator - RtcFastClock8m = 1, -} - -#[cfg(not(esp32c6))] -impl Clock for RtcFastClock { - fn frequency(&self) -> HertzU32 { - match self { - RtcFastClock::RtcFastClockXtalD4 => HertzU32::Hz(40_000_000 / 4), - #[cfg(any(esp32, esp32s2))] - RtcFastClock::RtcFastClock8m => HertzU32::Hz(8_500_000), - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - RtcFastClock::RtcFastClock8m => HertzU32::Hz(17_500_000), - } - } -} - -#[cfg(not(esp32c6))] -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -/// RTC SLOW_CLK frequency values -pub(crate) enum RtcSlowClock { - /// Internal slow RC oscillator - RtcSlowClockRtc = 0, - /// External 32 KHz XTAL - RtcSlowClock32kXtal = 1, - /// Internal fast RC oscillator, divided by 256 - RtcSlowClock8mD256 = 2, -} - -#[cfg(not(esp32c6))] -impl Clock for RtcSlowClock { - fn frequency(&self) -> HertzU32 { - match self { - #[cfg(esp32)] - RtcSlowClock::RtcSlowClockRtc => HertzU32::Hz(150_000), - #[cfg(esp32s2)] - RtcSlowClock::RtcSlowClockRtc => HertzU32::Hz(90_000), - #[cfg(any(esp32c2, esp32c3, esp32s3))] - RtcSlowClock::RtcSlowClockRtc => HertzU32::Hz(136_000), - RtcSlowClock::RtcSlowClock32kXtal => HertzU32::Hz(32_768), - #[cfg(any(esp32, esp32s2))] - RtcSlowClock::RtcSlowClock8mD256 => HertzU32::Hz(8_500_000 / 256), - #[cfg(any(esp32c2, esp32c3, esp32s3))] - RtcSlowClock::RtcSlowClock8mD256 => HertzU32::Hz(17_500_000 / 256), - } - } -} - -#[allow(unused)] -#[cfg(not(esp32c6))] -#[derive(Debug, Clone, Copy)] -/// Clock source to be calibrated using rtc_clk_cal function -pub(crate) enum RtcCalSel { - /// Currently selected RTC SLOW_CLK - RtcCalRtcMux = 0, - /// Internal 8 MHz RC oscillator, divided by 256 - RtcCal8mD256 = 1, - /// External 32 KHz XTAL - RtcCal32kXtal = 2, - #[cfg(not(esp32))] - /// Internal 150 KHz RC oscillator - RtcCalInternalOsc = 3, -} - -pub struct Rtc<'d> { - _inner: PeripheralRef<'d, RtcCntl>, - pub rwdt: Rwdt, - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - pub swd: Swd, -} - -impl<'d> Rtc<'d> { - pub fn new(rtc_cntl: impl Peripheral

    + 'd) -> Self { - rtc::init(); - rtc::configure_clock(); - - Self { - _inner: rtc_cntl.into_ref(), - rwdt: Rwdt::default(), - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - swd: Swd::new(), - } - } - - // TODO: implement for ESP32-C6 - #[cfg(not(esp32c6))] - pub fn estimate_xtal_frequency(&mut self) -> u32 { - RtcClock::estimate_xtal_frequency() - } -} - -#[cfg(not(esp32c6))] -/// RTC Watchdog Timer -pub struct RtcClock; - -#[cfg(not(esp32c6))] -/// RTC Watchdog Timer driver -impl RtcClock { - const CAL_FRACT: u32 = 19; - - /// Enable or disable 8 MHz internal oscillator - /// - /// Output from 8 MHz internal oscillator is passed into a configurable - /// divider, which by default divides the input clock frequency by 256. - /// Output of the divider may be used as RTC_SLOW_CLK source. - /// Output of the divider is referred to in register descriptions and code - /// as 8md256 or simply d256. Divider values other than 256 may be - /// configured, but this facility is not currently needed, so is not - /// exposed in the code. - /// - /// When 8MHz/256 divided output is not needed, the divider should be - /// disabled to reduce power consumption. - #[cfg(not(esp32c6))] - fn enable_8m(clk_8m_en: bool, d256_en: bool) { - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - - if clk_8m_en { - rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m().clear_bit()); - unsafe { - rtc_cntl.timer1.modify(|_, w| w.ck8m_wait().bits(5)); - ets_delay_us(50); - } - } else { - rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m().set_bit()); - rtc_cntl - .timer1 - .modify(|_, w| unsafe { w.ck8m_wait().bits(20) }); - } - - if d256_en { - rtc_cntl - .clk_conf - .modify(|_, w| w.enb_ck8m_div().clear_bit()); - } else { - rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m_div().set_bit()); - } - } - - /// Get main XTAL frequency - /// This is the value stored in RTC register RTC_XTAL_FREQ_REG by the - /// bootloader, as passed to rtc_clk_init function. - fn get_xtal_freq() -> XtalClock { - let xtal_freq_reg = unsafe { &*RTC_CNTL::PTR }.store4.read().bits(); - - // Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in - // lower and upper 16-bit halves. These are the routines to work with such a - // representation. - let clk_val_is_valid = |val| { - (val & 0xffffu32) == ((val >> 16u32) & 0xffffu32) && val != 0u32 && val != u32::MAX - }; - let reg_val_to_clk_val = |val| val & u16::MAX as u32; - - if !clk_val_is_valid(xtal_freq_reg) { - return XtalClock::RtcXtalFreq40M; - } - - match reg_val_to_clk_val(xtal_freq_reg) { - 40 => XtalClock::RtcXtalFreq40M, - #[cfg(any(esp32c3, esp32s3))] - 32 => XtalClock::RtcXtalFreq32M, - #[cfg(any(esp32, esp32c2))] - 26 => XtalClock::RtcXtalFreq26M, - #[cfg(esp32)] - 24 => XtalClock::RtcXtalFreq24M, - other => XtalClock::RtcXtalFreqOther(other), - } - } - - /// Get the RTC_SLOW_CLK source - #[cfg(not(esp32c6))] - fn get_slow_freq() -> RtcSlowClock { - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - let slow_freq = rtc_cntl.clk_conf.read().ana_clk_rtc_sel().bits(); - match slow_freq { - 0 => RtcSlowClock::RtcSlowClockRtc, - 1 => RtcSlowClock::RtcSlowClock32kXtal, - 2 => RtcSlowClock::RtcSlowClock8mD256, - _ => unreachable!(), - } - } - - /// Select source for RTC_SLOW_CLK - #[cfg(not(esp32c6))] - fn set_slow_freq(slow_freq: RtcSlowClock) { - unsafe { - let rtc_cntl = &*RTC_CNTL::PTR; - rtc_cntl.clk_conf.modify(|_, w| { - w.ana_clk_rtc_sel() - .bits(slow_freq as u8) - // Why we need to connect this clock to digital? - // Or maybe this clock should be connected to digital when - // XTAL 32k clock is enabled instead? - .dig_xtal32k_en() - .bit(match slow_freq { - RtcSlowClock::RtcSlowClock32kXtal => true, - _ => false, - }) - // The clk_8m_d256 will be closed when rtc_state in SLEEP, - // so if the slow_clk is 8md256, clk_8m must be force power on - .ck8m_force_pu() - .bit(match slow_freq { - RtcSlowClock::RtcSlowClock8mD256 => true, - _ => false, - }) - }); - - ets_delay_us(300u32); - }; - } - - /// Select source for RTC_FAST_CLK - #[cfg(not(esp32c6))] - fn set_fast_freq(fast_freq: RtcFastClock) { - unsafe { - let rtc_cntl = &*RTC_CNTL::PTR; - rtc_cntl.clk_conf.modify(|_, w| { - w.fast_clk_rtc_sel().bit(match fast_freq { - RtcFastClock::RtcFastClock8m => true, - RtcFastClock::RtcFastClockXtalD4 => false, - }) - }); - - ets_delay_us(3u32); - }; - } - - /// Calibration of RTC_SLOW_CLK is performed using a special feature of - /// TIMG0. This feature counts the number of XTAL clock cycles within a - /// given number of RTC_SLOW_CLK cycles. - #[cfg(not(esp32c6))] - fn calibrate_internal(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { - // Except for ESP32, choosing RTC_CAL_RTC_MUX results in calibration of - // the 150k RTC clock (90k on ESP32-S2) regardless of the currently selected - // SLOW_CLK. On the ESP32, it uses the currently selected SLOW_CLK. - // The following code emulates ESP32 behavior for the other chips: - #[cfg(not(esp32))] - let cal_clk = match cal_clk { - RtcCalSel::RtcCalRtcMux => match RtcClock::get_slow_freq() { - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - RtcSlowClock::RtcSlowClock8mD256 => RtcCalSel::RtcCal8mD256, - _ => cal_clk, - }, - RtcCalSel::RtcCalInternalOsc => RtcCalSel::RtcCalRtcMux, - _ => cal_clk, - }; - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - let timg0 = unsafe { &*TIMG0::PTR }; - - // Enable requested clock (150k clock is always on) - let dig_32k_xtal_enabled = rtc_cntl.clk_conf.read().dig_xtal32k_en().bit_is_set(); - - if matches!(cal_clk, RtcCalSel::RtcCal32kXtal) && !dig_32k_xtal_enabled { - rtc_cntl - .clk_conf - .modify(|_, w| w.dig_xtal32k_en().set_bit()); - } - - if matches!(cal_clk, RtcCalSel::RtcCal8mD256) { - rtc_cntl - .clk_conf - .modify(|_, w| w.dig_clk8m_d256_en().set_bit()); - } - - // There may be another calibration process already running during we - // call this function, so we should wait the last process is done. - #[cfg(not(esp32))] - if timg0 - .rtccalicfg - .read() - .rtc_cali_start_cycling() - .bit_is_set() - { - // Set a small timeout threshold to accelerate the generation of timeout. - // The internal circuit will be reset when the timeout occurs and will not - // affect the next calibration. - timg0 - .rtccalicfg2 - .modify(|_, w| unsafe { w.rtc_cali_timeout_thres().bits(1) }); - - while timg0.rtccalicfg.read().rtc_cali_rdy().bit_is_clear() - && timg0.rtccalicfg2.read().rtc_cali_timeout().bit_is_clear() - {} - } - - // Prepare calibration - timg0.rtccalicfg.modify(|_, w| unsafe { - w.rtc_cali_clk_sel() - .bits(cal_clk as u8) - .rtc_cali_start_cycling() - .clear_bit() - .rtc_cali_max() - .bits(slowclk_cycles as u16) - }); - - // Figure out how long to wait for calibration to finish - // Set timeout reg and expect time delay - let expected_freq = match cal_clk { - RtcCalSel::RtcCal32kXtal => { - #[cfg(not(esp32))] - timg0.rtccalicfg2.modify(|_, w| unsafe { - w.rtc_cali_timeout_thres().bits(slowclk_cycles << 12) - }); - RtcSlowClock::RtcSlowClock32kXtal - } - RtcCalSel::RtcCal8mD256 => { - #[cfg(not(esp32))] - timg0.rtccalicfg2.modify(|_, w| unsafe { - w.rtc_cali_timeout_thres().bits(slowclk_cycles << 12) - }); - RtcSlowClock::RtcSlowClock8mD256 - } - _ => { - #[cfg(not(esp32))] - timg0.rtccalicfg2.modify(|_, w| unsafe { - w.rtc_cali_timeout_thres().bits(slowclk_cycles << 10) - }); - RtcSlowClock::RtcSlowClockRtc - } - }; - - let us_time_estimate = HertzU32::MHz(slowclk_cycles) / expected_freq.frequency(); - - // Start calibration - timg0 - .rtccalicfg - .modify(|_, w| w.rtc_cali_start().clear_bit().rtc_cali_start().set_bit()); - - // Wait for calibration to finish up to another us_time_estimate - unsafe { - ets_delay_us(us_time_estimate); - } - - #[cfg(esp32)] - let mut timeout_us = us_time_estimate; - - let cal_val = loop { - if timg0.rtccalicfg.read().rtc_cali_rdy().bit_is_set() { - break timg0.rtccalicfg1.read().rtc_cali_value().bits(); - } - - #[cfg(not(esp32))] - if timg0.rtccalicfg2.read().rtc_cali_timeout().bit_is_set() { - // Timed out waiting for calibration - break 0; - } - - #[cfg(esp32)] - if timeout_us > 0 { - timeout_us -= 1; - unsafe { - ets_delay_us(1); - } - } else { - // Timed out waiting for calibration - break 0; - } - }; - - timg0 - .rtccalicfg - .modify(|_, w| w.rtc_cali_start().clear_bit()); - rtc_cntl - .clk_conf - .modify(|_, w| w.dig_xtal32k_en().bit(dig_32k_xtal_enabled)); - - if matches!(cal_clk, RtcCalSel::RtcCal8mD256) { - rtc_cntl - .clk_conf - .modify(|_, w| w.dig_clk8m_d256_en().clear_bit()); - } - - cal_val - } - - /// Measure ratio between XTAL frequency and RTC slow clock frequency - fn get_calibration_ratio(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { - let xtal_cycles = RtcClock::calibrate_internal(cal_clk, slowclk_cycles) as u64; - let ratio = (xtal_cycles << RtcClock::CAL_FRACT) / slowclk_cycles as u64; - - (ratio & (u32::MAX as u64)) as u32 - } - - /// Measure RTC slow clock's period, based on main XTAL frequency - /// - /// This function will time out and return 0 if the time for the given - /// number of cycles to be counted exceeds the expected time twice. This - /// may happen if 32k XTAL is being calibrated, but the oscillator has - /// not started up (due to incorrect loading capacitance, board design - /// issue, or lack of 32 XTAL on board). - fn calibrate(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { - let xtal_freq = RtcClock::get_xtal_freq(); - let xtal_cycles = RtcClock::calibrate_internal(cal_clk, slowclk_cycles) as u64; - let divider = xtal_freq.mhz() as u64 * slowclk_cycles as u64; - let period_64 = ((xtal_cycles << RtcClock::CAL_FRACT) + divider / 2u64 - 1u64) / divider; - - (period_64 & u32::MAX as u64) as u32 - } - - /// Calculate the necessary RTC_SLOW_CLK cycles to complete 1 millisecond. - fn cycles_to_1ms() -> u16 { - let period_13q19 = RtcClock::calibrate( - match RtcClock::get_slow_freq() { - RtcSlowClock::RtcSlowClockRtc => RtcCalSel::RtcCalRtcMux, - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - #[cfg(not(esp32c6))] - RtcSlowClock::RtcSlowClock8mD256 => RtcCalSel::RtcCal8mD256, - }, - 1024, - ); - - // 100_000_000 is used to get rid of `float` calculations - let period = (100_000_000 * period_13q19 as u64) / (1 << RtcClock::CAL_FRACT); - - (100_000_000 * 1000 / period) as u16 - } - - // TODO: implement for ESP32-C6 - #[cfg(not(esp32c6))] - fn estimate_xtal_frequency() -> u32 { - // Number of 8M/256 clock cycles to use for XTAL frequency estimation. - const XTAL_FREQ_EST_CYCLES: u32 = 10; - - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - let clk_8m_enabled = rtc_cntl.clk_conf.read().enb_ck8m().bit_is_clear(); - let clk_8md256_enabled = rtc_cntl.clk_conf.read().enb_ck8m_div().bit_is_clear(); - - if !clk_8md256_enabled { - RtcClock::enable_8m(true, true); - } - - let ratio = RtcClock::get_calibration_ratio(RtcCalSel::RtcCal8mD256, XTAL_FREQ_EST_CYCLES); - let freq_mhz = - ((ratio as u64 * RtcFastClock::RtcFastClock8m.hz() as u64 / 1_000_000u64 / 256u64) - >> RtcClock::CAL_FRACT) as u32; - - RtcClock::enable_8m(clk_8m_enabled, clk_8md256_enabled); - - freq_mhz - } -} - -/// Behavior of the RWDT stage if it times out -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -enum RwdtStageAction { - RwdtStageActionOff = 0, - RwdtStageActionInterrupt = 1, - RwdtStageActionResetCpu = 2, - RwdtStageActionResetSystem = 3, - RwdtStageActionResetRtc = 4, -} - -/// RTC Watchdog Timer -pub struct Rwdt { - stg0_action: RwdtStageAction, - stg1_action: RwdtStageAction, - stg2_action: RwdtStageAction, - stg3_action: RwdtStageAction, -} - -impl Default for Rwdt { - fn default() -> Self { - Self { - stg0_action: RwdtStageAction::RwdtStageActionResetRtc, - stg1_action: RwdtStageAction::RwdtStageActionOff, - stg2_action: RwdtStageAction::RwdtStageActionOff, - stg3_action: RwdtStageAction::RwdtStageActionOff, - } - } -} - -/// RTC Watchdog Timer driver -impl Rwdt { - pub fn listen(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.stg0_action = RwdtStageAction::RwdtStageActionInterrupt; - - self.set_write_protection(false); - - // Configure STAGE0 to trigger an interrupt upon expiration - rtc_cntl - .wdtconfig0 - .modify(|_, w| unsafe { w.wdt_stg0().bits(self.stg0_action as u8) }); - - #[cfg(esp32)] - rtc_cntl.int_ena.modify(|_, w| w.wdt_int_ena().set_bit()); - #[cfg(not(esp32))] - rtc_cntl - .int_ena_rtc - .modify(|_, w| w.wdt_int_ena().set_bit()); - - self.set_write_protection(true); - } - - pub fn unlisten(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.stg0_action = RwdtStageAction::RwdtStageActionResetRtc; - - self.set_write_protection(false); - - // Configure STAGE0 to reset the main system and the RTC upon expiration. - rtc_cntl - .wdtconfig0 - .modify(|_, w| unsafe { w.wdt_stg0().bits(self.stg0_action as u8) }); - - #[cfg(esp32)] - rtc_cntl.int_ena.modify(|_, w| w.wdt_int_ena().clear_bit()); - #[cfg(not(esp32))] - rtc_cntl - .int_ena_rtc - .modify(|_, w| w.wdt_int_ena().clear_bit()); - - self.set_write_protection(true); - } - - pub fn clear_interrupt(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.set_write_protection(false); - - #[cfg(esp32)] - rtc_cntl.int_clr.write(|w| w.wdt_int_clr().set_bit()); - #[cfg(not(esp32))] - rtc_cntl.int_clr_rtc.write(|w| w.wdt_int_clr().set_bit()); - - self.set_write_protection(true); - } - - pub fn is_interrupt_set(&self) -> bool { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - cfg_if::cfg_if! { - if #[cfg(esp32)] { - rtc_cntl.int_st.read().wdt_int_st().bit_is_set() - } else { - rtc_cntl.int_st_rtc.read().wdt_int_st().bit_is_set() - } - } - } - - /// Enable/disable write protection for WDT registers - fn set_write_protection(&mut self, enable: bool) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - let wkey = if enable { 0u32 } else { 0x50D8_3AA1 }; - - rtc_cntl.wdtwprotect.write(|w| unsafe { w.bits(wkey) }); - } -} - -impl WatchdogDisable for Rwdt { - fn disable(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.set_write_protection(false); - - rtc_cntl - .wdtconfig0 - .modify(|_, w| w.wdt_en().clear_bit().wdt_flashboot_mod_en().clear_bit()); - - self.set_write_protection(true); - } -} - -// TODO: this can be refactored -impl WatchdogEnable for Rwdt { - type Time = MicrosDurationU64; - - fn start(&mut self, period: T) - where - T: Into, - { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - let timeout_raw = (period.into().to_millis() * (RtcClock::cycles_to_1ms() as u64)) as u32; - self.set_write_protection(false); - - unsafe { - #[cfg(esp32)] - rtc_cntl - .wdtconfig1 - .modify(|_, w| w.wdt_stg0_hold().bits(timeout_raw)); - - #[cfg(esp32c6)] - (&*LP_WDT::PTR).config1.modify(|_, w| { - w.wdt_stg0_hold() - .bits(timeout_raw >> (1 + Efuse::get_rwdt_multiplier())) - }); - - #[cfg(not(any(esp32, esp32c6)))] - rtc_cntl.wdtconfig1.modify(|_, w| { - w.wdt_stg0_hold() - .bits(timeout_raw >> (1 + Efuse::get_rwdt_multiplier())) - }); - - rtc_cntl.wdtconfig0.modify(|_, w| { - w.wdt_stg0() - .bits(self.stg0_action as u8) - .wdt_cpu_reset_length() - .bits(7) - .wdt_sys_reset_length() - .bits(7) - .wdt_stg1() - .bits(self.stg1_action as u8) - .wdt_stg2() - .bits(self.stg2_action as u8) - .wdt_stg3() - .bits(self.stg3_action as u8) - .wdt_en() - .set_bit() - }); - } - - self.set_write_protection(true); - } -} - -impl Watchdog for Rwdt { - fn feed(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.set_write_protection(false); - rtc_cntl.wdtfeed.write(|w| unsafe { w.bits(1) }); - self.set_write_protection(true); - } -} - -#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] -/// Super Watchdog -pub struct Swd; - -#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] -/// Super Watchdog driver -impl Swd { - pub fn new() -> Self { - Self - } - - /// Enable/disable write protection for WDT registers - fn set_write_protection(&mut self, enable: bool) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - #[cfg(not(esp32c6))] - let wkey = if enable { 0u32 } else { 0x8F1D_312A }; - #[cfg(esp32c6)] - let wkey = if enable { 0u32 } else { 0x50D8_3AA1 }; - - rtc_cntl - .swd_wprotect - .write(|w| unsafe { w.swd_wkey().bits(wkey) }); - } -} - -#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] -impl WatchdogDisable for Swd { - fn disable(&mut self) { - #[cfg(not(esp32c6))] - let rtc_cntl = unsafe { &*RTC_CNTL::PTR }; - #[cfg(esp32c6)] - let rtc_cntl = unsafe { &*LP_WDT::PTR }; - - self.set_write_protection(false); - rtc_cntl.swd_conf.write(|w| w.swd_auto_feed_en().set_bit()); - self.set_write_protection(true); - } -} - -pub fn get_reset_reason(cpu: Cpu) -> Option { - let reason = unsafe { rtc_get_reset_reason(cpu as u32) }; - let reason = SocResetReason::from_repr(reason as usize); - - reason -} - -pub fn get_wakeup_cause() -> SleepSource { - // FIXME: check s_light_sleep_wakeup - // https://github.com/espressif/esp-idf/blob/afbdb0f3ef195ab51690a64e22bfb8a5cd487914/components/esp_hw_support/sleep_modes.c#L1394 - if get_reset_reason(Cpu::ProCpu).unwrap() != SocResetReason::CoreDeepSleep { - return SleepSource::Undefined; - } - - #[cfg(esp32c6)] - let wakeup_cause = WakeupReason::from_bits_retain(unsafe { - (&*crate::peripherals::PMU::PTR) - .slp_wakeup_status0 - .read() - .wakeup_cause() - .bits() - }); - #[cfg(not(any(esp32, esp32c6)))] - let wakeup_cause = WakeupReason::from_bits_retain(unsafe { - (&*RTC_CNTL::PTR) - .slp_wakeup_cause - .read() - .wakeup_cause() - .bits() - }); - #[cfg(esp32)] - let wakeup_cause = WakeupReason::from_bits_retain(unsafe { - (&*RTC_CNTL::PTR).wakeup_state.read().wakeup_cause().bits() as u32 - }); - - if wakeup_cause.contains(WakeupReason::TimerTrigEn) { - return SleepSource::Timer; - } - if wakeup_cause.contains(WakeupReason::GpioTrigEn) { - return SleepSource::Gpio; - } - if wakeup_cause.intersects(WakeupReason::Uart0TrigEn | WakeupReason::Uart1TrigEn) { - return SleepSource::Uart; - } - - #[cfg(pm_support_ext0_wakeup)] - if wakeup_cause.contains(WakeupReason::ExtEvent0Trig) { - return SleepSource::Ext0; - } - #[cfg(pm_support_ext1_wakeup)] - if wakeup_cause.contains(WakeupReason::ExtEvent1Trig) { - return SleepSource::Ext1; - } - - #[cfg(pm_support_touch_sensor_wakeup)] - if wakeup_cause.contains(WakeupReason::TouchTrigEn) { - return SleepSource::TouchPad; - } - - #[cfg(ulp_supported)] - if wakeup_cause.contains(WakeupReason::UlpTrigEn) { - return SleepSource::Ulp; - } - - #[cfg(pm_support_wifi_wakeup)] - if wakeup_cause.contains(WakeupReason::WifiTrigEn) { - return SleepSource::Wifi; - } - - #[cfg(pm_support_bt_wakeup)] - if wakeup_cause.contains(WakeupReason::BtTrigEn) { - return SleepSource::BT; - } - - #[cfg(riscv_coproc_supported)] - if wakeup_cause.contains(WakeupReason::CocpuTrigEn) { - return SleepSource::Ulp; - } else if wakeup_cause.contains(WakeupReason::CocpuTrapTrigEn) { - return SleepSource::CocpuTrapTrig; - } - - return SleepSource::Undefined; -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32.rs deleted file mode 100644 index b9e157e15ce..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32.rs +++ /dev/null @@ -1,85 +0,0 @@ -use strum::FromRepr; - -use crate::{ - clock::XtalClock, - peripherals::RTC_CNTL, - rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, -}; - -pub(crate) fn init() {} - -pub(crate) fn configure_clock() { - #[cfg(feature = "esp32_40mhz")] - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - #[cfg(feature = "esp32_26mhz")] - assert!( - matches!(RtcClock::get_xtal_freq(), XtalClock::RtcXtalFreq26M), - "Did you flash the right bootloader configured for 26Mhz xtal?" - ); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClock8m); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRtc); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let rtc_cntl = &*RTC_CNTL::ptr(); - rtc_cntl.store1.write(|w| w.bits(cal_val)); - } -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - ChipPowerOn = 0x01, - /// Software resets the digital core - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// SDIO module resets the digital core - CoreSdio = 0x06, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// Main watch dog 1 resets digital core - CoreMwdt1 = 0x08, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU - /// - /// In ESP-IDF there are `Cpu0Mwdt1` and `Cpu1Mwdt1`, however they have the - /// same values. - CpuMwdt0 = 0x0B, - /// Software resets CPU - /// - /// In ESP-IDF there are `Cpu0Sw` and `Cpu1Sw`, however they have the same - /// values. - Cpu0Sw = 0x0C, - /// RTC watch dog resets CPU - /// - /// In ESP-IDF there are `Cpu0RtcWdt` and `Cpu1RtcWdt`, however they have - /// the same values. - Cpu0RtcWdt = 0x0D, - /// CPU0 resets CPU1 by DPORT_APPCPU_RESETTING - Cpu1Cpu0 = 0x0E, - /// Reset when the VDD voltage is not stable - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs deleted file mode 100644 index 788c6a268fe..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs +++ /dev/null @@ -1,249 +0,0 @@ -use paste::paste; -use strum::FromRepr; - -use crate::{ - clock::XtalClock, - peripherals::{APB_CTRL, EXTMEM, RTC_CNTL, SPI0, SPI1, SYSTEM}, - regi2c_write_mask, - rom::rom_i2c_writeReg_Mask, - rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, -}; - -const I2C_DIG_REG: u32 = 0x6d; -const I2C_DIG_REG_HOSTID: u32 = 0; - -const I2C_ULP: u32 = 0x61; -const I2C_ULP_HOSTID: u32 = 0; - -const I2C_DIG_REG_XPD_RTC_REG: u32 = 13; -const I2C_DIG_REG_XPD_RTC_REG_MSB: u32 = 2; -const I2C_DIG_REG_XPD_RTC_REG_LSB: u32 = 2; - -const I2C_DIG_REG_XPD_DIG_REG: u32 = 13; -const I2C_DIG_REG_XPD_DIG_REG_MSB: u32 = 3; -const I2C_DIG_REG_XPD_DIG_REG_LSB: u32 = 3; - -const I2C_ULP_IR_FORCE_XPD_CK: u32 = 0; -const I2C_ULP_IR_FORCE_XPD_CK_MSB: u32 = 2; -const I2C_ULP_IR_FORCE_XPD_CK_LSB: u32 = 2; - -pub(crate) fn init() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - - unsafe { - regi2c_write_mask!(I2C_DIG_REG, I2C_DIG_REG_XPD_DIG_REG, 0); - regi2c_write_mask!(I2C_DIG_REG, I2C_DIG_REG_XPD_RTC_REG, 0); - } - - unsafe { - rtc_cntl - .timer1 - .modify(|_, w| w.pll_buf_wait().bits(20u8).ck8m_wait().bits(20u8)); - - rtc_cntl.timer5.modify(|_, w| w.min_slp_val().bits(2u8)); - } - - calibrate_ocode(); - - set_rtc_dig_dbias(); - - clock_control_init(); - - power_control_init(); - - unsafe { - rtc_cntl.int_ena_rtc.write(|w| w.bits(0)); - rtc_cntl.int_clr_rtc.write(|w| w.bits(u32::MAX)); - - regi2c_write_mask!(I2C_ULP, I2C_ULP_IR_FORCE_XPD_CK, 0); - } -} - -pub(crate) fn configure_clock() { - #[cfg(feature = "esp32c2_40mhz")] - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - #[cfg(feature = "esp32c2_26mhz")] - assert!( - matches!(RtcClock::get_xtal_freq(), XtalClock::RtcXtalFreq26M), - "Did you flash the right bootloader configured for 26MHz xtal?" - ); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClock8m); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRtc); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let rtc_cntl = &*RTC_CNTL::ptr(); - rtc_cntl.store1.write(|w| w.bits(cal_val)); - } -} - -fn calibrate_ocode() {} - -fn set_rtc_dig_dbias() {} - -/// Perform clock control related initialization -fn clock_control_init() { - let extmem = unsafe { &*EXTMEM::ptr() }; - let spi_mem_0 = unsafe { &*SPI0::ptr() }; - let spi_mem_1 = unsafe { &*SPI1::ptr() }; - - // Clear CMMU clock force on - extmem - .cache_mmu_power_ctrl - .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); - - // Clear tag clock force on - extmem - .icache_tag_power_ctrl - .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); - - // Clear register clock force on - spi_mem_0.clock_gate.modify(|_, w| w.clk_en().clear_bit()); - spi_mem_1.clock_gate.modify(|_, w| w.clk_en().clear_bit()); -} - -/// Perform power control related initialization -fn power_control_init() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - let system = unsafe { &*SYSTEM::ptr() }; - - rtc_cntl - .clk_conf - .modify(|_, w| w.ck8m_force_pu().clear_bit()); - - // Cancel XTAL force PU if no need to force power up - // Cannot cancel XTAL force PU if PLL is force power on - rtc_cntl - .options0 - .modify(|_, w| w.xtl_force_pu().clear_bit()); - - // Force PD APLL - rtc_cntl.ana_conf.modify(|_, w| { - w.plla_force_pu() - .clear_bit() - .plla_force_pd() - .set_bit() - // Open SAR_I2C protect function to avoid SAR_I2C - // Reset when rtc_ldo is low. - .i2c_reset_por_force_pd() - .clear_bit() - }); - - // Cancel BBPLL force PU if setting no force power up - rtc_cntl.options0.modify(|_, w| { - w.bbpll_force_pu() - .clear_bit() - .bbpll_i2c_force_pu() - .clear_bit() - .bb_i2c_force_pu() - .clear_bit() - }); - - rtc_cntl - .rtc_cntl - .modify(|_, w| w.regulator_force_pu().clear_bit()); - - // If this mask is enabled, all soc memories cannot enter power down mode. - // We should control soc memory power down mode from RTC, - // so we will not touch this register any more. - system - .mem_pd_mask - .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); - - rtc_sleep_pu(); - - rtc_cntl - .dig_pwc - .modify(|_, w| w.dg_wrap_force_pu().clear_bit()); - - rtc_cntl - .dig_iso - .modify(|_, w| w.dg_wrap_force_noiso().clear_bit()); - - // Cancel digital PADS force no iso - system - .cpu_per_conf - .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); - - // If SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0, - // the CPU clock will be closed when CPU enter WAITI mode. - rtc_cntl.dig_iso.modify(|_, w| { - w.dg_pad_force_unhold() - .clear_bit() - .dg_pad_force_noiso() - .clear_bit() - }); -} - -/// Configure whether certain peripherals are powered down in deep sleep -fn rtc_sleep_pu() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - let apb_ctrl = unsafe { &*APB_CTRL::ptr() }; - - rtc_cntl - .dig_pwc - .modify(|_, w| w.lslp_mem_force_pu().clear_bit()); - - apb_ctrl.front_end_mem_pd.modify(|_, w| { - w.dc_mem_force_pu() - .clear_bit() - .pbus_mem_force_pu() - .clear_bit() - .agc_mem_force_pu() - .clear_bit() - }); - apb_ctrl - .mem_power_up - .modify(|_, w| unsafe { w.sram_power_up().bits(0u8).rom_power_up().bits(0u8) }); -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - ChipPowerOn = 0x01, - /// Software resets the digital core by RTC_CNTL_SW_SYS_RST - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU 0 - Cpu0Mwdt0 = 0x0B, - /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST - Cpu0Sw = 0x0C, - /// RTC watch dog resets CPU 0 - Cpu0RtcWdt = 0x0D, - /// VDD voltage is not stable and resets the digital core - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, - /// Super watch dog resets the digital core and rtc module - SysSuperWdt = 0x12, - /// Glitch on clock resets the digital core and rtc module - SysClkGlitch = 0x13, - /// eFuse CRC error resets the digital core - CoreEfuseCrc = 0x14, - /// JTAG resets the CPU 0 - Cpu0Jtag = 0x18, -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs deleted file mode 100644 index 243559e6f9b..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs +++ /dev/null @@ -1,308 +0,0 @@ -use paste::paste; -use strum::FromRepr; - -use crate::{ - clock::XtalClock, - peripherals::{APB_CTRL, EXTMEM, RTC_CNTL, SPI0, SPI1, SYSTEM}, - regi2c_write_mask, - rom::rom_i2c_writeReg_Mask, - rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, -}; - -const I2C_DIG_REG: u32 = 0x6d; -const I2C_DIG_REG_HOSTID: u32 = 0; - -const I2C_ULP: u32 = 0x61; -const I2C_ULP_HOSTID: u32 = 0; - -const I2C_DIG_REG_XPD_RTC_REG: u32 = 13; -const I2C_DIG_REG_XPD_RTC_REG_MSB: u32 = 2; -const I2C_DIG_REG_XPD_RTC_REG_LSB: u32 = 2; - -const I2C_DIG_REG_XPD_DIG_REG: u32 = 13; -const I2C_DIG_REG_XPD_DIG_REG_MSB: u32 = 3; -const I2C_DIG_REG_XPD_DIG_REG_LSB: u32 = 3; - -const I2C_ULP_IR_FORCE_XPD_CK: u32 = 0; -const I2C_ULP_IR_FORCE_XPD_CK_MSB: u32 = 2; -const I2C_ULP_IR_FORCE_XPD_CK_LSB: u32 = 2; - -pub(crate) fn init() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - - unsafe { - regi2c_write_mask!(I2C_DIG_REG, I2C_DIG_REG_XPD_DIG_REG, 0); - - regi2c_write_mask!(I2C_DIG_REG, I2C_DIG_REG_XPD_RTC_REG, 0); - } - - rtc_cntl.ana_conf.modify(|_, w| w.pvtmon_pu().clear_bit()); - - unsafe { - rtc_cntl - .timer1 - .modify(|_, w| w.pll_buf_wait().bits(20u8).ck8m_wait().bits(20u8)); - rtc_cntl.timer5.modify(|_, w| w.min_slp_val().bits(2u8)); - - // Set default powerup & wait time - rtc_cntl.timer3.modify(|_, w| { - w.wifi_powerup_timer() - .bits(1u8) - .wifi_wait_timer() - .bits(1u16) - .bt_powerup_timer() - .bits(1u8) - .bt_wait_timer() - .bits(1u16) - }); - rtc_cntl.timer4.modify(|_, w| { - w.cpu_top_powerup_timer() - .bits(1u8) - .cpu_top_wait_timer() - .bits(1u16) - .dg_wrap_powerup_timer() - .bits(1u8) - .dg_wrap_wait_timer() - .bits(1u16) - }); - rtc_cntl.timer6.modify(|_, w| { - w.dg_peri_powerup_timer() - .bits(1u8) - .dg_peri_wait_timer() - .bits(1u16) - }); - } - - calibrate_ocode(); - - set_rtc_dig_dbias(); - - clock_control_init(); - - power_control_init(); - - unsafe { - rtc_cntl.int_ena_rtc.write(|w| w.bits(0)); - rtc_cntl.int_clr_rtc.write(|w| w.bits(u32::MAX)); - - regi2c_write_mask!(I2C_ULP, I2C_ULP_IR_FORCE_XPD_CK, 0); - } -} - -pub(crate) fn configure_clock() { - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClock8m); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRtc); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let rtc_cntl = &*RTC_CNTL::ptr(); - rtc_cntl.store1.write(|w| w.bits(cal_val)); - } -} - -fn calibrate_ocode() {} - -fn set_rtc_dig_dbias() {} - -/// Perform clock control related initialization -fn clock_control_init() { - let extmem = unsafe { &*EXTMEM::ptr() }; - let spi_mem_0 = unsafe { &*SPI0::ptr() }; - let spi_mem_1 = unsafe { &*SPI1::ptr() }; - - // Clear CMMU clock force on - extmem - .cache_mmu_power_ctrl - .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); - - // Clear tag clock force on - extmem - .icache_tag_power_ctrl - .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); - - // Clear register clock force on - spi_mem_0.clock_gate.modify(|_, w| w.clk_en().clear_bit()); - spi_mem_1.clock_gate.modify(|_, w| w.clk_en().clear_bit()); -} - -/// Perform power control related initialization -fn power_control_init() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - let system = unsafe { &*SYSTEM::ptr() }; - rtc_cntl - .clk_conf - .modify(|_, w| w.ck8m_force_pu().clear_bit()); - - // Cancel XTAL force PU if no need to force power up - // Cannot cancel XTAL force PU if PLL is force power on - rtc_cntl - .options0 - .modify(|_, w| w.xtl_force_pu().clear_bit()); - - // Force PD APLL - rtc_cntl.ana_conf.modify(|_, w| { - w.plla_force_pu() - .clear_bit() - .plla_force_pd() - .set_bit() - // Open SAR_I2C protect function to avoid SAR_I2C - // Reset when rtc_ldo is low. - .reset_por_force_pd() - .clear_bit() - }); - - // Cancel BBPLL force PU if setting no force power up - rtc_cntl.options0.modify(|_, w| { - w.bbpll_force_pu() - .clear_bit() - .bbpll_i2c_force_pu() - .clear_bit() - .bb_i2c_force_pu() - .clear_bit() - }); - rtc_cntl.rtc_cntl.modify(|_, w| { - w.regulator_force_pu() - .clear_bit() - .dboost_force_pu() - .clear_bit() - .dboost_force_pd() - .set_bit() - }); - - // If this mask is enabled, all soc memories cannot enter power down mode. - // We should control soc memory power down mode from RTC, - // so we will not touch this register any more. - system - .mem_pd_mask - .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); - - rtc_sleep_pu(); - - rtc_cntl.dig_pwc.modify(|_, w| { - w.dg_wrap_force_pu() - .clear_bit() - .wifi_force_pu() - .clear_bit() - .bt_force_pu() - .clear_bit() - .cpu_top_force_pu() - .clear_bit() - .dg_peri_force_pu() - .clear_bit() - }); - rtc_cntl.dig_iso.modify(|_, w| { - w.dg_wrap_force_noiso() - .clear_bit() - .wifi_force_noiso() - .clear_bit() - .bt_force_noiso() - .clear_bit() - .cpu_top_force_noiso() - .clear_bit() - .dg_peri_force_noiso() - .clear_bit() - }); - - // Cancel digital PADS force no iso - system - .cpu_per_conf - .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); - - // If SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0, - // the CPU clock will be closed when CPU enter WAITI mode. - rtc_cntl.dig_iso.modify(|_, w| { - w.dg_pad_force_unhold() - .clear_bit() - .dg_pad_force_noiso() - .clear_bit() - }); -} - -/// Configure whether certain peripherals are powered down in deep sleep -fn rtc_sleep_pu() { - let rtc_cntl = unsafe { &*RTC_CNTL::ptr() }; - let apb_ctrl = unsafe { &*APB_CTRL::ptr() }; - - rtc_cntl.dig_pwc.modify(|_, w| { - w.lslp_mem_force_pu() - .clear_bit() - .fastmem_force_lpu() - .clear_bit() - }); - - apb_ctrl.front_end_mem_pd.modify(|_, w| { - w.dc_mem_force_pu() - .clear_bit() - .pbus_mem_force_pu() - .clear_bit() - .agc_mem_force_pu() - .clear_bit() - }); - apb_ctrl - .mem_power_up - .modify(|_, w| unsafe { w.sram_power_up().bits(0u8).rom_power_up().bits(0u8) }); -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - /// - /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or - /// `ChipSuperWdt`, however that is not really compatible with Rust-style - /// enums. - ChipPowerOn = 0x01, - /// Software resets the digital core by RTC_CNTL_SW_SYS_RST - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// Main watch dog 1 resets digital core - CoreMwdt1 = 0x08, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU 0 - Cpu0Mwdt0 = 0x0B, - /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST - Cpu0Sw = 0x0C, - /// RTC watch dog resets CPU 0 - Cpu0RtcWdt = 0x0D, - /// VDD voltage is not stable and resets the digital core - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, - /// Main watch dog 1 resets CPU 0 - Cpu0Mwdt1 = 0x11, - /// Super watch dog resets the digital core and rtc module - SysSuperWdt = 0x12, - /// Glitch on clock resets the digital core and rtc module - SysClkGlitch = 0x13, - /// eFuse CRC error resets the digital core - CoreEfuseCrc = 0x14, - /// USB UART resets the digital core - CoreUsbUart = 0x15, - /// USB JTAG resets the digital core - CoreUsbJtag = 0x16, - /// Glitch on power resets the digital core - CorePwrGlitch = 0x17, -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs deleted file mode 100644 index f9fa52ce342..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs +++ /dev/null @@ -1,647 +0,0 @@ -use fugit::HertzU32; -use strum::FromRepr; - -use crate::{ - clock::{clocks_ll::regi2c_write_mask, Clock, XtalClock}, - peripherals::{LP_AON, LP_CLKRST, PCR, PMU, TIMG0}, -}; - -const I2C_DIG_REG: u8 = 0x6d; -const I2C_DIG_REG_HOSTID: u8 = 0; - -const I2C_DIG_REG_XPD_RTC_REG: u8 = 13; -const I2C_DIG_REG_XPD_RTC_REG_MSB: u8 = 2; -const I2C_DIG_REG_XPD_RTC_REG_LSB: u8 = 2; - -const I2C_DIG_REG_XPD_DIG_REG: u8 = 13; -const I2C_DIG_REG_XPD_DIG_REG_MSB: u8 = 3; -const I2C_DIG_REG_XPD_DIG_REG_LSB: u8 = 3; - -const I2C_DIG_REG_ENIF_RTC_DREG: u8 = 5; -const I2C_DIG_REG_ENIF_RTC_DREG_MSB: u8 = 7; -const I2C_DIG_REG_ENIF_RTC_DREG_LSB: u8 = 7; - -const I2C_DIG_REG_ENIF_DIG_DREG: u8 = 7; -const I2C_DIG_REG_ENIF_DIG_DREG_MSB: u8 = 7; -const I2C_DIG_REG_ENIF_DIG_DREG_LSB: u8 = 7; - -const I2C_DIG_REG_SCK_DCAP: u8 = 14; -const I2C_DIG_REG_SCK_DCAP_MSB: u8 = 7; -const I2C_DIG_REG_SCK_DCAP_LSB: u8 = 0; - -pub(crate) fn init() { - let pmu = unsafe { &*PMU::ptr() }; - - pmu.rf_pwc - .modify(|_, w| w.perif_i2c_rstb().set_bit().xpd_perif_i2c().set_bit()); - - unsafe { - regi2c_write_mask( - I2C_DIG_REG, - I2C_DIG_REG_HOSTID, - I2C_DIG_REG_ENIF_RTC_DREG, - I2C_DIG_REG_ENIF_RTC_DREG_MSB, - I2C_DIG_REG_ENIF_RTC_DREG_LSB, - 1, - ); - regi2c_write_mask( - I2C_DIG_REG, - I2C_DIG_REG_HOSTID, - I2C_DIG_REG_ENIF_DIG_DREG, - I2C_DIG_REG_ENIF_DIG_DREG_MSB, - I2C_DIG_REG_ENIF_DIG_DREG_LSB, - 1, - ); - - regi2c_write_mask( - I2C_DIG_REG, - I2C_DIG_REG_HOSTID, - I2C_DIG_REG_XPD_RTC_REG, - I2C_DIG_REG_XPD_RTC_REG_MSB, - I2C_DIG_REG_XPD_RTC_REG_LSB, - 0, - ); - regi2c_write_mask( - I2C_DIG_REG, - I2C_DIG_REG_HOSTID, - I2C_DIG_REG_XPD_DIG_REG, - I2C_DIG_REG_XPD_DIG_REG_MSB, - I2C_DIG_REG_XPD_DIG_REG_LSB, - 0, - ); - - pmu.hp_active_hp_regulator0 - .modify(|_, w| w.hp_active_hp_regulator_dbias().bits(25)); - pmu.hp_sleep_lp_regulator0 - .modify(|_, w| w.hp_sleep_lp_regulator_dbias().bits(26)); - } -} - -pub(crate) fn configure_clock() { - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClockRcFast); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRcSlow); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let lp_aon = &*LP_AON::ptr(); - lp_aon.store1.modify(|_, w| w.bits(cal_val)); - } - - modem_clk_domain_active_state_icg_map_preinit(); -} - -fn modem_clk_domain_active_state_icg_map_preinit() { - unsafe { - let pmu = &*PMU::PTR; - let lp_clkrst = &*LP_CLKRST::PTR; - - pmu.hp_active_icg_modem - .modify(|_, w| w.hp_active_dig_icg_modem_code().bits(2)); - - const MODEM_SYSCON_CLK_CONF_POWER_ST: u32 = 0x600A9800 + 0xc; - const MODEM_LPCON_CLK_CONF_POWER_ST: u32 = 0x600A9800 + 0x20; - - (MODEM_SYSCON_CLK_CONF_POWER_ST as *mut u32).write_volatile( - (MODEM_SYSCON_CLK_CONF_POWER_ST as *mut u32).read_volatile() & !(3 << 28) | 2 << 28, - ); - - (MODEM_LPCON_CLK_CONF_POWER_ST as *mut u32).write_volatile( - (MODEM_LPCON_CLK_CONF_POWER_ST as *mut u32).read_volatile() & !(3 << 28) | 2 << 28, - ); - - (MODEM_LPCON_CLK_CONF_POWER_ST as *mut u32).write_volatile( - (MODEM_LPCON_CLK_CONF_POWER_ST as *mut u32).read_volatile() & !(3 << 28) | 2 << 28, - ); - - pmu.imm_modem_icg - .write(|w| w.update_dig_icg_modem_en().set_bit()); - pmu.imm_sleep_sysclk - .write(|w| w.update_dig_icg_switch().set_bit()); - - lp_clkrst.fosc_cntl.modify(|_, w| w.fosc_dfreq().bits(100)); - regi2c_write_mask( - I2C_DIG_REG, - I2C_DIG_REG_HOSTID, - I2C_DIG_REG_SCK_DCAP, - I2C_DIG_REG_SCK_DCAP_MSB, - I2C_DIG_REG_SCK_DCAP_LSB, - 128, - ); - lp_clkrst - .rc32k_cntl - .modify(|_, w| w.rc32k_dfreq().bits(100)); - } -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - /// - /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or - /// `ChipSuperWdt`, however that is not really compatible with Rust-style - /// enums. - ChipPowerOn = 0x01, - /// Software resets the digital core by RTC_CNTL_SW_SYS_RST - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// SDIO Core reset - CoreSDIO = 0x06, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// Main watch dog 1 resets digital core - CoreMwdt1 = 0x08, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU 0 - Cpu0Mwdt0 = 0x0B, - /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST - Cpu0Sw = 0x0C, - /// RTC watch dog resets CPU 0 - Cpu0RtcWdt = 0x0D, - /// VDD voltage is not stable and resets the digital core - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, - /// Main watch dog 1 resets CPU 0 - Cpu0Mwdt1 = 0x11, - /// Super watch dog resets the digital core and rtc module - SysSuperWdt = 0x12, - /// eFuse CRC error resets the digital core - CoreEfuseCrc = 0x14, - /// USB UART resets the digital core - CoreUsbUart = 0x15, - /// USB JTAG resets the digital core - CoreUsbJtag = 0x16, - /// JTAG resets CPU - Cpu0JtagCpu = 0x18, -} - -extern "C" { - fn ets_delay_us(us: u32); -} - -#[allow(unused)] -#[derive(Debug, Clone, Copy)] -/// RTC SLOW_CLK frequency values -pub(crate) enum RtcFastClock { - /// Select RC_FAST_CLK as RTC_FAST_CLK source - RtcFastClockRcFast = 0, - /// Select XTAL_D2_CLK as RTC_FAST_CLK source - RtcFastClockXtalD2 = 1, -} - -impl Clock for RtcFastClock { - fn frequency(&self) -> HertzU32 { - match self { - RtcFastClock::RtcFastClockXtalD2 => HertzU32::Hz(40_000_000 / 2), /* TODO: Is the value correct? */ - RtcFastClock::RtcFastClockRcFast => HertzU32::Hz(17_500_000), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -/// RTC SLOW_CLK frequency values -pub(crate) enum RtcSlowClock { - /// Select RC_SLOW_CLK as RTC_SLOW_CLK source - RtcSlowClockRcSlow = 0, - /// Select XTAL32K_CLK as RTC_SLOW_CLK source - RtcSlowClock32kXtal = 1, - /// Select RC32K_CLK as RTC_SLOW_CLK source - RtcSlowClock32kRc = 2, - /// Select OSC_SLOW_CLK (external slow clock) as RTC_SLOW_CLK source - RtcSlowOscSlow = 3, -} - -impl Clock for RtcSlowClock { - fn frequency(&self) -> HertzU32 { - match self { - RtcSlowClock::RtcSlowClockRcSlow => HertzU32::Hz(136_000), - RtcSlowClock::RtcSlowClock32kXtal => HertzU32::Hz(32_768), - RtcSlowClock::RtcSlowClock32kRc => HertzU32::Hz(32_768), - RtcSlowClock::RtcSlowOscSlow => HertzU32::Hz(32_768), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -/// Clock source to be calibrated using rtc_clk_cal function -pub(crate) enum RtcCalSel { - /// Currently selected RTC SLOW_CLK - RtcCalRtcMux = -1, - /// Internal 150kHz RC oscillator - RtcCalRcSlow = 0, - /// External 32kHz XTAL, as one type of 32k clock - RtcCal32kXtal = 1, - /// Internal 32kHz RC oscillator, as one type of 32k clock - RtcCal32kRc = 2, - /// External slow clock signal input by lp_pad_gpio0, as one type of 32k - /// clock - RtcCal32kOscSlow = 3, - /// Internal 20MHz RC oscillator - RtcCalRcFast, -} - -#[derive(Clone)] -pub(crate) enum RtcCaliClkSel { - CaliClkRcSlow = 0, - CaliClkRcFast = 1, - CaliClk32k = 2, -} -/// RTC Watchdog Timer -pub struct RtcClock; - -/// RTC Watchdog Timer driver -impl RtcClock { - const CAL_FRACT: u32 = 19; - - /// Get main XTAL frequency - /// This is the value stored in RTC register RTC_XTAL_FREQ_REG by the - /// bootloader, as passed to rtc_clk_init function. - fn get_xtal_freq() -> XtalClock { - let xtal_freq_reg = unsafe { &*LP_AON::PTR }.store4.read().bits(); - - // Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in - // lower and upper 16-bit halves. These are the routines to work with such a - // representation. - let clk_val_is_valid = |val| { - (val & 0xffffu32) == ((val >> 16u32) & 0xffffu32) && val != 0u32 && val != u32::MAX - }; - let reg_val_to_clk_val = |val| val & u16::MAX as u32; - - if !clk_val_is_valid(xtal_freq_reg) { - return XtalClock::RtcXtalFreq40M; - } - - match reg_val_to_clk_val(xtal_freq_reg) { - 40 => XtalClock::RtcXtalFreq40M, - other => XtalClock::RtcXtalFreqOther(other), - } - } - - /// Get the RTC_SLOW_CLK source - fn get_slow_freq() -> RtcSlowClock { - let lp_clrst = unsafe { &*LP_CLKRST::ptr() }; - - let slow_freq = lp_clrst.lp_clk_conf.read().slow_clk_sel().bits(); - match slow_freq { - 0 => RtcSlowClock::RtcSlowClockRcSlow, - 1 => RtcSlowClock::RtcSlowClock32kXtal, - 2 => RtcSlowClock::RtcSlowClock32kRc, - 3 => RtcSlowClock::RtcSlowOscSlow, - _ => unreachable!(), - } - } - - fn set_slow_freq(slow_freq: RtcSlowClock) { - unsafe { - let lp_clkrst = &*LP_CLKRST::PTR; - - lp_clkrst - .lp_clk_conf - .modify(|_, w| w.slow_clk_sel().bits(slow_freq as u8)); - lp_clkrst.clk_to_hp.modify(|_, w| { - w.icg_hp_xtal32k() - .bit(match slow_freq { - RtcSlowClock::RtcSlowClock32kXtal => true, - _ => false, - }) - .icg_hp_xtal32k() - .bit(match slow_freq { - RtcSlowClock::RtcSlowClock32kXtal => true, - _ => false, - }) - }); - } - } - - // TODO: IDF-5781 Some of esp32c6 SOC_RTC_FAST_CLK_SRC_XTAL_D2 rtc_fast clock - // has timing issue Force to use SOC_RTC_FAST_CLK_SRC_RC_FAST since 2nd - // stage bootloader https://github.com/espressif/esp-idf/blob/master/components/bootloader_support/src/bootloader_clock_init.c#L65-L67 - fn set_fast_freq(fast_freq: RtcFastClock) { - unsafe { - let lp_clkrst = &*LP_CLKRST::PTR; - lp_clkrst.lp_clk_conf.modify(|_, w| { - w.fast_clk_sel().bit(match fast_freq { - RtcFastClock::RtcFastClockRcFast => false, - RtcFastClock::RtcFastClockXtalD2 => true, - }) - }); - ets_delay_us(3); - } - } - - /// Calibration of RTC_SLOW_CLK is performed using a special feature of - /// TIMG0. This feature counts the number of XTAL clock cycles within a - /// given number of RTC_SLOW_CLK cycles. - fn calibrate_internal(mut cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { - const SOC_CLK_RC_FAST_FREQ_APPROX: u32 = 17_500_000; - const SOC_CLK_RC_SLOW_FREQ_APPROX: u32 = 136_000; - const SOC_CLK_XTAL32K_FREQ_APPROX: u32 = 32768; - - if cal_clk == RtcCalSel::RtcCalRtcMux { - cal_clk = match cal_clk { - RtcCalSel::RtcCalRtcMux => match RtcClock::get_slow_freq() { - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - RtcSlowClock::RtcSlowClock32kRc => RtcCalSel::RtcCal32kRc, - _ => cal_clk, - }, - RtcCalSel::RtcCal32kOscSlow => RtcCalSel::RtcCalRtcMux, - _ => cal_clk, - }; - } - - let lp_clkrst = unsafe { &*LP_CLKRST::ptr() }; - let pcr = unsafe { &*PCR::ptr() }; - let pmu = unsafe { &*PMU::ptr() }; - - let clk_src = RtcClock::get_slow_freq(); - - if cal_clk == RtcCalSel::RtcCalRtcMux { - cal_clk = match clk_src { - RtcSlowClock::RtcSlowClockRcSlow => RtcCalSel::RtcCalRcSlow, - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - RtcSlowClock::RtcSlowClock32kRc => RtcCalSel::RtcCal32kRc, - RtcSlowClock::RtcSlowOscSlow => RtcCalSel::RtcCal32kOscSlow, - }; - } - - let cali_clk_sel; - if cal_clk == RtcCalSel::RtcCalRtcMux { - cal_clk = match clk_src { - RtcSlowClock::RtcSlowClockRcSlow => RtcCalSel::RtcCalRcSlow, - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - RtcSlowClock::RtcSlowClock32kRc => RtcCalSel::RtcCal32kRc, - RtcSlowClock::RtcSlowOscSlow => RtcCalSel::RtcCalRcSlow, - } - } - - if cal_clk == RtcCalSel::RtcCalRcFast { - cali_clk_sel = RtcCaliClkSel::CaliClkRcFast; - } else if cal_clk == RtcCalSel::RtcCalRcSlow { - cali_clk_sel = RtcCaliClkSel::CaliClkRcSlow; - } else { - cali_clk_sel = RtcCaliClkSel::CaliClk32k; - match cal_clk { - RtcCalSel::RtcCalRtcMux | RtcCalSel::RtcCalRcSlow | RtcCalSel::RtcCalRcFast => (), - RtcCalSel::RtcCal32kRc => pcr - .ctrl_32k_conf - .modify(|_, w| unsafe { w.clk_32k_sel().bits(0) }), - RtcCalSel::RtcCal32kXtal => pcr - .ctrl_32k_conf - .modify(|_, w| unsafe { w.clk_32k_sel().bits(1) }), - RtcCalSel::RtcCal32kOscSlow => pcr - .ctrl_32k_conf - .modify(|_, w| unsafe { w.clk_32k_sel().bits(2) }), - } - } - - // Enable requested clock (150k is always on) - // Some delay is required before the time is stable - // Only enable if originaly was disabled - // If clock is already on, do nothing - - let dig_32k_xtal_enabled = lp_clkrst.clk_to_hp.read().icg_hp_xtal32k().bit_is_set(); - - if cal_clk == RtcCalSel::RtcCal32kXtal && !dig_32k_xtal_enabled { - lp_clkrst - .clk_to_hp - .modify(|_, w| w.icg_hp_xtal32k().set_bit()); - } - - // TODO: very hacky - // in ESP-IDF these are not called in this function but the fields are set - lp_clkrst - .clk_to_hp - .modify(|_, w| w.icg_hp_xtal32k().set_bit()); - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_xtal32k().set_bit()); - - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_rc32k().set_bit()); - - let rc_fast_enabled = pmu - .hp_sleep_lp_ck_power - .read() - .hp_sleep_xpd_fosc_clk() - .bit_is_set(); - let dig_rc_fast_enabled = lp_clkrst.clk_to_hp.read().icg_hp_fosc().bit_is_set(); - - if cal_clk == RtcCalSel::RtcCalRcFast { - if !rc_fast_enabled { - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_fosc_clk().set_bit()); - unsafe { - ets_delay_us(50); - } - } - - if !dig_rc_fast_enabled { - lp_clkrst.clk_to_hp.modify(|_, w| w.icg_hp_fosc().set_bit()); - unsafe { - ets_delay_us(5); - } - } - } - - let rc32k_enabled = pmu - .hp_sleep_lp_ck_power - .read() - .hp_sleep_xpd_rc32k() - .bit_is_set(); - let dig_rc32k_enabled = lp_clkrst.clk_to_hp.read().icg_hp_osc32k().bit_is_set(); - - if cal_clk == RtcCalSel::RtcCal32kRc { - if !rc32k_enabled { - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_rc32k().set_bit()); - unsafe { - ets_delay_us(300); - } - } - - if !dig_rc32k_enabled { - lp_clkrst - .clk_to_hp - .modify(|_, w| w.icg_hp_osc32k().set_bit()); - } - } - - // Check if there is already running calibration process - // TODO: &mut TIMG0 for calibration - let timg0 = unsafe { &*TIMG0::ptr() }; - - if timg0 - .rtccalicfg - .read() - .rtc_cali_start_cycling() - .bit_is_set() - { - timg0 - .rtccalicfg2 - .modify(|_, w| unsafe { w.rtc_cali_timeout_thres().bits(1) }); - - // Set small timeout threshold to accelerate the generation of timeot - // Internal circuit will be reset when timeout occurs and will not affect the - // next calibration - while !timg0.rtccalicfg.read().rtc_cali_rdy().bit_is_set() - && !timg0.rtccalicfg2.read().rtc_cali_timeout().bit_is_set() - {} - } - - // Prepare calibration - timg0 - .rtccalicfg - .modify(|_, w| unsafe { w.rtc_cali_clk_sel().bits(cali_clk_sel.clone() as u8) }); - timg0 - .rtccalicfg - .modify(|_, w| w.rtc_cali_start_cycling().clear_bit()); - timg0 - .rtccalicfg - .modify(|_, w| unsafe { w.rtc_cali_max().bits(slowclk_cycles as u16) }); - - let expected_freq = match cali_clk_sel { - RtcCaliClkSel::CaliClk32k => { - timg0.rtccalicfg2.modify(|_, w| unsafe { - w.rtc_cali_timeout_thres().bits(slowclk_cycles << 12) - }); - SOC_CLK_XTAL32K_FREQ_APPROX - } - RtcCaliClkSel::CaliClkRcFast => { - timg0 - .rtccalicfg2 - .modify(|_, w| unsafe { w.rtc_cali_timeout_thres().bits(0x01FFFFFF) }); - SOC_CLK_RC_FAST_FREQ_APPROX - } - _ => { - timg0.rtccalicfg2.modify(|_, w| unsafe { - w.rtc_cali_timeout_thres().bits(slowclk_cycles << 10) - }); - SOC_CLK_RC_SLOW_FREQ_APPROX - } - }; - - let us_time_estimate = (HertzU32::MHz(slowclk_cycles) / expected_freq).to_Hz(); - - // Start calibration - timg0 - .rtccalicfg - .modify(|_, w| w.rtc_cali_start().clear_bit()); - timg0.rtccalicfg.modify(|_, w| w.rtc_cali_start().set_bit()); - - // Wait for calibration to finish up to another us_time_estimate - unsafe { - ets_delay_us(us_time_estimate); - } - - let cal_val = loop { - if timg0.rtccalicfg.read().rtc_cali_rdy().bit_is_set() { - break timg0.rtccalicfg1.read().rtc_cali_value().bits(); - } - - if timg0.rtccalicfg2.read().rtc_cali_timeout().bit_is_set() { - // Timed out waiting for calibration - break 0; - } - }; - - timg0 - .rtccalicfg - .modify(|_, w| w.rtc_cali_start().clear_bit()); - - if cal_clk == RtcCalSel::RtcCal32kXtal && !dig_32k_xtal_enabled { - lp_clkrst - .clk_to_hp - .modify(|_, w| w.icg_hp_xtal32k().clear_bit()); - } - - if cal_clk == RtcCalSel::RtcCalRcFast { - if rc_fast_enabled { - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_fosc_clk().set_bit()); - unsafe { - ets_delay_us(50); - } - } - - if dig_rc_fast_enabled { - lp_clkrst.clk_to_hp.modify(|_, w| w.icg_hp_fosc().set_bit()); - unsafe { - ets_delay_us(5); - } - } - } - - if cal_clk == RtcCalSel::RtcCal32kRc { - if rc32k_enabled { - pmu.hp_sleep_lp_ck_power - .modify(|_, w| w.hp_sleep_xpd_rc32k().set_bit()); - unsafe { - ets_delay_us(300); - } - } - if dig_rc32k_enabled { - lp_clkrst - .clk_to_hp - .modify(|_, w| w.icg_hp_osc32k().set_bit()); - } - } - - cal_val - } - - /// Measure RTC slow clock's period, based on main XTAL frequency - /// - /// This function will time out and return 0 if the time for the given - /// number of cycles to be counted exceeds the expected time twice. This - /// may happen if 32k XTAL is being calibrated, but the oscillator has - /// not started up (due to incorrect loading capacitance, board design - /// issue, or lack of 32 XTAL on board). - fn calibrate(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { - let xtal_freq = RtcClock::get_xtal_freq(); - let xtal_cycles = RtcClock::calibrate_internal(cal_clk, slowclk_cycles) as u64; - let divider = xtal_freq.mhz() as u64 * slowclk_cycles as u64; - let period_64 = ((xtal_cycles << RtcClock::CAL_FRACT) + divider / 2u64 - 1u64) / divider; - - (period_64 & u32::MAX as u64) as u32 - } - - /// Calculate the necessary RTC_SLOW_CLK cycles to complete 1 millisecond. - pub(crate) fn cycles_to_1ms() -> u16 { - let period_13q19 = RtcClock::calibrate( - match RtcClock::get_slow_freq() { - RtcSlowClock::RtcSlowClockRcSlow => RtcCalSel::RtcCalRtcMux, - RtcSlowClock::RtcSlowClock32kXtal => RtcCalSel::RtcCal32kXtal, - RtcSlowClock::RtcSlowClock32kRc => RtcCalSel::RtcCal32kRc, - RtcSlowClock::RtcSlowOscSlow => RtcCalSel::RtcCal32kOscSlow, - // RtcSlowClock::RtcCalRcFast => RtcCalSel::RtcCalRcFast, - }, - 1024, - ); - - // 100_000_000 is used to get rid of `float` calculations - let period = (100_000_000 * period_13q19 as u64) / (1 << RtcClock::CAL_FRACT); - - (100_000_000 * 1000 / period) as u16 - } -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32s2.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32s2.rs deleted file mode 100644 index 8b1241a6cab..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32s2.rs +++ /dev/null @@ -1,78 +0,0 @@ -use strum::FromRepr; - -use crate::{ - clock::XtalClock, - peripherals::RTC_CNTL, - rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, -}; - -pub(crate) fn init() {} - -pub(crate) fn configure_clock() { - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClock8m); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRtc); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let rtc_cntl = &*RTC_CNTL::ptr(); - rtc_cntl.store1.write(|w| w.bits(cal_val)); - } -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - /// - /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or - /// `ChipSuperWdt`, however that is not really compatible with Rust-style - /// enums. - ChipPowerOn = 0x01, - /// Software resets the digital core by RTC_CNTL_SW_SYS_RST - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// Main watch dog 1 resets digital core - CoreMwdt1 = 0x08, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU 0 - Cpu0Mwdt0 = 0x0B, - /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST - Cpu0Sw = 0x0C, - /// RTC watch dog resets CPU 0 - Cpu0RtcWdt = 0x0D, - /// VDD voltage is not stable and resets the digital core - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, - /// Main watch dog 1 resets CPU 0 - Cpu0Mwdt1 = 0x11, - /// Super watch dog resets the digital core and rtc module - SysSuperWdt = 0x12, - /// Glitch on clock resets the digital core and rtc module - SysClkGlitch = 0x13, - /// eFuse CRC error resets the digital core - CoreEfuseCrc = 0x14, -} diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32s3.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32s3.rs deleted file mode 100644 index 470cad87013..00000000000 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32s3.rs +++ /dev/null @@ -1,96 +0,0 @@ -use strum::FromRepr; - -use crate::{ - clock::XtalClock, - peripherals::RTC_CNTL, - rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, -}; - -pub(crate) fn init() {} - -pub(crate) fn configure_clock() { - assert!(matches!( - RtcClock::get_xtal_freq(), - XtalClock::RtcXtalFreq40M - )); - - RtcClock::set_fast_freq(RtcFastClock::RtcFastClock8m); - - let cal_val = loop { - RtcClock::set_slow_freq(RtcSlowClock::RtcSlowClockRtc); - - let res = RtcClock::calibrate(RtcCalSel::RtcCalRtcMux, 1024); - if res != 0 { - break res; - } - }; - - unsafe { - let rtc_cntl = &*RTC_CNTL::ptr(); - rtc_cntl.store1.write(|w| w.bits(cal_val)); - } -} - -// Terminology: -// -// CPU Reset: Reset CPU core only, once reset done, CPU will execute from -// reset vector -// Core Reset: Reset the whole digital system except RTC sub-system -// System Reset: Reset the whole digital system, including RTC sub-system -// Chip Reset: Reset the whole chip, including the analog part - -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] -pub enum SocResetReason { - /// Power on reset - /// - /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or - /// `ChipSuperWdt`, however that is not really compatible with Rust-style - /// enums. - ChipPowerOn = 0x01, - /// Software resets the digital core by RTC_CNTL_SW_SYS_RST - CoreSw = 0x03, - /// Deep sleep reset the digital core - CoreDeepSleep = 0x05, - /// Main watch dog 0 resets digital core - CoreMwdt0 = 0x07, - /// Main watch dog 1 resets digital core - CoreMwdt1 = 0x08, - /// RTC watch dog resets digital core - CoreRtcWdt = 0x09, - /// Main watch dog 0 resets CPU - /// - /// In ESP-IDF there are `Cpu0Mwdt0` and `Cpu1Mwdt0`, however they have the - /// same values. - CpuMwdt0 = 0x0B, - /// Software resets CPU by RTC_CNTL_SW_(PRO|APP)CPU_RST - /// - /// In ESP-IDF there are `Cpu0Sw` and `Cpu1Sw`, however they have the same - /// values. - CpuSw = 0x0C, - /// RTC watch dog resets CPU - /// - /// In ESP-IDF there are `Cpu0RtcWdt` and `Cpu1RtcWdt`, however they have - /// the same values. - CpuRtcWdt = 0x0D, - /// VDD voltage is not stable and resets the digital core - SysBrownOut = 0x0F, - /// RTC watch dog resets digital core and rtc module - SysRtcWdt = 0x10, - /// Main watch dog 1 resets CPU - /// - /// In ESP-IDF there are `Cpu0Mwdt1` and `Cpu1Mwdt1`, however they have the - /// same values. - CpuMwdt1 = 0x11, - /// Super watch dog resets the digital core and rtc module - SysSuperWdt = 0x12, - /// Glitch on clock resets the digital core and rtc module - SysClkGlitch = 0x13, - /// eFuse CRC error resets the digital core - CoreEfuseCrc = 0x14, - /// USB UART resets the digital core - CoreUsbUart = 0x15, - /// USB JTAG resets the digital core - CoreUsbJtag = 0x16, - /// Glitch on power resets the digital core - CorePwrGlitch = 0x17, -} diff --git a/esp-hal-common/src/sha.rs b/esp-hal-common/src/sha.rs deleted file mode 100644 index e921f55630a..00000000000 --- a/esp-hal-common/src/sha.rs +++ /dev/null @@ -1,521 +0,0 @@ -use core::convert::Infallible; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::SHA, - system::PeripheralClockControl, -}; - -// All the hash algorithms introduced in FIPS PUB 180-4 Spec. -// – SHA-1 -// – SHA-224 -// – SHA-256 -// – SHA-384 -// – SHA-512 -// – SHA-512/224 -// – SHA-512/256 -// – SHA-512/t (not implemented yet) -// Two working modes -// – Typical SHA -// – DMA-SHA (not implemented yet) - -const ALIGN_SIZE: usize = core::mem::size_of::(); - -// ESP32 does reversed order -#[cfg(esp32)] -const U32_FROM_BYTES: fn([u8; 4]) -> u32 = u32::from_be_bytes; - -#[cfg(not(esp32))] -const U32_FROM_BYTES: fn([u8; 4]) -> u32 = u32::from_ne_bytes; - -// The alignment helper helps you write to registers that only accepts u32 using -// regular u8s (bytes) It keeps a write buffer of 4 u8 (could in theory be 3 but -// less convient) And if the incoming data is not convertable to u32 (i.e. not a -// multiple of 4 in length) it will store the remainder in the buffer until the -// next call -// -// It assumes incoming `dst` are aligned to desired layout (in future -// ptr.is_aligned can be used) It also assumes that writes are done in FIFO -// order -#[derive(Debug)] -struct AlignmentHelper { - buf: [u8; ALIGN_SIZE], - buf_fill: usize, -} - -impl AlignmentHelper { - pub fn default() -> AlignmentHelper { - AlignmentHelper { - buf: [0u8; ALIGN_SIZE], - buf_fill: 0, - } - } - - // This function will write any remaining buffer to dst and return the amount of - // *bytes* written (0 means no write) - pub unsafe fn flush_to(&mut self, dst: *mut u32) -> usize { - if self.buf_fill != 0 { - for i in self.buf_fill..ALIGN_SIZE { - self.buf[i] = 0; - } - - dst.write_volatile(U32_FROM_BYTES(self.buf)); - } - - let flushed = self.buf_fill; - self.buf_fill = 0; - - return flushed; - } - - // This function is similar to `volatile_set_memory` but will prepend data that - // was previously ingested and ensure aligned (u32) writes - #[allow(unused)] - pub unsafe fn volatile_write_bytes(&mut self, dst: *mut u32, val: u8, count: usize) { - let mut cursor = 0; - if self.buf_fill != 0 { - for i in self.buf_fill..ALIGN_SIZE { - self.buf[i] = val; - } - - dst.write_volatile(U32_FROM_BYTES(self.buf)); - cursor = 1; - - self.buf_fill = 0; - } - - core::ptr::write_bytes(dst.add(cursor), val, count); - } - - // This function is similar to `volatile_copy_nonoverlapping_memory`, however it - // buffers up to a u32 in order to always write to registers in an aligned - // way. Additionally it will keep stop writing when the end of the register - // (defined by `dst_bound` relative to `dst`) and returns the remaining data - // (if not possible to write everything), and if it wrote till dst_bound or - // exited early (due to lack of data). - pub unsafe fn aligned_volatile_copy<'a>( - &mut self, - dst: *mut u32, - src: &'a [u8], - dst_bound: usize, - ) -> (&'a [u8], bool) { - assert!(dst_bound > 0); - - let mut nsrc = src; - let mut cursor = 0; - if self.buf_fill != 0 { - // First prepend existing data - let max_fill = ALIGN_SIZE - self.buf_fill; - let (nbuf, src) = src.split_at(core::cmp::min(src.len(), max_fill)); - nsrc = src; - for i in 0..max_fill { - match nbuf.get(i) { - Some(v) => { - self.buf[self.buf_fill + i] = *v; - self.buf_fill += 1; - } - None => return (&[], false), // Used up entire buffer before filling buff_fil - } - } - - dst.write_volatile(U32_FROM_BYTES(self.buf)); - cursor += 1; - - self.buf_fill = 0; - } - - if dst_bound <= cursor * ALIGN_SIZE { - return (nsrc, true); - } - - let (to_write, remaining) = nsrc.split_at(core::cmp::min( - dst_bound - cursor * ALIGN_SIZE, - (nsrc.len() / ALIGN_SIZE) * ALIGN_SIZE, // TODO: unstable div_floor for clarity? - )); - - if to_write.len() > 0 { - // Raw v_c_n_m also works but only when src.len() >= 4 * ALIGN_SIZE, otherwise - // it be broken - // core::intrinsics::volatile_copy_nonoverlapping_memory::(dst.add(cursor), - // to_write.as_ptr() as *const u32, to_write.len()/alignment); - for (i, v) in to_write.chunks_exact(ALIGN_SIZE).enumerate() { - dst.add(i) - .write_volatile(U32_FROM_BYTES(v.try_into().unwrap()).to_be()); - } - } - - // If it's data we can't store we don't need to try and align it, just wait for - // next write Generally this applies when (src/4*4) != src - let was_bounded = dst_bound - to_write.len() == 0; - if remaining.len() > 0 && remaining.len() < 4 { - for i in 0..remaining.len() { - self.buf[i] = remaining[i]; - } - - self.buf_fill = remaining.len(); - - return (&[], was_bounded); - } - - return (remaining, was_bounded); - } -} - -pub struct Sha<'d> { - sha: PeripheralRef<'d, SHA>, - mode: ShaMode, - alignment_helper: AlignmentHelper, - cursor: usize, - first_run: bool, - finished: bool, -} - -#[derive(Debug, Clone, Copy)] -pub enum ShaMode { - SHA1, - #[cfg(not(esp32))] - SHA224, - SHA256, - #[cfg(any(esp32s2, esp32s3, esp32))] - SHA384, - #[cfg(any(esp32s2, esp32s3, esp32))] - SHA512, - #[cfg(any(esp32s2, esp32s3))] - SHA512_224, - #[cfg(any(esp32s2, esp32s3))] - SHA512_256, - // SHA512_(u16) // Max 511 -} - -// TODO: Maybe make Sha Generic (Sha) in order to allow for better -// compiler optimizations? (Requires complex const generics which isn't stable -// yet) - -#[cfg(not(esp32))] -fn mode_as_bits(mode: ShaMode) -> u8 { - match mode { - ShaMode::SHA1 => 0, - ShaMode::SHA224 => 1, - ShaMode::SHA256 => 2, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA384 => 3, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA512 => 4, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA512_224 => 5, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA512_256 => 6, - // _ => 0 // TODO: SHA512/t - } -} - -// TODO: Allow/Implemenet SHA512_(u16) - -// A few notes on this implementation with regards to 'memcpy', -// - It seems that ptr::write_bytes already acts as volatile, while ptr::copy_* -// does not (in this case) -// - The registers are *not* cleared after processing, so padding needs to be -// written out -// - This component uses core::intrinsics::volatile_* which is unstable, but is -// the only way to -// efficiently copy memory with volatile -// - For this particular registers (and probably others), a full u32 needs to be -// written partial -// register writes (i.e. in u8 mode) does not work -// - This means that we need to buffer bytes coming in up to 4 u8's in order -// to create a full u32 - -// This implementation might fail after u32::MAX/8 bytes, to increase please see -// ::finish() length/self.cursor usage -impl<'d> Sha<'d> { - pub fn new( - sha: impl Peripheral

    + 'd, - mode: ShaMode, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(sha); - peripheral_clock_control.enable(crate::system::Peripheral::Sha); - - // Setup SHA Mode - #[cfg(not(esp32))] - sha.mode - .write(|w| unsafe { w.mode().bits(mode_as_bits(mode)) }); - - Self { - sha, - mode, - cursor: 0, - first_run: true, - finished: false, - alignment_helper: AlignmentHelper::default(), - } - } - - pub fn first_run(&self) -> bool { - self.first_run - } - - pub fn finished(&self) -> bool { - self.finished - } - - #[cfg(not(esp32))] - fn process_buffer(&mut self) { - // FIXME: SHA_START_REG & SHA_CONTINUE_REG are wrongly marked as RO (they are - // WO) - if self.first_run { - // Set SHA_START_REG - unsafe { - self.sha.start.as_ptr().write_volatile(1u32); - } - self.first_run = false; - } else { - // SET SHA_CONTINUE_REG - unsafe { - self.sha.continue_.as_ptr().write_volatile(1u32); - } - } - } - - #[cfg(esp32)] - fn process_buffer(&mut self) { - if self.first_run { - match self.mode { - ShaMode::SHA1 => self.sha.sha1_start.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA256 => self.sha.sha256_start.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA384 => self.sha.sha384_start.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA512 => self.sha.sha512_start.write(|w| unsafe { w.bits(1) }), - } - self.first_run = false; - } else { - match self.mode { - ShaMode::SHA1 => self.sha.sha1_continue.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA256 => self.sha.sha256_continue.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA384 => self.sha.sha384_continue.write(|w| unsafe { w.bits(1) }), - ShaMode::SHA512 => self.sha.sha512_continue.write(|w| unsafe { w.bits(1) }), - } - } - } - - fn chunk_length(&self) -> usize { - return match self.mode { - ShaMode::SHA1 | ShaMode::SHA256 => 64, - #[cfg(not(esp32))] - ShaMode::SHA224 => 64, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - _ => 128, - }; - } - - #[cfg(esp32)] - fn is_busy(&self) -> bool { - match self.mode { - ShaMode::SHA1 => self.sha.sha1_busy.read().sha1_busy().bit_is_set(), - ShaMode::SHA256 => self.sha.sha256_busy.read().sha256_busy().bit_is_set(), - ShaMode::SHA384 => self.sha.sha384_busy.read().sha384_busy().bit_is_set(), - ShaMode::SHA512 => self.sha.sha512_busy.read().sha512_busy().bit_is_set(), - } - } - - #[cfg(not(esp32))] - fn is_busy(&self) -> bool { - self.sha.busy.read().bits() != 0 - } - - pub fn digest_length(&self) -> usize { - match self.mode { - ShaMode::SHA1 => 20, - #[cfg(not(esp32))] - ShaMode::SHA224 => 28, - ShaMode::SHA256 => 32, - #[cfg(any(esp32, esp32s2, esp32s3))] - ShaMode::SHA384 => 48, - #[cfg(any(esp32, esp32s2, esp32s3))] - ShaMode::SHA512 => 64, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA512_224 => 28, - #[cfg(any(esp32s2, esp32s3))] - ShaMode::SHA512_256 => 32, - } - } - - #[cfg(not(esp32))] - fn input_ptr(&self) -> *mut u32 { - return self.sha.m_mem[0].as_ptr() as *mut u32; - } - - #[cfg(esp32)] - fn input_ptr(&self) -> *mut u32 { - return self.sha.text[0].as_ptr() as *mut u32; - } - - #[cfg(not(esp32))] - fn output_ptr(&self) -> *const u32 { - return self.sha.h_mem[0].as_ptr() as *const u32; - } - - #[cfg(esp32)] - fn output_ptr(&self) -> *const u32 { - return self.sha.text[0].as_ptr() as *const u32; - } - - fn flush_data(&mut self) -> nb::Result<(), Infallible> { - if self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - unsafe { - let dst_ptr = self - .input_ptr() - .add((self.cursor % self.chunk_length()) / ALIGN_SIZE); - let flushed = self.alignment_helper.flush_to(dst_ptr); - if flushed != 0 { - self.cursor = self.cursor.wrapping_add(ALIGN_SIZE - flushed); - if self.cursor % self.chunk_length() == 0 { - self.process_buffer(); - } - } - } - - Ok(()) - } - - // This function ensures that incoming data is aligned to u32 (due to issues - // with cpy_mem) - fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> { - let mod_cursor = self.cursor % self.chunk_length(); - - unsafe { - let ptr = self.input_ptr().add(mod_cursor / ALIGN_SIZE); - let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy( - ptr, - incoming, - self.chunk_length() - mod_cursor, - ); - self.cursor = self.cursor.wrapping_add(incoming.len() - remaining.len()); - if bound_reached { - self.process_buffer(); - } - - Ok(remaining) - } - } - - pub fn update<'a>(&mut self, buffer: &'a [u8]) -> nb::Result<&'a [u8], Infallible> { - if self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - self.finished = false; - - let remaining = self.write_data(buffer)?; - - Ok(remaining) - } - - // Finish of the calculation (if not alreaedy) and copy result to output - // After `finish()` is called `update()`s will contribute to a new hash which - // can be calculated again with `finish()`. - // - // Typically output is expected to be the size of digest_length(), but smaller - // inputs can be given to get a "short hash" - pub fn finish(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> { - // The main purpose of this function is to dynamically generate padding for the - // input. Padding: Append "1" bit, Pad zeros until 512/1024 filled - // then set the message length in the LSB (overwriting the padding) - // If not enough free space for length+1, add length at end of a new zero'd - // block - - if self.is_busy() { - return Err(nb::Error::WouldBlock); - } - - let chunk_len = self.chunk_length(); - - if !self.finished { - // Store message length for padding - let length = self.cursor * 8; - nb::block!(self.update(&[0x80]))?; // Append "1" bit - nb::block!(self.flush_data())?; // Flush partial data, ensures aligned cursor - debug_assert!(self.cursor % 4 == 0); - - let mod_cursor = self.cursor % chunk_len; - if chunk_len - mod_cursor < chunk_len / 8 { - // Zero out remaining data if buffer is almost full (>=448/896), and process - // buffer - let pad_len = chunk_len - mod_cursor; - unsafe { - let m_cursor_ptr = self.input_ptr().add(mod_cursor / ALIGN_SIZE); - self.alignment_helper.volatile_write_bytes( - m_cursor_ptr, - 0, - pad_len / ALIGN_SIZE, - ); - } - self.process_buffer(); - self.cursor = self.cursor.wrapping_add(pad_len); - - // Spin-wait for finish - while self.is_busy() {} - } - - let mod_cursor = self.cursor % chunk_len; // Should be zero if branched above - unsafe { - let m_cursor_ptr = self.input_ptr(); - // Pad zeros - let pad_ptr = m_cursor_ptr.add(mod_cursor / ALIGN_SIZE); - let pad_len = (chunk_len - mod_cursor) - ALIGN_SIZE; - - self.alignment_helper - .volatile_write_bytes(pad_ptr, 0, pad_len / ALIGN_SIZE); - - // Write length (BE) to end - // NOTE: aligned_volatile_copy does not work here - // The decompiler suggest volatile_copy_memory/write_volatile is optimized to a - // simple *v = *pv; While the aligned_volatile_copy makes an - // actual call to memcpy, why this makes a difference when - // memcpy does works in other places, I don't know - let end_ptr = m_cursor_ptr.add((chunk_len / ALIGN_SIZE) - 1); - #[cfg(not(esp32))] - end_ptr.write_volatile(length.to_be() as u32); - #[cfg(esp32)] - end_ptr.write_volatile(length.to_le() as u32); - } - - self.process_buffer(); - // Spin-wait for final buffer to be processed - while self.is_busy() {} - - // ESP32 requires additional load to retrieve output - #[cfg(esp32)] - { - match self.mode { - ShaMode::SHA1 => unsafe { self.sha.sha1_load.write(|w| w.bits(1)) }, - ShaMode::SHA256 => unsafe { self.sha.sha256_load.write(|w| w.bits(1)) }, - ShaMode::SHA384 => unsafe { self.sha.sha384_load.write(|w| w.bits(1)) }, - ShaMode::SHA512 => unsafe { self.sha.sha512_load.write(|w| w.bits(1)) }, - } - - // Spin wait for result, 8-20 clock cycles according to manual - while self.is_busy() {} - } - - self.finished = true; - } - - unsafe { - let digest_ptr = self.output_ptr(); - let out_ptr = output.as_mut_ptr() as *mut u32; - let digest_out = core::cmp::min(self.digest_length(), output.len()) / ALIGN_SIZE; - for i in 0..digest_out { - #[cfg(not(esp32))] - out_ptr.add(i).write(*digest_ptr.add(i)); - // ESP32 does reversed order - #[cfg(esp32)] - out_ptr.add(i).write((*digest_ptr.add(i)).to_be()); - } - } - - Ok(()) - } -} diff --git a/esp-hal-common/src/soc/esp32/cpu_control.rs b/esp-hal-common/src/soc/esp32/cpu_control.rs deleted file mode 100644 index a23d89f78d1..00000000000 --- a/esp-hal-common/src/soc/esp32/cpu_control.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Control CPU Cores - -use core::marker::PhantomData; - -use xtensa_lx::set_stack_pointer; - -use crate::Cpu; - -static mut START_CORE1_FUNCTION: Option<&'static mut (dyn FnMut() + 'static)> = None; - -/// Will park the APP (second) core when dropped -#[must_use] -pub struct AppCoreGuard<'a> { - phantom: PhantomData<&'a ()>, -} - -impl<'a> Drop for AppCoreGuard<'a> { - fn drop(&mut self) { - unsafe { - internal_park_core(Cpu::AppCpu); - } - } -} - -#[derive(Debug)] -pub enum Error { - CoreAlreadyRunning, -} - -/// Control CPU Cores -pub struct CpuControl { - _cpu_control: crate::system::CpuControl, -} - -unsafe fn internal_park_core(core: Cpu) { - let rtc_control = crate::peripherals::RTC_CNTL::PTR; - let rtc_control = &*rtc_control; - - match core { - Cpu::ProCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| w.sw_stall_procpu_c1().bits(0x21)); - rtc_control - .options0 - .modify(|_, w| w.sw_stall_procpu_c0().bits(0x02)); - } - Cpu::AppCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| w.sw_stall_appcpu_c1().bits(0x21)); - rtc_control - .options0 - .modify(|_, w| w.sw_stall_appcpu_c0().bits(0x02)); - } - } -} - -impl CpuControl { - pub fn new(cpu_control: crate::system::CpuControl) -> CpuControl { - CpuControl { - _cpu_control: cpu_control, - } - } - - /// Park the given core - pub unsafe fn park_core(&mut self, core: Cpu) { - internal_park_core(core); - } - - /// Unpark the given core - pub fn unpark_core(&mut self, core: Cpu) { - let rtc_control = crate::peripherals::RTC_CNTL::PTR; - let rtc_control = unsafe { &*rtc_control }; - - match core { - Cpu::ProCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| unsafe { w.sw_stall_procpu_c1().bits(0) }); - rtc_control - .options0 - .modify(|_, w| unsafe { w.sw_stall_procpu_c0().bits(0) }); - } - Cpu::AppCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| unsafe { w.sw_stall_appcpu_c1().bits(0) }); - rtc_control - .options0 - .modify(|_, w| unsafe { w.sw_stall_appcpu_c0().bits(0) }); - } - } - } - - fn flush_cache(&mut self, core: Cpu) { - let dport_control = crate::peripherals::DPORT::PTR; - let dport_control = unsafe { &*dport_control }; - - match core { - Cpu::ProCpu => { - dport_control - .pro_cache_ctrl - .modify(|_, w| w.pro_cache_flush_ena().clear_bit()); - dport_control - .pro_cache_ctrl - .modify(|_, w| w.pro_cache_flush_ena().set_bit()); - while dport_control - .pro_cache_ctrl - .read() - .pro_cache_flush_done() - .bit_is_clear() - {} - - dport_control - .pro_cache_ctrl - .modify(|_, w| w.pro_cache_flush_ena().clear_bit()); - } - Cpu::AppCpu => { - dport_control - .app_cache_ctrl - .modify(|_, w| w.app_cache_flush_ena().clear_bit()); - dport_control - .app_cache_ctrl - .modify(|_, w| w.app_cache_flush_ena().set_bit()); - while dport_control - .app_cache_ctrl - .read() - .app_cache_flush_done() - .bit_is_clear() - {} - dport_control - .app_cache_ctrl - .modify(|_, w| w.app_cache_flush_ena().clear_bit()); - } - }; - } - - fn enable_cache(&mut self, core: Cpu) { - let spi0 = unsafe { &(*crate::peripherals::SPI0::ptr()) }; - - let dport_control = crate::peripherals::DPORT::PTR; - let dport_control = unsafe { &*dport_control }; - - match core { - Cpu::ProCpu => { - spi0.cache_fctrl.modify(|_, w| w.cache_req_en().set_bit()); - dport_control - .pro_cache_ctrl - .modify(|_, w| w.pro_cache_enable().set_bit()); - } - Cpu::AppCpu => { - spi0.cache_fctrl.modify(|_, w| w.cache_req_en().set_bit()); - dport_control - .app_cache_ctrl - .modify(|_, w| w.app_cache_enable().set_bit()); - } - }; - } - - unsafe fn start_core1_init() -> ! { - extern "C" { - static mut _stack_end_cpu1: u32; - } - - // disables interrupts - xtensa_lx::interrupt::set_mask(0); - - // reset cycle compare registers - xtensa_lx::timer::set_ccompare0(0); - xtensa_lx::timer::set_ccompare1(0); - xtensa_lx::timer::set_ccompare2(0); - - // set stack pointer to end of memory: no need to retain stack up to this point - set_stack_pointer(&mut _stack_end_cpu1); - - match START_CORE1_FUNCTION.take() { - Some(entry) => (*entry)(), - None => panic!("No start function set"), - } - - panic!("Return from second core's entry"); - } - - /// Start the APP (second) core - /// - /// The second core will start running the closure `entry`. - /// - /// Dropping the returned guard will park the core. - pub fn start_app_core<'a>( - &mut self, - entry: &'a mut (dyn FnMut() + Send), - ) -> Result, Error> { - let dport_control = crate::peripherals::DPORT::PTR; - let dport_control = unsafe { &*dport_control }; - - if !xtensa_lx::is_debugger_attached() - && dport_control - .appcpu_ctrl_b - .read() - .appcpu_clkgate_en() - .bit_is_set() - { - return Err(Error::CoreAlreadyRunning); - } - - self.flush_cache(Cpu::AppCpu); - self.enable_cache(Cpu::AppCpu); - - unsafe { - let entry_fn: &'static mut (dyn FnMut() + 'static) = core::mem::transmute(entry); - START_CORE1_FUNCTION = Some(entry_fn); - } - - dport_control.appcpu_ctrl_d.write(|w| unsafe { - w.appcpu_boot_addr() - .bits(Self::start_core1_init as *const u32 as u32) - }); - - dport_control - .appcpu_ctrl_b - .modify(|_, w| w.appcpu_clkgate_en().set_bit()); - dport_control - .appcpu_ctrl_c - .modify(|_, w| w.appcpu_runstall().clear_bit()); - dport_control - .appcpu_ctrl_a - .modify(|_, w| w.appcpu_resetting().set_bit()); - dport_control - .appcpu_ctrl_a - .modify(|_, w| w.appcpu_resetting().clear_bit()); - - self.unpark_core(Cpu::AppCpu); - - Ok(AppCoreGuard { - phantom: PhantomData::default(), - }) - } -} diff --git a/esp-hal-common/src/soc/esp32/efuse.rs b/esp-hal-common/src/soc/esp32/efuse.rs deleted file mode 100644 index 4461b9b09a1..00000000000 --- a/esp-hal-common/src/soc/esp32/efuse.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Reading of eFuses - -use fugit::{HertzU32, RateExtU32}; - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum ChipType { - Esp32D0wdq6, - Esp32D0wdq5, - Esp32D2wdq5, - Esp32Picod2, - Esp32Picod4, - Unknown, -} - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.blk0_rdata1.read().rd_wifi_mac_crc_low().bits(); - let mac_high: u32 = efuse.blk0_rdata2.read().rd_wifi_mac_crc_high().bits(); - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[2], - mac_high_bytes[3], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Returns the number of CPUs available on the chip. - /// - /// While ESP32 chips usually come with two mostly equivalent CPUs (protocol - /// CPU and application CPU), the application CPU is unavailable on - /// some. - pub fn get_core_count() -> u32 { - let efuse = unsafe { &*EFUSE::ptr() }; - - let cpu_disabled = efuse.blk0_rdata3.read().rd_chip_ver_dis_app_cpu().bit(); - if cpu_disabled { - 1 - } else { - 2 - } - } - - /// Returns the maximum rated clock of the CPU in MHz. - /// - /// Note that the actual clock may be lower, depending on the current power - /// configuration of the chip, clock source, and other settings. - pub fn get_max_cpu_frequency() -> HertzU32 { - let efuse = unsafe { &*EFUSE::ptr() }; - - let has_rating = efuse.blk0_rdata3.read().rd_chip_cpu_freq_rated().bit(); - let has_low_rating = efuse.blk0_rdata3.read().rd_chip_cpu_freq_low().bit(); - - if has_rating && has_low_rating { - 160u32.MHz() - } else { - 240u32.MHz() - } - } - - /// Returns the CHIP_VER_DIS_BT eFuse value. - pub fn is_bluetooth_enabled() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - - !efuse.blk0_rdata3.read().rd_chip_ver_dis_bt().bit() - } - - /// Returns the CHIP_VER_PKG eFuse value. - pub fn get_chip_type() -> ChipType { - let efuse = unsafe { &*EFUSE::ptr() }; - - match efuse.blk0_rdata3.read().rd_chip_ver_pkg().bits() { - 0 => ChipType::Esp32D0wdq6, - 1 => ChipType::Esp32D0wdq5, - 2 => ChipType::Esp32D2wdq5, - 4 => ChipType::Esp32Picod2, - 5 => ChipType::Esp32Picod4, - _ => ChipType::Unknown, - } - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .blk0_rdata0 - .read() - .rd_flash_crypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } -} diff --git a/esp-hal-common/src/soc/esp32/gpio.rs b/esp-hal-common/src/soc/esp32/gpio.rs deleted file mode 100644 index f5b2aba18a4..00000000000 --- a/esp-hal-common/src/soc/esp32/gpio.rs +++ /dev/null @@ -1,718 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - Bank1GpioRegisterAccess, - GpioPin, - InputOnlyAnalogPinType, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 39; - -pub type OutputSignalType = u16; -pub const OUTPUT_SIGNAL_MAX: u16 = 548; -pub const INPUT_SIGNAL_MAX: u16 = 539; - -pub const ONE_INPUT: u8 = 0x38; -pub const ZERO_INPUT: u8 = 0x30; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function2; - -pub(crate) fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO0 { - unsafe { - let iomux = &*crate::peripherals::IO_MUX::PTR; - - match gpio_num { - 0 => core::mem::transmute(&(iomux.gpio0)), - 1 => core::mem::transmute(&(iomux.gpio1)), - 2 => core::mem::transmute(&(iomux.gpio2)), - 3 => core::mem::transmute(&(iomux.gpio3)), - 4 => core::mem::transmute(&(iomux.gpio4)), - 5 => core::mem::transmute(&(iomux.gpio5)), - 6 => core::mem::transmute(&(iomux.gpio6)), - 7 => core::mem::transmute(&(iomux.gpio7)), - 8 => core::mem::transmute(&(iomux.gpio8)), - 9 => core::mem::transmute(&(iomux.gpio9)), - 10 => core::mem::transmute(&(iomux.gpio10)), - 11 => core::mem::transmute(&(iomux.gpio11)), - 12 => core::mem::transmute(&(iomux.gpio12)), - 13 => core::mem::transmute(&(iomux.gpio13)), - 14 => core::mem::transmute(&(iomux.gpio14)), - 15 => core::mem::transmute(&(iomux.gpio15)), - 16 => core::mem::transmute(&(iomux.gpio16)), - 17 => core::mem::transmute(&(iomux.gpio17)), - 18 => core::mem::transmute(&(iomux.gpio18)), - 19 => core::mem::transmute(&(iomux.gpio19)), - 20 => core::mem::transmute(&(iomux.gpio20)), - 21 => core::mem::transmute(&(iomux.gpio21)), - 22 => core::mem::transmute(&(iomux.gpio22)), - 23 => core::mem::transmute(&(iomux.gpio23)), - 24 => core::mem::transmute(&(iomux.gpio24)), - 25 => core::mem::transmute(&(iomux.gpio25)), - 26 => core::mem::transmute(&(iomux.gpio26)), - 27 => core::mem::transmute(&(iomux.gpio27)), - 32 => core::mem::transmute(&(iomux.gpio32)), - 33 => core::mem::transmute(&(iomux.gpio33)), - 34 => core::mem::transmute(&(iomux.gpio34)), - 35 => core::mem::transmute(&(iomux.gpio35)), - 36 => core::mem::transmute(&(iomux.gpio36)), - 37 => core::mem::transmute(&(iomux.gpio37)), - 38 => core::mem::transmute(&(iomux.gpio38)), - 39 => core::mem::transmute(&(iomux.gpio39)), - _ => panic!(), - } - } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 - | ((nmi_enable as u8) << 1) - | (int_enable as u8) << 2 - | ((nmi_enable as u8) << 3) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum InputSignal { - SPICLK = 0, - SPIQ = 1, - SPID = 2, - SPIHD = 3, - SPIWP = 4, - SPICS0 = 5, - SPICS1 = 6, - SPICS2 = 7, - HSPICLK = 8, - HSPIQ = 9, - HSPID = 10, - HSPICS0 = 11, - HSPIHD = 12, - HSPIWP = 13, - U0RXD = 14, - U0CTS = 15, - U0DSR = 16, - U1RXD = 17, - U1CTS = 18, - I2CM_SDA = 20, - EXT_I2C_SDA = 22, - I2S0O_BCK = 23, - I2S1O_BCK = 24, - I2S0O_WS = 25, - I2S1O_WS = 26, - I2S0I_BCK = 27, - I2S0I_WS = 28, - I2CEXT0_SCL = 29, - I2CEXT0_SDA = 30, - PWM0_SYNC0 = 31, - PWM0_SYNC1 = 32, - PWM0_SYNC2 = 33, - PWM0_F0 = 34, - PWM0_F1 = 35, - PWM0_F2 = 36, - GPIO_BT_ACTIVE = 37, - GPIO_BT_PRIORITY = 38, - PCNT0_SIG_CH0 = 39, - PCNT0_SIG_CH1 = 40, - PCNT0_CTRL_CH0 = 41, - PCNT0_CTRL_CH1 = 42, - PCNT1_SIG_CH0 = 43, - PCNT1_SIG_CH1 = 44, - PCNT1_CTRL_CH0 = 45, - PCNT1_CTRL_CH1 = 46, - PCNT2_SIG_CH0 = 47, - PCNT2_SIG_CH1 = 48, - PCNT2_CTRL_CH0 = 49, - PCNT2_CTRL_CH1 = 50, - PCNT3_SIG_CH0 = 51, - PCNT3_SIG_CH1 = 52, - PCNT3_CTRL_CH0 = 53, - PCNT3_CTRL_CH1 = 54, - PCNT4_SIG_CH0 = 55, - PCNT4_SIG_CH1 = 56, - PCNT4_CTRL_CH0 = 57, - PCNT4_CTRL_CH1 = 58, - HSPICS1 = 61, - HSPICS2 = 62, - VSPICLK = 63, - VSPIQ = 64, - VSPID = 65, - VSPIHD = 66, - VSPIWP = 67, - VSPICS0 = 68, - VSPICS1 = 69, - VSPICS2 = 70, - PCNT5_SIG_CH0 = 71, - PCNT5_SIG_CH1 = 72, - PCNT5_CTRL_CH0 = 73, - PCNT5_CTRL_CH1 = 74, - PCNT6_SIG_CH0 = 75, - PCNT6_SIG_CH1 = 76, - PCNT6_CTRL_CH0 = 77, - PCNT6_CTRL_CH1 = 78, - PCNT7_SIG_CH0 = 79, - PCNT7_SIG_CH1 = 80, - PCNT7_CTRL_CH0 = 81, - PCNT7_CTRL_CH1 = 82, - RMT_SIG_0 = 83, - RMT_SIG_1 = 84, - RMT_SIG_2 = 85, - RMT_SIG_3 = 86, - RMT_SIG_4 = 87, - RMT_SIG_5 = 88, - RMT_SIG_6 = 89, - RMT_SIG_7 = 90, - EXT_ADC_START = 93, - CAN_RX = 94, - I2CEXT1_SCL = 95, - I2CEXT1_SDA = 96, - HOST_CARD_DETECT_N_1 = 97, - HOST_CARD_DETECT_N_2 = 98, - HOST_CARD_WRITE_PRT_1 = 99, - HOST_CARD_WRITE_PRT_2 = 100, - HOST_CARD_INT_N_1 = 101, - HOST_CARD_INT_N_2 = 102, - PWM1_SYNC0 = 103, - PWM1_SYNC1 = 104, - PWM1_SYNC2 = 105, - PWM1_F0 = 106, - PWM1_F1 = 107, - PWM1_F2 = 108, - PWM0_CAP0 = 109, - PWM0_CAP1 = 110, - PWM0_CAP2 = 111, - PWM1_CAP0 = 112, - PWM1_CAP1 = 113, - PWM1_CAP2 = 114, - PWM2_FLTA = 115, - PWM2_FLTB = 116, - PWM2_CAP1 = 117, - PWM2_CAP2 = 118, - PWM2_CAP3 = 119, - PWM3_FLTA = 120, - PWM3_FLTB = 121, - PWM3_CAP1 = 122, - PWM3_CAP2 = 123, - PWM3_CAP3 = 124, - CAN_CLKOUT = 125, - SPID4 = 128, - SPID5 = 129, - SPID6 = 130, - SPID7 = 131, - HSPID4 = 132, - HSPID5 = 133, - HSPID6 = 134, - HSPID7 = 135, - VSPID4 = 136, - VSPID5 = 137, - VSPID6 = 138, - VSPID7 = 139, - I2S0I_DATA_0 = 140, - I2S0I_DATA_1 = 141, - I2S0I_DATA_2 = 142, - I2S0I_DATA_3 = 143, - I2S0I_DATA_4 = 144, - I2S0I_DATA_5 = 145, - I2S0I_DATA_6 = 146, - I2S0I_DATA_7 = 147, - I2S0I_DATA_8 = 148, - I2S0I_DATA_9 = 149, - I2S0I_DATA_10 = 150, - I2S0I_DATA_11 = 151, - I2S0I_DATA_12 = 152, - I2S0I_DATA_13 = 153, - I2S0I_DATA_14 = 154, - I2S0I_DATA_15 = 155, - I2S1I_BCK = 164, - I2S1I_WS = 165, - I2S1I_DATA_0 = 166, - I2S1I_DATA_1 = 167, - I2S1I_DATA_2 = 168, - I2S1I_DATA_3 = 169, - I2S1I_DATA_4 = 170, - I2S1I_DATA_5 = 171, - I2S1I_DATA_6 = 172, - I2S1I_DATA_7 = 173, - I2S1I_DATA_8 = 174, - I2S1I_DATA_9 = 175, - I2S1I_DATA_10 = 176, - I2S1I_DATA_11 = 177, - I2S1I_DATA_12 = 178, - I2S1I_DATA_13 = 179, - I2S1I_DATA_14 = 180, - I2S1I_DATA_15 = 181, - I2S0I_H_SYNC = 190, - I2S0I_V_SYNC = 191, - I2S0I_H_ENABLE = 192, - I2S1I_H_SYNC = 193, - I2S1I_V_SYNC = 194, - I2S1I_H_ENABLE = 195, - U2RXD = 198, - U2CTS = 199, - EMAC_MDC = 200, - EMAC_MDI = 201, - EMAC_CRS = 202, - EMAC_COL = 203, - PCMFSYNC = 204, - PCMCLK = 205, - PCMDIN = 206, - SIG_IN_FUNC224 = 224, - SIG_IN_FUNC225 = 225, - SIG_IN_FUNC226 = 226, - SIG_IN_FUNC227 = 227, - SIG_IN_FUNC228 = 228, - - SD_DATA0 = 512, - SD_DATA1, - SD_DATA2, - SD_DATA3, - HS1_DATA0, - HS1_DATA1, - HS1_DATA2, - HS1_DATA3, - HS1_DATA4, - HS1_DATA5, - HS1_DATA6, - HS1_DATA7, - HS2_DATA0, - HS2_DATA1, - HS2_DATA2, - HS2_DATA3, - - EMAC_TX_CLK, - EMAC_RXD2, - EMAC_TX_ER, - EMAC_RX_CLK, - EMAC_RX_ER, - EMAC_RXD3, - EMAC_RXD0, - EMAC_RXD1, - EMAC_RX_DV, - - MTDI, - MTCK, - MTMS, -} - -/// Peripheral output signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum OutputSignal { - SPICLK = 0, - SPIQ = 1, - SPID = 2, - SPIHD = 3, - SPIWP = 4, - SPICS0 = 5, - SPICS1 = 6, - SPICS2 = 7, - HSPICLK = 8, - HSPIQ = 9, - HSPID = 10, - HSPICS0 = 11, - HSPIHD = 12, - HSPIWP = 13, - U0TXD = 14, - U0RTS = 15, - U0DTR = 16, - U1TXD = 17, - U1RTS = 18, - I2CM_SCL = 19, - I2CM_SDA = 20, - EXT2C_SCL = 21, - EXT2C_SDA = 22, - I2S0O_BCK = 23, - I2S1O_BCK = 24, - I2S0O_WS = 25, - I2S1O_WS = 26, - I2S0I_BCK = 27, - I2S0I_WS = 28, - I2CEXT0_SCL = 29, - I2CEXT0_SDA = 30, - SDIO_TOHOSTT = 31, - PWM0_0A = 32, - PWM0_0B = 33, - PWM0_1A = 34, - PWM0_1B = 35, - PWM0_2A = 36, - PWM0_2B = 37, - GPIO_WLAN_ACTIVE = 40, - BB_DIAG0 = 41, - BB_DIAG1 = 42, - BB_DIAG2 = 43, - BB_DIAG3 = 44, - BB_DIAG4 = 45, - BB_DIAG5 = 46, - BB_DIAG6 = 47, - BB_DIAG7 = 48, - BB_DIAG8 = 49, - BB_DIAG9 = 50, - BB_DIAG10 = 51, - BB_DIAG11 = 52, - BB_DIAG12 = 53, - BB_DIAG13 = 54, - BB_DIAG14 = 55, - BB_DIAG15 = 56, - BB_DIAG16 = 57, - BB_DIAG17 = 58, - BB_DIAG18 = 59, - BB_DIAG19 = 60, - HSPICS1 = 61, - HSPICS2 = 62, - VSPICLK = 63, - VSPIQ = 64, - VSPID = 65, - VSPIHD = 66, - VSPIWP = 67, - VSPICS0 = 68, - VSPICS1 = 69, - VSPICS2 = 70, - LEDC_HS_SIG0 = 71, - LEDC_HS_SIG1 = 72, - LEDC_HS_SIG2 = 73, - LEDC_HS_SIG3 = 74, - LEDC_HS_SIG4 = 75, - LEDC_HS_SIG5 = 76, - LEDC_HS_SIG6 = 77, - LEDC_HS_SIG7 = 78, - LEDC_LS_SIG0 = 79, - LEDC_LS_SIG1 = 80, - LEDC_LS_SIG2 = 81, - LEDC_LS_SIG3 = 82, - LEDC_LS_SIG4 = 83, - LEDC_LS_SIG5 = 84, - LEDC_LS_SIG6 = 85, - LEDC_LS_SIG7 = 86, - RMT_SIG_0 = 87, - RMT_SIG_1 = 88, - RMT_SIG_2 = 89, - RMT_SIG_3 = 90, - RMT_SIG_4 = 91, - RMT_SIG_5 = 92, - RMT_SIG_6 = 93, - RMT_SIG_7 = 94, - I2CEXT1_SCL = 95, - I2CEXT1_SDA = 96, - HOST_CCMD_OD_PULLUP_EN_N = 97, - HOST_RST_N_1 = 98, - HOST_RST_N_2 = 99, - GPIO_SD0 = 100, - GPIO_SD1 = 101, - GPIO_SD2 = 102, - GPIO_SD3 = 103, - GPIO_SD4 = 104, - GPIO_SD5 = 105, - GPIO_SD6 = 106, - GPIO_SD7 = 107, - PWM1_0A = 108, - PWM1_0B = 109, - PWM1_1A = 110, - PWM1_1B = 111, - PWM1_2A = 112, - PWM1_2B = 113, - PWM2_1H = 114, - PWM2_1L = 115, - PWM2_2H = 116, - PWM2_2L = 117, - PWM2_3H = 118, - PWM2_3L = 119, - PWM2_4H = 120, - PWM2_4L = 121, - CAN_TX = 123, - CAN_BUS_OFF_ON = 124, - SPID4 = 128, - SPID5 = 129, - SPID6 = 130, - SPID7 = 131, - HSPID4 = 132, - HSPID5 = 133, - HSPID6 = 134, - HSPID7 = 135, - VSPID4 = 136, - VSPID5 = 137, - VSPID6 = 138, - VSPID7 = 139, - I2S0O_DATA_0 = 140, - I2S0O_DATA_1 = 141, - I2S0O_DATA_2 = 142, - I2S0O_DATA_3 = 143, - I2S0O_DATA_4 = 144, - I2S0O_DATA_5 = 145, - I2S0O_DATA_6 = 146, - I2S0O_DATA_7 = 147, - I2S0O_DATA_8 = 148, - I2S0O_DATA_9 = 149, - I2S0O_DATA_10 = 150, - I2S0O_DATA_11 = 151, - I2S0O_DATA_12 = 152, - I2S0O_DATA_13 = 153, - I2S0O_DATA_14 = 154, - I2S0O_DATA_15 = 155, - I2S0O_DATA_16 = 156, - I2S0O_DATA_17 = 157, - I2S0O_DATA_18 = 158, - I2S0O_DATA_19 = 159, - I2S0O_DATA_20 = 160, - I2S0O_DATA_21 = 161, - I2S0O_DATA_22 = 162, - I2S0O_DATA_23 = 163, - I2S1I_BCK = 164, - I2S1I_WS = 165, - I2S1O_DATA_0 = 166, - I2S1O_DATA_1 = 167, - I2S1O_DATA_2 = 168, - I2S1O_DATA_3 = 169, - I2S1O_DATA_4 = 170, - I2S1O_DATA_5 = 171, - I2S1O_DATA_6 = 172, - I2S1O_DATA_7 = 173, - I2S1O_DATA_8 = 174, - I2S1O_DATA_9 = 175, - I2S1O_DATA_10 = 176, - I2S1O_DATA_11 = 177, - I2S1O_DATA_12 = 178, - I2S1O_DATA_13 = 179, - I2S1O_DATA_14 = 180, - I2S1O_DATA_15 = 181, - I2S1O_DATA_16 = 182, - I2S1O_DATA_17 = 183, - I2S1O_DATA_18 = 184, - I2S1O_DATA_19 = 185, - I2S1O_DATA_20 = 186, - I2S1O_DATA_21 = 187, - I2S1O_DATA_22 = 188, - I2S1O_DATA_23 = 189, - PWM3_1H = 190, - PWM3_1L = 191, - PWM3_2H = 192, - PWM3_2L = 193, - PWM3_3H = 194, - PWM3_3L = 195, - PWM3_4H = 196, - PWM3_4L = 197, - U2TXD = 198, - U2RTS = 199, - EMAC_MDC = 200, - EMAC_MDO = 201, - EMAC_CRS = 202, - EMAC_COL = 203, - BT_AUDIO0RQ = 204, - BT_AUDIO1RQ = 205, - BT_AUDIO2RQ = 206, - BLE_AUDIO0RQ = 207, - BLE_AUDIO1RQ = 208, - BLE_AUDIO2RQ = 209, - PCMFSYNC = 210, - PCMCLK = 211, - PCMDOUT = 212, - BLE_AUDIO_SYNC0_P = 213, - BLE_AUDIO_SYNC1_P = 214, - BLE_AUDIO_SYNC2_P = 215, - ANT_SEL0 = 216, - ANT_SEL1 = 217, - ANT_SEL2 = 218, - ANT_SEL3 = 219, - ANT_SEL4 = 220, - ANT_SEL5 = 221, - ANT_SEL6 = 222, - ANT_SEL7 = 223, - SIGNAL_224 = 224, - SIGNAL_225 = 225, - SIGNAL_226 = 226, - SIGNAL_227 = 227, - SIGNAL_228 = 228, - GPIO = 256, - - CLK_OUT1 = 512, - CLK_OUT2, - CLK_OUT3, - SD_CLK, - SD_CMD, - SD_DATA0, - SD_DATA1, - SD_DATA2, - SD_DATA3, - HS1_CLK, - HS1_CMD, - HS1_DATA0, - HS1_DATA1, - HS1_DATA2, - HS1_DATA3, - HS1_DATA4, - HS1_DATA5, - HS1_DATA6, - HS1_DATA7, - HS1_STROBE, - HS2_CLK, - HS2_CMD, - HS2_DATA0, - HS2_DATA1, - HS2_DATA2, - HS2_DATA3, - - EMAC_TX_CLK, - EMAC_TX_ER, - EMAC_TXD3, - EMAC_RX_ER, - EMAC_TXD2, - EMAC_CLK_OUT, - EMAC_CLK_180, - EMAC_TXD0, - EMAC_TX_EN, - EMAC_TXD1, - - MTDO, -} - -pub(crate) fn errata36(pin_num: u8, pull_up: bool, pull_down: bool) { - use crate::peripherals::RTC_IO; - let rtcio = unsafe { &*RTC_IO::PTR }; - - match pin_num { - 0 => { - rtcio - .touch_pad1 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 2 => { - rtcio - .touch_pad2 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 4 => { - rtcio - .touch_pad0 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 12 => { - rtcio - .touch_pad5 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 13 => { - rtcio - .touch_pad4 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 14 => { - rtcio - .touch_pad6 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 15 => { - rtcio - .touch_pad3 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 25 => { - rtcio.pad_dac1.modify(|r, w| unsafe { - w.bits(r.bits()) - .pdac1_rue() - .bit(pull_up) - .pdac1_rde() - .bit(pull_down) - }); - } - 26 => { - rtcio.pad_dac2.modify(|r, w| unsafe { - w.bits(r.bits()) - .pdac2_rue() - .bit(pull_up) - .pdac2_rde() - .bit(pull_down) - }); - } - 27 => { - rtcio - .touch_pad7 - .modify(|r, w| unsafe { w.bits(r.bits()).rue().bit(pull_up).rde().bit(pull_down) }); - } - 32 => { - rtcio.xtal_32k_pad.modify(|r, w| unsafe { - w.bits(r.bits()) - .x32n_rue() - .bit(pull_up) - .x32n_rde() - .bit(pull_down) - }); - } - 33 => { - rtcio.xtal_32k_pad.modify(|r, w| unsafe { - w.bits(r.bits()) - .x32p_rue() - .bit(pull_up) - .x32p_rde() - .bit(pull_down) - }); - } - _ => (), - } -} - -crate::gpio::gpio! { - Dual, - (0, 0, InputOutputAnalog (5 => EMAC_TX_CLK) (1 => CLK_OUT1)) - (1, 0, InputOutput (5 => EMAC_RXD2) (0 => U0TXD 1 => CLK_OUT3)) - (2, 0, InputOutputAnalog (1 => HSPIWP 3 => HS2_DATA0 4 => SD_DATA0) (3 => HS2_DATA0 4 => SD_DATA0)) - (3, 0, InputOutput (0 => U0RXD) (1 => CLK_OUT2)) - (4, 0, InputOutput (1 => HSPIHD 3 => HS2_DATA1 4 => SD_DATA1 5 => EMAC_TX_ER) (3 => HS2_DATA1 4 => SD_DATA1)) - (5, 0, InputOutput (1 => VSPICS0 3 => HS1_DATA6 5 => EMAC_RX_CLK) (3 => HS1_DATA6)) - (6, 0, InputOutput (4 => U1CTS) (0 => SD_CLK 1 => SPICLK 3 => HS1_CLK)) - (7, 0, InputOutput (0 => SD_DATA0 1 => SPIQ 3 => HS1_DATA0) (0 => SD_DATA0 1 => SPIQ 3 => HS1_DATA0 4 => U2RTS)) - (8, 0, InputOutput (0 => SD_DATA1 1 => SPID 3 => HS1_DATA1 4 => U2CTS) (0 => SD_DATA1 1 => SPID 3 => HS1_DATA1)) - (9, 0, InputOutput (0 => SD_DATA2 1 => SPIHD 3 => HS1_DATA2 4 => U1RXD) (0 => SD_DATA2 1 => SPIHD 3 => HS1_DATA2)) - (10, 0, InputOutput ( 0 => SD_DATA3 1 => SPIWP 3 => HS1_DATA3) (0 => SD_DATA3 1 => SPIWP 3 => HS1_DATA3 4 => U1TXD)) - (11, 0, InputOutput ( 1 => SPICS0) (0 => SD_CMD 1 => SPICS0 3 => HS1_CMD 4 => U1RTS)) - (12, 0, InputOutputAnalog (0 => MTDI 1 => HSPIQ 3 => HS2_DATA2 4 => SD_DATA2) (1 => HSPIQ 3 => HS2_DATA2 4 => SD_DATA2 5 => EMAC_TXD3)) - (13, 0, InputOutputAnalog (0 => MTCK 1 => HSPID 3 => HS2_DATA3 4 => SD_DATA3) (1 => HSPID 3 => HS2_DATA3 4 => SD_DATA3 5 => EMAC_RX_ER)) - (14, 0, InputOutputAnalog (0 => MTMS 1 => HSPICLK) (1 => HSPICLK 3 => HS2_CLK 4 => SD_CLK 5 => EMAC_TXD2)) - (15, 0, InputOutputAnalog (1 => HSPICS0 5 => EMAC_RXD3) (0 => MTDO 1 => HSPICS0 3 => HS2_CMD 4 => SD_CMD)) - (16, 0, InputOutput (3 => HS1_DATA4 4 => U2RXD) (3 => HS1_DATA4 5 => EMAC_CLK_OUT)) - (17, 0, InputOutput (3 => HS1_DATA5) (3 => HS1_DATA5 4 => U2TXD 5 => EMAC_CLK_180)) - (18, 0, InputOutput (1 => VSPICLK 3 => HS1_DATA7) (1 => VSPICLK 3 => HS1_DATA7)) - (19, 0, InputOutput (1 => VSPIQ 3 => U0CTS) (1 => VSPIQ 5 => EMAC_TXD0)) - (20, 0, InputOutput) - (21, 0, InputOutput (1 => VSPIHD) (1 => VSPIHD 5 => EMAC_TX_EN)) - (22, 0, InputOutput (1 => VSPIWP) (1 => VSPIWP 3 => U0RTS 5 => EMAC_TXD1)) - (23, 0, InputOutput (1 => VSPID) (1 => VSPID 3 => HS1_STROBE)) - (24, 0, InputOutput) - (25, 0, InputOutputAnalog (5 => EMAC_RXD0) ()) - (26, 0, InputOutputAnalog (5 => EMAC_RXD1) ()) - (27, 0, InputOutputAnalog (5 => EMAC_RX_DV) ()) - (32, 1, InputOutputAnalog) - (33, 1, InputOutputAnalog) - (34, 1, InputOnlyAnalog) - (35, 1, InputOnlyAnalog) - (36, 1, InputOnlyAnalog) - (37, 1, InputOnlyAnalog) - (38, 1, InputOnlyAnalog) - (39, 1, InputOnlyAnalog) -} - -crate::gpio::analog! { - (36, 0, sensor_pads, sense1_mux_sel, sense1_fun_sel, sense1_fun_ie) - (37, 1, sensor_pads, sense2_mux_sel, sense2_fun_sel, sense2_fun_ie) - (38, 2, sensor_pads, sense3_mux_sel, sense3_fun_sel, sense3_fun_ie) - (39, 3, sensor_pads, sense4_mux_sel, sense4_fun_sel, sense4_fun_ie) - (34, 4, adc_pad, adc1_mux_sel, adc1_fun_sel, adc1_fun_ie) - (35, 5, adc_pad, adc2_mux_sel, adc2_fun_sel, adc1_fun_ie) - (25, 6, pad_dac1, pdac1_mux_sel, pdac1_fun_sel, pdac1_fun_ie, pdac1_rue, pdac1_rde) - (26, 7, pad_dac2, pdac2_mux_sel, pdac2_fun_sel, pdac2_fun_ie, pdac2_rue, pdac2_rde) - (33, 8, xtal_32k_pad, x32n_mux_sel, x32n_fun_sel, x32n_fun_ie, x32n_rue, x32n_rde ) - (32, 9, xtal_32k_pad, x32p_mux_sel, x32p_fun_sel, x32p_fun_ie, x32p_rue, x32p_rde ) - (4, 10, touch_pad0, mux_sel, fun_sel, fun_ie, rue, rde ) - (0, 11, touch_pad1, mux_sel, fun_sel, fun_ie, rue, rde ) - (2, 12, touch_pad2, mux_sel, fun_sel, fun_ie, rue, rde ) - (15, 13, touch_pad3, mux_sel, fun_sel, fun_ie, rue, rde ) - (13, 14, touch_pad4, mux_sel, fun_sel, fun_ie, rue, rde ) - (12, 15, touch_pad5, mux_sel, fun_sel, fun_ie, rue, rde ) - (14, 16, touch_pad6, mux_sel, fun_sel, fun_ie, rue, rde ) - (27, 17, touch_pad7, mux_sel, fun_sel, fun_ie, rue, rde ) -} diff --git a/esp-hal-common/src/soc/esp32/mod.rs b/esp-hal-common/src/soc/esp32/mod.rs deleted file mode 100644 index 108ee6ad3df..00000000000 --- a/esp-hal-common/src/soc/esp32/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod cpu_control; -pub mod efuse; -pub mod gpio; -pub mod peripherals; -pub mod radio_clocks; diff --git a/esp-hal-common/src/soc/esp32/peripherals.rs b/esp-hal-common/src/soc/esp32/peripherals.rs deleted file mode 100644 index d711311ef77..00000000000 --- a/esp-hal-common/src/soc/esp32/peripherals.rs +++ /dev/null @@ -1,53 +0,0 @@ -use esp32 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - AES => true, - APB_CTRL => true, - BB => true, - DPORT => true, - EFUSE => true, - FLASH_ENCRYPTION => true, - FRC_TIMER => true, - GPIO => true, - GPIO_SD => true, - HINF => true, - I2C0 => true, - I2C1 => true, - I2S0 => true, - I2S1 => true, - IO_MUX => true, - LEDC => true, - MCPWM0 => true, - MCPWM1 => true, - NRX => true, - PCNT => true, - RMT => true, - RNG => true, - RSA => true, - RTC_CNTL => true, - RTC_IO => true, - RTC_I2C => true, - SDMMC => true, - SENS => true, - SHA => true, - SLC => true, - SLCHOST => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SPI3 => true, - TIMG0 => true, - TIMG1 => true, - TWAI0 => true, - UART0 => true, - UART1 => true, - UART2 => true, - UHCI0 => true, - UHCI1 => true, - RADIO => false -} diff --git a/esp-hal-common/src/soc/esp32/radio_clocks.rs b/esp-hal-common/src/soc/esp32/radio_clocks.rs deleted file mode 100644 index d25145c2820..00000000000 --- a/esp-hal-common/src/soc/esp32/radio_clocks.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -const SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M: u32 = 0x000003c9; -const SYSTEM_WIFI_CLK_EN: u32 = 0xFFFFFFFF; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_enable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_disable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - // nothing for this target - } - - fn reset_rpa(&mut self) { - // nothing for this target - } -} - -fn enable_phy() { - let system = unsafe { &*esp32::DPORT::PTR }; - system - .wifi_clk_en - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn disable_phy() { - let system = unsafe { &*esp32::DPORT::PTR }; - system - .wifi_clk_en - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn common_wifi_bt_clock_enable() { - let system = unsafe { &*esp32::DPORT::PTR }; - system - .perip_clk_en - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_EN) }); -} - -fn common_wifi_bt_clock_disable() { - let system = unsafe { &*esp32::DPORT::PTR }; - system - .perip_clk_en - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_EN) }); -} - -fn reset_mac() { - const SYSTEM_MAC_RST: u8 = 1 << 2; - let syscon = unsafe { &*esp32::DPORT::PTR }; - syscon - .core_rst_en - .modify(|r, w| unsafe { w.core_rst().bits(r.core_rst().bits() | SYSTEM_MAC_RST) }); - syscon - .core_rst_en - .modify(|r, w| unsafe { w.core_rst().bits(r.core_rst().bits() & !SYSTEM_MAC_RST) }); -} - -fn init_clocks() { - let syscon = unsafe { &*esp32::DPORT::PTR }; - syscon - .wifi_clk_en - .modify(|_, w| unsafe { w.bits(0xffffffff) }); -} diff --git a/esp-hal-common/src/soc/esp32c2/efuse.rs b/esp-hal-common/src/soc/esp32c2/efuse.rs deleted file mode 100644 index a0ca4d6f8fe..00000000000 --- a/esp-hal-common/src/soc/esp32c2/efuse.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Reading of eFuses - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.rd_blk2_data0.read().bits(); - let mac_high: u16 = efuse.rd_blk2_data1.read().mac_id_high().bits(); - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[0], - mac_high_bytes[1], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .rd_repeat_data0 - .read() - .spi_boot_encrypt_decrypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } - - /// Get the multiplier for the timeout value of the RWDT STAGE 0 register. - pub fn get_rwdt_multiplier() -> u8 { - let efuse = unsafe { &*EFUSE::ptr() }; - efuse.rd_repeat_data0.read().wdt_delay_sel().bits() - } -} diff --git a/esp-hal-common/src/soc/esp32c2/gpio.rs b/esp-hal-common/src/soc/esp32c2/gpio.rs deleted file mode 100644 index 02259dfbb96..00000000000 --- a/esp-hal-common/src/soc/esp32c2/gpio.rs +++ /dev/null @@ -1,165 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - GpioPin, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 20; - -pub type OutputSignalType = u8; -pub const OUTPUT_SIGNAL_MAX: u8 = 128; -pub const INPUT_SIGNAL_MAX: u8 = 100; - -pub const ONE_INPUT: u8 = 0x1e; -pub const ZERO_INPUT: u8 = 0x1f; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function1; - -pub(crate) const fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO { - unsafe { &(&*crate::peripherals::IO_MUX::PTR).gpio[gpio_num as usize] } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 | ((nmi_enable as u8) << 1) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq)] -pub enum InputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - U0RXD = 6, - U0CTS = 7, - U0DSR = 8, - U1RXD = 9, - U1CTS = 10, - U1DSR = 11, - CPU_GPIO_0 = 28, - CPU_GPIO_1 = 29, - CPU_GPIO_2 = 30, - CPU_GPIO_3 = 31, - CPU_GPIO_4 = 32, - CPU_GPIO_5 = 33, - CPU_GPIO_6 = 34, - CPU_GPIO_7 = 35, - EXT_ADC_START = 45, - RMT_SIG_0 = 51, - RMT_SIG_1 = 52, - I2CEXT0_SCL = 53, - I2CEXT0_SDA = 54, - FSPICLK = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - SIG_FUNC_97 = 97, - SIG_FUNC_98 = 98, - SIG_FUNC_99 = 99, - SIG_FUNC_100 = 100, -} - -/// Peripheral output signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq)] -pub enum OutputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPICLK_MUX = 4, - SPICS0 = 5, - U0TXD = 6, - U0RTS = 7, - U0DTR = 8, - U1TXD = 9, - U1RTS = 10, - U1DTR = 11, - SPIQ_MONITOR = 15, - SPID_MONITOR = 16, - SPIHD_MONITOR = 17, - SPIWP_MONITOR = 18, - SPICS1 = 19, - CPU_GPIO_0 = 28, - CPU_GPIO_1 = 29, - CPU_GPIO_2 = 30, - CPU_GPIO_3 = 31, - CPU_GPIO_4 = 32, - CPU_GPIO_5 = 33, - CPU_GPIO_6 = 34, - CPU_GPIO_7 = 35, - LEDC_LS_SIG0 = 45, - LEDC_LS_SIG1 = 46, - LEDC_LS_SIG2 = 47, - LEDC_LS_SIG3 = 48, - LEDC_LS_SIG4 = 49, - LEDC_LS_SIG5 = 50, - RMT_SIG_0 = 51, - RMT_SIG_1 = 52, - I2CEXT0_SCL = 53, - I2CEXT0_SDA = 54, - FSPICLK_MUX = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - FSPICS1 = 69, - FSPICS3 = 70, - FSPICS2 = 71, - FSPICS4 = 72, - FSPICS5 = 73, - ANT_SEL0 = 89, - ANT_SEL1 = 90, - ANT_SEL2 = 91, - ANT_SEL3 = 92, - ANT_SEL4 = 93, - ANT_SEL5 = 94, - ANT_SEL6 = 95, - ANT_SEL7 = 96, - SIG_FUNC_97 = 97, - SIG_FUNC_98 = 98, - SIG_FUNC_99 = 99, - SIG_FUNC_100 = 100, - CLK_OUT1 = 123, - CLK_OUT2 = 124, - CLK_OUT3 = 125, - GPIO = 128, -} - -crate::gpio::gpio! { - Single, - (0, 0, InputOutputAnalog) - (1, 0, InputOutputAnalog) - (2, 0, InputOutputAnalog (2 => FSPIQ) (2 => FSPIQ)) - (3, 0, InputOutputAnalog) - (4, 0, InputOutputAnalog (2 => FSPIHD) (2 => FSPIHD)) - (5, 0, InputOutput (2 => FSPIWP) (2 => FSPIWP)) - (6, 0, InputOutput (2 => FSPICLK) (2 => FSPICLK_MUX)) - (7, 0, InputOutput (2 => FSPID) (2 => FSPID)) - (8, 0, InputOutput) - (9, 0, InputOutput) - (10, 0, InputOutput (2 => FSPICS0) (2 => FSPICS0)) - (18, 0, InputOutput) - (19, 0, InputOutput) - (20, 0, InputOutput (0 => U0RXD) ()) -} - -crate::gpio::analog! { - 0 - 1 - 2 - 3 - 4 -} diff --git a/esp-hal-common/src/soc/esp32c2/mod.rs b/esp-hal-common/src/soc/esp32c2/mod.rs deleted file mode 100644 index 907b45c9302..00000000000 --- a/esp-hal-common/src/soc/esp32c2/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod efuse; -pub mod gpio; -pub mod peripherals; -pub mod radio_clocks; - -pub(crate) mod registers { - pub const INTERRUPT_MAP_BASE: u32 = 0x600c2000; -} diff --git a/esp-hal-common/src/soc/esp32c2/peripherals.rs b/esp-hal-common/src/soc/esp32c2/peripherals.rs deleted file mode 100644 index e6308f27972..00000000000 --- a/esp-hal-common/src/soc/esp32c2/peripherals.rs +++ /dev/null @@ -1,35 +0,0 @@ -use esp32c2 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - APB_CTRL => true, - APB_SARADC => true, - ASSIST_DEBUG => true, - DMA => true, - ECC => true, - EFUSE => true, - EXTMEM => true, - GPIO => true, - I2C0 => true, - INTERRUPT_CORE0 => true, - IO_MUX => true, - LEDC => true, - RNG => true, - RTC_CNTL => true, - SENSITIVE => true, - SHA => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SYSTEM => true, - SYSTIMER => true, - TIMG0 => true, - UART0 => true, - UART1 => true, - XTS_AES => true, - RADIO => false -} diff --git a/esp-hal-common/src/soc/esp32c2/radio_clocks.rs b/esp-hal-common/src/soc/esp32c2/radio_clocks.rs deleted file mode 100644 index ff89ceb8ab5..00000000000 --- a/esp-hal-common/src/soc/esp32c2/radio_clocks.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -// Mask for clock bits used by both WIFI and Bluetooth, 0, 1, 2, 3, 7, 8, 9, 10, -// 19, 20, 21, 22, 23 -const SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M: u32 = 0x78078F; -// SYSTEM_WIFI_CLK_EN : R/W ;bitpos:[31:0] ;default: 32'hfffce030 -// from experiments `0x00FB9FCF` is not enough for esp-wifi to work -const SYSTEM_WIFI_CLK_EN: u32 = 0xFFFFFFFF; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_enable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_disable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - ble_rtc_clk_init(); - } - - fn reset_rpa(&mut self) { - reset_rpa(); - } -} - -fn enable_phy() { - let system = unsafe { &*esp32c2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn disable_phy() { - let system = unsafe { &*esp32c2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn common_wifi_bt_clock_enable() { - let system = unsafe { &*esp32c2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_EN) }); -} - -fn common_wifi_bt_clock_disable() { - let system = unsafe { &*esp32c2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_EN) }); -} - -fn reset_mac() { - const SYSTEM_MAC_RST: u32 = 1 << 2; - let syscon = unsafe { &*esp32c2::APB_CTRL::PTR }; - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() | SYSTEM_MAC_RST) }); - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() & !SYSTEM_MAC_RST) }); -} - -fn init_clocks() { - let syscon = unsafe { &*esp32c2::APB_CTRL::PTR }; - syscon - .wifi_clk_en - .modify(|_, w| unsafe { w.bits(0xffffffff) }); -} - -fn ble_rtc_clk_init() { - let modem_clkrst = unsafe { &*esp32c2::MODEM_CLKRST::PTR }; - modem_clkrst - .modem_lp_timer_conf - .modify(|_, w| w.lp_timer_sel_xtal32k().clear_bit()); - modem_clkrst - .modem_lp_timer_conf - .modify(|_, w| w.lp_timer_sel_xtal().set_bit()); - modem_clkrst - .modem_lp_timer_conf - .modify(|_, w| w.lp_timer_sel_8m().clear_bit()); - modem_clkrst - .modem_lp_timer_conf - .modify(|_, w| w.lp_timer_sel_rtc_slow().clear_bit()); - - // assume 40MHz xtal - modem_clkrst - .modem_lp_timer_conf - .modify(|_, w| w.lp_timer_clk_div_num().variant(249)); - - modem_clkrst - .etm_clk_conf - .modify(|_, w| w.etm_clk_active().set_bit()); - modem_clkrst - .etm_clk_conf - .modify(|_, w| w.etm_clk_sel().clear_bit()); -} - -fn reset_rpa() { - const BLE_RPA_REST_BIT: u32 = 1 << 27; - let syscon = unsafe { &*esp32c2::APB_CTRL::PTR }; - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.bits(r.bits() | BLE_RPA_REST_BIT) }); - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.bits(r.bits() & !BLE_RPA_REST_BIT) }); -} diff --git a/esp-hal-common/src/soc/esp32c3/efuse.rs b/esp-hal-common/src/soc/esp32c3/efuse.rs deleted file mode 100644 index 81ae4ce5f84..00000000000 --- a/esp-hal-common/src/soc/esp32c3/efuse.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Reading of eFuses - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.rd_mac_spi_sys_0.read().mac_0().bits(); - let mac_high: u32 = efuse.rd_mac_spi_sys_1.read().mac_1().bits() as u32; - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[2], - mac_high_bytes[3], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .rd_repeat_data1 - .read() - .spi_boot_crypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } - - /// Get the multiplier for the timeout value of the RWDT STAGE 0 register. - pub fn get_rwdt_multiplier() -> u8 { - let efuse = unsafe { &*EFUSE::ptr() }; - efuse.rd_repeat_data1.read().wdt_delay_sel().bits() - } -} diff --git a/esp-hal-common/src/soc/esp32c3/gpio.rs b/esp-hal-common/src/soc/esp32c3/gpio.rs deleted file mode 100644 index 7f39a650ed0..00000000000 --- a/esp-hal-common/src/soc/esp32c3/gpio.rs +++ /dev/null @@ -1,200 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - GpioPin, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 21; - -pub type OutputSignalType = u8; -pub const OUTPUT_SIGNAL_MAX: u8 = 128; -pub const INPUT_SIGNAL_MAX: u8 = 100; - -pub const ONE_INPUT: u8 = 0x1e; -pub const ZERO_INPUT: u8 = 0x1f; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function1; - -pub(crate) const fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO { - unsafe { &(&*crate::peripherals::IO_MUX::PTR).gpio[gpio_num as usize] } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 | ((nmi_enable as u8) << 1) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq)] -pub enum InputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - U0RXD = 6, - U0CTS = 7, - U0DSR = 8, - U1RXD = 9, - U1CTS = 10, - U1DSR = 11, - I2S_MCLK = 12, - I2SO_BCK = 13, - I2SO_WS = 14, - I2SI_SD = 15, - I2SI_BCK = 16, - I2SI_WS = 17, - GPIO_BT_PRIORITY = 18, - GPIO_BT_ACTIVE = 19, - CPU_GPIO_0 = 28, - CPU_GPIO_1 = 29, - CPU_GPIO_2 = 30, - CPU_GPIO_3 = 31, - CPU_GPIO_4 = 32, - CPU_GPIO_5 = 33, - CPU_GPIO_6 = 34, - CPU_GPIO_7 = 35, - EXT_ADC_START = 45, - RMT_SIG_0 = 51, - RMT_SIG_1 = 52, - I2CEXT0_SCL = 53, - I2CEXT0_SDA = 54, - FSPICLK = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - TWAI_RX = 74, - SIG_FUNC_97 = 97, - SIG_FUNC_98 = 98, - SIG_FUNC_99 = 99, - SIG_FUNC_100 = 100, -} - -/// Peripheral output signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq)] -pub enum OutputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPICLK_MUX = 4, - SPICS0 = 5, - U0TXD = 6, - U0RTS = 7, - U0DTR = 8, - U1TXD = 9, - U1RTS = 10, - U1DTR = 11, - I2S_MCLK = 12, - I2SO_BCK = 13, - I2SO_WS = 14, - I2SO_SD = 15, - I2SI_BCK = 16, - I2SI_WS = 17, - GPIO_WLAN_PRIO = 18, - GPIO_WLAN_ACTIVE = 19, - CPU_GPIO_0 = 28, - CPU_GPIO_1 = 29, - CPU_GPIO_2 = 30, - CPU_GPIO_3 = 31, - CPU_GPIO_4 = 32, - CPU_GPIO_5 = 33, - CPU_GPIO_6 = 34, - CPU_GPIO_7 = 35, - USB_JTAG_TCK = 36, - USB_JTAG_TMS = 37, - USB_JTAG_TDI = 38, - USB_JTAG_TDO = 39, - LEDC_LS_SIG0 = 45, - LEDC_LS_SIG1 = 46, - LEDC_LS_SIG2 = 47, - LEDC_LS_SIG3 = 48, - LEDC_LS_SIG4 = 49, - LEDC_LS_SIG5 = 50, - RMT_SIG_0 = 51, - RMT_SIG_1 = 52, - I2CEXT0_SCL = 53, - I2CEXT0_SDA = 54, - GPIO_SD0 = 55, - GPIO_SD1 = 56, - GPIO_SD2 = 57, - GPIO_SD3 = 58, - I2SO_SD1 = 59, - FSPICLK_MUX = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - FSPICS1 = 69, - FSPICS3 = 70, - FSPICS2 = 71, - FSPICS4 = 72, - FSPICS5 = 73, - TWAI_TX = 74, - TWAI_BUS_OFF_ON = 75, - TWAI_CLKOUT = 76, - ANT_SEL0 = 89, - ANT_SEL1 = 90, - ANT_SEL2 = 91, - ANT_SEL3 = 92, - ANT_SEL4 = 93, - ANT_SEL5 = 94, - ANT_SEL6 = 95, - ANT_SEL7 = 96, - SIG_FUNC_97 = 97, - SIG_FUNC_98 = 98, - SIG_FUNC_99 = 99, - SIG_FUNC_100 = 100, - CLK_OUT1 = 123, - CLK_OUT2 = 124, - CLK_OUT3 = 125, - SPICS1 = 126, - USB_JTAG_TRST = 127, - GPIO = 128, -} - -crate::gpio::gpio! { - Single, - (0, 0, InputOutputAnalog) - (1, 0, InputOutputAnalog) - (2, 0, InputOutputAnalog (2 => FSPIQ) (2 => FSPIQ)) - (3, 0, InputOutputAnalog) - (4, 0, InputOutputAnalog (2 => FSPIHD) (0 => USB_JTAG_TMS 2 => FSPIHD)) - (5, 0, InputOutputAnalog (2 => FSPIWP) (0 => USB_JTAG_TDI 2 => FSPIWP)) - (6, 0, InputOutput (2 => FSPICLK) (0 => USB_JTAG_TCK 2 => FSPICLK_MUX)) - (7, 0, InputOutput (2 => FSPID) (0 => USB_JTAG_TDO 2 => FSPID)) - (8, 0, InputOutput) - (9, 0, InputOutput) - (10, 0, InputOutput (2 => FSPICS0) (2 => FSPICS0)) - (11, 0, InputOutput) - (12, 0, InputOutput (0 => SPIHD) (0 => SPIHD)) - (13, 0, InputOutput (0 => SPIWP) (0 => SPIWP)) - (14, 0, InputOutput () (0 => SPICS0)) - (15, 0, InputOutput () (0 => SPICLK_MUX)) - (16, 0, InputOutput (0 => SPID) (0 => SPID)) - (17, 0, InputOutput (0 => SPIQ) (0 => SPIQ)) - (18, 0, InputOutput) - (19, 0, InputOutput) - (20, 0, InputOutput (0 => U0RXD) ()) - (21, 0, InputOutput () (0 => U0TXD)) -} - -crate::gpio::analog! { - 0 - 1 - 2 - 3 - 4 - 5 -} diff --git a/esp-hal-common/src/soc/esp32c3/mod.rs b/esp-hal-common/src/soc/esp32c3/mod.rs deleted file mode 100644 index 907b45c9302..00000000000 --- a/esp-hal-common/src/soc/esp32c3/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod efuse; -pub mod gpio; -pub mod peripherals; -pub mod radio_clocks; - -pub(crate) mod registers { - pub const INTERRUPT_MAP_BASE: u32 = 0x600c2000; -} diff --git a/esp-hal-common/src/soc/esp32c3/peripherals.rs b/esp-hal-common/src/soc/esp32c3/peripherals.rs deleted file mode 100644 index 70d20dee3dd..00000000000 --- a/esp-hal-common/src/soc/esp32c3/peripherals.rs +++ /dev/null @@ -1,46 +0,0 @@ -use esp32c3 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - AES => true, - APB_CTRL => true, - APB_SARADC => true, - ASSIST_DEBUG => true, - DMA => true, - DS => true, - EFUSE => true, - EXTMEM => true, - GPIO => true, - GPIO_SD => true, - HMAC => true, - I2C0 => true, - I2S0 => true, - INTERRUPT_CORE0 => true, - IO_MUX => true, - LEDC => true, - RMT => true, - RNG => true, - RSA => true, - RTC_CNTL => true, - SENSITIVE => true, - SHA => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SYSTEM => true, - SYSTIMER => true, - TIMG0 => true, - TIMG1 => true, - TWAI0 => true, - UART0 => true, - UART1 => true, - UHCI0 => true, - UHCI1 => true, - USB_DEVICE => true, - XTS_AES => true, - RADIO => false -} diff --git a/esp-hal-common/src/soc/esp32c3/radio_clocks.rs b/esp-hal-common/src/soc/esp32c3/radio_clocks.rs deleted file mode 100644 index 466d3a7ed05..00000000000 --- a/esp-hal-common/src/soc/esp32c3/radio_clocks.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -// Mask for clock bits used by both WIFI and Bluetooth, 0, 1, 2, 3, 7, 8, 9, 10, -// 19, 20, 21, 22, 23 -const SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M: u32 = 0x78078F; -// SYSTEM_WIFI_CLK_EN : R/W ;bitpos:[31:0] ;default: 32'hfffce030 -// from experiments `0x00FB9FCF` is not enough for esp-wifi to work -const SYSTEM_WIFI_CLK_EN: u32 = 0xFFFFFFFF; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_enable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_disable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - // nothing for this target - } - - fn reset_rpa(&mut self) { - // nothing for this target - } -} - -fn enable_phy() { - let system = unsafe { &*esp32c3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn disable_phy() { - let system = unsafe { &*esp32c3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn common_wifi_bt_clock_enable() { - let system = unsafe { &*esp32c3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_EN) }); -} - -fn common_wifi_bt_clock_disable() { - let system = unsafe { &*esp32c3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_EN) }); -} - -fn reset_mac() { - const SYSTEM_MAC_RST: u32 = 1 << 2; - let syscon = unsafe { &*esp32c3::APB_CTRL::PTR }; - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() | SYSTEM_MAC_RST) }); - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() & !SYSTEM_MAC_RST) }); -} - -fn init_clocks() { - let syscon = unsafe { &*esp32c3::APB_CTRL::PTR }; - syscon - .wifi_clk_en - .modify(|_, w| unsafe { w.bits(0xffffffff) }); -} diff --git a/esp-hal-common/src/soc/esp32c6/efuse.rs b/esp-hal-common/src/soc/esp32c6/efuse.rs deleted file mode 100644 index 81ae4ce5f84..00000000000 --- a/esp-hal-common/src/soc/esp32c6/efuse.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Reading of eFuses - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.rd_mac_spi_sys_0.read().mac_0().bits(); - let mac_high: u32 = efuse.rd_mac_spi_sys_1.read().mac_1().bits() as u32; - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[2], - mac_high_bytes[3], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .rd_repeat_data1 - .read() - .spi_boot_crypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } - - /// Get the multiplier for the timeout value of the RWDT STAGE 0 register. - pub fn get_rwdt_multiplier() -> u8 { - let efuse = unsafe { &*EFUSE::ptr() }; - efuse.rd_repeat_data1.read().wdt_delay_sel().bits() - } -} diff --git a/esp-hal-common/src/soc/esp32c6/gpio.rs b/esp-hal-common/src/soc/esp32c6/gpio.rs deleted file mode 100644 index 2c25f11d122..00000000000 --- a/esp-hal-common/src/soc/esp32c6/gpio.rs +++ /dev/null @@ -1,288 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - GpioPin, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 30; - -pub type OutputSignalType = u8; -pub const OUTPUT_SIGNAL_MAX: u8 = 128; -pub const INPUT_SIGNAL_MAX: u8 = 124; - -pub const ONE_INPUT: u8 = 0x1e; -pub const ZERO_INPUT: u8 = 0x1f; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function1; - -pub(crate) const fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO { - unsafe { &(&*crate::peripherals::IO_MUX::PTR).gpio[gpio_num as usize] } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 | ((nmi_enable as u8) << 1) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum InputSignal { - EXT_ADC_START = 0, - U0RXD = 6, - U0CTS = 7, - U0DSR = 8, - U1RXD = 9, - U1CTS = 10, - U1DSR = 11, - I2S_MCLK = 12, - I2SO_BCK = 13, - I2SO_WS = 14, - I2SI_SD = 15, - I2SI_BCK = 16, - I2SI_WS = 17, - USB_JTAG_TDO_BRIDGE = 19, - CPU_TESTBUS0 = 20, // TODO: verify - CPU_GPIO_IN0 = 28, - CPU_GPIO_IN1 = 29, - CPU_GPIO_IN2 = 30, - CPU_GPIO_IN3 = 31, - CPU_GPIO_IN4 = 32, - CPU_GPIO_IN5 = 33, - CPU_GPIO_IN6 = 34, - CPU_GPIO_IN7 = 35, - USB_JTAG_TMS = 37, - USB_EXTPHY_OEN = 40, - USB_EXTPHY_VM = 41, - USB_EXTPHY_VPO = 42, - I2CEXT0_SCL = 45, - I2CEXT0_SDA = 46, - PARL_RX_DATA0 = 47, - PARL_RX_DATA1 = 48, - PARL_RX_DATA2 = 49, - PARL_RX_DATA3 = 50, - PARL_RX_DATA4 = 51, - PARL_RX_DATA5 = 52, - PARL_RX_DATA6 = 53, - PARL_RX_DATA7 = 54, - PARL_RX_DATA8 = 55, - PARL_RX_DATA9 = 56, - PARL_RX_DATA10 = 57, - PARL_RX_DATA11 = 58, - PARL_RX_DATA12 = 59, - PARL_RX_DATA13 = 60, - PARL_RX_DATA14 = 61, - PARL_RX_DATA15 = 62, - FSPICLK = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - PARL_RX_CLK = 69, - PARL_TX_CLK = 70, - RMT_SIG_0 = 71, - RMT_SIG_1 = 72, - TWAI0_RX = 73, - TWAI1_RX = 77, - PWM0_SYNC0 = 87, - PWM0_SYNC1 = 88, - PWM0_SYNC2 = 89, - PWM0_F0 = 90, - PWM0_F1 = 91, - PWM0_F2 = 92, - PWM0_CAP0 = 93, - PWM0_CAP1 = 94, - PWM0_CAP2 = 95, - SIG_IN_FUNC97 = 97, - SIG_IN_FUNC98 = 98, - SIG_IN_FUNC99 = 99, - SIG_IN_FUNC100 = 100, - PCNT0_SIG_CH0 = 101, - PCNT0_SIG_CH1 = 102, - PCNT0_CTRL_CH0 = 103, - PCNT0_CTRL_CH1 = 104, - PCNT1_SIG_CH0 = 105, - PCNT1_SIG_CH1 = 106, - PCNT1_CTRL_CH0 = 107, - PCNT1_CTRL_CH1 = 108, - PCNT2_SIG_CH0 = 109, - PCNT2_SIG_CH1 = 110, - PCNT2_CTRL_CH0 = 111, - PCNT2_CTRL_CH1 = 112, - PCNT3_SIG_CH0 = 113, - PCNT3_SIG_CH1 = 114, - PCNT3_CTRL_CH0 = 115, - PCNT3_CTRL_CH1 = 116, - SPIQ = 121, - SPID = 122, - SPIHD = 123, - SPIWP = 124, -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum OutputSignal { - LEDC_LS_SIG0 = 0, - LEDC_LS_SIG1 = 1, - LEDC_LS_SIG2 = 2, - LEDC_LS_SIG3 = 3, - LEDC_LS_SIG4 = 4, - LEDC_LS_SIG5 = 5, - U0TXD = 6, - U0RTS = 7, - U0DTR = 8, - U1TXD = 9, - U1RTS = 10, - U1DTR = 11, - I2S_MCLK = 12, - I2SO_BCK = 13, - I2SO_WS = 14, - I2SO_SD = 15, - I2SI_BCK = 16, - I2SI_WS = 17, - I2SO_SD1 = 18, - USB_JTAG_TRST = 19, // TODO: Verify - CPU_GPIO_OUT0 = 28, - CPU_GPIO_OUT1 = 29, - CPU_GPIO_OUT2 = 30, - CPU_GPIO_OUT3 = 31, - CPU_GPIO_OUT4 = 32, - CPU_GPIO_OUT5 = 33, - CPU_GPIO_OUT6 = 34, - CPU_GPIO_OUT7 = 35, - USB_JTAG_TCK = 36, - USB_JTAG_TMS = 37, - USB_JTAG_TDI = 38, - USB_JTAG_TDO = 39, - I2CEXT0_SCL = 45, - I2CEXT0_SDA = 46, - PARL_TX_DATA0 = 47, - PARL_TX_DATA1 = 48, - PARL_TX_DATA2 = 49, - PARL_TX_DATA3 = 50, - PARL_TX_DATA4 = 51, - PARL_TX_DATA5 = 52, - PARL_TX_DATA6 = 53, - PARL_TX_DATA7 = 54, - PARL_TX_DATA8 = 55, - PARL_TX_DATA9 = 56, - PARL_TX_DATA10 = 57, - PARL_TX_DATA11 = 58, - PARL_TX_DATA12 = 59, - PARL_TX_DATA13 = 60, - PARL_TX_DATA14 = 61, - PARL_TX_DATA15 = 62, - FSPICLK_MUX = 63, - FSPIQ = 64, - FSPID = 65, - FSPIHD = 66, - FSPIWP = 67, - FSPICS0 = 68, - SDIO_TOHOST_INT = 69, - PARL_TX_CLK = 70, - RMT_SIG_0 = 71, - RMT_SIG_1 = 72, - TWAI0_TX = 73, - TWAI0_BUS_OFF_ON = 74, - TWAI0_CLKOUT = 75, - TWAI0_STANDBY = 76, - TWAI1_TX = 77, - TWAI1_BUS_OFF_ON = 78, - TWAI1_CLKOUT = 79, - TWAI1_STANDBY = 80, - GPIO_SD0 = 83, - GPIO_SD1 = 84, - GPIO_SD2 = 85, - GPIO_SD3 = 86, - PWM0_0A = 87, - PWM0_0B = 88, - PWM0_1A = 89, - PWM0_1B = 90, - PWM0_2A = 91, - PWM0_2B = 92, - SIG_IN_FUNC97 = 97, - SIG_IN_FUNC98 = 98, - SIG_IN_FUNC99 = 99, - SIG_IN_FUNC100 = 100, - FSPICS1 = 101, - FSPICS2 = 102, - FSPICS3 = 103, - FSPICS4 = 104, - FSPICS5 = 105, - SPICLK_MUX = 114, - SPICS0 = 115, - SPICS1 = 116, - GPIO_TASK_MATRIX_OUT0 = 117, // TODO: verify rhis group - not in TRM but in ESP_IDF - GPIO_TASK_MATRIX_OUT1 = 118, - GPIO_TASK_MATRIX_OUT2 = 119, - GPIO_TASK_MATRIX_OUT3 = 120, - SPIQ = 121, - SPID = 122, - SPIHD = 123, - SPIWP = 124, - CLK_OUT_OUT1 = 125, - CLK_OUT_OUT2 = 126, - CLK_OUT_OUT3 = 127, - GPIO = 128, -} - -crate::gpio::gpio! { - Single, - (0, 0, InputOutputAnalog) - (1, 0, InputOutputAnalog) - (2, 0, InputOutputAnalog (2 => FSPIQ) (2 => FSPIQ)) - (3, 0, InputOutputAnalog) - (4, 0, InputOutputAnalog (2 => FSPIHD) (0 => USB_JTAG_TMS 2 => FSPIHD)) - (5, 0, InputOutputAnalog (2 => FSPIWP) (0 => USB_JTAG_TDI 2 => FSPIWP)) - (6, 0, InputOutputAnalog (2 => FSPICLK) (0 => USB_JTAG_TCK 2 => FSPICLK_MUX)) - (7, 0, InputOutputAnalog (2 => FSPID) (0 => USB_JTAG_TDO 2 => FSPID)) - (8, 0, InputOutput) - (9, 0, InputOutput) - (10, 0, InputOutput) - (11, 0, InputOutput) - (12, 0, InputOutput) - (13, 0, InputOutput) - (14, 0, InputOutput) - (15, 0, InputOutput) - (16, 0, InputOutput (0 => U0RXD) (2 => FSPICS0)) - (17, 0, InputOutput () (0 => U0TXD 2 => FSPICS1)) - (18, 0, InputOutput () (2 => FSPICS2)) // TODO 0 => SDIO_CMD - (19, 0, InputOutput () (2 => FSPICS3)) // TODO 0 => SDIO_CLK - (20, 0, InputOutput () (2 => FSPICS4)) //TODO 0 => SDIO_DATA0 - (21, 0, InputOutput () (2 => FSPICS5)) // TODO 0 => SDIO_DATA1 - (22, 0, InputOutput () ()) // TODO 0 => SDIO_DATA2 - (23, 0, InputOutput () ()) // TODO 0 => SDIO_DATA3 - (24, 0, InputOutput () (0 => SPICS0)) - (25, 0, InputOutput (0 => SPIQ) (0 => SPIQ)) - (26, 0, InputOutput (0 => SPIWP) (0 => SPIWP)) - (27, 0, InputOutput) - (28, 0, InputOutput (0 => SPIHD) (0 => SPIHD)) - (29, 0, InputOutput () (0 => SPICLK_MUX)) - (30, 0, InputOutput (0 => SPID) (0 => SPID)) -} - -crate::gpio::analog! { - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 -} - -// TODO USB pins -// implement marker traits on USB pins -// impl crate::otg_fs::UsbSel for Gpio?? {} -// impl crate::otg_fs::UsbDp for Gpio12 {} -// impl crate::otg_fs::UsbDm for Gpio13 {} diff --git a/esp-hal-common/src/soc/esp32c6/mod.rs b/esp-hal-common/src/soc/esp32c6/mod.rs deleted file mode 100644 index 310b8d7141c..00000000000 --- a/esp-hal-common/src/soc/esp32c6/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod efuse; -pub mod gpio; -pub mod peripherals; -pub mod radio_clocks; - -pub(crate) mod registers { - pub const INTERRUPT_MAP_BASE: u32 = 0x60010000; -} diff --git a/esp-hal-common/src/soc/esp32c6/peripherals.rs b/esp-hal-common/src/soc/esp32c6/peripherals.rs deleted file mode 100644 index 07de2dc6381..00000000000 --- a/esp-hal-common/src/soc/esp32c6/peripherals.rs +++ /dev/null @@ -1,72 +0,0 @@ -use esp32c6 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - AES => true, - APB_SARADC => true, - ASSIST_DEBUG => true, - ATOMIC => true, - DMA => true, - DS => true, - ECC => true, - EFUSE => true, - EXTMEM => true, - GPIO => true, - GPIO_SD => true, - HINF => true, - HMAC => true, - HP_APM => true, - HP_SYS => true, - I2C0 => true, - I2S0 => true, - INTERRUPT_CORE0 => true, - INTPRI => true, - IO_MUX => true, - LEDC => true, - LP_PERI => true, - LP_ANA => true, - LP_AON => true, - LP_APM => true, - LP_APM0 => true, - LP_CLKRST => true, - LP_I2C0 => true, - LP_I2C_ANA_MST => true, - LP_IO => true, - LP_TEE => true, - LP_TIMER => true, - LP_UART => true, - LP_WDT => true, - MCPWM0 => true, - MEM_MONITOR => true, - OTP_DEBUG => true, - PARL_IO => true, - PAU => true, - PCNT => true, - PCR => true, - PMU => true, - RMT => true, - RNG => true, - RSA => true, - SHA => true, - SLCHOST => true, - SOC_ETM => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SYSTIMER => true, - TEE => true, - TIMG0 => true, - TIMG1 => true, - TRACE => true, - TWAI0 => true, - TWAI1 => true, - UART0 => true, - UART1 => true, - UHCI0 => true, - USB_DEVICE => true, - RADIO => false, -} diff --git a/esp-hal-common/src/soc/esp32c6/radio_clocks.rs b/esp-hal-common/src/soc/esp32c6/radio_clocks.rs deleted file mode 100644 index ea43f5d5cc6..00000000000 --- a/esp-hal-common/src/soc/esp32c6/radio_clocks.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Wifi => wifi_clock_enable(), - RadioPeripherals::Bt => todo!("BLE not yet supported"), - RadioPeripherals::Ieee802154 => ieee802154_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Wifi => wifi_clock_disable(), - RadioPeripherals::Bt => todo!("BLE not yet supported"), - RadioPeripherals::Ieee802154 => ieee802154_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - // nothing for this target (yet) - } - - fn reset_rpa(&mut self) { - // nothing for this target (yet) - } -} - -fn enable_phy() { - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_i2c_mst_en().set_bit()); - modem_lpcon - .i2c_mst_clk_conf - .modify(|_, w| w.clk_i2c_mst_sel_160m().set_bit()); -} - -fn disable_phy() { - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_i2c_mst_en().clear_bit()); - modem_lpcon - .i2c_mst_clk_conf - .modify(|_, w| w.clk_i2c_mst_sel_160m().clear_bit()); -} - -fn wifi_clock_enable() { - let modem_syscon = unsafe { &*esp32c6::MODEM_SYSCON::PTR }; - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - - modem_syscon.clk_conf1.modify(|_, w| { - w.clk_wifi_apb_en() - .set_bit() - .clk_wifimac_en() - .set_bit() - .clk_fe_apb_en() - .set_bit() - .clk_fe_cal_160m_en() - .set_bit() - .clk_fe_160m_en() - .set_bit() - .clk_fe_80m_en() - .set_bit() - .clk_wifibb_160x1_en() - .set_bit() - .clk_wifibb_80x1_en() - .set_bit() - .clk_wifibb_40x1_en() - .set_bit() - .clk_wifibb_80x_en() - .set_bit() - .clk_wifibb_40x_en() - .set_bit() - .clk_wifibb_80m_en() - .set_bit() - .clk_wifibb_44m_en() - .set_bit() - .clk_wifibb_40m_en() - .set_bit() - .clk_wifibb_22m_en() - .set_bit() - }); - - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_wifipwr_en().set_bit().clk_coex_en().set_bit()); -} - -fn wifi_clock_disable() { - let modem_syscon = unsafe { &*esp32c6::MODEM_SYSCON::PTR }; - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - - modem_syscon.clk_conf1.modify(|_, w| { - w.clk_wifi_apb_en() - .clear_bit() - .clk_wifimac_en() - .clear_bit() - .clk_fe_apb_en() - .clear_bit() - .clk_fe_cal_160m_en() - .clear_bit() - .clk_fe_160m_en() - .clear_bit() - .clk_fe_80m_en() - .clear_bit() - .clk_wifibb_160x1_en() - .clear_bit() - .clk_wifibb_80x1_en() - .clear_bit() - .clk_wifibb_40x1_en() - .clear_bit() - .clk_wifibb_80x_en() - .clear_bit() - .clk_wifibb_40x_en() - .clear_bit() - .clk_wifibb_80m_en() - .clear_bit() - .clk_wifibb_44m_en() - .clear_bit() - .clk_wifibb_40m_en() - .clear_bit() - .clk_wifibb_22m_en() - .clear_bit() - }); - - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_wifipwr_en().clear_bit().clk_coex_en().clear_bit()); -} - -fn ieee802154_clock_enable() { - let modem_syscon = unsafe { &*esp32c6::MODEM_SYSCON::PTR }; - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - - modem_syscon - .clk_conf - .modify(|_, w| w.clk_zb_apb_en().set_bit().clk_zb_mac_en().set_bit()); - - modem_syscon.clk_conf1.modify(|_, w| { - w.clk_fe_apb_en() - .set_bit() - .clk_fe_cal_160m_en() - .set_bit() - .clk_fe_160m_en() - .set_bit() - .clk_fe_80m_en() - .set_bit() - .clk_bt_apb_en() - .set_bit() - .clk_bt_en() - .set_bit() - .clk_wifibb_160x1_en() - .set_bit() - .clk_wifibb_80x1_en() - .set_bit() - .clk_wifibb_40x1_en() - .set_bit() - .clk_wifibb_80x_en() - .set_bit() - .clk_wifibb_40x_en() - .set_bit() - .clk_wifibb_80m_en() - .set_bit() - .clk_wifibb_44m_en() - .set_bit() - .clk_wifibb_40m_en() - .set_bit() - .clk_wifibb_22m_en() - .set_bit() - }); - - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_coex_en().set_bit()); -} - -fn ieee802154_clock_disable() { - let modem_syscon = unsafe { &*esp32c6::MODEM_SYSCON::PTR }; - let modem_lpcon = unsafe { &*esp32c6::MODEM_LPCON::PTR }; - - modem_syscon - .clk_conf - .modify(|_, w| w.clk_zb_apb_en().clear_bit().clk_zb_mac_en().clear_bit()); - - modem_syscon.clk_conf1.modify(|_, w| { - w.clk_fe_apb_en() - .clear_bit() - .clk_fe_cal_160m_en() - .clear_bit() - .clk_fe_160m_en() - .clear_bit() - .clk_fe_80m_en() - .clear_bit() - .clk_bt_apb_en() - .clear_bit() - .clk_bt_en() - .clear_bit() - .clk_wifibb_160x1_en() - .clear_bit() - .clk_wifibb_80x1_en() - .clear_bit() - .clk_wifibb_40x1_en() - .clear_bit() - .clk_wifibb_80x_en() - .clear_bit() - .clk_wifibb_40x_en() - .clear_bit() - .clk_wifibb_80m_en() - .clear_bit() - .clk_wifibb_44m_en() - .clear_bit() - .clk_wifibb_40m_en() - .clear_bit() - .clk_wifibb_22m_en() - .clear_bit() - }); - - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_coex_en().clear_bit()); -} - -fn reset_mac() { - // empty -} - -fn init_clocks() { - unsafe { - let pmu = &*esp32c6::PMU::PTR; - - pmu.hp_sleep_icg_modem - .modify(|_, w| w.hp_sleep_dig_icg_modem_code().variant(0)); - pmu.hp_modem_icg_modem - .modify(|_, w| w.hp_modem_dig_icg_modem_code().variant(1)); - pmu.hp_active_icg_modem - .modify(|_, w| w.hp_active_dig_icg_modem_code().variant(2)); - pmu.imm_modem_icg - .as_ptr() - .write_volatile(pmu.imm_modem_icg.as_ptr().read_volatile() | 1 << 31); - pmu.imm_sleep_sysclk - .as_ptr() - .write_volatile(pmu.imm_sleep_sysclk.as_ptr().read_volatile() | 1 << 28); - - let modem_syscon = &*esp32c6::MODEM_SYSCON::PTR; - modem_syscon.clk_conf_power_st.modify(|_, w| { - w.clk_modem_apb_st_map() - .variant(6) - .clk_modem_peri_st_map() - .variant(4) - .clk_wifi_st_map() - .variant(6) - .clk_bt_st_map() - .variant(6) - .clk_fe_st_map() - .variant(6) - .clk_zb_st_map() - .variant(6) - }); - - let modem_lpcon = &*esp32c6::MODEM_LPCON::PTR; - modem_lpcon.clk_conf_power_st.modify(|_, w| { - w.clk_lp_apb_st_map() - .variant(6) - .clk_i2c_mst_st_map() - .variant(6) - .clk_coex_st_map() - .variant(6) - .clk_wifipwr_st_map() - .variant(6) - }); - - modem_lpcon.wifi_lp_clk_conf.modify(|_, w| { - w.clk_wifipwr_lp_sel_osc_slow() - .set_bit() - .clk_wifipwr_lp_sel_osc_fast() - .set_bit() - .clk_wifipwr_lp_sel_xtal32k() - .set_bit() - .clk_wifipwr_lp_sel_xtal() - .set_bit() - }); - - modem_lpcon - .wifi_lp_clk_conf - .modify(|_, w| w.clk_wifipwr_lp_div_num().variant(0)); - - modem_lpcon - .clk_conf - .modify(|_, w| w.clk_wifipwr_en().set_bit()); - } -} diff --git a/esp-hal-common/src/soc/esp32s2/efuse.rs b/esp-hal-common/src/soc/esp32s2/efuse.rs deleted file mode 100644 index 81ae4ce5f84..00000000000 --- a/esp-hal-common/src/soc/esp32s2/efuse.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Reading of eFuses - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.rd_mac_spi_sys_0.read().mac_0().bits(); - let mac_high: u32 = efuse.rd_mac_spi_sys_1.read().mac_1().bits() as u32; - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[2], - mac_high_bytes[3], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .rd_repeat_data1 - .read() - .spi_boot_crypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } - - /// Get the multiplier for the timeout value of the RWDT STAGE 0 register. - pub fn get_rwdt_multiplier() -> u8 { - let efuse = unsafe { &*EFUSE::ptr() }; - efuse.rd_repeat_data1.read().wdt_delay_sel().bits() - } -} diff --git a/esp-hal-common/src/soc/esp32s2/gpio.rs b/esp-hal-common/src/soc/esp32s2/gpio.rs deleted file mode 100644 index 8f381e99450..00000000000 --- a/esp-hal-common/src/soc/esp32s2/gpio.rs +++ /dev/null @@ -1,388 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - Bank1GpioRegisterAccess, - GpioPin, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 46; - -pub type OutputSignalType = u16; -pub const OUTPUT_SIGNAL_MAX: u16 = 256; -pub const INPUT_SIGNAL_MAX: u16 = 204; - -pub const ONE_INPUT: u8 = 0x38; -pub const ZERO_INPUT: u8 = 0x3c; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function1; - -pub(crate) const fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO0 { - unsafe { - let iomux = &*crate::peripherals::IO_MUX::PTR; - - match gpio_num { - 0 => core::mem::transmute(&(iomux.gpio0)), - 1 => core::mem::transmute(&(iomux.gpio1)), - 2 => core::mem::transmute(&(iomux.gpio2)), - 3 => core::mem::transmute(&(iomux.gpio3)), - 4 => core::mem::transmute(&(iomux.gpio4)), - 5 => core::mem::transmute(&(iomux.gpio5)), - 6 => core::mem::transmute(&(iomux.gpio6)), - 7 => core::mem::transmute(&(iomux.gpio7)), - 8 => core::mem::transmute(&(iomux.gpio8)), - 9 => core::mem::transmute(&(iomux.gpio9)), - 10 => core::mem::transmute(&(iomux.gpio10)), - 11 => core::mem::transmute(&(iomux.gpio11)), - 12 => core::mem::transmute(&(iomux.gpio12)), - 13 => core::mem::transmute(&(iomux.gpio13)), - 14 => core::mem::transmute(&(iomux.gpio14)), - 15 => core::mem::transmute(&(iomux.gpio15)), - 16 => core::mem::transmute(&(iomux.gpio16)), - 17 => core::mem::transmute(&(iomux.gpio17)), - 18 => core::mem::transmute(&(iomux.gpio18)), - 19 => core::mem::transmute(&(iomux.gpio19)), - 20 => core::mem::transmute(&(iomux.gpio20)), - 21 => core::mem::transmute(&(iomux.gpio21)), - 26 => core::mem::transmute(&(iomux.gpio26)), - 27 => core::mem::transmute(&(iomux.gpio27)), - 32 => core::mem::transmute(&(iomux.gpio32)), - 33 => core::mem::transmute(&(iomux.gpio33)), - 34 => core::mem::transmute(&(iomux.gpio34)), - 35 => core::mem::transmute(&(iomux.gpio35)), - 36 => core::mem::transmute(&(iomux.gpio36)), - 37 => core::mem::transmute(&(iomux.gpio37)), - 38 => core::mem::transmute(&(iomux.gpio38)), - 39 => core::mem::transmute(&(iomux.gpio39)), - 40 => core::mem::transmute(&(iomux.gpio40)), - 41 => core::mem::transmute(&(iomux.gpio41)), - 42 => core::mem::transmute(&(iomux.gpio42)), - 43 => core::mem::transmute(&(iomux.gpio43)), - 44 => core::mem::transmute(&(iomux.gpio44)), - 45 => core::mem::transmute(&(iomux.gpio45)), - 46 => core::mem::transmute(&(iomux.gpio46)), - _ => panic!(), - } - } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 - | ((nmi_enable as u8) << 1) - | (int_enable as u8) << 2 - | ((nmi_enable as u8) << 3) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum InputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPID4 = 7, - SPID5 = 8, - SPID6 = 9, - SPID7 = 10, - SPIDQS = 11, - U0RXD = 14, - U0CTS = 15, - U0DSR = 16, - U1RXD = 17, - U1CTS = 18, - U1DSR = 21, - I2S0O_BCK = 23, - I2S0O_WS = 25, - I2S0I_BCK = 27, - I2S0I_WS = 28, - I2CEXT0_SCL = 29, - I2CEXT0_SDA = 30, - PCNT0_SIG_CH0 = 39, - PCNT0_SIG_CH1 = 40, - PCNT0_CTRL_CH0 = 41, - PCNT0_CTRL_CH1 = 42, - PCNT1_SIG_CH0 = 43, - PCNT1_SIG_CH1 = 44, - PCNT1_CTRL_CH0 = 45, - PCNT1_CTRL_CH1 = 46, - PCNT2_SIG_CH0 = 47, - PCNT2_SIG_CH1 = 48, - PCNT2_CTRL_CH0 = 49, - PCNT2_CTRL_CH1 = 50, - PCNT3_SIG_CH0 = 51, - PCNT3_SIG_CH1 = 52, - PCNT3_CTRL_CH0 = 53, - PCNT3_CTRL_CH1 = 54, - USB_OTG_IDDIG = 64, - USB_OTG_AVALID = 65, - USB_SRP_BVALID = 66, - USB_OTG_VBUSVALID = 67, - USB_SRP_SESSEND = 68, - SPI3_CLK = 72, - SPI3_Q = 73, - SPI3_D = 74, - SPI3_HD = 75, - SPI3_CS0 = 76, - RMT_SIG_IN0 = 83, - RMT_SIG_IN1 = 84, - RMT_SIG_IN2 = 85, - RMT_SIG_IN3 = 86, - I2CEXT1_SCL = 95, - I2CEXT1_SDA = 96, - FSPICLK = 108, - FSPIQ = 109, - FSPID = 110, - FSPIHD = 111, - FSPIWP = 112, - FSPIIO4 = 113, - FSPIIO5 = 114, - FSPIIO6 = 115, - FSPIIO7 = 116, - FSPICS0 = 117, - SUBSPIQ = 127, - SUBSPID = 128, - SUBSPIHD = 129, - SUBSPIWP = 130, - I2S0I_DATA_IN15 = 158, - SUBSPID4 = 167, - SUBSPID5 = 168, - SUBSPID6 = 169, - SUBSPID7 = 170, - SUBSPIDQS = 171, - PCMFSYNC = 203, - PCMCLK = 204, -} - -/// Peripheral output signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum OutputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPICLK = 4, - SPICS0 = 5, - SPICS1 = 6, - SPID4 = 7, - SPID5 = 8, - SPID6 = 9, - SPID7 = 10, - SPIDQS = 11, - U0TXD = 14, - U0RTS = 15, - U0DTR = 16, - U1TXD = 17, - U1RTS = 18, - U1DTR = 21, - I2S0O_BCK = 23, - I2S0O_WS = 25, - I2S0I_BCK = 27, - I2S0I_WS = 28, - I2CEXT0_SCL = 29, - I2CEXT0_SDA = 30, - SDIO_TOHOST_INT = 31, - SPI3_CLK = 72, - SPI3_Q = 73, - SPI3_D = 74, - SPI3_HD = 75, - SPI3_CS0 = 76, - SPI3_CS1 = 77, - SPI3_CS2 = 78, - LEDC_LS_SIG0 = 79, - LEDC_LS_SIG1 = 80, - LEDC_LS_SIG2 = 81, - LEDC_LS_SIG3 = 82, - LEDC_LS_SIG4 = 83, - LEDC_LS_SIG5 = 84, - LEDC_LS_SIG6 = 85, - LEDC_LS_SIG7 = 86, - RMT_SIG_OUT0 = 87, - RMT_SIG_OUT1 = 88, - RMT_SIG_OUT2 = 89, - RMT_SIG_OUT3 = 90, - I2CEXT1_SCL = 95, - I2CEXT1_SDA = 96, - GPIO_SD0 = 100, - GPIO_SD1 = 101, - GPIO_SD2 = 102, - GPIO_SD3 = 103, - GPIO_SD4 = 104, - GPIO_SD5 = 105, - GPIO_SD6 = 106, - GPIO_SD7 = 107, - FSPICLK = 108, - FSPIQ = 109, - FSPID = 110, - FSPIHD = 111, - FSPIWP = 112, - FSPIIO4 = 113, - FSPIIO5 = 114, - FSPIIO6 = 115, - FSPIIO7 = 116, - FSPICS0 = 117, - FSPICS1 = 118, - FSPICS2 = 119, - FSPICS3 = 120, - FSPICS4 = 121, - FSPICS5 = 122, - SUBSPICLK = 126, - SUBSPIQ = 127, - SUBSPID = 128, - SUBSPIHD = 129, - SUBSPIWP = 130, - SUBSPICS0 = 131, - SUBSPICS1 = 132, - FSPIDQS = 133, - FSPI_HSYNC = 134, - FSPI_VSYNC = 135, - FSPI_DE = 136, - FSPICD = 137, - SPI3_CD = 139, - SPI3_DQS = 140, - I2S0O_DATA_OUT23 = 166, - SUBSPID4 = 167, - SUBSPID5 = 168, - SUBSPID6 = 169, - SUBSPID7 = 170, - SUBSPIDQS = 171, - PCMFSYNC = 209, - PCMCLK = 210, - CLK_I2S = 251, - GPIO = 256, -} - -crate::gpio::gpio! { - Single, - (0, 0, InputOutputAnalog) - (1, 0, InputOutputAnalog) - (2, 0, InputOutputAnalog) - (3, 0, InputOutputAnalog) - (4, 0, InputOutputAnalog) - (5, 0, InputOutputAnalog) - (6, 0, InputOutputAnalog) - (7, 0, InputOutputAnalog) - (8, 0, InputOutputAnalog) - (9, 0, InputOutputAnalog) - (10, 0, InputOutputAnalog) - (11, 0, InputOutputAnalog) - (12, 0, InputOutputAnalog) - (13, 0, InputOutputAnalog) - (14, 0, InputOutputAnalog) - (15, 0, InputOutputAnalog) - (16, 0, InputOutputAnalog) - (17, 0, InputOutputAnalog) - (18, 0, InputOutputAnalog) - (19, 0, InputOutputAnalog) - (20, 0, InputOutputAnalog) - (21, 0, InputOutputAnalog) - - (26, 0, InputOutput) - (27, 0, InputOutput) - (28, 0, InputOutput) - (29, 0, InputOutput) - (30, 0, InputOutput) - (31, 0, InputOutput) - (32, 1, InputOutput) - (33, 1, InputOutput) - (34, 1, InputOutput) - (35, 1, InputOutput) - (36, 1, InputOutput) - (37, 1, InputOutput) - (38, 1, InputOutput) - (39, 1, InputOutput) - (40, 1, InputOutput) - (41, 1, InputOutput) - (42, 1, InputOutput) - (43, 1, InputOutput) - (44, 1, InputOutput) - (45, 1, InputOutput) - (46, 1, InputOutput) -} - -// on ESP32-S2 the touch_pad registers are indexed and the fields are weirdly -// named -macro_rules! impl_get_rtc_pad { - ($pad_name:ident) => { - paste!{ - pub(crate) fn []() -> &'static crate::peripherals::rtc_io::[< $pad_name:upper >] { - use crate::peripherals::RTC_IO; - let rtc_io = unsafe{ &*RTC_IO::ptr() }; - &rtc_io.$pad_name - } - } - }; -} - -macro_rules! impl_get_rtc_pad_indexed { - ($pad_name:ident, $idx:literal) => { - paste!{ - pub(crate) fn []() -> &'static crate::peripherals::rtc_io::[< $pad_name:upper >] { - use crate::peripherals::RTC_IO; - let rtc_io = unsafe{ &*RTC_IO::ptr() }; - &rtc_io.$pad_name[$idx] - } - } - }; -} - -impl_get_rtc_pad_indexed!(touch_pad, 0); -impl_get_rtc_pad_indexed!(touch_pad, 1); -impl_get_rtc_pad_indexed!(touch_pad, 2); -impl_get_rtc_pad_indexed!(touch_pad, 3); -impl_get_rtc_pad_indexed!(touch_pad, 4); -impl_get_rtc_pad_indexed!(touch_pad, 5); -impl_get_rtc_pad_indexed!(touch_pad, 6); -impl_get_rtc_pad_indexed!(touch_pad, 7); -impl_get_rtc_pad_indexed!(touch_pad, 8); -impl_get_rtc_pad_indexed!(touch_pad, 9); -impl_get_rtc_pad_indexed!(touch_pad, 10); -impl_get_rtc_pad_indexed!(touch_pad, 11); -impl_get_rtc_pad_indexed!(touch_pad, 12); -impl_get_rtc_pad_indexed!(touch_pad, 13); -impl_get_rtc_pad_indexed!(touch_pad, 14); -impl_get_rtc_pad!(xtal_32p_pad); -impl_get_rtc_pad!(xtal_32n_pad); -impl_get_rtc_pad!(pad_dac1); -impl_get_rtc_pad!(pad_dac2); -impl_get_rtc_pad!(rtc_pad19); -impl_get_rtc_pad!(rtc_pad20); -impl_get_rtc_pad!(rtc_pad21); - -crate::gpio::analog! { - ( 0, 0, touch_pad0, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 1, 1, touch_pad1, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 2, 2, touch_pad2, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 3, 3, touch_pad3, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 4, 4, touch_pad4, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 5, 5, touch_pad5, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 6, 6, touch_pad6, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 7, 7, touch_pad7, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 8, 8, touch_pad8, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - ( 9, 9, touch_pad9, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (10, 10, touch_pad10, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (11, 11, touch_pad11, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (12, 12, touch_pad12, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (13, 13, touch_pad13, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (14, 14, touch_pad14, touch_pad0_mux_sel, touch_pad0_fun_sel, touch_pad0_fun_ie, touch_pad0_rue, touch_pad0_rde) - (15, 15, xtal_32p_pad, x32p_mux_sel, x32p_fun_sel, x32p_fun_ie, x32p_rue, x32p_rde) - (16, 16, xtal_32n_pad, x32n_mux_sel, x32n_fun_sel, x32n_fun_ie, x32n_rue, x32n_rde) - (17, 17, pad_dac1, pdac1_mux_sel, pdac1_fun_sel, pdac1_fun_ie, pdac1_rue, pdac1_rde) - (18, 18, pad_dac2, pdac2_mux_sel, pdac2_fun_sel, pdac2_fun_ie, pdac2_rue, pdac2_rde) - (19, 19, rtc_pad19, mux_sel, fun_sel, fun_ie, rue, rde) - (20, 20, rtc_pad20, mux_sel, fun_sel, fun_ie, rue, rde) - (21, 21, rtc_pad21, mux_sel, fun_sel, fun_ie, rue, rde) -} - -// implement marker traits on USB pins -impl crate::otg_fs::UsbSel for Gpio18 {} -impl crate::otg_fs::UsbDp for Gpio19 {} -impl crate::otg_fs::UsbDm for Gpio20 {} diff --git a/esp-hal-common/src/soc/esp32s2/mod.rs b/esp-hal-common/src/soc/esp32s2/mod.rs deleted file mode 100644 index 3a0b2633d00..00000000000 --- a/esp-hal-common/src/soc/esp32s2/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod efuse; -pub mod gpio; -pub mod peripherals; -#[cfg(psram)] -pub mod psram; -pub mod radio_clocks; diff --git a/esp-hal-common/src/soc/esp32s2/peripherals.rs b/esp-hal-common/src/soc/esp32s2/peripherals.rs deleted file mode 100644 index 56da54c5e65..00000000000 --- a/esp-hal-common/src/soc/esp32s2/peripherals.rs +++ /dev/null @@ -1,51 +0,0 @@ -use esp32s2 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - AES => true, - APB_SARADC => true, - DEDICATED_GPIO => true, - DS => true, - EFUSE => true, - EXTMEM => true, - GPIO => true, - GPIO_SD => true, - HMAC => true, - I2C0 => true, - I2C1 => true, - I2S0 => true, - INTERRUPT_CORE0 => true, - IO_MUX => true, - LEDC => true, - PCNT => true, - PMS => true, - RMT => true, - RNG => true, - RSA => true, - RTC_IO => true, - RTC_CNTL => true, - RTC_I2C => true, - SENS => true, - SHA => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SPI3 => true, - SPI4 => true, - SYSTEM => true, - SYSTIMER => true, - TIMG0 => true, - TIMG1 => true, - TWAI0 => true, - UART0 => true, - UART1 => true, - UHCI0 => true, - USB0 => true, - USB_WRAP => true, - XTS_AES => true, - RADIO => false -} diff --git a/esp-hal-common/src/soc/esp32s2/psram.rs b/esp-hal-common/src/soc/esp32s2/psram.rs deleted file mode 100644 index 09c8e9846de..00000000000 --- a/esp-hal-common/src/soc/esp32s2/psram.rs +++ /dev/null @@ -1,591 +0,0 @@ -#[cfg(not(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m")))] -pub(crate) fn init_psram() { - // nothing to do -} - -#[cfg(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m"))] -pub(crate) fn init_psram() { - #[allow(unused)] - enum CacheLayout { - CacheMemoryInvalid = 0, - CacheMemoryICacheLow = 1 << 0, - CacheMemoryICacheHigh = 1 << 1, - CacheMemoryDCacheLow = 1 << 2, - CacheMemoryDCacheHigh = 1 << 3, - } - - const MMU_ACCESS_SPIRAM: u32 = 1 << 16; - - const PSRAM_VADDR: u32 = 0x3f500000; - - const CACHE_SIZE_8KB: u32 = 0; - const CACHE_4WAYS_ASSOC: u32 = 0; - const CACHE_LINE_SIZE_16B: u32 = 0; - - extern "C" { - /// Allocate memory to used by ICache and DCache. - /// - /// [`sram0_layout`]: u32 the usage of first 8KB internal memory block, - /// can be CACHE_MEMORY_INVALID, - /// CACHE_MEMORY_ICACHE_LOW, - /// CACHE_MEMORY_ICACHE_HIGH, CACHE_MEMORY_DCACHE_LOW and - /// CACHE_MEMORY_DCACHE_HIGH - /// [`sram1_layout`]: the usage of second 8KB internal memory block, - /// [`sram2_layout`]: the usage of third 8KB internal memory block - /// [`sram3_layout`]: the usage of forth 8KB internal memory block - fn Cache_Allocate_SRAM( - sram0_layout: u32, - sram1_layout: u32, - sram2_layout: u32, - sram3_layout: u32, - ); - - /// Set DCache mmu mapping. - /// - /// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid. - /// [`vaddr`]: u32 Virtual address in CPU address space. - /// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize. - /// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here. - /// [`num`]: u32 Pages to be set. - /// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page. - fn cache_dbus_mmu_set( - ext_ram: u32, - vaddr: u32, - paddr: u32, - psize: u32, - num: u32, - fixed: u32, - ) -> i32; - - /// Set DCache modes: cache size, associate ways and cache line size. - /// - /// [`cache_size`]: u32 the cache size, can be CACHE_SIZE_HALF and CACHE_SIZE_FULL - /// [`ways`]: u32 the associate ways of cache, can only be CACHE_4WAYS_ASSOC - /// [`cache_line_size`]: u32 the cache line size, can be CACHE_LINE_SIZE_16B, CACHE_LINE_SIZE_32B - fn Cache_Set_DCache_Mode(cache_size: u32, ways: u32, cache_line_size: u32); - - /// Invalidate all cache items in DCache. - fn Cache_Invalidate_DCache_All(); - } - - unsafe { - Cache_Allocate_SRAM( - CacheLayout::CacheMemoryICacheLow as u32, - CacheLayout::CacheMemoryDCacheLow as u32, - CacheLayout::CacheMemoryInvalid as u32, - CacheLayout::CacheMemoryInvalid as u32, - ); - Cache_Set_DCache_Mode(CACHE_SIZE_8KB, CACHE_4WAYS_ASSOC, CACHE_LINE_SIZE_16B); - Cache_Invalidate_DCache_All(); - - const START_PAGE: u32 = 0; - - cfg_if::cfg_if! { - if #[cfg(feature = "psram_2m")] { - const PSRAM_SIZE: u32 = 2; - } else if #[cfg(feature = "psram_4m")] { - const PSRAM_SIZE: u32 = 4; - } else if #[cfg(feature = "psram_8m")] { - const PSRAM_SIZE: u32 = 8; - } else { - const PSRAM_SIZE: u32 = 0; - } - }; - - #[no_mangle] - static PSRAM_BYTES: usize = PSRAM_SIZE as usize * 1024 * 1024; - - #[no_mangle] - static PSRAM_VADDR_START: usize = PSRAM_VADDR as usize; - - if cache_dbus_mmu_set( - MMU_ACCESS_SPIRAM, - PSRAM_VADDR, - START_PAGE << 16, - 64, - PSRAM_SIZE * 1024 / 64, // number of pages to map - 0, - ) != 0 - { - panic!("cache_dbus_mmu_set failed"); - } - - let extmem = &*esp32s2::EXTMEM::PTR; - extmem.pro_dcache_ctrl1.modify(|_, w| { - w.pro_dcache_mask_bus0() - .clear_bit() - .pro_dcache_mask_bus1() - .clear_bit() - .pro_dcache_mask_bus2() - .clear_bit() - }); - - utils::psram_init(); - } -} - -#[cfg(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m"))] -pub(crate) mod utils { - pub(crate) fn psram_init() { - psram_gpio_config(); - - psram_reset_mode(); - psram_enable_qio_mode(); - - psram_cache_init( - PsramCacheSpeed::PsramCacheMax, - PsramVaddrMode::PsramVaddrModeNormal, - ); - } - - // send reset command to psram, in spi mode - fn psram_reset_mode() { - const PSRAM_RESET_EN: u16 = 0x66; - const PSRAM_RESET: u16 = 0x99; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET_EN, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - } - - /// Enter QPI mode - fn psram_enable_qio_mode() { - const PSRAM_ENTER_QMODE: u16 = 0x35; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_ENTER_QMODE, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, // whether is program/erase operation - ); - } - - #[derive(PartialEq)] - #[allow(unused)] - enum CommandMode { - PsramCmdQpi = 0, - PsramCmdSpi = 1, - } - - fn psram_exec_cmd( - mode: CommandMode, - cmd: u16, - cmd_bit_len: u16, - addr: u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - cs_mask: u8, - is_write_erase_operation: bool, - ) { - extern "C" { - /// Start a spi user command sequence - /// [`spi_num`] spi port - /// [`rx_buf`] buffer pointer to receive data - /// [`rx_len`] receive data length in byte - /// [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1 - /// [`is_write_erase`] to indicate whether this is a write or erase - /// operation, since the CPU would check permission - fn esp_rom_spi_cmd_start( - spi_num: u32, - rx_buf: *const u8, - rx_len: u16, - cs_en_mask: u8, - is_write_erase: bool, - ); - } - - unsafe { - let spi1 = &*esp32s2::SPI1::PTR; - let backup_usr = spi1.user.read().bits(); - let backup_usr1 = spi1.user1.read().bits(); - let backup_usr2 = spi1.user2.read().bits(); - let backup_ctrl = spi1.ctrl.read().bits(); - psram_set_op_mode(mode); - _psram_exec_cmd( - cmd, - cmd_bit_len, - addr, - addr_bit_len, - dummy_bits, - mosi_data, - mosi_bit_len, - miso_data, - miso_bit_len, - ); - esp_rom_spi_cmd_start( - 1, - miso_data, - (miso_bit_len / 8) as u16, - cs_mask, - is_write_erase_operation, - ); - - spi1.user.write(|w| w.bits(backup_usr)); - spi1.user1.write(|w| w.bits(backup_usr1)); - spi1.user2.write(|w| w.bits(backup_usr2)); - spi1.ctrl.write(|w| w.bits(backup_ctrl)); - } - } - - fn _psram_exec_cmd( - cmd: u16, - cmd_bit_len: u16, - addr: u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - ) { - #[repr(C)] - struct esp_rom_spi_cmd_t { - cmd: u16, // Command value - cmd_bit_len: u16, // Command byte length - addr: *const u32, // Point to address value - addr_bit_len: u32, // Address byte length - tx_data: *const u32, // Point to send data buffer - tx_data_bit_len: u32, // Send data byte length. - rx_data: *mut u32, // Point to recevie data buffer - rx_data_bit_len: u32, // Recevie Data byte length. - dummy_bit_len: u32, - } - - extern "C" { - /// Config the spi user command - /// [`spi_num`] spi port - /// [`pcmd`] pointer to accept the spi command struct - fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t); - } - - let conf = esp_rom_spi_cmd_t { - cmd, - cmd_bit_len, - addr: addr as *const u32, - addr_bit_len, - tx_data: mosi_data as *const u32, - tx_data_bit_len: mosi_bit_len, - rx_data: miso_data as *mut u32, - rx_data_bit_len: miso_bit_len, - dummy_bit_len: dummy_bits, - }; - - unsafe { - esp_rom_spi_cmd_config(1, &conf); - } - } - - fn psram_set_op_mode(mode: CommandMode) { - extern "C" { - fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); - } - - const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; - const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; - - unsafe { - match mode { - CommandMode::PsramCmdQpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); - let spi1 = &*esp32s2::SPI1::PTR; - spi1.ctrl.modify(|_, w| w.fcmd_quad().set_bit()); - } - CommandMode::PsramCmdSpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); - } - } - } - } - - #[repr(C)] - struct PsRamIo { - flash_clk_io: u8, - flash_cs_io: u8, - psram_clk_io: u8, - psram_cs_io: u8, - psram_spiq_sd0_io: u8, - psram_spid_sd1_io: u8, - psram_spiwp_sd3_io: u8, - psram_spihd_sd2_io: u8, - } - - fn psram_gpio_config() { - extern "C" { - fn esp_rom_efuse_get_flash_gpio_info() -> u32; - - /// Enable Quad I/O pin functions - /// - /// Sets the HD & WP pin functions for Quad I/O modes, based on the - /// efuse SPI pin configuration. - /// - /// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O - /// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig(). - /// - If this parameter is 0, default SPI pins are used and - /// wp_gpio_num parameter is ignored. - /// - If this parameter is 1, default HSPI pins are used and - /// wp_gpio_num parameter is ignored. - /// - For other values, this parameter encodes the HD pin number and - /// also the CLK pin number. CLK pin selection is used to - /// determine if HSPI or SPI peripheral will be used (use HSPI if - /// CLK pin is the HSPI clock pin, otherwise use SPI). - // Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral. - fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32); - } - - let psram_io = PsRamIo { - flash_clk_io: 30, // SPI_CLK_GPIO_NUM - flash_cs_io: 29, // SPI_CS0_GPIO_NUM - psram_clk_io: 30, - psram_cs_io: 26, // SPI_CS1_GPIO_NUM - psram_spiq_sd0_io: 31, // SPI_Q_GPIO_NUM - psram_spid_sd1_io: 32, // SPI_D_GPIO_NUM - psram_spiwp_sd3_io: 28, // SPI_WP_GPIO_NUM - psram_spihd_sd2_io: 27, // SPI_HD_GPIO_NUM - }; - - const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; - - unsafe { - let spiconfig = esp_rom_efuse_get_flash_gpio_info(); - if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI { - // FLASH pins(except wp / hd) are all configured via IO_MUX in - // rom. - } else { - // this case is currently not yet supported - panic!("Unsupported for now! The case 'FLASH pins are all configured via GPIO matrix in ROM.' is not yet supported."); - - // FLASH pins are all configured via GPIO matrix in ROM. - // psram_io.flash_clk_io = - // EFUSE_SPICONFIG_RET_SPICLK(spiconfig); - // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig); - // psram_io.psram_spiq_sd0_io = - // EFUSE_SPICONFIG_RET_SPIQ(spiconfig); - // psram_io.psram_spid_sd1_io = - // EFUSE_SPICONFIG_RET_SPID(spiconfig); - // psram_io.psram_spihd_sd2_io = - // EFUSE_SPICONFIG_RET_SPIHD(spiconfig); - // psram_io.psram_spiwp_sd3_io = - // esp_rom_efuse_get_flash_wp_gpio(); - } - esp_rom_spiflash_select_qio_pins(psram_io.psram_spiwp_sd3_io, spiconfig); - // s_psram_cs_io = psram_io.psram_cs_io; - } - } - - #[derive(PartialEq, Eq, Debug)] - #[allow(unused)] - enum PsramCacheSpeed { - PsramCacheS80m = 1, - PsramCacheS40m, - PsramCacheS26m, - PsramCacheS20m, - PsramCacheMax, - } - - #[derive(PartialEq, Eq, Debug)] - #[allow(unused)] - enum PsramVaddrMode { - /// App and pro CPU use their own flash cache for external RAM access - PsramVaddrModeNormal = 0, - /// App and pro CPU share external RAM caches: pro CPU has low2M, app - /// CPU has high 2M - PsramVaddrModeLowhigh, - /// App and pro CPU share external RAM caches: pro CPU does even 32yte - /// ranges, app does odd ones. - PsramVaddrModeEvenodd, - } - - const PSRAM_IO_MATRIX_DUMMY_20M: u32 = 0; - const PSRAM_IO_MATRIX_DUMMY_40M: u32 = 0; - const PSRAM_IO_MATRIX_DUMMY_80M: u32 = 0; - - /// Register initialization for sram cache params and r/w commands - fn psram_cache_init(psram_cache_mode: PsramCacheSpeed, _vaddrmode: PsramVaddrMode) { - let mut extra_dummy = 0; - match psram_cache_mode { - PsramCacheSpeed::PsramCacheS80m => { - psram_clock_set(1); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M; - } - PsramCacheSpeed::PsramCacheS40m => { - psram_clock_set(2); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M; - } - PsramCacheSpeed::PsramCacheS26m => { - psram_clock_set(3); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; - } - PsramCacheSpeed::PsramCacheS20m => { - psram_clock_set(4); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; - } - _ => { - psram_clock_set(2); - } - } - - const REG_SPI_MEM_BASE: u32 = 0x3f403000; - const SPI_MEM_CACHE_SCTRL_REG: u32 = REG_SPI_MEM_BASE + 0x40; - const SPI_MEM_SRAM_DWR_CMD_REG: u32 = REG_SPI_MEM_BASE + 0x4c; - const SPI_MEM_SRAM_DRD_CMD_REG: u32 = REG_SPI_MEM_BASE + 0x48; - const SPI_MEM_MISC_REG: u32 = REG_SPI_MEM_BASE + 0x34; - - const SPI_MEM_USR_SRAM_DIO_M: u32 = 1 << 1; - const SPI_MEM_USR_SRAM_QIO_M: u32 = 1 << 2; - const SPI_MEM_CACHE_SRAM_USR_RCMD_M: u32 = 1 << 5; - const SPI_MEM_CACHE_SRAM_USR_WCMD_M: u32 = 1 << 20; - const SPI_MEM_SRAM_ADDR_BITLEN_V: u32 = 0x3f; - const SPI_MEM_SRAM_ADDR_BITLEN_S: u32 = 14; - const SPI_MEM_USR_RD_SRAM_DUMMY_M: u32 = 1 << 4; - const SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN: u32 = 0x0000000F; - const SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN_S: u32 = 28; - const SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE: u32 = 0x0000FFFF; - const PSRAM_QUAD_WRITE: u32 = 0x38; - const SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE_S: u32 = 0; - const SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_V: u32 = 0xF; - const SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_S: u32 = 28; - const SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_V: u32 = 0xFFFF; - const PSRAM_FAST_READ_QUAD: u32 = 0xEB; - const SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_S: u32 = 0; - const SPI_MEM_SRAM_RDUMMY_CYCLELEN_V: u32 = 0xFF; - const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5; - const SPI_MEM_SRAM_RDUMMY_CYCLELEN_S: u32 = 6; - const SPI_MEM_CS1_DIS_M: u32 = 1 << 1; - - fn clear_peri_reg_mask(reg: u32, mask: u32) { - unsafe { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !mask); - } - } - - fn set_peri_reg_mask(reg: u32, mask: u32) { - unsafe { - (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | mask); - } - } - - fn set_peri_reg_bits(reg: u32, bitmap: u32, value: u32, shift: u32) { - unsafe { - (reg as *mut u32).write_volatile( - ((reg as *mut u32).read_volatile() & !(bitmap << shift)) - | ((value & bitmap) << shift), - ); - } - } - - clear_peri_reg_mask(SPI_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_SRAM_DIO_M); // disable dio mode for cache command - set_peri_reg_mask(SPI_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_SRAM_QIO_M); // enable qio mode for cache command - set_peri_reg_mask(SPI_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_RCMD_M); // enable cache read command - set_peri_reg_mask(SPI_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_WCMD_M); // enable cache write command - set_peri_reg_bits( - SPI_MEM_CACHE_SCTRL_REG, - SPI_MEM_SRAM_ADDR_BITLEN_V, - 23, - SPI_MEM_SRAM_ADDR_BITLEN_S, - ); // write address for cache command. - set_peri_reg_mask(SPI_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_RD_SRAM_DUMMY_M); // enable cache read dummy - - // config sram cache r/w command - set_peri_reg_bits( - SPI_MEM_SRAM_DWR_CMD_REG, - SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN, - 7, - SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN_S, - ); - set_peri_reg_bits( - SPI_MEM_SRAM_DWR_CMD_REG, - SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE, - PSRAM_QUAD_WRITE, - SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE_S, - ); // 0x38 - set_peri_reg_bits( - SPI_MEM_SRAM_DRD_CMD_REG, - SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_V, - 7, - SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_S, - ); - set_peri_reg_bits( - SPI_MEM_SRAM_DRD_CMD_REG, - SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_V, - PSRAM_FAST_READ_QUAD, - SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_S, - ); // 0x0b - set_peri_reg_bits( - SPI_MEM_CACHE_SCTRL_REG, - SPI_MEM_SRAM_RDUMMY_CYCLELEN_V, - PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy, - SPI_MEM_SRAM_RDUMMY_CYCLELEN_S, - ); // dummy, psram cache : 40m--+1dummy,80m--+2dummy - - // ESP-IDF has some code here to deal with `!CONFIG_FREERTOS_UNICORE` - not - // needed for ESP32-S2 - - // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) - clear_peri_reg_mask(SPI_MEM_MISC_REG, SPI_MEM_CS1_DIS_M); - } - - fn psram_clock_set(freqdiv: i8) { - const REG_SPI_MEM_BASE: u32 = 0x3f403000; - const SPI_MEM_SRAM_CLK_REG: u32 = REG_SPI_MEM_BASE + 0x50; - - const SPI_MEM_SCLK_EQU_SYSCLK: u32 = 1 << 31; - const SPI_MEM_SCLKCNT_N_S: u32 = 16; - const SPI_MEM_SCLKCNT_H_S: u32 = 8; - const SPI_MEM_SCLKCNT_L_S: u32 = 0; - - fn write_peri_reg(reg: u32, val: u32) { - unsafe { - (reg as *mut u32).write_volatile(val); - } - } - - if 1 >= freqdiv { - write_peri_reg(SPI_MEM_SRAM_CLK_REG, SPI_MEM_SCLK_EQU_SYSCLK); - } else { - let freqbits: u32 = (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_N_S) - | (((freqdiv / 2 - 1) as u32) << SPI_MEM_SCLKCNT_H_S) - | (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_L_S); - write_peri_reg(SPI_MEM_SRAM_CLK_REG, freqbits); - } - } -} diff --git a/esp-hal-common/src/soc/esp32s2/radio_clocks.rs b/esp-hal-common/src/soc/esp32s2/radio_clocks.rs deleted file mode 100644 index cd9e5401faf..00000000000 --- a/esp-hal-common/src/soc/esp32s2/radio_clocks.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -// Mask for clock bits used by both WIFI and Bluetooth, 0, 1, 2, 3, 7, 8, 9, 10, -// 19, 20, 21, 22, 23 -const SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M: u32 = 0x78078F; -// SYSTEM_WIFI_CLK_EN : R/W ;bitpos:[31:0] ;default: 32'hfffce030 -// from experiments `0x00FB9FCF` is not enough for esp-wifi to work -const SYSTEM_WIFI_CLK_EN: u32 = 0xFFFFFFFF; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Wifi => common_wifi_bt_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Wifi => common_wifi_bt_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - // nothing for this target - } - - fn reset_rpa(&mut self) { - // nothing for this target - } -} - -fn enable_phy() { - let system = unsafe { &*esp32s2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn disable_phy() { - let system = unsafe { &*esp32s2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn common_wifi_bt_clock_enable() { - let system = unsafe { &*esp32s2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_EN) }); -} - -fn common_wifi_bt_clock_disable() { - let system = unsafe { &*esp32s2::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_EN) }); -} - -fn reset_mac() { - const SYSTEM_MAC_RST: u32 = 1 << 2; - let syscon = unsafe { &*esp32s2::SYSCON::PTR }; - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() | SYSTEM_MAC_RST) }); - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() & !SYSTEM_MAC_RST) }); -} - -fn init_clocks() { - let syscon = unsafe { &*esp32s2::SYSCON::PTR }; - syscon - .wifi_clk_en - .modify(|_, w| unsafe { w.bits(0xffffffff) }); -} diff --git a/esp-hal-common/src/soc/esp32s3/cpu_control.rs b/esp-hal-common/src/soc/esp32s3/cpu_control.rs deleted file mode 100644 index 63aab5d2174..00000000000 --- a/esp-hal-common/src/soc/esp32s3/cpu_control.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Control CPU Cores - -use core::marker::PhantomData; - -use xtensa_lx::set_stack_pointer; - -use crate::Cpu; - -static mut START_CORE1_FUNCTION: Option<&'static mut (dyn FnMut() + 'static)> = None; - -/// Will park the APP (second) core when dropped -#[must_use] -pub struct AppCoreGuard<'a> { - phantom: PhantomData<&'a ()>, -} - -impl<'a> Drop for AppCoreGuard<'a> { - fn drop(&mut self) { - unsafe { - internal_park_core(Cpu::AppCpu); - } - } -} - -#[derive(Debug)] -pub enum Error { - CoreAlreadyRunning, -} - -/// Control CPU Cores -pub struct CpuControl { - _cpu_control: crate::system::CpuControl, -} - -unsafe fn internal_park_core(core: Cpu) { - let rtc_control = crate::peripherals::RTC_CNTL::PTR; - let rtc_control = &*rtc_control; - - match core { - Cpu::ProCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| w.sw_stall_procpu_c1().bits(0x21)); - rtc_control - .options0 - .modify(|_, w| w.sw_stall_procpu_c0().bits(0x02)); - } - Cpu::AppCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| w.sw_stall_appcpu_c1().bits(0x21)); - rtc_control - .options0 - .modify(|_, w| w.sw_stall_appcpu_c0().bits(0x02)); - } - } -} - -impl CpuControl { - pub fn new(cpu_control: crate::system::CpuControl) -> CpuControl { - CpuControl { - _cpu_control: cpu_control, - } - } - - /// Park the given core - pub unsafe fn park_core(&mut self, core: Cpu) { - internal_park_core(core); - } - - /// Unpark the given core - pub fn unpark_core(&mut self, core: Cpu) { - let rtc_control = crate::peripherals::RTC_CNTL::PTR; - let rtc_control = unsafe { &*rtc_control }; - - match core { - Cpu::ProCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| unsafe { w.sw_stall_procpu_c1().bits(0) }); - rtc_control - .options0 - .modify(|_, w| unsafe { w.sw_stall_procpu_c0().bits(0) }); - } - Cpu::AppCpu => { - rtc_control - .sw_cpu_stall - .modify(|_, w| unsafe { w.sw_stall_appcpu_c1().bits(0) }); - rtc_control - .options0 - .modify(|_, w| unsafe { w.sw_stall_appcpu_c0().bits(0) }); - } - } - } - - unsafe fn start_core1_init() -> ! { - extern "C" { - static mut _stack_end_cpu1: u32; - } - - // disables interrupts - xtensa_lx::interrupt::set_mask(0); - - // reset cycle compare registers - xtensa_lx::timer::set_ccompare0(0); - xtensa_lx::timer::set_ccompare1(0); - xtensa_lx::timer::set_ccompare2(0); - - // set stack pointer to end of memory: no need to retain stack up to this point - set_stack_pointer(&mut _stack_end_cpu1); - - match START_CORE1_FUNCTION.take() { - Some(entry) => (*entry)(), - None => panic!("No start function set"), - } - - panic!("Return from second core's entry"); - } - - /// Start the APP (second) core - /// - /// The second core will start running the closure `entry`. - /// - /// Dropping the returned guard will park the core. - pub fn start_app_core<'a>( - &mut self, - entry: &'a mut (dyn FnMut() + Send), - ) -> Result, Error> { - let system_control = crate::peripherals::SYSTEM::PTR; - let system_control = unsafe { &*system_control }; - - if !xtensa_lx::is_debugger_attached() - && system_control - .core_1_control_0 - .read() - .control_core_1_clkgate_en() - .bit_is_set() - { - return Err(Error::CoreAlreadyRunning); - } - - unsafe { - let entry_fn: &'static mut (dyn FnMut() + 'static) = core::mem::transmute(entry); - START_CORE1_FUNCTION = Some(entry_fn); - } - - // TODO there is no boot_addr register in SVD or TRM - ESP-IDF uses a ROM - // function so we also have to for now - const ETS_SET_APPCPU_BOOT_ADDR: usize = 0x40000720; - unsafe { - let ets_set_appcpu_boot_addr: unsafe extern "C" fn(u32) = - core::mem::transmute(ETS_SET_APPCPU_BOOT_ADDR); - ets_set_appcpu_boot_addr(Self::start_core1_init as *const u32 as u32); - }; - - system_control - .core_1_control_0 - .modify(|_, w| w.control_core_1_clkgate_en().set_bit()); - system_control - .core_1_control_0 - .modify(|_, w| w.control_core_1_runstall().clear_bit()); - system_control - .core_1_control_0 - .modify(|_, w| w.control_core_1_reseting().set_bit()); - system_control - .core_1_control_0 - .modify(|_, w| w.control_core_1_reseting().clear_bit()); - - self.unpark_core(Cpu::AppCpu); - - Ok(AppCoreGuard { - phantom: PhantomData::default(), - }) - } -} diff --git a/esp-hal-common/src/soc/esp32s3/efuse.rs b/esp-hal-common/src/soc/esp32s3/efuse.rs deleted file mode 100644 index 81ae4ce5f84..00000000000 --- a/esp-hal-common/src/soc/esp32s3/efuse.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Reading of eFuses - -use crate::peripherals::EFUSE; - -pub struct Efuse; - -impl Efuse { - /// Reads chip's MAC address from the eFuse storage. - /// - /// # Example - /// - /// ``` - /// let mac_address = Efuse::get_mac_address(); - /// writeln!( - /// serial_tx, - /// "MAC: {:#X}:{:#X}:{:#X}:{:#X}:{:#X}:{:#X}", - /// mac_address[0], - /// mac_address[1], - /// mac_address[2], - /// mac_address[3], - /// mac_address[4], - /// mac_address[5] - /// ); - /// ``` - pub fn get_mac_address() -> [u8; 6] { - let efuse = unsafe { &*EFUSE::ptr() }; - - let mac_low: u32 = efuse.rd_mac_spi_sys_0.read().mac_0().bits(); - let mac_high: u32 = efuse.rd_mac_spi_sys_1.read().mac_1().bits() as u32; - - let mac_low_bytes = mac_low.to_be_bytes(); - let mac_high_bytes = mac_high.to_be_bytes(); - - [ - mac_high_bytes[2], - mac_high_bytes[3], - mac_low_bytes[0], - mac_low_bytes[1], - mac_low_bytes[2], - mac_low_bytes[3], - ] - } - - /// Get status of SPI boot encryption. - pub fn get_flash_encryption() -> bool { - let efuse = unsafe { &*EFUSE::ptr() }; - (efuse - .rd_repeat_data1 - .read() - .spi_boot_crypt_cnt() - .bits() - .count_ones() - % 2) - != 0 - } - - /// Get the multiplier for the timeout value of the RWDT STAGE 0 register. - pub fn get_rwdt_multiplier() -> u8 { - let efuse = unsafe { &*EFUSE::ptr() }; - efuse.rd_repeat_data1.read().wdt_delay_sel().bits() - } -} diff --git a/esp-hal-common/src/soc/esp32s3/gpio.rs b/esp-hal-common/src/soc/esp32s3/gpio.rs deleted file mode 100644 index 42ed47c3d52..00000000000 --- a/esp-hal-common/src/soc/esp32s3/gpio.rs +++ /dev/null @@ -1,343 +0,0 @@ -use paste::paste; - -use crate::{ - gpio::{ - AlternateFunction, - Bank0GpioRegisterAccess, - Bank1GpioRegisterAccess, - GpioPin, - InputOutputAnalogPinType, - InputOutputPinType, - Unknown, - }, - peripherals::GPIO, -}; - -pub const NUM_PINS: usize = 48; - -pub type OutputSignalType = u16; -pub const OUTPUT_SIGNAL_MAX: u16 = 256; -pub const INPUT_SIGNAL_MAX: u16 = 189; - -pub const ONE_INPUT: u8 = 0x38; -pub const ZERO_INPUT: u8 = 0x3c; - -pub(crate) const GPIO_FUNCTION: AlternateFunction = AlternateFunction::Function1; - -pub(crate) const fn get_io_mux_reg(gpio_num: u8) -> &'static crate::peripherals::io_mux::GPIO { - unsafe { &(&*crate::peripherals::IO_MUX::PTR).gpio[gpio_num as usize] } -} - -pub(crate) fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { - int_enable as u8 | ((nmi_enable as u8) << 1) -} - -/// Peripheral input signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum InputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPID4 = 7, - SPID5 = 8, - SPID6 = 9, - SPID7 = 10, - SPIDQS = 11, - U0RXD = 12, - U0CTS = 13, - U0DSR = 14, - U1RXD = 15, - U1CTS = 16, - U1DSR = 17, - U2RXD = 18, - U2CTS = 19, - U2DSR = 20, - I2S1_MCLK = 21, - I2S0O_BCK = 22, - I2S0_MCLK = 23, - I2S0O_WS = 24, - I2S0I_SD = 25, - I2S0I_BCK = 26, - I2S0I_WS = 27, - I2S1O_BCK = 28, - I2S1O_WS = 29, - I2S1I_SD = 30, - I2S1I_BCK = 31, - I2S1I_WS = 32, - PCNT0_SIG_CH0 = 33, - PCNT0_SIG_CH1 = 34, - PCNT0_CTRL_CH0 = 35, - PCNT0_CTRL_CH1 = 36, - PCNT1_SIG_CH0 = 37, - PCNT1_SIG_CH1 = 38, - PCNT1_CTRL_CH0 = 39, - PCNT1_CTRL_CH1 = 40, - PCNT2_SIG_CH0 = 41, - PCNT2_SIG_CH1 = 42, - PCNT2_CTRL_CH0 = 43, - PCNT2_CTRL_CH1 = 44, - PCNT3_SIG_CH0 = 45, - PCNT3_SIG_CH1 = 46, - PCNT3_CTRL_CH0 = 47, - PCNT3_CTRL_CH1 = 48, - I2S0I_SD1 = 51, - I2S0I_SD2 = 52, - I2S0I_SD3 = 53, - USB_OTG_IDDIG = 58, - USB_OTG_AVALID = 59, - USB_SRP_BVALID = 60, - USB_OTG_VBUSVALID = 61, - USB_SRP_SESSEND = 62, - SPI3_CLK = 66, - SPI3_Q = 67, - SPI3_D = 68, - SPI3_HD = 69, - SPI3_WP = 70, - SPI3_CS0 = 71, - RMT_SIG_IN0 = 81, - RMT_SIG_IN1 = 82, - RMT_SIG_IN2 = 83, - RMT_SIG_IN3 = 84, - I2CEXT0_SCL = 89, - I2CEXT0_SDA = 90, - I2CEXT1_SCL = 91, - I2CEXT1_SDA = 92, - FSPICLK = 101, - FSPIQ = 102, - FSPID = 103, - FSPIHD = 104, - FSPIWP = 105, - FSPIIO4 = 106, - FSPIIO5 = 107, - FSPIIO6 = 108, - FSPIIO7 = 109, - FSPICS0 = 110, - TWAI_RX = 116, - SUBSPIQ = 120, - SUBSPID = 121, - SUBSPIHD = 122, - SUBSPIWP = 123, - SUBSPID4 = 155, - SUBSPID5 = 156, - SUBSPID6 = 157, - SUBSPID7 = 158, - SUBSPIDQS = 159, - PWM0_SYNC0 = 160, - PWM0_SYNC1 = 161, - PWM0_SYNC2 = 162, - PWM0_F0 = 163, - PWM0_F1 = 164, - PWM0_F2 = 165, - PWM0_CAP0 = 166, - PWM0_CAP1 = 167, - PWM0_CAP2 = 168, - PWM1_SYNC0 = 169, - PWM1_SYNC1 = 170, - PWM1_SYNC2 = 171, - PWM1_F0 = 172, - PWM1_F1 = 173, - PWM1_F2 = 174, - PWM1_CAP0 = 175, - PWM1_CAP1 = 176, - PWM1_CAP2 = 177, - PCMFSYNC = 188, - PCMCLK = 189, -} - -/// Peripheral output signals for the GPIO mux -#[allow(non_camel_case_types)] -#[derive(PartialEq, Copy, Clone)] -pub enum OutputSignal { - SPIQ = 0, - SPID = 1, - SPIHD = 2, - SPIWP = 3, - SPICLK = 4, - SPICS0 = 5, - SPICS1 = 6, - SPID4 = 7, - SPID5 = 8, - SPID6 = 9, - SPID7 = 10, - SPIDQS = 11, - U0TXD = 12, - U0RTS = 13, - U0DTR = 14, - U1TXD = 15, - U1RTS = 16, - U1DTR = 17, - U2TXD = 18, - U2RTS = 19, - U2DTR = 20, - I2S1_MCLK = 21, - I2S0O_BCK = 22, - I2S0_MCLK = 23, - I2S0O_WS = 24, - I2S0O_SD = 25, - I2S0I_BCK = 26, - I2S0I_WS = 27, - I2S1O_BCK = 28, - I2S1O_WS = 29, - I2S1O_SD = 30, - I2S1I_BCK = 31, - I2S1I_WS = 32, - SPI3_CLK = 66, - SPI3_Q = 67, - SPI3_D = 68, - SPI3_HD = 69, - SPI3_WP = 70, - SPI3_CS0 = 71, - SPI3_CS1 = 72, - LEDC_LS_SIG0 = 73, - LEDC_LS_SIG1 = 74, - LEDC_LS_SIG2 = 75, - LEDC_LS_SIG3 = 76, - LEDC_LS_SIG4 = 77, - LEDC_LS_SIG5 = 78, - LEDC_LS_SIG6 = 79, - LEDC_LS_SIG7 = 80, - RMT_SIG_OUT0 = 81, - RMT_SIG_OUT1 = 82, - RMT_SIG_OUT2 = 83, - RMT_SIG_OUT3 = 84, - I2CEXT0_SCL = 89, - I2CEXT0_SDA = 90, - I2CEXT1_SCL = 91, - I2CEXT1_SDA = 92, - GPIO_SD0 = 93, - GPIO_SD1 = 94, - GPIO_SD2 = 95, - GPIO_SD3 = 96, - GPIO_SD4 = 97, - GPIO_SD5 = 98, - GPIO_SD6 = 99, - GPIO_SD7 = 100, - FSPICLK = 101, - FSPIQ = 102, - FSPID = 103, - FSPIHD = 104, - FSPIWP = 105, - FSPIIO4 = 106, - FSPIIO5 = 107, - FSPIIO6 = 108, - FSPIIO7 = 109, - FSPICS0 = 110, - FSPICS1 = 111, - FSPICS2 = 112, - FSPICS3 = 113, - FSPICS4 = 114, - FSPICS5 = 115, - TWAI_TX = 116, - SUBSPICLK = 119, - SUBSPIQ = 120, - SUBSPID = 121, - SUBSPIHD = 122, - SUBSPIWP = 123, - SUBSPICS0 = 124, - SUBSPICS1 = 125, - FSPIDQS = 126, - SPI3_CS2 = 127, - I2S0O_SD1 = 128, - SUBSPID4 = 155, - SUBSPID5 = 156, - SUBSPID6 = 157, - SUBSPID7 = 158, - SUBSPIDQS = 159, - PWM0_0A = 160, - PWM0_0B = 161, - PWM0_1A = 162, - PWM0_1B = 163, - PWM0_2A = 164, - PWM0_2B = 165, - PWM1_0A = 166, - PWM1_0B = 167, - PWM1_1A = 168, - PWM1_1B = 169, - PWM1_2A = 170, - PWM1_2B = 171, - SDIO_TOHOST_INT = 177, - PCMFSYNC = 194, - PCMCLK = 195, - GPIO = 256, -} - -crate::gpio::gpio! { - Single, - (0, 0, InputOutputAnalog) - (1, 0, InputOutputAnalog) - (2, 0, InputOutputAnalog) - (3, 0, InputOutputAnalog) - (4, 0, InputOutputAnalog) - (5, 0, InputOutputAnalog) - (6, 0, InputOutputAnalog) - (7, 0, InputOutputAnalog) - (8, 0, InputOutputAnalog () (3 => SUBSPICS1)) - (9, 0, InputOutputAnalog (3 => SUBSPIHD 4 => FSPIHD) (3 => SUBSPIHD 4 => FSPIHD)) - (10, 0, InputOutputAnalog (2 => FSPIIO4 4 => FSPICS0) (2 => FSPIIO4 3 => SUBSPICS0 4 => FSPICS0)) - (11, 0, InputOutputAnalog (2 => FSPIIO5 3 => SUBSPID 4 => FSPID) (2 => FSPIIO5 3 => SUBSPID 4 => FSPID)) - (12, 0, InputOutputAnalog (2 => FSPIIO6 4 => FSPICLK) (2 => FSPIIO6 3=> SUBSPICLK 4 => FSPICLK)) - (13, 0, InputOutputAnalog (2 => FSPIIO7 3 => SUBSPIQ 4 => FSPIQ) (2 => FSPIIO7 3 => SUBSPIQ 4 => FSPIQ)) - (14, 0, InputOutputAnalog (3 => SUBSPIWP 4 => FSPIWP) (2 => FSPIDQS 3 => SUBSPIWP 4 => FSPIWP)) - (15, 0, InputOutputAnalog () (2 => U0RTS)) - (16, 0, InputOutputAnalog (2 => U0CTS) ()) - (17, 0, InputOutputAnalog () (2 => U1TXD)) - (18, 0, InputOutputAnalog (2 => U1RXD) ()) - (19, 0, InputOutputAnalog () (2 => U1RTS)) - (20, 0, InputOutputAnalog (2 => U1CTS) ()) - (21, 0, InputOutputAnalog) - (26, 0, InputOutput) - (27, 0, InputOutput) - (28, 0, InputOutput) - (29, 0, InputOutput) - (30, 0, InputOutput) - (31, 0, InputOutput) - (32, 1, InputOutput) - (33, 1, InputOutput (2 => FSPIHD 3 => SUBSPIHD) (2 => FSPIHD 3 => SUBSPIHD)) - (34, 1, InputOutput (2 => FSPICS0) (2 => FSPICS0 3 => SUBSPICS0)) - (35, 1, InputOutput (2 => FSPID 3 => SUBSPID) (2 => FSPID 3 => SUBSPID)) - (36, 1, InputOutput (2 => FSPICLK) (2 => FSPICLK 3 => SUBSPICLK)) - (37, 1, InputOutput (2 => FSPIQ 3 => SUBSPIQ 4 => SPIDQS) (2 => FSPIQ 3=> SUBSPIQ 4 => SPIDQS)) - (38, 1, InputOutput (2 => FSPIWP 3 => SUBSPIWP) (3 => FSPIWP 3 => SUBSPIWP)) - (39, 1, InputOutput () (4 => SUBSPICS1)) - (40, 1, InputOutput) - (41, 1, InputOutput) - (42, 1, InputOutput) - (43, 1, InputOutput) - (44, 1, InputOutput) - (45, 1, InputOutput) - (46, 1, InputOutput) - (47, 1, InputOutput) - (48, 1, InputOutput) -} - -crate::gpio::analog! { - ( 0, 0, touch_pad0, mux_sel, fun_sel, fun_ie, rue, rde) - ( 1, 1, touch_pad1, mux_sel, fun_sel, fun_ie, rue, rde) - ( 2, 2, touch_pad2, mux_sel, fun_sel, fun_ie, rue, rde) - ( 3, 3, touch_pad3, mux_sel, fun_sel, fun_ie, rue, rde) - ( 4, 4, touch_pad4, mux_sel, fun_sel, fun_ie, rue, rde) - ( 5, 5, touch_pad5, mux_sel, fun_sel, fun_ie, rue, rde) - ( 6, 6, touch_pad6, mux_sel, fun_sel, fun_ie, rue, rde) - ( 7, 7, touch_pad7, mux_sel, fun_sel, fun_ie, rue, rde) - ( 8, 8, touch_pad8, mux_sel, fun_sel, fun_ie, rue, rde) - ( 9, 9, touch_pad9, mux_sel, fun_sel, fun_ie, rue, rde) - (10, 10, touch_pad10, mux_sel, fun_sel, fun_ie, rue, rde) - (11, 11, touch_pad11, mux_sel, fun_sel, fun_ie, rue, rde) - (12, 12, touch_pad12, mux_sel, fun_sel, fun_ie, rue, rde) - (13, 13, touch_pad13, mux_sel, fun_sel, fun_ie, rue, rde) - (14, 14, touch_pad14, mux_sel, fun_sel, fun_ie, rue, rde) - (15, 15, xtal_32p_pad, x32p_mux_sel, x32p_fun_sel, x32p_fun_ie, x32p_rue, x32p_rde) - (16, 16, xtal_32n_pad, x32n_mux_sel, x32n_fun_sel, x32n_fun_ie, x32n_rue, x32n_rde) - (17, 17, pad_dac1, pdac1_mux_sel,pdac1_fun_sel,pdac1_fun_ie, pdac1_rue, pdac1_rde) - (18, 18, pad_dac2, pdac2_mux_sel,pdac2_fun_sel,pdac2_fun_ie, pdac2_rue, pdac2_rde) - (19, 19, rtc_pad19, mux_sel, fun_sel, fun_ie, rue, rde) - (20, 20, rtc_pad20, mux_sel, fun_sel, fun_ie, rue, rde) - (21, 21, rtc_pad21, mux_sel, fun_sel, fun_ie, rue, rde) -} - -// implement marker traits on USB pins -impl crate::otg_fs::UsbSel for Gpio18 {} -impl crate::otg_fs::UsbDp for Gpio19 {} -impl crate::otg_fs::UsbDm for Gpio20 {} diff --git a/esp-hal-common/src/soc/esp32s3/mod.rs b/esp-hal-common/src/soc/esp32s3/mod.rs deleted file mode 100644 index 108ee6ad3df..00000000000 --- a/esp-hal-common/src/soc/esp32s3/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod cpu_control; -pub mod efuse; -pub mod gpio; -pub mod peripherals; -pub mod radio_clocks; diff --git a/esp-hal-common/src/soc/esp32s3/peripherals.rs b/esp-hal-common/src/soc/esp32s3/peripherals.rs deleted file mode 100644 index e80e4f79657..00000000000 --- a/esp-hal-common/src/soc/esp32s3/peripherals.rs +++ /dev/null @@ -1,62 +0,0 @@ -use esp32s3 as pac; -// We need to export this for users to use -pub use pac::Interrupt; - -// We need to export this in the hal for the drivers to use -pub(crate) use self::peripherals::*; - -crate::peripherals! { - AES => true, - APB_CTRL => true, - APB_SARADC => true, - ASSIST_DEBUG => true, - DMA => true, - DS => true, - EFUSE => true, - EXTMEM => true, - GPIO => true, - GPIO_SD => true, - HMAC => true, - I2C0 => true, - I2C1 => true, - I2S0 => true, - I2S1 => true, - INTERRUPT_CORE0 => true, - INTERRUPT_CORE1 => true, - IO_MUX => true, - LCD_CAM => true, - LEDC => true, - PCNT => true, - PERI_BACKUP => true, - MCPWM0 => true, - MCPWM1 => true, - RMT => true, - RNG => true, - RSA => true, - RTC_CNTL => true, - RTC_I2C => true, - RTC_IO => true, - SENS => true, - SENSITIVE => true, - SHA => true, - SPI0 => true, - SPI1 => true, - SPI2 => true, - SPI3 => true, - SYSTEM => true, - SYSTIMER => true, - TIMG0 => true, - TIMG1 => true, - TWAI0 => true, - UART0 => true, - UART1 => true, - UART2 => true, - UHCI0 => true, - UHCI1 => true, - USB0 => true, - USB_DEVICE => true, - USB_WRAP => true, - WCL => true, - XTS_AES => true, - RADIO => false -} diff --git a/esp-hal-common/src/soc/esp32s3/radio_clocks.rs b/esp-hal-common/src/soc/esp32s3/radio_clocks.rs deleted file mode 100644 index b0b9a243c3b..00000000000 --- a/esp-hal-common/src/soc/esp32s3/radio_clocks.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::system::{RadioClockControl, RadioClockController, RadioPeripherals}; - -// Mask for clock bits used by both WIFI and Bluetooth, 0, 1, 2, 3, 7, 8, 9, 10, -// 19, 20, 21, 22, 23 -const SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M: u32 = 0x78078F; -// SYSTEM_WIFI_CLK_EN : R/W ;bitpos:[31:0] ;default: 32'hfffce030 -// from experiments `0x00FB9FCF` is not enough for esp-wifi to work -const SYSTEM_WIFI_CLK_EN: u32 = 0xFFFFFFFF; - -impl RadioClockController for RadioClockControl { - fn enable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => enable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_enable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_enable(), - } - } - - fn disable(&mut self, peripheral: RadioPeripherals) { - match peripheral { - RadioPeripherals::Phy => disable_phy(), - RadioPeripherals::Bt => common_wifi_bt_clock_disable(), - RadioPeripherals::Wifi => common_wifi_bt_clock_disable(), - } - } - - fn reset_mac(&mut self) { - reset_mac(); - } - - fn init_clocks(&mut self) { - init_clocks(); - } - - fn ble_rtc_clk_init(&mut self) { - // nothing for this target - } - - fn reset_rpa(&mut self) { - // nothing for this target - } -} - -fn enable_phy() { - let system = unsafe { &*esp32s3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn disable_phy() { - let system = unsafe { &*esp32s3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_WIFI_BT_COMMON_M) }); -} - -fn common_wifi_bt_clock_enable() { - let system = unsafe { &*esp32s3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() | SYSTEM_WIFI_CLK_EN) }); -} - -fn common_wifi_bt_clock_disable() { - let system = unsafe { &*esp32s3::SYSTEM::PTR }; - system - .perip_clk_en1 - .modify(|r, w| unsafe { w.bits(r.bits() & !SYSTEM_WIFI_CLK_EN) }); -} - -fn reset_mac() { - const SYSTEM_MAC_RST: u32 = 1 << 2; - let syscon = unsafe { &*esp32s3::APB_CTRL::PTR }; - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() | SYSTEM_MAC_RST) }); - syscon - .wifi_rst_en - .modify(|r, w| unsafe { w.wifi_rst().bits(r.wifi_rst().bits() & !SYSTEM_MAC_RST) }); -} - -fn init_clocks() { - let syscon = unsafe { &*esp32s3::APB_CTRL::PTR }; - syscon - .wifi_clk_en - .modify(|_, w| unsafe { w.bits(0xffffffff) }); -} diff --git a/esp-hal-common/src/soc/mod.rs b/esp-hal-common/src/soc/mod.rs deleted file mode 100644 index 542c7f511c9..00000000000 --- a/esp-hal-common/src/soc/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub use self::soc::*; - -#[cfg_attr(esp32, path = "esp32/mod.rs")] -#[cfg_attr(esp32c2, path = "esp32c2/mod.rs")] -#[cfg_attr(esp32c3, path = "esp32c3/mod.rs")] -#[cfg_attr(esp32c6, path = "esp32c6/mod.rs")] -#[cfg_attr(esp32s2, path = "esp32s2/mod.rs")] -#[cfg_attr(esp32s3, path = "esp32s3/mod.rs")] -mod soc; diff --git a/esp-hal-common/src/spi.rs b/esp-hal-common/src/spi.rs deleted file mode 100644 index c894c62f2db..00000000000 --- a/esp-hal-common/src/spi.rs +++ /dev/null @@ -1,3235 +0,0 @@ -//! # Serial Peripheral Interface -//! -//! There are multiple ways to use SPI, depending on your needs. Regardless of -//! which way you choose, you must first create an SPI instance with -//! [`Spi::new`]. -//! -//! ```rust -//! let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); -//! let sclk = io.pins.gpio12; -//! let miso = io.pins.gpio11; -//! let mosi = io.pins.gpio13; -//! let cs = io.pins.gpio10; -//! -//! let mut spi = hal::spi::Spi::new( -//! peripherals.SPI2, -//! sclk, -//! mosi, -//! miso, -//! cs, -//! 100u32.kHz(), -//! SpiMode::Mode0, -//! &mut peripheral_clock_control, -//! &mut clocks, -//! ); -//! ``` -//! -//! ## Exclusive access to the SPI bus -//! -//! If all you want to do is to communicate to a single device, and you initiate -//! transactions yourself, there are a number of ways to achieve this: -//! -//! - Use the [`FullDuplex`](embedded_hal::spi::FullDuplex) trait to read/write -//! single bytes at a time, -//! - Use the [`SpiBus`](embedded_hal_1::spi::SpiBus) trait (requires the "eh1" -//! feature) and its associated functions to initiate transactions with -//! simultaneous reads and writes, or -//! - Use the [`SpiBusWrite`](embedded_hal_1::spi::SpiBusWrite) and -//! [`SpiBusRead`](embedded_hal_1::spi::SpiBusRead) traits (requires the "eh1" -//! feature) and their associated functions to read or write mutiple bytes at -//! a time. -//! -//! -//! ## Shared SPI access -//! -//! If you have multiple devices on the same SPI bus that each have their own CS -//! line, you may want to have a look at the [`SpiBusController`] and -//! [`SpiBusDevice`] implemented here. These give exclusive access to the -//! underlying SPI bus by means of a Mutex. This ensures that device -//! transactions do not interfere with each other. - -use core::marker::PhantomData; - -use fugit::HertzU32; - -use crate::{ - clock::Clocks, - dma::{DmaError, DmaPeripheral, Rx, Tx}, - gpio::{InputPin, InputSignal, OutputPin, OutputSignal}, - peripheral::{Peripheral, PeripheralRef}, - peripherals::spi2::RegisterBlock, - system::PeripheralClockControl, -}; - -/// The size of the FIFO buffer for SPI -#[cfg(not(esp32s2))] -const FIFO_SIZE: usize = 64; -#[cfg(esp32s2)] -const FIFO_SIZE: usize = 72; -/// Padding byte for empty write transfers -const EMPTY_WRITE_PAD: u8 = 0x00u8; - -#[allow(unused)] -const MAX_DMA_SIZE: usize = 32736; - -#[derive(Debug, Clone, Copy)] -pub enum Error { - DmaError(DmaError), - MaxDmaTransferSizeExceeded, - FifoSizeExeeded, - Unsupported, - Unknown, -} - -impl From for Error { - fn from(value: DmaError) -> Self { - Error::DmaError(value) - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::spi::Error for Error { - fn kind(&self) -> embedded_hal_1::spi::ErrorKind { - embedded_hal_1::spi::ErrorKind::Other - } -} - -#[derive(Debug, Clone, Copy)] -pub enum SpiMode { - Mode0, - Mode1, - Mode2, - Mode3, -} - -pub trait DuplexMode {} -pub trait IsFullDuplex: DuplexMode {} -pub trait IsHalfDuplex: DuplexMode {} - -/// SPI data mode -/// -/// Single = 1 bit, 2 wires -/// Dual = 2 bit, 2 wires -/// Quad = 4 bit, 4 wires -#[derive(Debug, Clone, Copy)] -pub enum SpiDataMode { - Single, - Dual, - Quad, -} - -pub struct FullDuplexMode {} -impl DuplexMode for FullDuplexMode {} -impl IsFullDuplex for FullDuplexMode {} - -pub struct HalfDuplexMode {} -impl DuplexMode for HalfDuplexMode {} -impl IsHalfDuplex for HalfDuplexMode {} - -/// SPI command, 1 to 16 bits. -/// -/// Can be [Command::None] if command phase should be suppressed. -pub enum Command { - None, - Command1(u16, SpiDataMode), - Command2(u16, SpiDataMode), - Command3(u16, SpiDataMode), - Command4(u16, SpiDataMode), - Command5(u16, SpiDataMode), - Command6(u16, SpiDataMode), - Command7(u16, SpiDataMode), - Command8(u16, SpiDataMode), - Command9(u16, SpiDataMode), - Command10(u16, SpiDataMode), - Command11(u16, SpiDataMode), - Command12(u16, SpiDataMode), - Command13(u16, SpiDataMode), - Command14(u16, SpiDataMode), - Command15(u16, SpiDataMode), - Command16(u16, SpiDataMode), -} - -impl Command { - fn width(&self) -> usize { - match self { - Command::None => 0, - Command::Command1(_, _) => 1, - Command::Command2(_, _) => 2, - Command::Command3(_, _) => 3, - Command::Command4(_, _) => 4, - Command::Command5(_, _) => 5, - Command::Command6(_, _) => 6, - Command::Command7(_, _) => 7, - Command::Command8(_, _) => 8, - Command::Command9(_, _) => 9, - Command::Command10(_, _) => 10, - Command::Command11(_, _) => 11, - Command::Command12(_, _) => 12, - Command::Command13(_, _) => 13, - Command::Command14(_, _) => 14, - Command::Command15(_, _) => 15, - Command::Command16(_, _) => 16, - } - } - - fn value(&self) -> u16 { - match self { - Command::None => 0, - Command::Command1(value, _) - | Command::Command2(value, _) - | Command::Command3(value, _) - | Command::Command4(value, _) - | Command::Command5(value, _) - | Command::Command6(value, _) - | Command::Command7(value, _) - | Command::Command8(value, _) - | Command::Command9(value, _) - | Command::Command10(value, _) - | Command::Command11(value, _) - | Command::Command12(value, _) - | Command::Command13(value, _) - | Command::Command14(value, _) - | Command::Command15(value, _) - | Command::Command16(value, _) => *value, - } - } - - fn mode(&self) -> SpiDataMode { - match self { - Command::None => SpiDataMode::Single, - Command::Command1(_, mode) - | Command::Command2(_, mode) - | Command::Command3(_, mode) - | Command::Command4(_, mode) - | Command::Command5(_, mode) - | Command::Command6(_, mode) - | Command::Command7(_, mode) - | Command::Command8(_, mode) - | Command::Command9(_, mode) - | Command::Command10(_, mode) - | Command::Command11(_, mode) - | Command::Command12(_, mode) - | Command::Command13(_, mode) - | Command::Command14(_, mode) - | Command::Command15(_, mode) - | Command::Command16(_, mode) => *mode, - } - } - - fn is_none(&self) -> bool { - match self { - Command::None => true, - _ => false, - } - } -} - -/// SPI address, 1 to 32 bits. -/// -/// Can be [Address::None] if address phase should be suppressed. -pub enum Address { - None, - Address1(u32, SpiDataMode), - Address2(u32, SpiDataMode), - Address3(u32, SpiDataMode), - Address4(u32, SpiDataMode), - Address5(u32, SpiDataMode), - Address6(u32, SpiDataMode), - Address7(u32, SpiDataMode), - Address8(u32, SpiDataMode), - Address9(u32, SpiDataMode), - Address10(u32, SpiDataMode), - Address11(u32, SpiDataMode), - Address12(u32, SpiDataMode), - Address13(u32, SpiDataMode), - Address14(u32, SpiDataMode), - Address15(u32, SpiDataMode), - Address16(u32, SpiDataMode), - Address17(u32, SpiDataMode), - Address18(u32, SpiDataMode), - Address19(u32, SpiDataMode), - Address20(u32, SpiDataMode), - Address21(u32, SpiDataMode), - Address22(u32, SpiDataMode), - Address23(u32, SpiDataMode), - Address24(u32, SpiDataMode), - Address25(u32, SpiDataMode), - Address26(u32, SpiDataMode), - Address27(u32, SpiDataMode), - Address28(u32, SpiDataMode), - Address29(u32, SpiDataMode), - Address30(u32, SpiDataMode), - Address31(u32, SpiDataMode), - Address32(u32, SpiDataMode), -} - -impl Address { - fn width(&self) -> usize { - match self { - Address::None => 0, - Address::Address1(_, _) => 1, - Address::Address2(_, _) => 2, - Address::Address3(_, _) => 3, - Address::Address4(_, _) => 4, - Address::Address5(_, _) => 5, - Address::Address6(_, _) => 6, - Address::Address7(_, _) => 7, - Address::Address8(_, _) => 8, - Address::Address9(_, _) => 9, - Address::Address10(_, _) => 10, - Address::Address11(_, _) => 11, - Address::Address12(_, _) => 12, - Address::Address13(_, _) => 13, - Address::Address14(_, _) => 14, - Address::Address15(_, _) => 15, - Address::Address16(_, _) => 16, - Address::Address17(_, _) => 17, - Address::Address18(_, _) => 18, - Address::Address19(_, _) => 19, - Address::Address20(_, _) => 20, - Address::Address21(_, _) => 21, - Address::Address22(_, _) => 22, - Address::Address23(_, _) => 23, - Address::Address24(_, _) => 24, - Address::Address25(_, _) => 25, - Address::Address26(_, _) => 26, - Address::Address27(_, _) => 27, - Address::Address28(_, _) => 28, - Address::Address29(_, _) => 29, - Address::Address30(_, _) => 30, - Address::Address31(_, _) => 31, - Address::Address32(_, _) => 32, - } - } - - fn value(&self) -> u32 { - match self { - Address::None => 0, - Address::Address1(value, _) - | Address::Address2(value, _) - | Address::Address3(value, _) - | Address::Address4(value, _) - | Address::Address5(value, _) - | Address::Address6(value, _) - | Address::Address7(value, _) - | Address::Address8(value, _) - | Address::Address9(value, _) - | Address::Address10(value, _) - | Address::Address11(value, _) - | Address::Address12(value, _) - | Address::Address13(value, _) - | Address::Address14(value, _) - | Address::Address15(value, _) - | Address::Address16(value, _) - | Address::Address17(value, _) - | Address::Address18(value, _) - | Address::Address19(value, _) - | Address::Address20(value, _) - | Address::Address21(value, _) - | Address::Address22(value, _) - | Address::Address23(value, _) - | Address::Address24(value, _) - | Address::Address25(value, _) - | Address::Address26(value, _) - | Address::Address27(value, _) - | Address::Address28(value, _) - | Address::Address29(value, _) - | Address::Address30(value, _) - | Address::Address31(value, _) - | Address::Address32(value, _) => *value, - } - } - - fn is_none(&self) -> bool { - match self { - Address::None => true, - _ => false, - } - } - - fn mode(&self) -> SpiDataMode { - match self { - Address::None => SpiDataMode::Single, - Address::Address1(_, mode) - | Address::Address2(_, mode) - | Address::Address3(_, mode) - | Address::Address4(_, mode) - | Address::Address5(_, mode) - | Address::Address6(_, mode) - | Address::Address7(_, mode) - | Address::Address8(_, mode) - | Address::Address9(_, mode) - | Address::Address10(_, mode) - | Address::Address11(_, mode) - | Address::Address12(_, mode) - | Address::Address13(_, mode) - | Address::Address14(_, mode) - | Address::Address15(_, mode) - | Address::Address16(_, mode) - | Address::Address17(_, mode) - | Address::Address18(_, mode) - | Address::Address19(_, mode) - | Address::Address20(_, mode) - | Address::Address21(_, mode) - | Address::Address22(_, mode) - | Address::Address23(_, mode) - | Address::Address24(_, mode) - | Address::Address25(_, mode) - | Address::Address26(_, mode) - | Address::Address27(_, mode) - | Address::Address28(_, mode) - | Address::Address29(_, mode) - | Address::Address30(_, mode) - | Address::Address31(_, mode) - | Address::Address32(_, mode) => *mode, - } - } -} - -/// Read and Write in half duplex mode. -pub trait HalfDuplexReadWrite { - type Error; - - /// Half-duplex read. - fn read( - &mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - buffer: &mut [u8], - ) -> Result<(), Self::Error>; - - /// Half-duplex write. - fn write( - &mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - buffer: &[u8], - ) -> Result<(), Self::Error>; -} - -pub struct Spi<'d, T, M> { - spi: PeripheralRef<'d, T>, - _mode: PhantomData, -} - -impl<'d, T> Spi<'d, T, FullDuplexMode> -where - T: Instance, -{ - /// Constructs an SPI instance in 8bit dataframe mode. - pub fn new( - spi: impl Peripheral

    + 'd, - sck: impl Peripheral

    + 'd, - mosi: impl Peripheral

    + 'd, - miso: impl Peripheral

    + 'd, - cs: impl Peripheral

    + 'd, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, FullDuplexMode> { - crate::into_ref!(spi, sck, mosi, miso, cs); - sck.set_to_push_pull_output() - .connect_peripheral_to_output(spi.sclk_signal()); - - mosi.set_to_push_pull_output() - .connect_peripheral_to_output(spi.mosi_signal()); - - miso.set_to_input() - .connect_input_to_peripheral(spi.miso_signal()); - - cs.set_to_push_pull_output() - .connect_peripheral_to_output(spi.cs_signal()); - - Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) - } - - /// Constructs an SPI instance in 8bit dataframe mode without CS pin. - pub fn new_no_cs( - spi: impl Peripheral

    + 'd, - sck: impl Peripheral

    + 'd, - mosi: impl Peripheral

    + 'd, - miso: impl Peripheral

    + 'd, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, FullDuplexMode> { - crate::into_ref!(spi, sck, mosi, miso); - sck.set_to_push_pull_output() - .connect_peripheral_to_output(spi.sclk_signal()); - - mosi.set_to_push_pull_output() - .connect_peripheral_to_output(spi.mosi_signal()); - - miso.set_to_input() - .connect_input_to_peripheral(spi.miso_signal()); - - Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) - } - - /// Constructs an SPI instance in 8bit dataframe mode without CS and MISO - /// pin. - pub fn new_no_cs_no_miso( - spi: impl Peripheral

    + 'd, - sck: impl Peripheral

    + 'd, - mosi: impl Peripheral

    + 'd, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, FullDuplexMode> { - crate::into_ref!(spi, sck, mosi); - sck.set_to_push_pull_output() - .connect_peripheral_to_output(spi.sclk_signal()); - - mosi.set_to_push_pull_output() - .connect_peripheral_to_output(spi.mosi_signal()); - - Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) - } - - /// Constructs an SPI instance in 8bit dataframe mode with only MOSI - /// connected. This might be useful for (ab)using SPI to implement - /// other protocols by bitbanging (WS2812B, onewire, generating arbitrary - /// waveforms…) - pub fn new_mosi_only( - spi: impl Peripheral

    + 'd, - mosi: impl Peripheral

    + 'd, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, FullDuplexMode> { - crate::into_ref!(spi, mosi); - mosi.set_to_push_pull_output() - .connect_peripheral_to_output(spi.mosi_signal()); - - Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) - } - - pub(crate) fn new_internal( - spi: PeripheralRef<'d, T>, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, FullDuplexMode> { - spi.enable_peripheral(peripheral_clock_control); - - let mut spi = Spi { - spi, - _mode: PhantomData::default(), - }; - spi.spi.setup(frequency, clocks); - spi.spi.init(); - spi.spi.set_data_mode(mode); - - spi - } - - pub fn change_bus_frequency(&mut self, frequency: HertzU32, clocks: &Clocks) { - self.spi.ch_bus_freq(frequency, clocks); - } -} - -impl<'d, T> Spi<'d, T, HalfDuplexMode> -where - T: ExtendedInstance, -{ - /// Constructs an SPI instance in half-duplex mode. - /// - /// All pins are optional. Pass [crate::gpio::NO_PIN] if you don't need the - /// given pin. - pub fn new_half_duplex< - SCK: OutputPin, - MOSI: OutputPin + InputPin, - MISO: OutputPin + InputPin, - SIO2: OutputPin + InputPin, - SIO3: OutputPin + InputPin, - CS: OutputPin, - >( - spi: impl Peripheral

    + 'd, - sck: Option + 'd>, - mosi: Option + 'd>, - miso: Option + 'd>, - sio2: Option + 'd>, - sio3: Option + 'd>, - cs: Option + 'd>, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, HalfDuplexMode> { - crate::into_ref!(spi); - if let Some(sck) = sck { - crate::into_ref!(sck); - sck.set_to_push_pull_output() - .connect_peripheral_to_output(spi.sclk_signal()); - } - - if let Some(mosi) = mosi { - crate::into_ref!(mosi); - mosi.enable_output(true); - mosi.connect_peripheral_to_output(spi.mosi_signal()); - mosi.enable_input(true); - mosi.connect_input_to_peripheral(spi.sio0_input_signal()); - } - - if let Some(miso) = miso { - crate::into_ref!(miso); - miso.enable_output(true); - miso.connect_peripheral_to_output(spi.sio1_output_signal()); - miso.enable_input(true); - miso.connect_input_to_peripheral(spi.miso_signal()); - } - - if let Some(sio2) = sio2 { - crate::into_ref!(sio2); - sio2.enable_output(true); - sio2.connect_peripheral_to_output(spi.sio2_output_signal()); - sio2.enable_input(true); - sio2.connect_input_to_peripheral(spi.sio2_input_signal()); - } - - if let Some(sio3) = sio3 { - crate::into_ref!(sio3); - sio3.enable_output(true); - sio3.connect_peripheral_to_output(spi.sio3_output_signal()); - sio3.enable_input(true); - sio3.connect_input_to_peripheral(spi.sio3_input_signal()); - } - - if let Some(cs) = cs { - crate::into_ref!(cs); - cs.set_to_push_pull_output() - .connect_peripheral_to_output(spi.cs_signal()); - } - - Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) - } - - pub(crate) fn new_internal( - spi: PeripheralRef<'d, T>, - frequency: HertzU32, - mode: SpiMode, - peripheral_clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - ) -> Spi<'d, T, HalfDuplexMode> { - spi.enable_peripheral(peripheral_clock_control); - - let mut spi = Spi { - spi, - _mode: PhantomData::default(), - }; - spi.spi.setup(frequency, clocks); - spi.spi.init(); - spi.spi.set_data_mode(mode); - - spi - } - - pub fn change_bus_frequency(&mut self, frequency: HertzU32, clocks: &Clocks) { - self.spi.ch_bus_freq(frequency, clocks); - } -} - -impl HalfDuplexReadWrite for Spi<'_, T, M> -where - T: Instance, - M: IsHalfDuplex, -{ - type Error = Error; - - fn read( - &mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - if buffer.len() > FIFO_SIZE { - return Err(Error::FifoSizeExeeded); - } - - if buffer.len() == 0 { - return Err(Error::Unsupported); - } - - self.spi - .init_spi_data_mode(cmd.mode(), address.mode(), data_mode); - self.spi.read_bytes_half_duplex(cmd, address, dummy, buffer) - } - - fn write( - &mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - buffer: &[u8], - ) -> Result<(), Self::Error> { - if buffer.len() > FIFO_SIZE { - return Err(Error::FifoSizeExeeded); - } - - self.spi - .init_spi_data_mode(cmd.mode(), address.mode(), data_mode); - self.spi - .write_bytes_half_duplex(cmd, address, dummy, buffer) - } -} - -impl embedded_hal::spi::FullDuplex for Spi<'_, T, M> -where - T: Instance, - M: IsFullDuplex, -{ - type Error = Error; - - fn read(&mut self) -> nb::Result { - self.spi.read_byte() - } - - fn send(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.spi.write_byte(word) - } -} - -impl embedded_hal::blocking::spi::Transfer for Spi<'_, T, M> -where - T: Instance, - M: IsFullDuplex, -{ - type Error = Error; - - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - self.spi.transfer(words) - } -} - -impl embedded_hal::blocking::spi::Write for Spi<'_, T, M> -where - T: Instance, - M: IsFullDuplex, -{ - type Error = Error; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.spi.write_bytes(words)?; - self.spi.flush()?; - Ok(()) - } -} - -pub mod dma { - use core::{marker::PhantomData, mem}; - - use embedded_dma::{ReadBuffer, WriteBuffer}; - - #[cfg(any(esp32, esp32s2))] - use super::Spi3Instance; - use super::{ - Address, - Command, - DuplexMode, - Instance, - InstanceDma, - IsFullDuplex, - IsHalfDuplex, - Spi, - Spi2Instance, - SpiDataMode, - MAX_DMA_SIZE, - }; - #[cfg(any(esp32, esp32s2))] - use crate::dma::Spi3Peripheral; - use crate::{ - dma::{Channel, DmaTransfer, DmaTransferRxTx, Rx, Spi2Peripheral, SpiPeripheral, Tx}, - peripheral::PeripheralRef, - }; - - pub trait WithDmaSpi2<'d, T, RX, TX, P, M> - where - T: Instance + Spi2Instance, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - fn with_dma(self, channel: Channel) -> SpiDma<'d, T, TX, RX, P, M>; - } - - #[cfg(any(esp32, esp32s2))] - pub trait WithDmaSpi3<'d, T, RX, TX, P, M> - where - T: Instance + Spi3Instance, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - fn with_dma(self, channel: Channel) -> SpiDma<'d, T, TX, RX, P, M>; - } - - impl<'d, T, RX, TX, P, M> WithDmaSpi2<'d, T, RX, TX, P, M> for Spi<'d, T, M> - where - T: Instance + Spi2Instance, - TX: Tx, - RX: Rx, - P: SpiPeripheral + Spi2Peripheral, - M: DuplexMode, - { - fn with_dma(self, mut channel: Channel) -> SpiDma<'d, T, TX, RX, P, M> { - channel.tx.init_channel(); // no need to call this for both, TX and RX - - SpiDma { - spi: self.spi, - channel, - _mode: PhantomData::default(), - } - } - } - - #[cfg(any(esp32, esp32s2))] - impl<'d, T, RX, TX, P, M> WithDmaSpi3<'d, T, RX, TX, P, M> for Spi<'d, T, M> - where - T: Instance + Spi3Instance, - TX: Tx, - RX: Rx, - P: SpiPeripheral + Spi3Peripheral, - M: DuplexMode, - { - fn with_dma(self, mut channel: Channel) -> SpiDma<'d, T, TX, RX, P, M> { - channel.tx.init_channel(); // no need to call this for both, TX and RX - - SpiDma { - spi: self.spi, - channel, - _mode: PhantomData::default(), - } - } - } - /// An in-progress DMA transfer - pub struct SpiDmaTransferRxTx<'d, T, TX, RX, P, RBUFFER, TBUFFER, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - spi_dma: SpiDma<'d, T, TX, RX, P, M>, - rbuffer: RBUFFER, - tbuffer: TBUFFER, - } - - impl<'d, T, TX, RX, P, RXBUF, TXBUF, M> - DmaTransferRxTx> - for SpiDmaTransferRxTx<'d, T, TX, RX, P, RXBUF, TXBUF, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - /// Wait for the DMA transfer to complete and return the buffers and the - /// SPI instance. - fn wait(mut self) -> (RXBUF, TXBUF, SpiDma<'d, T, TX, RX, P, M>) { - self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough - - // `DmaTransfer` needs to have a `Drop` implementation, because we accept - // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let rbuffer = core::ptr::read(&self.rbuffer); - let tbuffer = core::ptr::read(&self.tbuffer); - let payload = core::ptr::read(&self.spi_dma); - mem::forget(self); - (rbuffer, tbuffer, payload) - } - } - } - - impl<'d, T, TX, RX, P, RXBUF, TXBUF, M> Drop - for SpiDmaTransferRxTx<'d, T, TX, RX, P, RXBUF, TXBUF, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - fn drop(&mut self) { - self.spi_dma.spi.flush().ok(); - } - } - - /// An in-progress DMA transfer. - pub struct SpiDmaTransfer<'d, T, TX, RX, P, BUFFER, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - spi_dma: SpiDma<'d, T, TX, RX, P, M>, - buffer: BUFFER, - } - - impl<'d, T, TX, RX, P, BUFFER, M> DmaTransfer> - for SpiDmaTransfer<'d, T, TX, RX, P, BUFFER, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - /// Wait for the DMA transfer to complete and return the buffers and the - /// SPI instance. - fn wait(mut self) -> (BUFFER, SpiDma<'d, T, TX, RX, P, M>) { - self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough - - // `DmaTransfer` needs to have a `Drop` implementation, because we accept - // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = core::ptr::read(&self.buffer); - let payload = core::ptr::read(&self.spi_dma); - mem::forget(self); - (buffer, payload) - } - } - } - - impl<'d, T, TX, RX, P, BUFFER, M> Drop for SpiDmaTransfer<'d, T, TX, RX, P, BUFFER, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - fn drop(&mut self) { - self.spi_dma.spi.flush().ok(); - } - } - - /// A DMA capable SPI instance. - pub struct SpiDma<'d, T, TX, RX, P, M> - where - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: DuplexMode, - { - pub(crate) spi: PeripheralRef<'d, T>, - pub(crate) channel: Channel, - _mode: PhantomData, - } - - impl<'d, T, TX, RX, P, M> SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - /// Perform a DMA write. - /// - /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI - /// instance. The maximum amount of data to be sent is 32736 - /// bytes. - pub fn dma_write( - mut self, - words: TXBUF, - ) -> Result, super::Error> - where - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { words.read_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(super::Error::MaxDmaTransferSizeExceeded); - } - - self.spi - .start_write_bytes_dma(ptr, len, &mut self.channel.tx)?; - Ok(SpiDmaTransfer { - spi_dma: self, - buffer: words, - }) - } - - /// Perform a DMA read. - /// - /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI - /// instance. The maximum amount of data to be received is 32736 - /// bytes. - pub fn dma_read( - mut self, - mut words: RXBUF, - ) -> Result, super::Error> - where - RXBUF: WriteBuffer, - { - let (ptr, len) = unsafe { words.write_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(super::Error::MaxDmaTransferSizeExceeded); - } - - self.spi - .start_read_bytes_dma(ptr, len, &mut self.channel.rx)?; - Ok(SpiDmaTransfer { - spi_dma: self, - buffer: words, - }) - } - - /// Perform a DMA transfer. - /// - /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI - /// instance. The maximum amount of data to be sent/received is - /// 32736 bytes. - pub fn dma_transfer( - mut self, - words: TXBUF, - mut read_buffer: RXBUF, - ) -> Result, super::Error> - where - TXBUF: ReadBuffer, - RXBUF: WriteBuffer, - { - let (write_ptr, write_len) = unsafe { words.read_buffer() }; - let (read_ptr, read_len) = unsafe { read_buffer.write_buffer() }; - - if write_len > MAX_DMA_SIZE || read_len > MAX_DMA_SIZE { - return Err(super::Error::MaxDmaTransferSizeExceeded); - } - - self.spi.start_transfer_dma( - write_ptr, - write_len, - read_ptr, - read_len, - &mut self.channel.tx, - &mut self.channel.rx, - )?; - Ok(SpiDmaTransferRxTx { - spi_dma: self, - rbuffer: read_buffer, - tbuffer: words, - }) - } - } - - impl<'d, T, TX, RX, P, M> SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsHalfDuplex, - { - pub fn read( - mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - mut buffer: RXBUF, - ) -> Result, super::Error> - where - RXBUF: WriteBuffer, - { - let (ptr, len) = unsafe { buffer.write_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(super::Error::MaxDmaTransferSizeExceeded); - } - - self.spi.init_half_duplex( - false, - !cmd.is_none(), - !address.is_none(), - false, - dummy != 0, - len == 0, - ); - self.spi - .init_spi_data_mode(cmd.mode(), address.mode(), data_mode); - - // set cmd, address, dummy cycles - let reg_block = self.spi.register_block(); - if !cmd.is_none() { - reg_block.user2.modify(|_, w| { - w.usr_command_bitlen() - .variant((cmd.width() - 1) as u8) - .usr_command_value() - .variant(cmd.value()) - }); - } - - #[cfg(not(esp32))] - if !address.is_none() { - reg_block - .user1 - .modify(|_, w| w.usr_addr_bitlen().variant((address.width() - 1) as u8)); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| w.usr_addr_value().variant(addr)); - } - - #[cfg(esp32)] - if !address.is_none() { - reg_block.user1.modify(|r, w| unsafe { - w.bits(r.bits() & !(0x3f << 26) | (((address.width() - 1) as u32) & 0x3f) << 26) - }); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| unsafe { w.bits(addr) }); - } - - if dummy > 0 { - reg_block - .user1 - .modify(|_, w| w.usr_dummy_cyclelen().variant(dummy - 1)); - } - - self.spi - .start_read_bytes_dma(ptr, len, &mut self.channel.rx)?; - Ok(SpiDmaTransfer { - spi_dma: self, - buffer: buffer, - }) - } - - pub fn write( - mut self, - data_mode: SpiDataMode, - cmd: Command, - address: Address, - dummy: u8, - buffer: TXBUF, - ) -> Result, super::Error> - where - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { buffer.read_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(super::Error::MaxDmaTransferSizeExceeded); - } - - self.spi.init_half_duplex( - true, - !cmd.is_none(), - !address.is_none(), - false, - dummy != 0, - len == 0, - ); - self.spi - .init_spi_data_mode(cmd.mode(), address.mode(), data_mode); - - // set cmd, address, dummy cycles - let reg_block = self.spi.register_block(); - if !cmd.is_none() { - reg_block.user2.modify(|_, w| { - w.usr_command_bitlen() - .variant((cmd.width() - 1) as u8) - .usr_command_value() - .variant(cmd.value()) - }); - } - - #[cfg(not(esp32))] - if !address.is_none() { - reg_block - .user1 - .modify(|_, w| w.usr_addr_bitlen().variant((address.width() - 1) as u8)); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| w.usr_addr_value().variant(addr)); - } - - #[cfg(esp32)] - if !address.is_none() { - reg_block.user1.modify(|r, w| unsafe { - w.bits(r.bits() & !(0x3f << 26) | (((address.width() - 1) as u32) & 0x3f) << 26) - }); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| unsafe { w.bits(addr) }); - } - - if dummy > 0 { - reg_block - .user1 - .modify(|_, w| w.usr_dummy_cyclelen().variant(dummy - 1)); - } - - self.spi - .start_write_bytes_dma(ptr, len, &mut self.channel.tx)?; - Ok(SpiDmaTransfer { - spi_dma: self, - buffer: buffer, - }) - } - } - - impl<'d, T, TX, RX, P, M> embedded_hal::blocking::spi::Transfer for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - type Error = super::Error; - - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - self.spi - .transfer_in_place_dma(words, &mut self.channel.tx, &mut self.channel.rx) - } - } - - impl<'d, T, TX, RX, P, M> embedded_hal::blocking::spi::Write for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - type Error = super::Error; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.spi.write_bytes_dma(words, &mut self.channel.tx)?; - self.spi.flush()?; - Ok(()) - } - } - - #[cfg(feature = "async")] - mod asynch { - use super::*; - - impl<'d, T, TX, RX, P, M> embedded_hal_async::spi::SpiBusWrite for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - for chunk in words.chunks(MAX_DMA_SIZE) { - self.spi.start_write_bytes_dma( - chunk.as_ptr(), - chunk.len(), - &mut self.channel.tx, - )?; - - crate::dma::asynch::DmaTxFuture::new(&mut self.channel.tx).await; - - // FIXME: in the future we should use the peripheral DMA status registers to - // await on both the dma transfer _and_ the peripherals status - self.spi.flush()?; - } - - Ok(()) - } - } - - impl<'d, T, TX, RX, P, M> embedded_hal_async::spi::SpiBusFlush for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - async fn flush(&mut self) -> Result<(), Self::Error> { - // TODO use async flush in the future - self.spi.flush() - } - } - - impl<'d, T, TX, RX, P, M> embedded_hal_async::spi::SpiBusRead for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.spi.start_read_bytes_dma( - words.as_mut_ptr(), - words.len(), - &mut self.channel.rx, - )?; - - crate::dma::asynch::DmaRxFuture::new(&mut self.channel.rx).await; - - Ok(()) - } - } - - impl<'d, T, TX, RX, P, M> embedded_hal_async::spi::SpiBus for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - async fn transfer<'a>( - &'a mut self, - read: &'a mut [u8], - write: &'a [u8], - ) -> Result<(), Self::Error> { - let mut idx = 0; - loop { - let write_idx = isize::min(idx, write.len() as isize); - let write_len = usize::min(write.len() - idx as usize, MAX_DMA_SIZE); - - let read_idx = isize::min(idx, read.len() as isize); - let read_len = usize::min(read.len() - idx as usize, MAX_DMA_SIZE); - - self.spi.start_transfer_dma( - unsafe { write.as_ptr().offset(write_idx) }, - write_len, - unsafe { read.as_mut_ptr().offset(read_idx) }, - read_len, - &mut self.channel.tx, - &mut self.channel.rx, - )?; - - embassy_futures::join::join( - crate::dma::asynch::DmaTxFuture::new(&mut self.channel.tx), - crate::dma::asynch::DmaRxFuture::new(&mut self.channel.rx), - ) - .await; - - // FIXME: in the future we should use the peripheral DMA status registers to - // await on both the dma transfer _and_ the peripherals status - self.spi.flush()?; - - idx += MAX_DMA_SIZE as isize; - if idx >= write.len() as isize && idx >= read.len() as isize { - break; - } - } - - Ok(()) - } - - async fn transfer_in_place<'a>( - &'a mut self, - words: &'a mut [u8], - ) -> Result<(), Self::Error> { - for chunk in words.chunks_mut(MAX_DMA_SIZE) { - self.spi.start_transfer_dma( - chunk.as_ptr(), - chunk.len(), - chunk.as_mut_ptr(), - chunk.len(), - &mut self.channel.tx, - &mut self.channel.rx, - )?; - - embassy_futures::join::join( - crate::dma::asynch::DmaTxFuture::new(&mut self.channel.tx), - crate::dma::asynch::DmaRxFuture::new(&mut self.channel.rx), - ) - .await; - - // FIXME: in the future we should use the peripheral DMA status registers to - // await on both the dma transfer _and_ the peripherals status - self.spi.flush()?; - } - - Ok(()) - } - } - } - - #[cfg(feature = "eh1")] - mod ehal1 { - use embedded_hal_1::spi::{SpiBus, SpiBusFlush, SpiBusRead, SpiBusWrite}; - - use super::{super::InstanceDma, SpiDma, SpiPeripheral}; - use crate::{ - dma::{Rx, Tx}, - spi::IsFullDuplex, - }; - - impl<'d, T, TX, RX, P, M> embedded_hal_1::spi::ErrorType for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - type Error = super::super::Error; - } - - impl<'d, T, TX, RX, P, M> SpiBusWrite for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - /// See also: [`write_bytes`]. - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.spi.write_bytes_dma(words, &mut self.channel.tx)?; - self.flush() - } - } - - impl<'d, T, TX, RX, P, M> SpiBusRead for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.spi - .transfer_dma(&[], words, &mut self.channel.tx, &mut self.channel.rx)?; - self.flush() - } - } - - impl<'d, T, TX, RX, P, M> SpiBus for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - /// Write out data from `write`, read response into `read`. - /// - /// `read` and `write` are allowed to have different lengths. If - /// `write` is longer, all other bytes received are - /// discarded. If `read` is longer, [`EMPTY_WRITE_PAD`] - /// is written out as necessary until enough bytes have - /// been read. Reading and writing happens - /// simultaneously. - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - self.spi - .transfer_dma(write, read, &mut self.channel.tx, &mut self.channel.rx)?; - self.flush() - } - - /// Transfer data in place. - /// - /// Writes data from `words` out on the bus and stores the reply - /// into `words`. A convenient wrapper around - /// [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and - /// [`read`](SpiBusRead::read). - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.spi.transfer_in_place_dma( - words, - &mut self.channel.tx, - &mut self.channel.rx, - )?; - self.flush() - } - } - - impl<'d, T, TX, RX, P, M> SpiBusFlush for SpiDma<'d, T, TX, RX, P, M> - where - T: InstanceDma, - TX: Tx, - RX: Rx, - P: SpiPeripheral, - M: IsFullDuplex, - { - fn flush(&mut self) -> Result<(), Self::Error> { - self.spi.flush() - } - } - } -} - -#[cfg(feature = "eh1")] -pub use ehal1::*; - -#[cfg(feature = "eh1")] -mod ehal1 { - use core::cell::RefCell; - - use embedded_hal_1::spi::{ - self, - ErrorType, - Operation, - SpiBus, - SpiBusFlush, - SpiBusRead, - SpiBusWrite, - SpiDevice, - SpiDeviceRead, - SpiDeviceWrite, - }; - use embedded_hal_nb::spi::FullDuplex; - - use super::*; - use crate::gpio::OutputPin; - - impl embedded_hal_1::spi::ErrorType for Spi<'_, T, M> { - type Error = super::Error; - } - - impl FullDuplex for Spi<'_, T, M> - where - T: Instance, - M: IsFullDuplex, - { - fn read(&mut self) -> nb::Result { - self.spi.read_byte() - } - - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.spi.write_byte(word) - } - } - - impl SpiBusWrite for Spi<'_, T, M> - where - T: Instance, - M: IsFullDuplex, - { - /// See also: [`write_bytes`]. - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.spi.write_bytes(words) - } - } - - impl SpiBusRead for Spi<'_, T, M> - where - T: Instance, - M: IsFullDuplex, - { - /// See also: [`read_bytes`]. - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.spi.read_bytes(words) - } - } - - impl SpiBus for Spi<'_, T, M> - where - T: Instance, - M: IsFullDuplex, - { - /// Write out data from `write`, read response into `read`. - /// - /// `read` and `write` are allowed to have different lengths. If `write` - /// is longer, all other bytes received are discarded. If `read` - /// is longer, [`EMPTY_WRITE_PAD`] is written out as necessary - /// until enough bytes have been read. Reading and writing happens - /// simultaneously. - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - // Optimizations - if read.len() == 0 { - SpiBusWrite::write(self, write)?; - } else if write.len() == 0 { - SpiBusRead::read(self, read)?; - } - - let mut write_from = 0; - let mut read_from = 0; - - loop { - // How many bytes we write in this chunk - let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from); - let write_to = write_from + write_inc; - // How many bytes we read in this chunk - let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from); - let read_to = read_from + read_inc; - - if (write_inc == 0) && (read_inc == 0) { - break; - } - - if write_to < read_to { - // Read more than we write, must pad writing part with zeros - let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE]; - empty[0..write_inc].copy_from_slice(&write[write_from..write_to]); - SpiBusWrite::write(self, &empty)?; - } else { - SpiBusWrite::write(self, &write[write_from..write_to])?; - } - - SpiBusFlush::flush(self)?; - - if read_inc > 0 { - self.spi - .read_bytes_from_fifo(&mut read[read_from..read_to])?; - } - - write_from = write_to; - read_from = read_to; - } - Ok(()) - } - - /// Transfer data in place. - /// - /// Writes data from `words` out on the bus and stores the reply into - /// `words`. A convenient wrapper around - /// [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and - /// [`read`](SpiBusRead::read). - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - // Since we have the traits so neatly implemented above, use them! - for chunk in words.chunks_mut(FIFO_SIZE) { - SpiBusWrite::write(self, chunk)?; - SpiBusFlush::flush(self)?; - self.spi.read_bytes_from_fifo(chunk)?; - } - Ok(()) - } - } - - impl SpiBusFlush for Spi<'_, T, M> - where - T: Instance, - M: IsFullDuplex, - { - fn flush(&mut self) -> Result<(), Self::Error> { - self.spi.flush() - } - } - - /// SPI bus controller. - /// - /// Has exclusive access to an SPI bus, which is managed via a `Mutex`. Used - /// as basis for the [`SpiBusDevice`] implementation. Note that the - /// wrapped [`RefCell`] is used solely to achieve interior mutability. - pub struct SpiBusController<'d, I: Instance, M: IsFullDuplex> { - lock: critical_section::Mutex>>, - } - - impl<'d, I: Instance, M: IsFullDuplex> SpiBusController<'d, I, M> { - /// Create a new controller from an SPI bus instance. - /// - /// Takes ownership of the SPI bus in the process. Afterwards, the SPI - /// bus can only be accessed via instances of [`SpiBusDevice`]. - pub fn from_spi(bus: Spi<'d, I, M>) -> Self { - SpiBusController { - lock: critical_section::Mutex::new(RefCell::new(bus)), - } - } - - pub fn add_device<'a, CS: OutputPin>(&'a self, cs: CS) -> SpiBusDevice<'a, 'd, I, CS, M> { - SpiBusDevice::new(self, cs) - } - } - - impl<'d, I: Instance, M: IsFullDuplex> ErrorType for SpiBusController<'d, I, M> { - type Error = spi::ErrorKind; - } - - /// An SPI device on a shared SPI bus. - /// - /// Provides device specific access on a shared SPI bus. Enables attaching - /// multiple SPI devices to the same bus, each with its own CS line, and - /// performing safe transfers on them. - pub struct SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - bus: &'a SpiBusController<'d, I, M>, - cs: CS, - } - - impl<'a, 'd, I, CS, M> SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - pub fn new(bus: &'a SpiBusController<'d, I, M>, mut cs: CS) -> Self { - cs.set_to_push_pull_output().set_output_high(true); - SpiBusDevice { bus, cs } - } - } - - impl<'a, 'd, I, CS, M> ErrorType for SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - type Error = spi::ErrorKind; - } - - impl<'a, 'd, I, CS, M> SpiDeviceRead for SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> { - critical_section::with(|cs| { - let mut bus = self.bus.lock.borrow_ref_mut(cs); - self.cs.connect_peripheral_to_output(bus.spi.cs_signal()); - - let op_res = operations - .iter_mut() - .try_for_each(|buf| SpiBusRead::read(&mut (*bus), buf)); - - // On failure, it's important to still flush and de-assert CS. - let flush_res = bus.flush(); - self.cs.disconnect_peripheral_from_output(); - - op_res.map_err(|_| spi::ErrorKind::Other)?; - flush_res.map_err(|_| spi::ErrorKind::Other)?; - - Ok(()) - }) - } - } - - impl<'a, 'd, I, CS, M> SpiDeviceWrite for SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> { - critical_section::with(|cs| { - let mut bus = self.bus.lock.borrow_ref_mut(cs); - self.cs.connect_peripheral_to_output(bus.spi.cs_signal()); - - let op_res = operations - .iter() - .try_for_each(|buf| SpiBusWrite::write(&mut (*bus), buf)); - - // On failure, it's important to still flush and de-assert CS. - let flush_res = bus.flush(); - self.cs.disconnect_peripheral_from_output(); - - op_res.map_err(|_| spi::ErrorKind::Other)?; - flush_res.map_err(|_| spi::ErrorKind::Other)?; - - Ok(()) - }) - } - } - - impl<'a, 'd, I, CS, M> SpiDevice for SpiBusDevice<'a, 'd, I, CS, M> - where - I: Instance, - CS: OutputPin, - M: IsFullDuplex, - { - fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { - critical_section::with(|cs| { - let mut bus = self.bus.lock.borrow_ref_mut(cs); - self.cs.connect_peripheral_to_output(bus.spi.cs_signal()); - - let op_res = operations.iter_mut().try_for_each(|op| match op { - Operation::Read(buf) => SpiBusRead::read(&mut (*bus), buf), - Operation::Write(buf) => SpiBusWrite::write(&mut (*bus), buf), - Operation::Transfer(read, write) => bus.transfer(read, write), - Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), - }); - - // On failure, it's important to still flush and de-assert CS. - let flush_res = bus.flush(); - self.cs.disconnect_peripheral_from_output(); - - op_res.map_err(|_| spi::ErrorKind::Other)?; - flush_res.map_err(|_| spi::ErrorKind::Other)?; - - Ok(()) - }) - } - } -} - -pub trait InstanceDma: Instance -where - TX: Tx, - RX: Rx, -{ - fn transfer_in_place_dma<'w>( - &mut self, - words: &'w mut [u8], - tx: &mut TX, - rx: &mut RX, - ) -> Result<&'w [u8], Error> { - for chunk in words.chunks_mut(MAX_DMA_SIZE) { - self.start_transfer_dma( - chunk.as_ptr(), - chunk.len(), - chunk.as_mut_ptr(), - chunk.len(), - tx, - rx, - )?; - - while !tx.is_done() && !rx.is_done() {} - self.flush().unwrap(); - } - - return Ok(words); - } - - fn transfer_dma<'w>( - &mut self, - write_buffer: &'w [u8], - read_buffer: &'w mut [u8], - tx: &mut TX, - rx: &mut RX, - ) -> Result<&'w [u8], Error> { - let mut idx = 0; - loop { - let write_idx = isize::min(idx, write_buffer.len() as isize); - let write_len = usize::min(write_buffer.len() - idx as usize, MAX_DMA_SIZE); - - let read_idx = isize::min(idx, read_buffer.len() as isize); - let read_len = usize::min(read_buffer.len() - idx as usize, MAX_DMA_SIZE); - - self.start_transfer_dma( - unsafe { write_buffer.as_ptr().offset(write_idx) }, - write_len, - unsafe { read_buffer.as_mut_ptr().offset(read_idx) }, - read_len, - tx, - rx, - )?; - - while !tx.is_done() && !rx.is_done() {} - self.flush().unwrap(); - - idx += MAX_DMA_SIZE as isize; - if idx >= write_buffer.len() as isize && idx >= read_buffer.len() as isize { - break; - } - } - - return Ok(read_buffer); - } - - fn start_transfer_dma<'w>( - &mut self, - write_buffer_ptr: *const u8, - write_buffer_len: usize, - read_buffer_ptr: *mut u8, - read_buffer_len: usize, - tx: &mut TX, - rx: &mut RX, - ) -> Result<(), Error> { - let reg_block = self.register_block(); - self.configure_datalen(usize::max(read_buffer_len, write_buffer_len) as u32 * 8); - - tx.is_done(); - rx.is_done(); - - self.enable_dma(); - self.update(); - - reset_dma_before_load_dma_dscr(reg_block); - tx.prepare_transfer( - self.dma_peripheral(), - false, - write_buffer_ptr, - write_buffer_len, - )?; - rx.prepare_transfer( - false, - self.dma_peripheral(), - read_buffer_ptr, - read_buffer_len, - )?; - - self.clear_dma_interrupts(); - reset_dma_before_usr_cmd(reg_block); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - Ok(()) - } - - fn write_bytes_dma<'w>(&mut self, words: &'w [u8], tx: &mut TX) -> Result<&'w [u8], Error> { - for chunk in words.chunks(MAX_DMA_SIZE) { - self.start_write_bytes_dma(chunk.as_ptr(), chunk.len(), tx)?; - - while !tx.is_done() {} - self.flush().unwrap(); // seems "is_done" doesn't work as intended? - } - - return Ok(words); - } - - fn start_write_bytes_dma<'w>( - &mut self, - ptr: *const u8, - len: usize, - tx: &mut TX, - ) -> Result<(), Error> { - let reg_block = self.register_block(); - self.configure_datalen(len as u32 * 8); - - tx.is_done(); - - self.enable_dma(); - self.update(); - - reset_dma_before_load_dma_dscr(reg_block); - tx.prepare_transfer(self.dma_peripheral(), false, ptr, len)?; - - self.clear_dma_interrupts(); - reset_dma_before_usr_cmd(reg_block); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - return Ok(()); - } - - fn start_read_bytes_dma<'w>( - &mut self, - ptr: *mut u8, - len: usize, - rx: &mut RX, - ) -> Result<(), Error> { - let reg_block = self.register_block(); - self.configure_datalen(len as u32 * 8); - - rx.is_done(); - - self.enable_dma(); - self.update(); - - reset_dma_before_load_dma_dscr(reg_block); - rx.prepare_transfer(false, self.dma_peripheral(), ptr, len)?; - - self.clear_dma_interrupts(); - reset_dma_before_usr_cmd(reg_block); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - return Ok(()); - } - - fn dma_peripheral(&self) -> DmaPeripheral { - match self.spi_num() { - 2 => DmaPeripheral::Spi2, - #[cfg(any(esp32, esp32s2))] - 3 => DmaPeripheral::Spi3, - _ => panic!("Illegal SPI instance"), - } - } - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - fn enable_dma(&self) { - let reg_block = self.register_block(); - reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit()); - reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit()); - } - - #[cfg(any(esp32, esp32s2))] - fn enable_dma(&self) { - // for non GDMA this is done in `assign_tx_device` / `assign_rx_device` - } - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - fn clear_dma_interrupts(&self) { - let reg_block = self.register_block(); - reg_block.dma_int_clr.write(|w| { - w.dma_infifo_full_err_int_clr() - .set_bit() - .dma_outfifo_empty_err_int_clr() - .set_bit() - .trans_done_int_clr() - .set_bit() - .mst_rx_afifo_wfull_err_int_clr() - .set_bit() - .mst_tx_afifo_rempty_err_int_clr() - .set_bit() - }); - } - - #[cfg(any(esp32, esp32s2))] - fn clear_dma_interrupts(&self) { - let reg_block = self.register_block(); - reg_block.dma_int_clr.write(|w| { - w.inlink_dscr_empty_int_clr() - .set_bit() - .outlink_dscr_error_int_clr() - .set_bit() - .inlink_dscr_error_int_clr() - .set_bit() - .in_done_int_clr() - .set_bit() - .in_err_eof_int_clr() - .set_bit() - .in_suc_eof_int_clr() - .set_bit() - .out_done_int_clr() - .set_bit() - .out_eof_int_clr() - .set_bit() - .out_total_eof_int_clr() - .set_bit() - }); - } -} - -#[cfg(not(any(esp32, esp32s2)))] -fn reset_dma_before_usr_cmd(reg_block: &RegisterBlock) { - reg_block.dma_conf.modify(|_, w| { - w.rx_afifo_rst() - .set_bit() - .buf_afifo_rst() - .set_bit() - .dma_afifo_rst() - .set_bit() - }); -} - -#[cfg(any(esp32, esp32s2))] -fn reset_dma_before_usr_cmd(_reg_block: &RegisterBlock) {} - -#[cfg(not(any(esp32, esp32s2)))] -fn reset_dma_before_load_dma_dscr(_reg_block: &RegisterBlock) {} - -#[cfg(any(esp32, esp32s2))] -fn reset_dma_before_load_dma_dscr(reg_block: &RegisterBlock) { - reg_block.dma_conf.modify(|_, w| { - w.out_rst() - .set_bit() - .in_rst() - .set_bit() - .ahbm_fifo_rst() - .set_bit() - .ahbm_rst() - .set_bit() - }); - - reg_block.dma_conf.modify(|_, w| { - w.out_rst() - .clear_bit() - .in_rst() - .clear_bit() - .ahbm_fifo_rst() - .clear_bit() - .ahbm_rst() - .clear_bit() - }); -} - -impl InstanceDma for crate::peripherals::SPI2 -where - TX: Tx, - RX: Rx, -{ -} - -#[cfg(any(esp32, esp32s2, esp32s3))] -impl InstanceDma for crate::peripherals::SPI3 -where - TX: Tx, - RX: Rx, -{ -} - -pub trait ExtendedInstance: Instance { - fn sio0_input_signal(&self) -> InputSignal; - - fn sio1_output_signal(&self) -> OutputSignal; - - fn sio2_output_signal(&self) -> OutputSignal; - - fn sio2_input_signal(&self) -> InputSignal; - - fn sio3_output_signal(&self) -> OutputSignal; - - fn sio3_input_signal(&self) -> InputSignal; -} - -pub trait Instance { - fn register_block(&self) -> &RegisterBlock; - - fn sclk_signal(&self) -> OutputSignal; - - fn mosi_signal(&self) -> OutputSignal; - - fn miso_signal(&self) -> InputSignal; - - fn cs_signal(&self) -> OutputSignal; - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl); - - fn spi_num(&self) -> u8; - - /// Initialize for full-duplex 1 bit mode - fn init(&mut self) { - let reg_block = self.register_block(); - reg_block.user.modify(|_, w| { - w.usr_miso_highpart() - .clear_bit() - .usr_miso_highpart() - .clear_bit() - .doutdin() - .set_bit() - .usr_miso() - .set_bit() - .usr_mosi() - .set_bit() - .cs_hold() - .set_bit() - .usr_dummy_idle() - .set_bit() - .usr_addr() - .clear_bit() - .usr_command() - .clear_bit() - }); - - #[cfg(not(any(esp32, esp32s2)))] - reg_block.clk_gate.modify(|_, w| { - w.clk_en() - .set_bit() - .mst_clk_active() - .set_bit() - .mst_clk_sel() - .set_bit() - }); - - #[cfg(esp32c6)] - unsafe { - let pcr = &*esp32c6::PCR::PTR; - - // use default clock source PLL_F80M_CLK - pcr.spi2_clkm_conf.modify(|_, w| w.spi2_clkm_sel().bits(1)); - } - - #[cfg(not(any(esp32, esp32s2)))] - reg_block.ctrl.modify(|_, w| { - w.q_pol() - .clear_bit() - .d_pol() - .clear_bit() - .hold_pol() - .clear_bit() - }); - - #[cfg(esp32s2)] - reg_block - .ctrl - .modify(|_, w| w.q_pol().clear_bit().d_pol().clear_bit().wp().clear_bit()); - - #[cfg(esp32)] - reg_block.ctrl.modify(|_, w| w.wp().clear_bit()); - - #[cfg(not(esp32))] - reg_block.misc.write(|w| unsafe { w.bits(0) }); - - reg_block.slave.write(|w| unsafe { w.bits(0) }); - } - - #[cfg(not(esp32))] - fn init_spi_data_mode( - &mut self, - cmd_mode: SpiDataMode, - address_mode: SpiDataMode, - data_mode: SpiDataMode, - ) { - let reg_block = self.register_block(); - match cmd_mode { - SpiDataMode::Single => reg_block - .ctrl - .modify(|_, w| w.fcmd_dual().clear_bit().fcmd_quad().clear_bit()), - SpiDataMode::Dual => reg_block - .ctrl - .modify(|_, w| w.fcmd_dual().set_bit().fcmd_quad().clear_bit()), - SpiDataMode::Quad => reg_block - .ctrl - .modify(|_, w| w.fcmd_dual().clear_bit().fcmd_quad().set_bit()), - } - - match address_mode { - SpiDataMode::Single => reg_block - .ctrl - .modify(|_, w| w.faddr_dual().clear_bit().faddr_quad().clear_bit()), - SpiDataMode::Dual => reg_block - .ctrl - .modify(|_, w| w.faddr_dual().set_bit().faddr_quad().clear_bit()), - SpiDataMode::Quad => reg_block - .ctrl - .modify(|_, w| w.faddr_dual().clear_bit().faddr_quad().set_bit()), - } - - match data_mode { - SpiDataMode::Single => { - reg_block - .ctrl - .modify(|_, w| w.fread_dual().clear_bit().fread_quad().clear_bit()); - reg_block - .user - .modify(|_, w| w.fwrite_dual().clear_bit().fwrite_quad().clear_bit()); - } - SpiDataMode::Dual => { - reg_block - .ctrl - .modify(|_, w| w.fread_dual().set_bit().fread_quad().clear_bit()); - reg_block - .user - .modify(|_, w| w.fwrite_dual().set_bit().fwrite_quad().clear_bit()); - } - SpiDataMode::Quad => { - reg_block - .ctrl - .modify(|_, w| w.fread_quad().set_bit().fread_dual().clear_bit()); - reg_block - .user - .modify(|_, w| w.fwrite_quad().set_bit().fwrite_dual().clear_bit()); - } - } - } - - #[cfg(esp32)] - fn init_spi_data_mode( - &mut self, - cmd_mode: SpiDataMode, - address_mode: SpiDataMode, - data_mode: SpiDataMode, - ) { - let reg_block = self.register_block(); - match cmd_mode { - SpiDataMode::Single => (), - _ => panic!("Only 1-bit command supported"), - } - - match (address_mode, data_mode) { - (SpiDataMode::Single, SpiDataMode::Single) => { - reg_block.ctrl.modify(|_, w| { - w.fread_dio() - .clear_bit() - .fread_qio() - .clear_bit() - .fread_dual() - .clear_bit() - .fread_quad() - .clear_bit() - }); - - reg_block.user.modify(|_, w| { - w.fwrite_dio() - .clear_bit() - .fwrite_qio() - .clear_bit() - .fwrite_dual() - .clear_bit() - .fwrite_quad() - .clear_bit() - }); - } - (SpiDataMode::Single, SpiDataMode::Dual) => { - reg_block.ctrl.modify(|_, w| { - w.fread_dio() - .clear_bit() - .fread_qio() - .clear_bit() - .fread_dual() - .set_bit() - .fread_quad() - .clear_bit() - }); - - reg_block.user.modify(|_, w| { - w.fwrite_dio() - .clear_bit() - .fwrite_qio() - .clear_bit() - .fwrite_dual() - .set_bit() - .fwrite_quad() - .clear_bit() - }); - } - (SpiDataMode::Single, SpiDataMode::Quad) => { - reg_block.ctrl.modify(|_, w| { - w.fread_dio() - .clear_bit() - .fread_qio() - .clear_bit() - .fread_dual() - .clear_bit() - .fread_quad() - .set_bit() - }); - - reg_block.user.modify(|_, w| { - w.fwrite_dio() - .clear_bit() - .fwrite_qio() - .clear_bit() - .fwrite_dual() - .clear_bit() - .fwrite_quad() - .set_bit() - }); - } - (SpiDataMode::Dual, SpiDataMode::Single) => { - panic!("Unsupported combination of data-modes") - } - (SpiDataMode::Dual, SpiDataMode::Dual) => { - reg_block.ctrl.modify(|_, w| { - w.fread_dio() - .set_bit() - .fread_qio() - .clear_bit() - .fread_dual() - .clear_bit() - .fread_quad() - .clear_bit() - }); - - reg_block.user.modify(|_, w| { - w.fwrite_dio() - .set_bit() - .fwrite_qio() - .clear_bit() - .fwrite_dual() - .clear_bit() - .fwrite_quad() - .clear_bit() - }); - } - (SpiDataMode::Dual, SpiDataMode::Quad) => { - panic!("Unsupported combination of data-modes") - } - (SpiDataMode::Quad, SpiDataMode::Single) => { - panic!("Unsupported combination of data-modes") - } - (SpiDataMode::Quad, SpiDataMode::Dual) => { - panic!("Unsupported combination of data-modes") - } - (SpiDataMode::Quad, SpiDataMode::Quad) => { - reg_block.ctrl.modify(|_, w| { - w.fread_dio() - .clear_bit() - .fread_qio() - .set_bit() - .fread_dual() - .clear_bit() - .fread_quad() - .clear_bit() - }); - - reg_block.user.modify(|_, w| { - w.fwrite_dio() - .clear_bit() - .fwrite_qio() - .set_bit() - .fwrite_dual() - .clear_bit() - .fwrite_quad() - .clear_bit() - }); - } - } - } - - // taken from https://github.com/apache/incubator-nuttx/blob/8267a7618629838231256edfa666e44b5313348e/arch/risc-v/src/esp32c3/esp32c3_spi.c#L496 - fn setup(&mut self, frequency: HertzU32, clocks: &Clocks) { - // FIXME: this might not be always true - let apb_clk_freq: HertzU32 = HertzU32::Hz(clocks.apb_clock.to_Hz()); - - let reg_val: u32; - let duty_cycle = 128; - - // In HW, n, h and l fields range from 1 to 64, pre ranges from 1 to 8K. - // The value written to register is one lower than the used value. - - if frequency > ((apb_clk_freq / 4) * 3) { - // Using APB frequency directly will give us the best result here. - reg_val = 1 << 31; - } else { - /* For best duty cycle resolution, we want n to be as close to 32 as - * possible, but we also need a pre/n combo that gets us as close as - * possible to the intended frequency. To do this, we bruteforce n and - * calculate the best pre to go along with that. If there's a choice - * between pre/n combos that give the same result, use the one with the - * higher n. - */ - - let mut pre: i32; - let mut bestn: i32 = -1; - let mut bestpre: i32 = -1; - let mut besterr: i32 = 0; - let mut errval: i32; - - /* Start at n = 2. We need to be able to set h/l so we have at least - * one high and one low pulse. - */ - - for n in 2..64 { - /* Effectively, this does: - * pre = round((APB_CLK_FREQ / n) / frequency) - */ - - pre = ((apb_clk_freq.raw() as i32 / n) + (frequency.raw() as i32 / 2)) - / frequency.raw() as i32; - - if pre <= 0 { - pre = 1; - } - - if pre > 16 { - pre = 16; - } - - errval = (apb_clk_freq.raw() as i32 / (pre as i32 * n as i32) - - frequency.raw() as i32) - .abs(); - if bestn == -1 || errval <= besterr { - besterr = errval; - bestn = n as i32; - bestpre = pre as i32; - } - } - - let n: i32 = bestn; - pre = bestpre as i32; - let l: i32 = n; - - /* Effectively, this does: - * h = round((duty_cycle * n) / 256) - */ - - let mut h: i32 = (duty_cycle * n + 127) / 256; - if h <= 0 { - h = 1; - } - - reg_val = (l as u32 - 1) - | ((h as u32 - 1) << 6) - | ((n as u32 - 1) << 12) - | ((pre as u32 - 1) << 18); - } - - self.register_block() - .clock - .write(|w| unsafe { w.bits(reg_val) }); - } - - #[cfg(not(esp32))] - fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self { - let reg_block = self.register_block(); - - match data_mode { - SpiMode::Mode0 => { - reg_block.misc.modify(|_, w| w.ck_idle_edge().clear_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit()); - } - SpiMode::Mode1 => { - reg_block.misc.modify(|_, w| w.ck_idle_edge().clear_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().set_bit()); - } - SpiMode::Mode2 => { - reg_block.misc.modify(|_, w| w.ck_idle_edge().set_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().set_bit()); - } - SpiMode::Mode3 => { - reg_block.misc.modify(|_, w| w.ck_idle_edge().set_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit()); - } - } - self - } - - #[cfg(esp32)] - fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self { - let reg_block = self.register_block(); - - match data_mode { - SpiMode::Mode0 => { - reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit()); - } - SpiMode::Mode1 => { - reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().set_bit()); - } - SpiMode::Mode2 => { - reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().set_bit()); - } - SpiMode::Mode3 => { - reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit()); - reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit()); - } - } - self - } - - fn ch_bus_freq(&mut self, frequency: HertzU32, clocks: &Clocks) { - // Disable clock source - #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] - self.register_block().clk_gate.modify(|_, w| { - w.clk_en() - .clear_bit() - .mst_clk_active() - .clear_bit() - .mst_clk_sel() - .clear_bit() - }); - - // Change clock frequency - self.setup(frequency, clocks); - - // Enable clock source - #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] - self.register_block().clk_gate.modify(|_, w| { - w.clk_en() - .set_bit() - .mst_clk_active() - .set_bit() - .mst_clk_sel() - .set_bit() - }); - } - - fn read_byte(&mut self) -> nb::Result { - let reg_block = self.register_block(); - - if reg_block.cmd.read().usr().bit_is_set() { - return Err(nb::Error::WouldBlock); - } - - Ok(u32::try_into(reg_block.w0.read().bits()).unwrap_or_default()) - } - - fn write_byte(&mut self, word: u8) -> nb::Result<(), Error> { - let reg_block = self.register_block(); - - if reg_block.cmd.read().usr().bit_is_set() { - return Err(nb::Error::WouldBlock); - } - - self.configure_datalen(8); - - reg_block.w0.write(|w| unsafe { w.bits(word.into()) }); - - self.update(); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - Ok(()) - } - - /// Write bytes to SPI. - /// - /// Copies the content of `words` in chunks of 64 bytes into the SPI - /// transmission FIFO. If `words` is longer than 64 bytes, multiple - /// sequential transfers are performed. This function will return before - /// all bytes of the last chunk to transmit have been sent to the wire. If - /// you must ensure that the whole messages was written correctly, use - /// [`flush`]. - // FIXME: See below. - fn write_bytes(&mut self, words: &[u8]) -> Result<(), Error> { - let reg_block = self.register_block(); - let num_chunks = words.len() / FIFO_SIZE; - - // The fifo has a limited fixed size, so the data must be chunked and then - // transmitted - for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() { - self.configure_datalen(chunk.len() as u32 * 8); - - let fifo_ptr = reg_block.w0.as_ptr(); - for i in (0..chunk.len()).step_by(4) { - let state = if chunk.len() - i < 4 { - chunk.len() % 4 - } else { - 0 - }; - let word = match state { - 0 => { - (chunk[i] as u32) - | (chunk[i + 1] as u32) << 8 - | (chunk[i + 2] as u32) << 16 - | (chunk[i + 3] as u32) << 24 - } - - 3 => { - (chunk[i] as u32) | (chunk[i + 1] as u32) << 8 | (chunk[i + 2] as u32) << 16 - } - - 2 => (chunk[i] as u32) | (chunk[i + 1] as u32) << 8, - - 1 => chunk[i] as u32, - - _ => panic!(), - }; - unsafe { - fifo_ptr.add(i / 4).write_volatile(word); - } - } - - self.update(); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - // Wait for all chunks to complete except the last one. - // The function is allowed to return before the bus is idle. - // see [embedded-hal flushing](https://docs.rs/embedded-hal/1.0.0-alpha.8/embedded_hal/spi/blocking/index.html#flushing) - // - // THIS IS NOT TRUE FOR EH 0.2.X! MAKE SURE TO FLUSH IN EH 0.2.X TRAIT - // IMPLEMENTATIONS! - if i < num_chunks { - while reg_block.cmd.read().usr().bit_is_set() { - // wait - } - } - } - Ok(()) - } - - /// Read bytes from SPI. - /// - /// Sends out a stuffing byte for every byte to read. This function doesn't - /// perform flushing. If you want to read the response to something you - /// have written before, consider using [`transfer`] instead. - fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Error> { - let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; - - for chunk in words.chunks_mut(FIFO_SIZE) { - self.write_bytes(&empty_array[0..chunk.len()])?; - self.flush()?; - self.read_bytes_from_fifo(chunk)?; - } - Ok(()) - } - - /// Read received bytes from SPI FIFO. - /// - /// Copies the contents of the SPI receive FIFO into `words`. This function - /// doesn't perform flushing. If you want to read the response to - /// something you have written before, consider using [`transfer`] - /// instead. - // FIXME: Using something like `core::slice::from_raw_parts` and - // `copy_from_slice` on the receive registers works only for the esp32 and - // esp32c3 varaints. The reason for this is unknown. - fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Error> { - let reg_block = self.register_block(); - - for chunk in words.chunks_mut(FIFO_SIZE) { - self.configure_datalen(chunk.len() as u32 * 8); - - let mut fifo_ptr = reg_block.w0.as_ptr(); - for index in (0..chunk.len()).step_by(4) { - let reg_val = unsafe { *fifo_ptr }; - let bytes = reg_val.to_le_bytes(); - - let len = usize::min(chunk.len(), index + 4) - index; - chunk[index..(index + len)].clone_from_slice(&bytes[0..len]); - - unsafe { - fifo_ptr = fifo_ptr.offset(1); - }; - } - } - - Ok(()) - } - - // Check if the bus is busy and if it is wait for it to be idle - fn flush(&mut self) -> Result<(), Error> { - let reg_block = self.register_block(); - - while reg_block.cmd.read().usr().bit_is_set() { - // wait for bus to be clear - } - Ok(()) - } - - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> { - for chunk in words.chunks_mut(FIFO_SIZE) { - self.write_bytes(chunk)?; - self.flush()?; - self.read_bytes_from_fifo(chunk)?; - } - - Ok(words) - } - - fn start_operation(&self) { - let reg_block = self.register_block(); - self.update(); - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - } - - fn init_half_duplex( - &mut self, - is_write: bool, - command_state: bool, - address_state: bool, - dummy_idle: bool, - dummy_state: bool, - no_mosi_miso: bool, - ) { - let reg_block = self.register_block(); - reg_block.user.modify(|_, w| { - w.usr_miso_highpart() - .clear_bit() - .usr_miso_highpart() - .clear_bit() - .doutdin() - .clear_bit() - .usr_miso() - .bit(!is_write && !no_mosi_miso) - .usr_mosi() - .bit(is_write && !no_mosi_miso) - .cs_hold() - .set_bit() - .usr_dummy_idle() - .bit(dummy_idle) - .usr_dummy() - .bit(dummy_state) - .usr_addr() - .bit(address_state) - .usr_command() - .bit(command_state) - }); - - #[cfg(not(any(esp32, esp32s2)))] - reg_block.clk_gate.modify(|_, w| { - w.clk_en() - .set_bit() - .mst_clk_active() - .set_bit() - .mst_clk_sel() - .set_bit() - }); - - #[cfg(esp32c6)] - unsafe { - let pcr = &*esp32c6::PCR::PTR; - - // use default clock source PLL_F80M_CLK - pcr.spi2_clkm_conf.modify(|_, w| w.spi2_clkm_sel().bits(1)); - } - - #[cfg(not(esp32))] - reg_block.misc.write(|w| unsafe { w.bits(0) }); - - reg_block.slave.write(|w| unsafe { w.bits(0) }); - - self.update(); - } - - fn write_bytes_half_duplex( - &mut self, - cmd: crate::spi::Command, - address: crate::spi::Address, - dummy: u8, - buffer: &[u8], - ) -> Result<(), Error> { - self.init_half_duplex( - true, - !cmd.is_none(), - !address.is_none(), - false, - dummy != 0, - buffer.len() == 0, - ); - - // set cmd, address, dummy cycles - let reg_block = self.register_block(); - if !cmd.is_none() { - reg_block.user2.modify(|_, w| { - w.usr_command_bitlen() - .variant((cmd.width() - 1) as u8) - .usr_command_value() - .variant(cmd.value()) - }); - } - - #[cfg(not(esp32))] - if !address.is_none() { - reg_block - .user1 - .modify(|_, w| w.usr_addr_bitlen().variant((address.width() - 1) as u8)); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| w.usr_addr_value().variant(addr)); - } - - #[cfg(esp32)] - if !address.is_none() { - reg_block.user1.modify(|r, w| unsafe { - w.bits(r.bits() & !(0x3f << 26) | (((address.width() - 1) as u32) & 0x3f) << 26) - }); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| unsafe { w.bits(addr) }); - } - - if dummy > 0 { - reg_block - .user1 - .modify(|_, w| w.usr_dummy_cyclelen().variant(dummy - 1)); - } - - if buffer.len() > 0 { - // re-using the full-duplex write here - self.write_bytes(buffer)?; - } else { - self.start_operation(); - } - - self.flush() - } - - fn read_bytes_half_duplex( - &mut self, - cmd: crate::spi::Command, - address: crate::spi::Address, - dummy: u8, - buffer: &mut [u8], - ) -> Result<(), Error> { - self.init_half_duplex( - false, - !cmd.is_none(), - !address.is_none(), - false, - dummy != 0, - buffer.len() == 0, - ); - - // set cmd, address, dummy cycles - let reg_block = self.register_block(); - if !cmd.is_none() { - reg_block.user2.modify(|_, w| { - w.usr_command_bitlen() - .variant((cmd.width() - 1) as u8) - .usr_command_value() - .variant(cmd.value()) - }); - } - - #[cfg(not(esp32))] - if !address.is_none() { - reg_block - .user1 - .modify(|_, w| w.usr_addr_bitlen().variant((address.width() - 1) as u8)); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| w.usr_addr_value().variant(addr)); - } - - #[cfg(esp32)] - if !address.is_none() { - reg_block.user1.modify(|r, w| unsafe { - w.bits(r.bits() & !(0x3f << 26) | (((address.width() - 1) as u32) & 0x3f) << 26) - }); - - let addr = address.value() << (32 - address.width()); - reg_block.addr.write(|w| unsafe { w.bits(addr) }); - } - - if dummy > 0 { - reg_block - .user1 - .modify(|_, w| w.usr_dummy_cyclelen().variant(dummy - 1)); - } - - // re-using the full-duplex read which does dummy writes which is okay - self.read_bytes(buffer) - } - - #[cfg(not(any(esp32, esp32s2)))] - fn update(&self) { - let reg_block = self.register_block(); - - reg_block.cmd.modify(|_, w| w.update().set_bit()); - - while reg_block.cmd.read().update().bit_is_set() { - // wait - } - } - - #[cfg(any(esp32, esp32s2))] - fn update(&self) { - // not need/available on ESP32/ESP32S2 - } - - fn configure_datalen(&self, len: u32) { - let reg_block = self.register_block(); - let len = if len > 0 { len - 1 } else { 0 }; - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - reg_block - .ms_dlen - .write(|w| unsafe { w.ms_data_bitlen().bits(len) }); - - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32s3)))] - { - reg_block - .mosi_dlen - .write(|w| unsafe { w.usr_mosi_dbitlen().bits(len) }); - - reg_block - .miso_dlen - .write(|w| unsafe { w.usr_miso_dbitlen().bits(len) }); - } - } -} - -#[cfg(any(esp32c2, esp32c3, esp32c6))] -impl Instance for crate::peripherals::SPI2 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn sclk_signal(&self) -> OutputSignal { - OutputSignal::FSPICLK_MUX - } - - #[inline(always)] - fn mosi_signal(&self) -> OutputSignal { - OutputSignal::FSPID - } - - #[inline(always)] - fn miso_signal(&self) -> InputSignal { - InputSignal::FSPIQ - } - - #[inline(always)] - fn cs_signal(&self) -> OutputSignal { - OutputSignal::FSPICS0 - } - - #[inline(always)] - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Spi2); - } - - #[inline(always)] - fn spi_num(&self) -> u8 { - 2 - } -} - -#[cfg(any(esp32c2, esp32c3, esp32c6))] -impl ExtendedInstance for crate::peripherals::SPI2 { - #[inline(always)] - fn sio0_input_signal(&self) -> InputSignal { - InputSignal::FSPID - } - - #[inline(always)] - fn sio1_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIQ - } - - #[inline(always)] - fn sio2_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIWP - } - - #[inline(always)] - fn sio2_input_signal(&self) -> InputSignal { - InputSignal::FSPIWP - } - - #[inline(always)] - fn sio3_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIHD - } - - #[inline(always)] - fn sio3_input_signal(&self) -> InputSignal { - InputSignal::FSPIHD - } -} - -#[cfg(any(esp32))] -impl Instance for crate::peripherals::SPI2 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn sclk_signal(&self) -> OutputSignal { - OutputSignal::HSPICLK - } - - #[inline(always)] - fn mosi_signal(&self) -> OutputSignal { - OutputSignal::HSPID - } - - #[inline(always)] - fn miso_signal(&self) -> InputSignal { - InputSignal::HSPIQ - } - - #[inline(always)] - fn cs_signal(&self) -> OutputSignal { - OutputSignal::HSPICS0 - } - - #[inline(always)] - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Spi2); - } - - #[inline(always)] - fn spi_num(&self) -> u8 { - 2 - } -} - -#[cfg(any(esp32))] -impl ExtendedInstance for crate::peripherals::SPI2 { - fn sio0_input_signal(&self) -> InputSignal { - InputSignal::HSPID - } - - fn sio1_output_signal(&self) -> OutputSignal { - OutputSignal::HSPIQ - } - - fn sio2_output_signal(&self) -> OutputSignal { - OutputSignal::HSPIWP - } - - fn sio2_input_signal(&self) -> InputSignal { - InputSignal::HSPIWP - } - - fn sio3_output_signal(&self) -> OutputSignal { - OutputSignal::HSPIHD - } - - fn sio3_input_signal(&self) -> InputSignal { - InputSignal::HSPIHD - } -} - -#[cfg(any(esp32))] -impl Instance for crate::peripherals::SPI3 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn sclk_signal(&self) -> OutputSignal { - OutputSignal::VSPICLK - } - - #[inline(always)] - fn mosi_signal(&self) -> OutputSignal { - OutputSignal::VSPID - } - - #[inline(always)] - fn miso_signal(&self) -> InputSignal { - InputSignal::VSPIQ - } - - #[inline(always)] - fn cs_signal(&self) -> OutputSignal { - OutputSignal::VSPICS0 - } - - #[inline(always)] - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Spi3) - } - - #[inline(always)] - fn spi_num(&self) -> u8 { - 3 - } -} - -#[cfg(any(esp32s2, esp32s3))] -impl Instance for crate::peripherals::SPI2 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn sclk_signal(&self) -> OutputSignal { - OutputSignal::FSPICLK - } - - #[inline(always)] - fn mosi_signal(&self) -> OutputSignal { - OutputSignal::FSPID - } - - #[inline(always)] - fn miso_signal(&self) -> InputSignal { - InputSignal::FSPIQ - } - - #[inline(always)] - fn cs_signal(&self) -> OutputSignal { - OutputSignal::FSPICS0 - } - - #[inline(always)] - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Spi2) - } - - #[inline(always)] - fn spi_num(&self) -> u8 { - 2 - } -} - -#[cfg(any(esp32s2, esp32s3))] -impl ExtendedInstance for crate::peripherals::SPI2 { - #[inline(always)] - fn sio0_input_signal(&self) -> InputSignal { - InputSignal::FSPID - } - - #[inline(always)] - fn sio1_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIQ - } - - #[inline(always)] - fn sio2_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIWP - } - - #[inline(always)] - fn sio2_input_signal(&self) -> InputSignal { - InputSignal::FSPIWP - } - - #[inline(always)] - fn sio3_output_signal(&self) -> OutputSignal { - OutputSignal::FSPIHD - } - - #[inline(always)] - fn sio3_input_signal(&self) -> InputSignal { - InputSignal::FSPIHD - } -} - -#[cfg(any(esp32s2, esp32s3))] -impl Instance for crate::peripherals::SPI3 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn sclk_signal(&self) -> OutputSignal { - OutputSignal::SPI3_CLK - } - - #[inline(always)] - fn mosi_signal(&self) -> OutputSignal { - OutputSignal::SPI3_D - } - - #[inline(always)] - fn miso_signal(&self) -> InputSignal { - InputSignal::SPI3_Q - } - - #[inline(always)] - fn cs_signal(&self) -> OutputSignal { - OutputSignal::SPI3_CS0 - } - - #[inline(always)] - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Spi3) - } - - #[inline(always)] - fn spi_num(&self) -> u8 { - 3 - } -} - -#[cfg(esp32s3)] -impl ExtendedInstance for crate::peripherals::SPI3 { - #[inline(always)] - fn sio0_input_signal(&self) -> InputSignal { - InputSignal::SPI3_D - } - - #[inline(always)] - fn sio1_output_signal(&self) -> OutputSignal { - OutputSignal::SPI3_Q - } - - #[inline(always)] - fn sio2_output_signal(&self) -> OutputSignal { - OutputSignal::SPI3_WP - } - - #[inline(always)] - fn sio2_input_signal(&self) -> InputSignal { - InputSignal::SPI3_WP - } - - #[inline(always)] - fn sio3_output_signal(&self) -> OutputSignal { - OutputSignal::SPI3_HD - } - - #[inline(always)] - fn sio3_input_signal(&self) -> InputSignal { - InputSignal::SPI3_HD - } -} - -pub trait Spi2Instance {} - -#[cfg(any(esp32, esp32s2, esp32s3))] -pub trait Spi3Instance {} - -impl Spi2Instance for crate::peripherals::SPI2 {} - -#[cfg(any(esp32, esp32s2, esp32s3))] -impl Spi3Instance for crate::peripherals::SPI3 {} diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs deleted file mode 100755 index c2c98b69c9f..00000000000 --- a/esp-hal-common/src/system.rs +++ /dev/null @@ -1,606 +0,0 @@ -//! System -//! -//! The SYSTEM/DPORT peripheral needs to be split into several logical parts. -//! -//! Example -//! ```no_run -//! let peripherals = Peripherals::take(); -//! let system = peripherals.SYSTEM.split(); -//! let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); -//! ``` - -use crate::peripheral::PeripheralRef; - -#[cfg(esp32)] -type SystemPeripheral = crate::peripherals::DPORT; -#[cfg(esp32c6)] -type SystemPeripheral = crate::peripherals::PCR; -#[cfg(esp32c6)] -type IntPri = crate::peripherals::INTPRI; -#[cfg(not(any(esp32, esp32c6)))] -type SystemPeripheral = crate::peripherals::SYSTEM; - -pub enum SoftwareInterrupt { - SoftwareInterrupt0, - SoftwareInterrupt1, - SoftwareInterrupt2, - SoftwareInterrupt3, -} - -/// Peripherals which can be enabled via [PeripheralClockControl] -pub enum Peripheral { - #[cfg(spi2)] - Spi2, - #[cfg(spi3)] - Spi3, - I2cExt0, - #[cfg(i2c1)] - I2cExt1, - #[cfg(rmt)] - Rmt, - Ledc, - #[cfg(mcpwm0)] - Mcpwm0, - #[cfg(mcpwm1)] - Mcpwm1, - #[cfg(pcnt)] - Pcnt, - #[cfg(apb_saradc)] - ApbSarAdc, - #[cfg(gdma)] - Gdma, - #[cfg(pdma)] - Dma, - #[cfg(i2s0)] - I2s0, - #[cfg(i2s1)] - I2s1, - #[cfg(usb0)] - Usb, - #[cfg(aes)] - Aes, - #[cfg(twai0)] - Twai0, - #[cfg(twai1)] - Twai1, - #[cfg(timg0)] - Timg0, - #[cfg(timg1)] - Timg1, - #[cfg(lp_wdt)] - Wdt, - Sha, - #[cfg(usb_device)] - UsbDevice, - Uart0, - Uart1, - #[cfg(uart2)] - Uart2, - #[cfg(rsa)] - Rsa, -} -pub struct SoftwareInterruptControl { - _private: (), -} -impl SoftwareInterruptControl { - pub fn raise(&mut self, interrupt: SoftwareInterrupt) { - #[cfg(not(esp32c6))] - let system = unsafe { &*SystemPeripheral::PTR }; - #[cfg(esp32c6)] - let system = unsafe { &*IntPri::PTR }; - match interrupt { - SoftwareInterrupt::SoftwareInterrupt0 => { - system - .cpu_intr_from_cpu_0 - .write(|w| w.cpu_intr_from_cpu_0().bit(true)); - } - SoftwareInterrupt::SoftwareInterrupt1 => { - system - .cpu_intr_from_cpu_1 - .write(|w| w.cpu_intr_from_cpu_1().bit(true)); - } - SoftwareInterrupt::SoftwareInterrupt2 => { - system - .cpu_intr_from_cpu_2 - .write(|w| w.cpu_intr_from_cpu_2().bit(true)); - } - SoftwareInterrupt::SoftwareInterrupt3 => { - system - .cpu_intr_from_cpu_3 - .write(|w| w.cpu_intr_from_cpu_3().bit(true)); - } - } - } - pub fn reset(&mut self, interrupt: SoftwareInterrupt) { - #[cfg(not(esp32c6))] - let system = unsafe { &*SystemPeripheral::PTR }; - #[cfg(esp32c6)] - let system = unsafe { &*IntPri::PTR }; - match interrupt { - SoftwareInterrupt::SoftwareInterrupt0 => { - system - .cpu_intr_from_cpu_0 - .write(|w| w.cpu_intr_from_cpu_0().bit(false)); - } - SoftwareInterrupt::SoftwareInterrupt1 => { - system - .cpu_intr_from_cpu_1 - .write(|w| w.cpu_intr_from_cpu_1().bit(false)); - } - SoftwareInterrupt::SoftwareInterrupt2 => { - system - .cpu_intr_from_cpu_2 - .write(|w| w.cpu_intr_from_cpu_2().bit(false)); - } - SoftwareInterrupt::SoftwareInterrupt3 => { - system - .cpu_intr_from_cpu_3 - .write(|w| w.cpu_intr_from_cpu_3().bit(false)); - } - } - } -} - -/// Controls the enablement of peripheral clocks. -pub struct PeripheralClockControl { - _private: (), -} - -#[cfg(not(esp32c6))] -impl PeripheralClockControl { - /// Enables and resets the given peripheral - pub fn enable(&mut self, peripheral: Peripheral) { - let system = unsafe { &*SystemPeripheral::PTR }; - - #[cfg(not(esp32))] - let (perip_clk_en0, perip_rst_en0) = { (&system.perip_clk_en0, &system.perip_rst_en0) }; - #[cfg(esp32)] - let (perip_clk_en0, perip_rst_en0, peri_clk_en, peri_rst_en) = { - ( - &system.perip_clk_en, - &system.perip_rst_en, - &system.peri_clk_en, - &system.peri_rst_en, - ) - }; - - #[cfg(any(esp32c2, esp32c3, esp32s2, esp32s3))] - let (perip_clk_en1, perip_rst_en1) = { (&system.perip_clk_en1, &system.perip_rst_en1) }; - - match peripheral { - #[cfg(spi2)] - Peripheral::Spi2 => { - perip_clk_en0.modify(|_, w| w.spi2_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.spi2_rst().clear_bit()); - } - #[cfg(spi3)] - Peripheral::Spi3 => { - perip_clk_en0.modify(|_, w| w.spi3_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.spi3_rst().clear_bit()); - } - #[cfg(esp32)] - Peripheral::I2cExt0 => { - perip_clk_en0.modify(|_, w| w.i2c0_ext0_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2c0_ext0_rst().clear_bit()); - } - #[cfg(not(esp32))] - Peripheral::I2cExt0 => { - perip_clk_en0.modify(|_, w| w.i2c_ext0_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2c_ext0_rst().clear_bit()); - } - #[cfg(i2c1)] - Peripheral::I2cExt1 => { - perip_clk_en0.modify(|_, w| w.i2c_ext1_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2c_ext1_rst().clear_bit()); - } - #[cfg(rmt)] - Peripheral::Rmt => { - perip_clk_en0.modify(|_, w| w.rmt_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.rmt_rst().clear_bit()); - } - Peripheral::Ledc => { - perip_clk_en0.modify(|_, w| w.ledc_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.ledc_rst().clear_bit()); - } - #[cfg(mcpwm0)] - Peripheral::Mcpwm0 => { - perip_clk_en0.modify(|_, w| w.pwm0_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.pwm0_rst().clear_bit()); - } - #[cfg(mcpwm1)] - Peripheral::Mcpwm1 => { - perip_clk_en0.modify(|_, w| w.pwm1_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.pwm1_rst().clear_bit()); - } - #[cfg(pcnt)] - Peripheral::Pcnt => { - perip_clk_en0.modify(|_, w| w.pcnt_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.pcnt_rst().clear_bit()); - } - #[cfg(apb_saradc)] - Peripheral::ApbSarAdc => { - perip_clk_en0.modify(|_, w| w.apb_saradc_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.apb_saradc_rst().clear_bit()); - } - #[cfg(gdma)] - Peripheral::Gdma => { - perip_clk_en1.modify(|_, w| w.dma_clk_en().set_bit()); - perip_rst_en1.modify(|_, w| w.dma_rst().clear_bit()); - } - #[cfg(esp32)] - Peripheral::Dma => { - perip_clk_en0.modify(|_, w| w.spi_dma_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.spi_dma_rst().clear_bit()); - } - #[cfg(esp32s2)] - Peripheral::Dma => { - perip_clk_en0.modify(|_, w| w.spi2_dma_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.spi2_dma_rst().clear_bit()); - perip_clk_en0.modify(|_, w| w.spi3_dma_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.spi3_dma_rst().clear_bit()); - } - #[cfg(esp32c3)] - Peripheral::I2s0 => { - // on ESP32-C3 note that i2s1_clk_en / rst is really I2s0 - perip_clk_en0.modify(|_, w| w.i2s1_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2s1_rst().clear_bit()); - } - #[cfg(any(esp32s3, esp32, esp32s2))] - Peripheral::I2s0 => { - perip_clk_en0.modify(|_, w| w.i2s0_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2s0_rst().clear_bit()); - } - #[cfg(any(esp32s3, esp32))] - Peripheral::I2s1 => { - perip_clk_en0.modify(|_, w| w.i2s1_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.i2s1_rst().clear_bit()); - } - #[cfg(usb0)] - Peripheral::Usb => { - perip_clk_en0.modify(|_, w| w.usb_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.usb_rst().clear_bit()); - } - #[cfg(twai0)] - Peripheral::Twai0 => { - perip_clk_en0.modify(|_, w| w.twai_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.twai_rst().clear_bit()); - } - #[cfg(esp32)] - Peripheral::Aes => { - peri_clk_en.modify(|r, w| unsafe { w.bits(r.bits() | 1) }); - peri_rst_en.modify(|r, w| unsafe { w.bits(r.bits() & (!1)) }); - } - #[cfg(any(esp32c3, esp32s2, esp32s3))] - Peripheral::Aes => { - perip_clk_en1.modify(|_, w| w.crypto_aes_clk_en().set_bit()); - perip_rst_en1.modify(|_, w| w.crypto_aes_rst().clear_bit()); - } - #[cfg(timg0)] - Peripheral::Timg0 => { - #[cfg(any(esp32c3, esp32s2, esp32s3))] - perip_clk_en0.modify(|_, w| w.timers_clk_en().set_bit()); - perip_clk_en0.modify(|_, w| w.timergroup_clk_en().set_bit()); - - #[cfg(any(esp32c3, esp32s2, esp32s3))] - perip_rst_en0.modify(|_, w| w.timers_rst().clear_bit()); - perip_rst_en0.modify(|_, w| w.timergroup_rst().clear_bit()); - } - #[cfg(timg1)] - Peripheral::Timg1 => { - #[cfg(any(esp32c3, esp32s2, esp32s3))] - perip_clk_en0.modify(|_, w| w.timers_clk_en().set_bit()); - perip_clk_en0.modify(|_, w| w.timergroup1_clk_en().set_bit()); - - #[cfg(any(esp32c3, esp32s2, esp32s3))] - perip_rst_en0.modify(|_, w| w.timers_rst().clear_bit()); - perip_rst_en0.modify(|_, w| w.timergroup1_rst().clear_bit()); - } - Peripheral::Sha => { - #[cfg(not(esp32))] - perip_clk_en1.modify(|_, w| w.crypto_sha_clk_en().set_bit()); - #[cfg(not(esp32))] - perip_rst_en1.modify(|_, w| w.crypto_sha_rst().clear_bit()); - } - #[cfg(esp32c3)] - Peripheral::UsbDevice => { - perip_clk_en0.modify(|_, w| w.usb_device_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.usb_device_rst().clear_bit()); - } - #[cfg(esp32s3)] - Peripheral::UsbDevice => { - perip_clk_en1.modify(|_, w| w.usb_device_clk_en().set_bit()); - perip_rst_en1.modify(|_, w| w.usb_device_rst().clear_bit()); - } - Peripheral::Uart0 => { - perip_clk_en0.modify(|_, w| w.uart_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.uart_rst().clear_bit()); - } - Peripheral::Uart1 => { - perip_clk_en0.modify(|_, w| w.uart1_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.uart1_rst().clear_bit()); - } - #[cfg(all(uart2, esp32s3))] - Peripheral::Uart2 => { - perip_clk_en1.modify(|_, w| w.uart2_clk_en().set_bit()); - perip_rst_en1.modify(|_, w| w.uart2_rst().clear_bit()); - } - #[cfg(all(uart2, esp32))] - Peripheral::Uart2 => { - perip_clk_en0.modify(|_, w| w.uart2_clk_en().set_bit()); - perip_rst_en0.modify(|_, w| w.uart2_rst().clear_bit()); - } - #[cfg(esp32)] - Peripheral::Rsa => { - peri_clk_en.modify(|r, w| unsafe { w.bits(r.bits() | 1 << 2) }); - peri_rst_en.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << 2)) }); - } - #[cfg(any(esp32c3, esp32s2, esp32s3))] - Peripheral::Rsa => { - perip_clk_en1.modify(|_, w| w.crypto_rsa_clk_en().set_bit()); - perip_rst_en1.modify(|_, w| w.crypto_rsa_rst().clear_bit()); - system.rsa_pd_ctrl.modify(|_, w| w.rsa_mem_pd().clear_bit()); - } - } - } -} - -#[cfg(esp32c6)] -impl PeripheralClockControl { - /// Enables and resets the given peripheral - pub fn enable(&mut self, peripheral: Peripheral) { - let system = unsafe { &*SystemPeripheral::PTR }; - - match peripheral { - #[cfg(spi2)] - Peripheral::Spi2 => { - system.spi2_conf.modify(|_, w| w.spi2_clk_en().set_bit()); - system.spi2_conf.modify(|_, w| w.spi2_rst_en().clear_bit()); - } - Peripheral::I2cExt0 => { - system.i2c_conf.modify(|_, w| w.i2c_clk_en().set_bit()); - system.i2c_conf.modify(|_, w| w.i2c_rst_en().clear_bit()); - } - #[cfg(rmt)] - Peripheral::Rmt => { - system.rmt_conf.modify(|_, w| w.rmt_clk_en().set_bit()); - system.rmt_conf.modify(|_, w| w.rmt_rst_en().clear_bit()); - } - #[cfg(ledc)] - Peripheral::Ledc => { - system.ledc_conf.modify(|_, w| w.ledc_clk_en().set_bit()); - system.ledc_conf.modify(|_, w| w.ledc_rst_en().clear_bit()); - } - #[cfg(mcpwm0)] - Peripheral::Mcpwm0 => { - system.pwm_conf.modify(|_, w| w.pwm_clk_en().set_bit()); - system.pwm_conf.modify(|_, w| w.pwm_rst_en().clear_bit()); - } - #[cfg(mcpwm1)] - Peripheral::Mcpwm1 => { - system.pwm_conf.modify(|_, w| w.pwm_clk_en().set_bit()); - system.pwm_conf.modify(|_, w| w.pwm_rst_en().clear_bit()); - } - #[cfg(apb_saradc)] - Peripheral::ApbSarAdc => { - system - .saradc_conf - .modify(|_, w| w.saradc_reg_clk_en().set_bit()); - system - .saradc_conf - .modify(|_, w| w.saradc_reg_rst_en().clear_bit()); - } - #[cfg(gdma)] - Peripheral::Gdma => { - system.gdma_conf.modify(|_, w| w.gdma_clk_en().set_bit()); - system.gdma_conf.modify(|_, w| w.gdma_rst_en().clear_bit()); - } - #[cfg(i2s0)] - Peripheral::I2s0 => { - system.i2s_conf.modify(|_, w| w.i2s_clk_en().set_bit()); - system.i2s_conf.modify(|_, w| w.i2s_rst_en().clear_bit()); - } - #[cfg(twai0)] - Peripheral::Twai0 => { - system.twai0_conf.modify(|_, w| w.twai0_clk_en().set_bit()); - system - .twai0_conf - .modify(|_, w| w.twai0_rst_en().clear_bit()); - } - #[cfg(twai1)] - Peripheral::Twai1 => { - system.twai1_conf.modify(|_, w| w.twai1_clk_en().set_bit()); - system - .twai1_conf - .modify(|_, w| w.twai1_rst_en().clear_bit()); - } - #[cfg(aes)] - Peripheral::Aes => { - system.aes_conf.modify(|_, w| w.aes_clk_en().set_bit()); - system.aes_conf.modify(|_, w| w.aes_rst_en().clear_bit()); - } - #[cfg(pcnt)] - Peripheral::Pcnt => { - system.pcnt_conf.modify(|_, w| w.pcnt_clk_en().set_bit()); - system.pcnt_conf.modify(|_, w| w.pcnt_rst_en().clear_bit()); - } - #[cfg(timg0)] - Peripheral::Timg0 => { - system - .timergroup0_timer_clk_conf - .write(|w| w.tg0_timer_clk_en().set_bit()); - system - .timergroup0_timer_clk_conf - .write(|w| unsafe { w.tg0_timer_clk_sel().bits(1) }); - } - #[cfg(timg1)] - Peripheral::Timg1 => { - system - .timergroup1_timer_clk_conf - .write(|w| w.tg1_timer_clk_en().set_bit()); - system - .timergroup1_timer_clk_conf - .write(|w| unsafe { w.tg1_timer_clk_sel().bits(1) }); - } - #[cfg(lp_wdt)] - Peripheral::Wdt => { - system - .timergroup0_wdt_clk_conf - .write(|w| w.tg0_wdt_clk_en().set_bit()); - system - .timergroup0_wdt_clk_conf - .write(|w| unsafe { w.tg0_wdt_clk_sel().bits(1) }); - - system - .timergroup1_timer_clk_conf - .write(|w| w.tg1_timer_clk_en().set_bit()); - system - .timergroup1_timer_clk_conf - .write(|w| unsafe { w.tg1_timer_clk_sel().bits(1) }); - } - Peripheral::Sha => { - system.sha_conf.modify(|_, w| w.sha_clk_en().set_bit()); - system.sha_conf.modify(|_, w| w.sha_rst_en().clear_bit()); - } - Peripheral::UsbDevice => { - system - .usb_device_conf - .modify(|_, w| w.usb_device_clk_en().set_bit()); - system - .usb_device_conf - .modify(|_, w| w.usb_device_rst_en().clear_bit()); - } - Peripheral::Uart0 => { - system.uart0_conf.modify(|_, w| w.uart0_clk_en().set_bit()); - system - .uart0_conf - .modify(|_, w| w.uart0_rst_en().clear_bit()); - } - Peripheral::Uart1 => { - system.uart1_conf.modify(|_, w| w.uart1_clk_en().set_bit()); - system - .uart1_conf - .modify(|_, w| w.uart1_rst_en().clear_bit()); - } - Peripheral::Rsa => { - system.rsa_conf.modify(|_, w| w.rsa_clk_en().set_bit()); - system.rsa_conf.modify(|_, w| w.rsa_rst_en().clear_bit()); - system.rsa_pd_ctrl.modify(|_, w| w.rsa_mem_pd().clear_bit()); - } - } - } -} - -/// Controls the configuration of the chip's clocks. -pub struct SystemClockControl { - _private: (), -} - -/// Controls the configuration of the chip's clocks. -pub struct CpuControl { - _private: (), -} - -/// Dummy DMA peripheral. -#[cfg(pdma)] -pub struct Dma { - _private: (), -} - -pub enum RadioPeripherals { - #[cfg(phy)] - Phy, - #[cfg(bt)] - Bt, - #[cfg(wifi)] - Wifi, - #[cfg(ieee802154)] - Ieee802154, -} - -pub struct RadioClockControl { - _private: (), -} - -/// Control the radio peripheral clocks -pub trait RadioClockController { - /// Enable the peripheral - fn enable(&mut self, peripheral: RadioPeripherals); - - /// Disable the peripheral - fn disable(&mut self, peripheral: RadioPeripherals); - - /// Reset the MAC - fn reset_mac(&mut self); - - /// Do any common initial initialization needed - fn init_clocks(&mut self); - - /// Initialize BLE RTC clocks - fn ble_rtc_clk_init(&mut self); - - fn reset_rpa(&mut self); -} - -/// The SYSTEM/DPORT splitted into it's different logical parts. -pub struct SystemParts<'d> { - _private: PeripheralRef<'d, SystemPeripheral>, - pub peripheral_clock_control: PeripheralClockControl, - pub clock_control: SystemClockControl, - pub cpu_control: CpuControl, - #[cfg(pdma)] - pub dma: Dma, - pub radio_clock_control: RadioClockControl, - pub software_interrupt_control: SoftwareInterruptControl, -} - -/// Extension trait to split a SYSTEM/DPORT peripheral in independent logical -/// parts -pub trait SystemExt<'d> { - type Parts; - - /// Splits the SYSTEM/DPORT peripheral into it's parts. - fn split(self) -> Self::Parts; -} - -impl<'d, T: crate::peripheral::Peripheral

    + 'd> SystemExt<'d> for T { - type Parts = SystemParts<'d>; - - fn split(self) -> Self::Parts { - Self::Parts { - _private: self.into_ref(), - peripheral_clock_control: PeripheralClockControl { _private: () }, - clock_control: SystemClockControl { _private: () }, - cpu_control: CpuControl { _private: () }, - #[cfg(pdma)] - dma: Dma { _private: () }, - radio_clock_control: RadioClockControl { _private: () }, - software_interrupt_control: SoftwareInterruptControl { _private: () }, - } - } -} - -impl crate::peripheral::Peripheral for SystemClockControl { - type P = SystemClockControl; - - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - SystemClockControl { _private: () } - } -} - -impl crate::peripheral::sealed::Sealed for SystemClockControl {} - -#[cfg(pdma)] -mod dma_peripheral { - use super::Dma; - - impl crate::peripheral::Peripheral for Dma { - type P = Dma; - #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - Dma { _private: () } - } - } - - impl crate::peripheral::sealed::Sealed for Dma {} -} diff --git a/esp-hal-common/src/systimer.rs b/esp-hal-common/src/systimer.rs deleted file mode 100644 index 5bab37803ca..00000000000 --- a/esp-hal-common/src/systimer.rs +++ /dev/null @@ -1,238 +0,0 @@ -use core::{intrinsics::transmute, marker::PhantomData}; - -use fugit::MillisDurationU32; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::{ - generic::Reg, - systimer::{ - target0_conf::TARGET0_CONF_SPEC, - target0_hi::TARGET0_HI_SPEC, - target0_lo::TARGET0_LO_SPEC, - }, - SYSTIMER, - }, -}; - -// TODO this only handles unit0 of the systimer - -pub struct SystemTimer<'d> { - _inner: PeripheralRef<'d, SYSTIMER>, - pub alarm0: Alarm, - pub alarm1: Alarm, - pub alarm2: Alarm, -} - -impl<'d> SystemTimer<'d> { - #[cfg(esp32s2)] - pub const BIT_MASK: u64 = u64::MAX; - #[cfg(not(esp32s2))] - pub const BIT_MASK: u64 = 0xFFFFFFFFFFFFF; - - #[cfg(esp32s2)] - pub const TICKS_PER_SECOND: u64 = 80_000_000; // TODO this can change when we have support for changing APB frequency - #[cfg(not(esp32s2))] - pub const TICKS_PER_SECOND: u64 = 16_000_000; - - pub fn new(p: impl Peripheral

    + 'd) -> Self { - crate::into_ref!(p); - Self { - _inner: p, - alarm0: Alarm::new(), - alarm1: Alarm::new(), - alarm2: Alarm::new(), - } - } - - // TODO use fugit types - pub fn now() -> u64 { - // This should be safe to access from multiple contexts - // worst case scenario the second accesor ends up reading - // an older time stamp - let systimer = unsafe { &*SYSTIMER::ptr() }; - systimer - .unit0_op - .modify(|_, w| w.timer_unit0_update().set_bit()); - - while !systimer - .unit0_op - .read() - .timer_unit0_value_valid() - .bit_is_set() - {} - - let value_lo = systimer.unit0_value_lo.read().bits(); - let value_hi = systimer.unit0_value_hi.read().bits(); - - ((value_hi as u64) << 32) | value_lo as u64 - } -} - -#[derive(Debug)] -pub struct Target; - -#[derive(Debug)] -pub struct Periodic; // TODO, also impl e-h timer traits - -#[derive(Debug)] -pub struct Alarm { - _pd: PhantomData, -} - -impl Alarm { - // private constructor - fn new() -> Self { - Self { _pd: PhantomData } - } - - pub fn interrupt_enable(&self, val: bool) { - let systimer = unsafe { &*SYSTIMER::ptr() }; - match CHANNEL { - 0 => systimer.int_ena.modify(|_, w| w.target0_int_ena().bit(val)), - 1 => systimer.int_ena.modify(|_, w| w.target1_int_ena().bit(val)), - 2 => systimer.int_ena.modify(|_, w| w.target2_int_ena().bit(val)), - _ => unreachable!(), - } - } - - pub fn clear_interrupt(&self) { - let systimer = unsafe { &*SYSTIMER::ptr() }; - match CHANNEL { - 0 => systimer.int_clr.write(|w| w.target0_int_clr().set_bit()), - 1 => systimer.int_clr.write(|w| w.target1_int_clr().set_bit()), - 2 => systimer.int_clr.write(|w| w.target2_int_clr().set_bit()), - _ => unreachable!(), - } - } - - fn configure( - &self, - conf: impl FnOnce(&Reg, &Reg, &Reg), - ) { - unsafe { - let systimer = &*SYSTIMER::ptr(); - let (tconf, hi, lo): ( - &Reg, - &Reg, - &Reg, - ) = match CHANNEL { - 0 => ( - &systimer.target0_conf, - &systimer.target0_hi, - &systimer.target0_lo, - ), - 1 => ( - transmute(&systimer.target1_conf), - transmute(&systimer.target1_hi), - transmute(&systimer.target1_lo), - ), - 2 => ( - transmute(&systimer.target2_conf), - transmute(&systimer.target2_hi), - transmute(&systimer.target2_lo), - ), - _ => unreachable!(), - }; - - #[cfg(esp32s2)] - systimer.step.write(|w| w.timer_xtal_step().bits(0x1)); // run at XTAL freq, not 80 * XTAL freq - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - { - tconf.write(|w| w.target0_timer_unit_sel().clear_bit()); // default, use unit 0 - systimer - .conf - .modify(|_, w| w.timer_unit0_core0_stall_en().clear_bit()); - } - - conf(tconf, hi, lo); - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] - { - match CHANNEL { - 0 => { - systimer - .comp0_load - .write(|w| w.timer_comp0_load().set_bit()); - } - 1 => systimer - .comp1_load - .write(|w| w.timer_comp1_load().set_bit()), - 2 => systimer - .comp2_load - .write(|w| w.timer_comp2_load().set_bit()), - _ => unreachable!(), - } - - systimer.conf.modify(|_r, w| match CHANNEL { - 0 => w.target0_work_en().set_bit(), - 1 => w.target1_work_en().set_bit(), - 2 => w.target2_work_en().set_bit(), - _ => unreachable!(), - }); - } - - #[cfg(esp32s2)] - tconf.modify(|_r, w| match CHANNEL { - 0 => w.target0_work_en().set_bit(), - 1 => w.target0_work_en().set_bit(), - 2 => w.target0_work_en().set_bit(), - _ => unreachable!(), - }); - } - } -} - -impl Alarm { - pub fn set_target(&self, timestamp: u64) { - self.configure(|tconf, hi, lo| unsafe { - tconf.write(|w| w.target0_period_mode().clear_bit()); // target mode - hi.write(|w| w.timer_target0_hi().bits((timestamp >> 32) as u32)); - lo.write(|w| w.timer_target0_lo().bits((timestamp & 0xFFFF_FFFF) as u32)); - }) - } - - pub fn into_periodic(self) -> Alarm { - Alarm { _pd: PhantomData } - } -} - -impl Alarm { - pub fn set_period(&self, period: fugit::HertzU32) { - let time_period: MillisDurationU32 = period.into_duration(); - let cycles = time_period.ticks(); - self.configure(|tconf, hi, lo| unsafe { - tconf.write(|w| { - w.target0_period_mode() - .set_bit() - .target0_period() - .bits(cycles * (SystemTimer::TICKS_PER_SECOND as u32 / 1000)) - }); - hi.write(|w| w.timer_target0_hi().bits(0)); - lo.write(|w| w.timer_target0_lo().bits(0)); - }) - } - - pub fn into_target(self) -> Alarm { - Alarm { _pd: PhantomData } - } -} - -impl Alarm { - pub const unsafe fn conjure() -> Self { - Self { _pd: PhantomData } - } -} - -impl Alarm { - pub const unsafe fn conjure() -> Self { - Self { _pd: PhantomData } - } -} - -impl Alarm { - pub const unsafe fn conjure() -> Self { - Self { _pd: PhantomData } - } -} diff --git a/esp-hal-common/src/timer.rs b/esp-hal-common/src/timer.rs deleted file mode 100644 index 89d69fabb93..00000000000 --- a/esp-hal-common/src/timer.rs +++ /dev/null @@ -1,690 +0,0 @@ -//! General-purpose timers - -use core::{ - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use embedded_hal::{ - timer::{Cancel, CountDown, Periodic}, - watchdog::{Watchdog, WatchdogDisable, WatchdogEnable}, -}; -use fugit::{HertzU32, MicrosDurationU64}; -use void::Void; - -#[cfg(timg1)] -use crate::peripherals::TIMG1; -use crate::{ - clock::Clocks, - peripheral::{Peripheral, PeripheralRef}, - peripherals::{timg0::RegisterBlock, TIMG0}, - system::PeripheralClockControl, -}; - -/// Custom timer error type -#[derive(Debug)] -pub enum Error { - TimerActive, - TimerInactive, - AlarmInactive, -} - -// A timergroup consisting of up to 2 timers (chip dependent) and a watchdog -// timer -pub struct TimerGroup<'d, T> -where - T: TimerGroupInstance, -{ - _timer_group: PeripheralRef<'d, T>, - pub timer0: Timer>, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - pub timer1: Timer>, - pub wdt: Wdt, -} - -pub trait TimerGroupInstance { - fn register_block() -> *const RegisterBlock; -} - -impl TimerGroupInstance for TIMG0 { - #[inline(always)] - fn register_block() -> *const RegisterBlock { - crate::peripherals::TIMG0::PTR - } -} - -#[cfg(timg1)] -impl TimerGroupInstance for TIMG1 { - #[inline(always)] - fn register_block() -> *const RegisterBlock { - crate::peripherals::TIMG1::PTR - } -} - -impl<'d, T> TimerGroup<'d, T> -where - T: TimerGroupInstance, -{ - pub fn new( - timer_group: impl Peripheral

    + 'd, - clocks: &Clocks, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(timer_group); - - let timer0 = Timer::new( - Timer0 { - phantom: PhantomData::default(), - }, - clocks.apb_clock, - peripheral_clock_control, - ); - - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - let timer1 = Timer::new( - Timer1 { - phantom: PhantomData::default(), - }, - clocks.apb_clock, - peripheral_clock_control, - ); - - let wdt = Wdt::new(peripheral_clock_control); - - Self { - _timer_group: timer_group, - timer0, - #[cfg(not(any(esp32c2, esp32c3, esp32c6)))] - timer1, - wdt, - } - } -} - -/// General-purpose timer -pub struct Timer { - timg: T, - apb_clk_freq: HertzU32, -} - -/// Timer driver -impl Timer -where - T: Instance, -{ - /// Create a new timer instance - pub fn new( - timg: T, - apb_clk_freq: HertzU32, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - // TODO: this currently assumes APB_CLK is being used, as we don't yet have a - // way to select the XTAL_CLK. - timg.enable_peripheral(peripheral_clock_control); - Self { timg, apb_clk_freq } - } - - /// Return the raw interface to the underlying timer instance - pub fn free(self) -> T { - self.timg - } -} - -impl Deref for Timer -where - T: Instance, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.timg - } -} - -impl DerefMut for Timer -where - T: Instance, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.timg - } -} - -/// Timer peripheral instance -pub trait Instance { - fn reset_counter(&mut self); - - fn set_counter_active(&mut self, state: bool); - - fn is_counter_active(&self) -> bool; - - fn set_counter_decrementing(&mut self, decrementing: bool); - - fn set_auto_reload(&mut self, auto_reload: bool); - - fn set_alarm_active(&mut self, state: bool); - - fn is_alarm_active(&self) -> bool; - - fn load_alarm_value(&mut self, value: u64); - - fn listen(&mut self); - - fn unlisten(&mut self); - - fn clear_interrupt(&mut self); - - fn now(&self) -> u64; - - fn divider(&self) -> u32; - - fn set_divider(&mut self, divider: u16); - - fn is_interrupt_set(&self) -> bool; - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl); -} - -pub struct Timer0 { - phantom: PhantomData, -} - -/// Timer peripheral instance -impl Instance for Timer0 -where - TG: TimerGroupInstance, -{ - fn reset_counter(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0loadlo.write(|w| unsafe { w.load_lo().bits(0) }); - - reg_block.t0loadhi.write(|w| unsafe { w.load_hi().bits(0) }); - - reg_block.t0load.write(|w| unsafe { w.load().bits(1) }); - } - - fn set_counter_active(&mut self, state: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0config.modify(|_, w| w.en().bit(state)); - } - - fn is_counter_active(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0config.read().en().bit_is_set() - } - - fn set_counter_decrementing(&mut self, decrementing: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t0config - .modify(|_, w| w.increase().bit(!decrementing)); - } - - fn set_auto_reload(&mut self, auto_reload: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t0config - .modify(|_, w| w.autoreload().bit(auto_reload)); - } - - fn set_alarm_active(&mut self, state: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0config.modify(|_, w| w.alarm_en().bit(state)); - } - - fn is_alarm_active(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0config.read().alarm_en().bit_is_set() - } - - fn load_alarm_value(&mut self, value: u64) { - let value = value & 0x3F_FFFF_FFFF_FFFF; - let high = (value >> 32) as u32; - let low = (value & 0xFFFF_FFFF) as u32; - - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t0alarmlo - .write(|w| unsafe { w.alarm_lo().bits(low) }); - - reg_block - .t0alarmhi - .write(|w| unsafe { w.alarm_hi().bits(high) }); - } - - fn listen(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - // always use level interrupt - #[cfg(any(esp32, esp32s2))] - reg_block.t0config.modify(|_, w| w.level_int_en().set_bit()); - - reg_block - .int_ena_timers - .modify(|_, w| w.t0_int_ena().set_bit()); - } - - fn unlisten(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .int_ena_timers - .modify(|_, w| w.t0_int_ena().clear_bit()); - } - - fn clear_interrupt(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.int_clr_timers.write(|w| w.t0_int_clr().set_bit()); - } - - fn now(&self) -> u64 { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t0update.write(|w| unsafe { w.bits(0) }); - - let value_lo = reg_block.t0lo.read().bits() as u64; - let value_hi = (reg_block.t0hi.read().bits() as u64) << 32; - - (value_lo | value_hi) as u64 - } - - fn divider(&self) -> u32 { - let reg_block = unsafe { &*TG::register_block() }; - - // From the ESP32 TRM, "11.2.1 16­-bit Prescaler and Clock Selection": - // - // "The prescaler can divide the APB clock by a factor from 2 to 65536. - // Specifically, when TIMGn_Tx_DIVIDER is either 1 or 2, the clock divisor is 2; - // when TIMGn_Tx_DIVIDER is 0, the clock divisor is 65536. Any other value will - // cause the clock to be divided by exactly that value." - match reg_block.t0config.read().divider().bits() { - 0 => 65536, - 1 | 2 => 2, - n => n as u32, - } - } - - fn is_interrupt_set(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.int_raw_timers.read().t0_int_raw().bit_is_set() - } - - fn set_divider(&mut self, divider: u16) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t0config - .modify(|_, w| unsafe { w.divider().bits(divider) }) - } - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Timg0); - } -} - -#[cfg(not(any(esp32c2, esp32c3, esp32c6)))] -pub struct Timer1 { - phantom: PhantomData, -} - -/// Timer peripheral instance -#[cfg(not(any(esp32c2, esp32c3, esp32c6)))] -impl Instance for Timer1 -where - TG: TimerGroupInstance, -{ - fn reset_counter(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1loadlo.write(|w| unsafe { w.load_lo().bits(0) }); - - reg_block.t1loadhi.write(|w| unsafe { w.load_hi().bits(0) }); - - reg_block.t1load.write(|w| unsafe { w.load().bits(1) }); - } - - fn set_counter_active(&mut self, state: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1config.modify(|_, w| w.en().bit(state)); - } - - fn is_counter_active(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1config.read().en().bit_is_set() - } - - fn set_counter_decrementing(&mut self, decrementing: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t1config - .modify(|_, w| w.increase().bit(!decrementing)); - } - - fn set_auto_reload(&mut self, auto_reload: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t1config - .modify(|_, w| w.autoreload().bit(auto_reload)); - } - - fn set_alarm_active(&mut self, state: bool) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1config.modify(|_, w| w.alarm_en().bit(state)); - } - - fn is_alarm_active(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1config.read().alarm_en().bit_is_set() - } - - fn load_alarm_value(&mut self, value: u64) { - let value = value & 0x3F_FFFF_FFFF_FFFF; - let high = (value >> 32) as u32; - let low = (value & 0xFFFF_FFFF) as u32; - - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t1alarmlo - .write(|w| unsafe { w.alarm_lo().bits(low) }); - - reg_block - .t1alarmhi - .write(|w| unsafe { w.alarm_hi().bits(high) }); - } - - fn listen(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - // always use level interrupt - #[cfg(any(esp32, esp32s2))] - reg_block.t1config.modify(|_, w| w.level_int_en().set_bit()); - - reg_block - .int_ena_timers - .modify(|_, w| w.t1_int_ena().set_bit()); - } - - fn unlisten(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .int_ena_timers - .modify(|_, w| w.t1_int_ena().clear_bit()); - } - - fn clear_interrupt(&mut self) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.int_clr_timers.write(|w| w.t1_int_clr().set_bit()); - } - - fn now(&self) -> u64 { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.t1update.write(|w| unsafe { w.bits(0) }); - - let value_lo = reg_block.t1lo.read().bits() as u64; - let value_hi = (reg_block.t1hi.read().bits() as u64) << 32; - - (value_lo | value_hi) as u64 - } - - fn divider(&self) -> u32 { - let reg_block = unsafe { &*TG::register_block() }; - - // From the ESP32 TRM, "11.2.1 16­-bit Prescaler and Clock Selection": - // - // "The prescaler can divide the APB clock by a factor from 2 to 65536. - // Specifically, when TIMGn_Tx_DIVIDER is either 1 or 2, the clock divisor is 2; - // when TIMGn_Tx_DIVIDER is 0, the clock divisor is 65536. Any other value will - // cause the clock to be divided by exactly that value." - match reg_block.t1config.read().divider().bits() { - 0 => 65536, - 1 | 2 => 2, - n => n as u32, - } - } - - fn is_interrupt_set(&self) -> bool { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block.int_raw_timers.read().t1_int_raw().bit_is_set() - } - - fn set_divider(&mut self, divider: u16) { - let reg_block = unsafe { &*TG::register_block() }; - - reg_block - .t1config - .modify(|_, w| unsafe { w.divider().bits(divider) }) - } - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Timg1); - } -} - -fn timeout_to_ticks(timeout: T, clock: F, divider: u32) -> u64 -where - T: Into, - F: Into, -{ - let timeout: MicrosDurationU64 = timeout.into(); - let micros = timeout.to_micros(); - - let clock: HertzU32 = clock.into(); - - // 1_000_000 is used to get rid of `float` calculations - let period: u64 = 1_000_000 * 1_000_000 / (clock.to_Hz() as u64 / divider as u64); - (1_000_000 * micros / period as u64) as u64 -} - -impl CountDown for Timer -where - T: Instance, -{ - type Time = MicrosDurationU64; - - fn start

    + 'd, - tx_pin: impl Peripheral

    + 'd, - rx_pin: impl Peripheral

    + 'd, - clock_control: &mut PeripheralClockControl, - clocks: &Clocks, - baud_rate: BaudRate, - ) -> Self { - // Enable the peripheral clock for the TWAI peripheral. - clock_control.enable(T::SYSTEM_PERIPHERAL); - - // Set up the GPIO pins. - crate::into_ref!(tx_pin, rx_pin); - tx_pin.connect_peripheral_to_output(T::OUTPUT_SIGNAL); - rx_pin.connect_input_to_peripheral(T::INPUT_SIGNAL); - - crate::into_ref!(peripheral); - let mut cfg = TwaiConfiguration { peripheral }; - - cfg.set_baud_rate(baud_rate, clocks); - - cfg - } - - /// Set the bitrate of the bus. - /// - /// Note: The timings currently assume a APB_CLK of 80MHz. - fn set_baud_rate(&mut self, baud_rate: BaudRate, clocks: &Clocks) { - // TWAI is clocked from the APB_CLK according to Table 6-4 [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf) - // Included timings are all for 80MHz so assert that we are running at 80MHz. - assert!(clocks.apb_clock == HertzU32::MHz(80)); - - // Unpack the baud rate timings and convert them to the values needed for the - // register. Many of the registers have a minimum value of 1 which is - // represented by having zero bits set, therefore many values need to - // have 1 subtracted from them before being stored into the register. - let timing = baud_rate.timing(); - - let prescale = (timing.baud_rate_prescaler / 2) - 1; - let sjw = timing.sync_jump_width - 1; - let tseg_1 = timing.tseg_1 - 1; - let tseg_2 = timing.tseg_2 - 1; - let triple_sample = timing.triple_sample; - - // Set up the prescaler and sync jump width. - self.peripheral - .register_block() - .bus_timing_0 - .modify(|_, w| { - w.baud_presc() - .variant(prescale) - .sync_jump_width() - .variant(sjw) - }); - - // Set up the time segment 1, time segment 2, and triple sample. - self.peripheral - .register_block() - .bus_timing_1 - .modify(|_, w| { - w.time_seg1() - .variant(tseg_1) - .time_seg2() - .variant(tseg_2) - .time_samp() - .bit(triple_sample) - }); - } - - /// Set up the acceptance filter on the device. - /// - /// NOTE: On a bus with mixed 11-bit and 29-bit packet id's, you may - /// experience an 11-bit filter match against a 29-bit frame and vice - /// versa. Your application should check the id again once a frame has - /// been received to make sure it is the expected value. - /// - /// [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf#subsubsection.29.4.6) - pub fn set_filter(&mut self, filter: impl Filter) { - // Set or clear the rx filter mode bit depending on the filter type. - let filter_mode_bit = filter.filter_type() == FilterType::Single; - self.peripheral - .register_block() - .mode - .modify(|_, w| w.rx_filter_mode().bit(filter_mode_bit)); - - // Convert the filter into values for the registers and store them to the - // registers. - let registers = filter.to_registers(); - - // Copy the filter to the peripheral. - unsafe { - copy_to_data_register(self.peripheral.register_block().data_0.as_ptr(), ®isters); - } - } - - /// Set the error warning threshold. - /// - /// In the case when any of an error counter value exceeds the threshold, or - /// all the error counter values are below the threshold, an error - /// warning interrupt will be triggered (given the enable signal is - /// valid). - pub fn set_error_warning_limit(&mut self, limit: u8) { - self.peripheral - .register_block() - .err_warning_limit - .write(|w| w.err_warning_limit().variant(limit)); - } - - /// Put the peripheral into Operation Mode, allowing the transmission and - /// reception of packets using the new object. - pub fn start(self) -> Twai<'d, T> { - // Put the peripheral into operation mode by clearing the reset mode bit. - self.peripheral - .register_block() - .mode - .modify(|_, w| w.reset_mode().clear_bit()); - - Twai { - peripheral: self.peripheral, - } - } -} - -/// An active TWAI peripheral in Normal Mode. -/// -/// In this mode, the TWAI controller can transmit and receive messages -/// including error signals (such as error and overload frames). -pub struct Twai<'d, T> { - peripheral: PeripheralRef<'d, T>, -} - -impl<'d, T> Twai<'d, T> -where - T: Instance, -{ - /// Stop the peripheral, putting it into reset mode and enabling - /// reconfiguration. - pub fn stop(self) -> TwaiConfiguration<'d, T> { - // Put the peripheral into reset/configuration mode by setting the reset mode - // bit. - self.peripheral - .register_block() - .mode - .modify(|_, w| w.reset_mode().set_bit()); - - TwaiConfiguration { - peripheral: self.peripheral, - } - } - - pub fn receive_error_count(&self) -> u8 { - self.peripheral - .register_block() - .rx_err_cnt - .read() - .rx_err_cnt() - .bits() - } - - pub fn transmit_error_count(&self) -> u8 { - self.peripheral - .register_block() - .tx_err_cnt - .read() - .tx_err_cnt() - .bits() - } - - /// Check if the controller is in a bus off state. - pub fn is_bus_off(&self) -> bool { - self.peripheral - .register_block() - .status - .read() - .bus_off_st() - .bit_is_set() - } - - /// Get the number of messages that the peripheral has available in the - /// receive FIFO. - /// - /// Note that this may not be the number of valid messages in the receive - /// FIFO due to fifo overflow/overrun. - pub fn num_available_messages(&self) -> u8 { - self.peripheral - .register_block() - .rx_message_cnt - .read() - .rx_message_counter() - .bits() - } - - /// Clear the receive FIFO, discarding any valid, partial, or invalid - /// packets. - /// - /// This is typically used to clear an overrun receive FIFO. - /// - /// TODO: Not sure if this needs to be guarded against Bus Off or other - /// error states. - pub fn clear_receive_fifo(&self) { - while self.num_available_messages() > 0 { - self.release_receive_fifo(); - } - } - - /// Release the message in the buffer. This will decrement the received - /// message counter and prepare the next message in the FIFO for - /// reading. - fn release_receive_fifo(&self) { - self.peripheral - .register_block() - .cmd - .write(|w| w.release_buf().set_bit()); - } -} - -#[derive(Debug)] -pub enum EspTwaiError { - BusOff, - EmbeddedHAL(ErrorKind), -} - -impl Error for EspTwaiError { - fn kind(&self) -> ErrorKind { - match self { - Self::BusOff => ErrorKind::Other, - Self::EmbeddedHAL(kind) => *kind, - } - } -} - -/// Copy data from multiple TWAI_DATA_x_REG registers, packing the source into -/// the destination. -/// -/// # Safety -/// This function is marked unsafe because it reads arbitrarily from -/// memory-mapped registers. Specifically, this function is used with the -/// TWAI_DATA_x_REG registers which has different results based on the mode of -/// the peripheral. -#[inline(always)] -unsafe fn copy_from_data_register(dest: &mut [u8], src: *const u32) { - for (i, dest) in dest.iter_mut().enumerate() { - // Perform a volatile read to avoid compiler optimizations. - *dest = src.add(i).read_volatile() as u8; - } -} - -/// Copy data to multiple TWAI_DATA_x_REG registers, unpacking the source into -/// the destination. -/// -/// # Safety -/// This function is marked unsafe because it writes arbitrarily to -/// memory-mapped registers. Specifically, this function is used with the -/// TWAI_DATA_x_REG registers which has different results based on the mode of -/// the peripheral. -#[inline(always)] -unsafe fn copy_to_data_register(dest: *mut u32, src: &[u8]) { - for (i, src) in src.iter().enumerate() { - // Perform a volatile write to avoid compiler optimizations. - dest.add(i).write_volatile(*src as u32); - } -} - -impl Can for Twai<'_, T> -where - T: Instance, -{ - type Frame = EspTwaiFrame; - type Error = EspTwaiError; - /// Transmit a frame. - /// - /// Because of how the TWAI registers are set up, we have to do some - /// assembly of bytes. Note that these registers serve a filter - /// configuration role when the device is in configuration mode so - /// patching the svd files to improve this may be non-trivial. - /// - /// [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf#subsubsection.29.4.4.2) - /// - /// NOTE: TODO: This may not work if using the self reception/self test - /// functionality. See notes 1 and 2 in the "Frame Identifier" section - /// of the reference manual. - fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { - let status = self.peripheral.register_block().status.read(); - - // Check that the peripheral is not in a bus off state. - if status.bus_off_st().bit_is_set() { - return nb::Result::Err(nb::Error::Other(EspTwaiError::BusOff)); - } - // Check that the peripheral is not already transmitting a packet. - if !status.tx_buf_st().bit_is_set() { - return nb::Result::Err(nb::Error::WouldBlock); - } - - // Assemble the frame information into the data_0 byte. - let frame_format: u8 = frame.is_extended() as u8; - let rtr_bit: u8 = frame.is_remote_frame() as u8; - let dlc_bits: u8 = frame.dlc() as u8 & 0b1111; - - let data_0: u8 = frame_format << 7 | rtr_bit << 6 | dlc_bits; - - self.peripheral - .register_block() - .data_0 - .write(|w| w.tx_byte_0().variant(data_0)); - - // Assemble the identifier information of the packet. - match frame.id() { - Id::Standard(id) => { - let id = id.as_raw(); - - self.peripheral - .register_block() - .data_1 - .write(|w| w.tx_byte_1().variant((id >> 3) as u8)); - - self.peripheral - .register_block() - .data_2 - .write(|w| w.tx_byte_2().variant((id << 5) as u8)); - } - Id::Extended(id) => { - let id = id.as_raw(); - - self.peripheral - .register_block() - .data_1 - .write(|w| w.tx_byte_1().variant((id >> 21) as u8)); - self.peripheral - .register_block() - .data_2 - .write(|w| w.tx_byte_2().variant((id >> 13) as u8)); - self.peripheral - .register_block() - .data_3 - .write(|w| w.tx_byte_3().variant((id >> 5) as u8)); - self.peripheral - .register_block() - .data_4 - .write(|w| w.tx_byte_4().variant((id << 3) as u8)); - } - } - - // Store the data portion of the packet into the transmit buffer. - if frame.is_data_frame() { - match frame.id() { - Id::Standard(_) => unsafe { - copy_to_data_register( - self.peripheral.register_block().data_3.as_ptr(), - frame.data(), - ) - }, - Id::Extended(_) => unsafe { - copy_to_data_register( - self.peripheral.register_block().data_5.as_ptr(), - frame.data(), - ) - }, - } - } else { - // Is RTR frame, so no data is included. - } - - // Set the transmit request command, this will lock the transmit buffer until - // the transmission is complete or aborted. - self.peripheral - .register_block() - .cmd - .write(|w| w.tx_req().set_bit()); - - // Success in readying packet for transmit. No packets can be replaced in the - // transmit buffer so return None in accordance with the - // embedded-can/embedded-hal trait. - nb::Result::Ok(None) - } - - /// Return a received frame if there are any available. - fn receive(&mut self) -> nb::Result { - let status = self.peripheral.register_block().status.read(); - - // Check that the peripheral is not in a bus off state. - if status.bus_off_st().bit_is_set() { - return nb::Result::Err(nb::Error::Other(EspTwaiError::BusOff)); - } - - // Check that we actually have packets to receive. - if !status.rx_buf_st().bit_is_set() { - return nb::Result::Err(nb::Error::WouldBlock); - } - - // Check if the packet in the receive buffer is valid or overrun. - if status.miss_st().bit_is_set() { - return nb::Result::Err(nb::Error::Other(EspTwaiError::EmbeddedHAL( - ErrorKind::Overrun, - ))); - } - - // Read the frame information and extract the frame id format and dlc. - let data_0 = self - .peripheral - .register_block() - .data_0 - .read() - .tx_byte_0() - .bits(); - - let is_standard_format = data_0 & 0b1 << 7 == 0; - let is_data_frame = data_0 & 0b1 << 6 == 0; - let dlc = (data_0 & 0b1111) as usize; - - // Read the payload from the packet and construct a frame. - let frame = if is_standard_format { - // Frame uses standard 11 bit id. - let data_1 = self - .peripheral - .register_block() - .data_1 - .read() - .tx_byte_1() - .bits(); - - let data_2 = self - .peripheral - .register_block() - .data_2 - .read() - .tx_byte_2() - .bits(); - - let raw_id: u16 = ((data_1 as u16) << 3) | ((data_2 as u16) >> 5); - - let id = StandardId::new(raw_id).unwrap(); - - if is_data_frame { - // Create a new frame from the contents of the appropriate TWAI_DATA_x_REG - // registers. - unsafe { - EspTwaiFrame::new_from_data_registers( - id, - self.peripheral.register_block().data_3.as_ptr(), - dlc, - ) - } - } else { - EspTwaiFrame::new_remote(id, dlc).unwrap() - } - } else { - // Frame uses extended 29 bit id. - let data_1 = self - .peripheral - .register_block() - .data_1 - .read() - .tx_byte_1() - .bits(); - - let data_2 = self - .peripheral - .register_block() - .data_2 - .read() - .tx_byte_2() - .bits(); - - let data_3 = self - .peripheral - .register_block() - .data_3 - .read() - .tx_byte_3() - .bits(); - - let data_4 = self - .peripheral - .register_block() - .data_4 - .read() - .tx_byte_4() - .bits(); - - let raw_id: u32 = (data_1 as u32) << 21 - | (data_2 as u32) << 13 - | (data_3 as u32) << 5 - | (data_4 as u32) >> 3; - - let id = ExtendedId::new(raw_id).unwrap(); - - if is_data_frame { - unsafe { - EspTwaiFrame::new_from_data_registers( - id, - self.peripheral.register_block().data_5.as_ptr(), - dlc, - ) - } - } else { - EspTwaiFrame::new_remote(id, dlc).unwrap() - } - }; - - // Release the packet we read from the FIFO, allowing the peripheral to prepare - // the next packet. - self.release_receive_fifo(); - - nb::Result::Ok(frame) - } -} - -pub trait Instance { - const SYSTEM_PERIPHERAL: system::Peripheral; - - const INPUT_SIGNAL: InputSignal; - const OUTPUT_SIGNAL: OutputSignal; - - fn register_block(&self) -> &RegisterBlock; -} - -#[cfg(any(esp32c3, esp32s3))] -impl Instance for crate::peripherals::TWAI0 { - const SYSTEM_PERIPHERAL: system::Peripheral = system::Peripheral::Twai0; - - const INPUT_SIGNAL: InputSignal = InputSignal::TWAI_RX; - const OUTPUT_SIGNAL: OutputSignal = OutputSignal::TWAI_TX; - - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } -} - -#[cfg(esp32c6)] -impl Instance for crate::peripherals::TWAI0 { - const SYSTEM_PERIPHERAL: system::Peripheral = system::Peripheral::Twai0; - - const INPUT_SIGNAL: InputSignal = InputSignal::TWAI0_RX; - const OUTPUT_SIGNAL: OutputSignal = OutputSignal::TWAI0_TX; - - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } -} - -#[cfg(esp32c6)] -impl Instance for crate::peripherals::TWAI1 { - const SYSTEM_PERIPHERAL: system::Peripheral = system::Peripheral::Twai1; - - const INPUT_SIGNAL: InputSignal = InputSignal::TWAI1_RX; - const OUTPUT_SIGNAL: OutputSignal = OutputSignal::TWAI1_TX; - - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } -} diff --git a/esp-hal-common/src/uart.rs b/esp-hal-common/src/uart.rs deleted file mode 100644 index 04b6b3c75a4..00000000000 --- a/esp-hal-common/src/uart.rs +++ /dev/null @@ -1,980 +0,0 @@ -//! UART driver - -use self::config::Config; -#[cfg(uart2)] -use crate::peripherals::UART2; -use crate::{ - clock::Clocks, - gpio::{InputPin, InputSignal, OutputPin, OutputSignal}, - peripheral::{Peripheral, PeripheralRef}, - peripherals::{ - uart0::{fifo::FIFO_SPEC, RegisterBlock}, - UART0, - UART1, - }, - system::PeripheralClockControl, -}; - -const UART_FIFO_SIZE: u16 = 128; - -/// Custom serial error type -#[derive(Debug)] -pub enum Error {} - -/// UART configuration -pub mod config { - /// Number of data bits - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - pub enum DataBits { - DataBits5 = 0, - DataBits6 = 1, - DataBits7 = 2, - DataBits8 = 3, - } - - /// Parity check - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - pub enum Parity { - ParityNone, - ParityEven, - ParityOdd, - } - - /// Number of stop bits - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - pub enum StopBits { - /// 1 stop bit - STOP1 = 1, - /// 1.5 stop bits - STOP1P5 = 2, - /// 2 stop bits - STOP2 = 3, - } - - /// UART configuration - #[derive(Debug, Copy, Clone)] - pub struct Config { - pub baudrate: u32, - pub data_bits: DataBits, - pub parity: Parity, - pub stop_bits: StopBits, - } - - impl Config { - pub fn baudrate(mut self, baudrate: u32) -> Self { - self.baudrate = baudrate; - self - } - - pub fn parity_none(mut self) -> Self { - self.parity = Parity::ParityNone; - self - } - - pub fn parity_even(mut self) -> Self { - self.parity = Parity::ParityEven; - self - } - - pub fn parity_odd(mut self) -> Self { - self.parity = Parity::ParityOdd; - self - } - - pub fn data_bits(mut self, data_bits: DataBits) -> Self { - self.data_bits = data_bits; - self - } - - pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { - self.stop_bits = stop_bits; - self - } - } - - impl Default for Config { - fn default() -> Config { - Config { - baudrate: 115_200, - data_bits: DataBits::DataBits8, - parity: Parity::ParityNone, - stop_bits: StopBits::STOP1, - } - } - } - - /// Configuration for the AT-CMD detection functionality - pub struct AtCmdConfig { - pub pre_idle_count: Option, - pub post_idle_count: Option, - pub gap_timeout: Option, - pub cmd_char: u8, - pub char_num: Option, - } - - impl AtCmdConfig { - pub fn new( - pre_idle_count: Option, - post_idle_count: Option, - gap_timeout: Option, - cmd_char: u8, - char_num: Option, - ) -> AtCmdConfig { - Self { - pre_idle_count, - post_idle_count, - gap_timeout, - cmd_char, - char_num, - } - } - } -} - -/// Pins used by the UART interface -pub trait UartPins { - fn configure_pins( - &mut self, - tx_signal: OutputSignal, - rx_signal: InputSignal, - cts_signal: InputSignal, - rts_signal: OutputSignal, - ); -} - -/// All pins offered by UART -pub struct AllPins<'d, TX: OutputPin, RX: InputPin, CTS: InputPin, RTS: OutputPin> { - pub(crate) tx: Option>, - pub(crate) rx: Option>, - pub(crate) cts: Option>, - pub(crate) rts: Option>, -} - -/// Tx and Rx pins -impl<'d, TX: OutputPin, RX: InputPin, CTS: InputPin, RTS: OutputPin> AllPins<'d, TX, RX, CTS, RTS> { - pub fn new( - tx: impl Peripheral

    + 'd, - rx: impl Peripheral

    + 'd, - cts: impl Peripheral

    + 'd, - rts: impl Peripheral

    + 'd, - ) -> AllPins<'d, TX, RX, CTS, RTS> { - crate::into_ref!(tx, rx, cts, rts); - AllPins { - tx: Some(tx), - rx: Some(rx), - cts: Some(cts), - rts: Some(rts), - } - } -} - -impl UartPins - for AllPins<'_, TX, RX, CTS, RTS> -{ - fn configure_pins( - &mut self, - tx_signal: OutputSignal, - rx_signal: InputSignal, - cts_signal: InputSignal, - rts_signal: OutputSignal, - ) { - if let Some(ref mut tx) = self.tx { - tx.set_to_push_pull_output() - .connect_peripheral_to_output(tx_signal); - } - - if let Some(ref mut rx) = self.rx { - rx.set_to_input().connect_input_to_peripheral(rx_signal); - } - - if let Some(ref mut cts) = self.cts { - cts.set_to_input().connect_input_to_peripheral(cts_signal); - } - - if let Some(ref mut rts) = self.rts { - rts.set_to_push_pull_output() - .connect_peripheral_to_output(rts_signal); - } - } -} - -pub struct TxRxPins<'d, TX: OutputPin, RX: InputPin> { - pub tx: Option>, - pub rx: Option>, -} - -impl<'d, TX: OutputPin, RX: InputPin> TxRxPins<'d, TX, RX> { - pub fn new_tx_rx( - tx: impl Peripheral

    + 'd, - rx: impl Peripheral

    + 'd, - ) -> TxRxPins<'d, TX, RX> { - crate::into_ref!(tx, rx); - TxRxPins { - tx: Some(tx), - rx: Some(rx), - } - } -} - -impl UartPins for TxRxPins<'_, TX, RX> { - fn configure_pins( - &mut self, - tx_signal: OutputSignal, - rx_signal: InputSignal, - _cts_signal: InputSignal, - _rts_signal: OutputSignal, - ) { - if let Some(ref mut tx) = self.tx { - tx.set_to_push_pull_output() - .connect_peripheral_to_output(tx_signal); - } - - if let Some(ref mut rx) = self.rx { - rx.set_to_input().connect_input_to_peripheral(rx_signal); - } - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::serial::Error for Error { - fn kind(&self) -> embedded_hal_1::serial::ErrorKind { - embedded_hal_1::serial::ErrorKind::Other - } -} - -/// UART driver -pub struct Uart<'d, T> { - uart: PeripheralRef<'d, T>, -} - -impl<'d, T> Uart<'d, T> -where - T: Instance, -{ - /// Create a new UART instance with defaults - pub fn new_with_config

    ( - uart: impl Peripheral

    + 'd, - config: Option, - mut pins: Option

    , - clocks: &Clocks, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self - where - P: UartPins, - { - crate::into_ref!(uart); - uart.enable_peripheral(peripheral_clock_control); - let mut serial = Uart { uart }; - serial.uart.disable_rx_interrupts(); - serial.uart.disable_tx_interrupts(); - - if let Some(ref mut pins) = pins { - pins.configure_pins( - serial.uart.tx_signal(), - serial.uart.rx_signal(), - serial.uart.cts_signal(), - serial.uart.rts_signal(), - ); - } - - config.map(|config| { - serial.change_data_bits(config.data_bits); - serial.change_parity(config.parity); - serial.change_stop_bits(config.stop_bits); - serial.change_baud(config.baudrate, clocks); - }); - - serial - } - - /// Create a new UART instance with defaults - pub fn new( - uart: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(uart); - uart.enable_peripheral(peripheral_clock_control); - let mut serial = Uart { uart }; - serial.uart.disable_rx_interrupts(); - serial.uart.disable_tx_interrupts(); - - serial - } - - /// Writes bytes - pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { - data.iter() - .try_for_each(|c| nb::block!(self.write_byte(*c))) - } - - /// Configures the AT-CMD detection settings. - pub fn set_at_cmd(&mut self, config: config::AtCmdConfig) { - #[cfg(not(any(esp32, esp32s2)))] - self.uart - .register_block() - .clk_conf - .modify(|_, w| w.sclk_en().clear_bit()); - - self.uart.register_block().at_cmd_char.write(|w| unsafe { - w.at_cmd_char() - .bits(config.cmd_char) - .char_num() - .bits(config.char_num.or(Some(1)).unwrap()) - }); - - if let Some(pre_idle_count) = config.pre_idle_count { - self.uart - .register_block() - .at_cmd_precnt - .write(|w| unsafe { w.pre_idle_num().bits(pre_idle_count.into()) }); - } - - if let Some(post_idle_count) = config.post_idle_count { - self.uart - .register_block() - .at_cmd_postcnt - .write(|w| unsafe { w.post_idle_num().bits(post_idle_count.into()) }); - } - - if let Some(gap_timeout) = config.gap_timeout { - self.uart - .register_block() - .at_cmd_gaptout - .write(|w| unsafe { w.rx_gap_tout().bits(gap_timeout.into()) }); - } - - #[cfg(not(any(esp32, esp32s2)))] - self.uart - .register_block() - .clk_conf - .modify(|_, w| w.sclk_en().set_bit()); - - self.sync_regs(); - } - - /// Configures the RX-FIFO threshold - pub fn set_rx_fifo_full_threshold(&mut self, threshold: u16) { - #[cfg(any(esp32, esp32c6))] - let threshold: u8 = threshold as u8; - - self.uart - .register_block() - .conf1 - .modify(|_, w| unsafe { w.rxfifo_full_thrhd().bits(threshold) }); - } - - /// Listen for AT-CMD interrupts - pub fn listen_at_cmd(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.at_cmd_char_det_int_ena().set_bit()); - } - - /// Stop listening for AT-CMD interrupts - pub fn unlisten_at_cmd(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.at_cmd_char_det_int_ena().clear_bit()); - } - - /// Listen for TX-DONE interrupts - pub fn listen_tx_done(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.tx_done_int_ena().set_bit()); - } - - /// Stop listening for TX-DONE interrupts - pub fn unlisten_tx_done(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.tx_done_int_ena().clear_bit()); - } - - /// Listen for RX-FIFO-FULL interrupts - pub fn listen_rx_fifo_full(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.rxfifo_full_int_ena().set_bit()); - } - - /// Stop listening for RX-FIFO-FULL interrupts - pub fn unlisten_rx_fifo_full(&mut self) { - self.uart - .register_block() - .int_ena - .modify(|_, w| w.rxfifo_full_int_ena().clear_bit()); - } - - /// Checks if AT-CMD interrupt is set - pub fn at_cmd_interrupt_set(&self) -> bool { - self.uart - .register_block() - .int_raw - .read() - .at_cmd_char_det_int_raw() - .bit_is_set() - } - - /// Checks if TX-DONE interrupt is set - pub fn tx_done_interrupt_set(&self) -> bool { - self.uart - .register_block() - .int_raw - .read() - .tx_done_int_raw() - .bit_is_set() - } - - /// Checks if RX-FIFO-FULL interrupt is set - pub fn rx_fifo_full_interrupt_set(&self) -> bool { - self.uart - .register_block() - .int_raw - .read() - .rxfifo_full_int_raw() - .bit_is_set() - } - - /// Reset AT-CMD interrupt - pub fn reset_at_cmd_interrupt(&self) { - self.uart - .register_block() - .int_clr - .write(|w| w.at_cmd_char_det_int_clr().set_bit()); - } - - /// Reset TX-DONE interrupt - pub fn reset_tx_done_interrupt(&self) { - self.uart - .register_block() - .int_clr - .write(|w| w.tx_done_int_clr().set_bit()); - } - - /// Reset RX-FIFO-FULL interrupt - pub fn reset_rx_fifo_full_interrupt(&self) { - self.uart - .register_block() - .int_clr - .write(|w| w.rxfifo_full_int_clr().set_bit()); - } - - fn write_byte(&mut self, word: u8) -> nb::Result<(), Error> { - if self.uart.get_tx_fifo_count() < UART_FIFO_SIZE { - self.uart - .register_block() - .fifo - .write(|w| unsafe { w.rxfifo_rd_byte().bits(word) }); - - Ok(()) - } else { - Err(nb::Error::WouldBlock) - } - } - - fn flush_tx(&self) -> nb::Result<(), Error> { - if self.uart.is_tx_idle() { - Ok(()) - } else { - Err(nb::Error::WouldBlock) - } - } - - fn read_byte(&mut self) -> nb::Result { - #[allow(unused_variables)] - let offset = 0; - - // on ESP32-S2 we need to use PeriBus2 to read the FIFO - #[cfg(esp32s2)] - let offset = 0x20c00000; - - if self.uart.get_rx_fifo_count() > 0 { - let value = unsafe { - let fifo = (self.uart.register_block().fifo.as_ptr() as *mut u8).offset(offset) - as *mut crate::peripherals::generic::Reg; - (*fifo).read().rxfifo_rd_byte().bits() - }; - - Ok(value) - } else { - Err(nb::Error::WouldBlock) - } - } - - /// Change the number of stop bits - pub fn change_stop_bits(&mut self, stop_bits: config::StopBits) -> &mut Self { - // workaround for hardware issue, when UART stop bit set as 2-bit mode. - #[cfg(esp32)] - if stop_bits == config::StopBits::STOP2 { - self.uart - .register_block() - .rs485_conf - .modify(|_, w| w.dl1_en().bit(true)); - - self.uart - .register_block() - .conf0 - .modify(|_, w| unsafe { w.stop_bit_num().bits(1) }); - } else { - self.uart - .register_block() - .rs485_conf - .modify(|_, w| w.dl1_en().bit(false)); - - self.uart - .register_block() - .conf0 - .modify(|_, w| unsafe { w.stop_bit_num().bits(stop_bits as u8) }); - } - - #[cfg(not(esp32))] - self.uart - .register_block() - .conf0 - .modify(|_, w| unsafe { w.stop_bit_num().bits(stop_bits as u8) }); - - self - } - - /// Change the number of data bits - fn change_data_bits(&mut self, data_bits: config::DataBits) -> &mut Self { - self.uart - .register_block() - .conf0 - .modify(|_, w| unsafe { w.bit_num().bits(data_bits as u8) }); - - self - } - - /// Change the type of parity checking - fn change_parity(&mut self, parity: config::Parity) -> &mut Self { - self.uart - .register_block() - .conf0 - .modify(|_, w| match parity { - config::Parity::ParityNone => w.parity_en().clear_bit(), - config::Parity::ParityEven => w.parity_en().set_bit().parity().clear_bit(), - config::Parity::ParityOdd => w.parity_en().set_bit().parity().set_bit(), - }); - - self - } - - #[cfg(any(esp32c2, esp32c3, esp32s3))] - fn change_baud(&self, baudrate: u32, clocks: &Clocks) { - // we force the clock source to be APB and don't use the decimal part of the - // divider - let clk = clocks.apb_clock.to_Hz(); - let max_div = 0b1111_1111_1111 - 1; - let clk_div = ((clk) + (max_div * baudrate) - 1) / (max_div * baudrate); - - self.uart.register_block().clk_conf.write(|w| unsafe { - w.sclk_sel() - .bits(1) // APB - .sclk_div_a() - .bits(0) - .sclk_div_b() - .bits(0) - .sclk_div_num() - .bits(clk_div as u8 - 1) - .rx_sclk_en() - .bit(true) - .tx_sclk_en() - .bit(true) - }); - - let clk = clk / clk_div; - let divider = clk / baudrate; - let divider = divider as u16; - - self.uart - .register_block() - .clkdiv - .write(|w| unsafe { w.clkdiv().bits(divider).frag().bits(0) }); - } - - #[cfg(esp32c6)] - fn change_baud(&self, baudrate: u32, clocks: &Clocks) { - // we force the clock source to be APB and don't use the decimal part of the - // divider - let clk = clocks.apb_clock.to_Hz(); - let max_div = 0b1111_1111_1111 - 1; - let clk_div = ((clk) + (max_div * baudrate) - 1) / (max_div * baudrate); - - // UART clocks are configured via PCR - let pcr = unsafe { &*esp32c6::PCR::PTR }; - - match self.uart.uart_number() { - 0 => { - pcr.uart0_conf - .modify(|_, w| w.uart0_rst_en().clear_bit().uart0_clk_en().set_bit()); - - pcr.uart0_sclk_conf.modify(|_, w| unsafe { - w.uart0_sclk_div_a() - .bits(0) - .uart0_sclk_div_b() - .bits(0) - .uart0_sclk_div_num() - .bits(clk_div as u8 - 1) - .uart0_sclk_sel() - .bits(0x1) // TODO: this probably shouldn't be hard-coded - .uart0_sclk_en() - .set_bit() - }); - } - 1 => { - pcr.uart1_conf - .modify(|_, w| w.uart1_rst_en().clear_bit().uart1_clk_en().set_bit()); - - pcr.uart1_sclk_conf.modify(|_, w| unsafe { - w.uart1_sclk_div_a() - .bits(0) - .uart1_sclk_div_b() - .bits(0) - .uart1_sclk_div_num() - .bits(clk_div as u8 - 1) - .uart1_sclk_sel() - .bits(0x1) // TODO: this probably shouldn't be hard-coded - .uart1_sclk_en() - .set_bit() - }); - } - _ => unreachable!(), // ESP32-C6 only has 2 UART instances - } - - let clk = clk / clk_div; - let divider = clk / baudrate; - let divider = divider as u16; - - self.uart - .register_block() - .clkdiv - .write(|w| unsafe { w.clkdiv().bits(divider).frag().bits(0) }); - } - - #[cfg(any(esp32, esp32s2))] - fn change_baud(&self, baudrate: u32, clocks: &Clocks) { - // we force the clock source to be APB and don't use the decimal part of the - // divider - let clk = clocks.apb_clock.to_Hz(); - - self.uart - .register_block() - .conf0 - .modify(|_, w| w.tick_ref_always_on().bit(true)); - let divider = clk / baudrate; - - self.uart - .register_block() - .clkdiv - .write(|w| unsafe { w.clkdiv().bits(divider).frag().bits(0) }); - } - - #[cfg(esp32c6)] // TODO introduce a cfg symbol for this - #[inline(always)] - fn sync_regs(&mut self) { - self.uart - .register_block() - .reg_update - .modify(|_, w| w.reg_update().set_bit()); - - while self - .uart - .register_block() - .reg_update - .read() - .reg_update() - .bit_is_set() - { - // wait - } - } - - #[cfg(not(esp32c6))] - #[inline(always)] - fn sync_regs(&mut self) {} -} - -/// UART peripheral instance -pub trait Instance { - fn register_block(&self) -> &RegisterBlock; - - fn uart_number(&self) -> usize; - - fn disable_tx_interrupts(&mut self) { - self.register_block().int_clr.write(|w| { - w.txfifo_empty_int_clr() - .set_bit() - .tx_brk_done_int_clr() - .set_bit() - .tx_brk_idle_done_int_clr() - .set_bit() - .tx_done_int_clr() - .set_bit() - }); - - self.register_block().int_ena.write(|w| { - w.txfifo_empty_int_ena() - .clear_bit() - .tx_brk_done_int_ena() - .clear_bit() - .tx_brk_idle_done_int_ena() - .clear_bit() - .tx_done_int_ena() - .clear_bit() - }); - } - - fn disable_rx_interrupts(&mut self) { - self.register_block().int_clr.write(|w| { - w.rxfifo_full_int_clr() - .set_bit() - .rxfifo_ovf_int_clr() - .set_bit() - .rxfifo_tout_int_clr() - .set_bit() - }); - - self.register_block().int_ena.write(|w| { - w.rxfifo_full_int_ena() - .clear_bit() - .rxfifo_ovf_int_ena() - .clear_bit() - .rxfifo_tout_int_ena() - .clear_bit() - }); - } - - fn get_tx_fifo_count(&mut self) -> u16 { - self.register_block() - .status - .read() - .txfifo_cnt() - .bits() - .into() - } - - fn get_rx_fifo_count(&mut self) -> u16 { - self.register_block() - .status - .read() - .rxfifo_cnt() - .bits() - .into() - } - - fn is_tx_idle(&self) -> bool { - #[cfg(esp32)] - let idle = self.register_block().status.read().st_utx_out().bits() == 0x0u8; - #[cfg(not(esp32))] - let idle = self.register_block().fsm_status.read().st_utx_out().bits() == 0x0u8; - - idle - } - - fn is_rx_idle(&self) -> bool { - #[cfg(esp32)] - let idle = self.register_block().status.read().st_urx_out().bits() == 0x0u8; - #[cfg(not(esp32))] - let idle = self.register_block().fsm_status.read().st_urx_out().bits() == 0x0u8; - - idle - } - - fn tx_signal(&self) -> OutputSignal; - - fn rx_signal(&self) -> InputSignal; - - fn cts_signal(&self) -> InputSignal; - - fn rts_signal(&self) -> OutputSignal; - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl); -} - -impl Instance for UART0 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn uart_number(&self) -> usize { - 0 - } - - fn tx_signal(&self) -> OutputSignal { - OutputSignal::U0TXD - } - - fn rx_signal(&self) -> InputSignal { - InputSignal::U0RXD - } - - fn cts_signal(&self) -> InputSignal { - InputSignal::U0CTS - } - - fn rts_signal(&self) -> OutputSignal { - OutputSignal::U0RTS - } - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Uart0); - } -} - -impl Instance for UART1 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn uart_number(&self) -> usize { - 1 - } - - fn tx_signal(&self) -> OutputSignal { - OutputSignal::U1TXD - } - - fn rx_signal(&self) -> InputSignal { - InputSignal::U1RXD - } - - fn cts_signal(&self) -> InputSignal { - InputSignal::U1CTS - } - - fn rts_signal(&self) -> OutputSignal { - OutputSignal::U1RTS - } - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Uart1); - } -} - -#[cfg(uart2)] -impl Instance for UART2 { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn uart_number(&self) -> usize { - 2 - } - - fn tx_signal(&self) -> OutputSignal { - OutputSignal::U2TXD - } - - fn rx_signal(&self) -> InputSignal { - InputSignal::U2RXD - } - - fn cts_signal(&self) -> InputSignal { - InputSignal::U2CTS - } - - fn rts_signal(&self) -> OutputSignal { - OutputSignal::U2RTS - } - - fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) { - peripheral_clock_control.enable(crate::system::Peripheral::Uart2); - } -} - -#[cfg(feature = "ufmt")] -impl ufmt_write::uWrite for Uart<'_, T> -where - T: Instance, -{ - type Error = Error; - - #[inline] - fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - self.write_bytes(s.as_bytes()) - } - - #[inline] - fn write_char(&mut self, ch: char) -> Result<(), Self::Error> { - let mut buffer = [0u8; 4]; - self.write_bytes(ch.encode_utf8(&mut buffer).as_bytes()) - } -} - -impl core::fmt::Write for Uart<'_, T> -where - T: Instance, -{ - #[inline] - fn write_str(&mut self, s: &str) -> core::fmt::Result { - self.write_bytes(s.as_bytes()).map_err(|_| core::fmt::Error) - } -} - -impl embedded_hal::serial::Write for Uart<'_, T> -where - T: Instance, -{ - type Error = Error; - - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_byte(word) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.flush_tx() - } -} - -impl embedded_hal::serial::Read for Uart<'_, T> -where - T: Instance, -{ - type Error = Error; - - fn read(&mut self) -> nb::Result { - self.read_byte() - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::serial::ErrorType for Uart<'_, T> { - type Error = Error; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_nb::serial::Read for Uart<'_, T> -where - T: Instance, -{ - fn read(&mut self) -> nb::Result { - self.read_byte() - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_nb::serial::Write for Uart<'_, T> -where - T: Instance, -{ - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_byte(word) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.flush_tx() - } -} diff --git a/esp-hal-common/src/usb_serial_jtag.rs b/esp-hal-common/src/usb_serial_jtag.rs deleted file mode 100644 index e014a816f57..00000000000 --- a/esp-hal-common/src/usb_serial_jtag.rs +++ /dev/null @@ -1,248 +0,0 @@ -use core::convert::Infallible; - -use crate::{ - peripheral::{Peripheral, PeripheralRef}, - peripherals::{usb_device::RegisterBlock, USB_DEVICE}, - system::PeripheralClockControl, -}; - -pub struct UsbSerialJtag<'d> { - usb_serial: PeripheralRef<'d, USB_DEVICE>, -} - -/// Custom USB serial error type -type Error = Infallible; - -impl<'d> UsbSerialJtag<'d> { - /// Create a new USB serial/JTAG instance with defaults - pub fn new( - usb_serial: impl Peripheral

    + 'd, - peripheral_clock_control: &mut PeripheralClockControl, - ) -> Self { - crate::into_ref!(usb_serial); - - peripheral_clock_control.enable(crate::system::Peripheral::Sha); - - let mut dev = Self { usb_serial }; - dev.usb_serial.disable_rx_interrupts(); - dev.usb_serial.disable_tx_interrupts(); - - dev - } - - /// Write data to the serial output in chunks of up to 64 bytes - pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { - let reg_block = self.usb_serial.register_block(); - - for chunk in data.chunks(64) { - unsafe { - for &b in chunk { - reg_block.ep1.write(|w| w.rdwr_byte().bits(b.into())) - } - reg_block.ep1_conf.write(|w| w.wr_done().set_bit()); - - while reg_block.ep1_conf.read().bits() & 0b011 == 0b000 { - // wait - } - } - } - - Ok(()) - } - - /// Write data to the serial output in a non-blocking manner - /// Requires manual flushing (automatically flushed every 64 bytes) - pub fn write_byte_nb(&mut self, word: u8) -> nb::Result<(), Error> { - let reg_block = self.usb_serial.register_block(); - - if reg_block - .ep1_conf - .read() - .serial_in_ep_data_free() - .bit_is_set() - { - // the FIFO is not full - unsafe { - reg_block.ep1.write(|w| w.rdwr_byte().bits(word.into())); - } - - Ok(()) - } else { - Err(nb::Error::WouldBlock) - } - } - - /// Flush the output FIFO and block until it has been sent - pub fn flush_tx(&mut self) -> Result<(), Error> { - let reg_block = self.usb_serial.register_block(); - reg_block.ep1_conf.write(|w| w.wr_done().set_bit()); - - while reg_block.ep1_conf.read().bits() & 0b011 == 0b000 { - // wait - } - - Ok(()) - } - - /// Flush the output FIFO but don't block if it isn't ready immediately - pub fn flush_tx_nb(&mut self) -> nb::Result<(), Error> { - let reg_block = self.usb_serial.register_block(); - reg_block.ep1_conf.write(|w| w.wr_done().set_bit()); - - if reg_block.ep1_conf.read().bits() & 0b011 == 0b000 { - Err(nb::Error::WouldBlock) - } else { - Ok(()) - } - } - - pub fn read_byte(&mut self) -> nb::Result { - let reg_block = self.usb_serial.register_block(); - - // Check if there are any bytes to read - if reg_block - .ep1_conf - .read() - .serial_out_ep_data_avail() - .bit_is_set() - { - let value = reg_block.ep1.read().rdwr_byte().bits(); - - Ok(value) - } else { - Err(nb::Error::WouldBlock) - } - } - - /// Listen for RX-PACKET-RECV interrupts - pub fn listen_rx_packet_recv_interrupt(&mut self) { - let reg_block = self.usb_serial.register_block(); - reg_block - .int_ena - .modify(|_, w| w.serial_out_recv_pkt_int_ena().set_bit()); - } - - /// Stop listening for RX-PACKET-RECV interrupts - pub fn unlisten_rx_packet_recv_interrupt(&mut self) { - let reg_block = self.usb_serial.register_block(); - reg_block - .int_ena - .modify(|_, w| w.serial_out_recv_pkt_int_ena().clear_bit()); - } - - /// Checks if RX-PACKET-RECV interrupt is set - pub fn rx_packet_recv_interrupt_set(&mut self) -> bool { - let reg_block = unsafe { &*USB_DEVICE::PTR }; - reg_block - .int_st - .read() - .serial_out_recv_pkt_int_st() - .bit_is_set() - } - - /// Reset RX-PACKET-RECV interrupt - pub fn reset_rx_packet_recv_interrupt(&mut self) { - let reg_block = unsafe { &*USB_DEVICE::PTR }; - - reg_block - .int_clr - .write(|w| w.serial_out_recv_pkt_int_clr().set_bit()) - } -} - -/// USB serial/JTAG peripheral instance -pub trait Instance { - fn register_block(&self) -> &RegisterBlock; - - fn disable_tx_interrupts(&mut self) { - self.register_block() - .int_ena - .write(|w| w.serial_in_empty_int_ena().clear_bit()); - - self.register_block() - .int_clr - .write(|w| w.serial_in_empty_int_clr().set_bit()) - } - - fn disable_rx_interrupts(&mut self) { - self.register_block() - .int_ena - .write(|w| w.serial_out_recv_pkt_int_ena().clear_bit()); - - self.register_block() - .int_clr - .write(|w| w.serial_out_recv_pkt_int_clr().set_bit()) - } - - fn get_rx_fifo_count(&self) -> u16 { - let ep0_state = self.register_block().in_ep0_st.read(); - let wr_addr = ep0_state.in_ep0_wr_addr().bits(); - let rd_addr = ep0_state.in_ep0_rd_addr().bits(); - - (wr_addr - rd_addr).into() - } - - fn get_tx_fifo_count(&self) -> u16 { - let ep1_state = self.register_block().in_ep1_st.read(); - let wr_addr = ep1_state.in_ep1_wr_addr().bits(); - let rd_addr = ep1_state.in_ep1_rd_addr().bits(); - - (wr_addr - rd_addr).into() - } -} - -impl Instance for USB_DEVICE { - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } -} - -impl core::fmt::Write for UsbSerialJtag<'_> { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - self.write_bytes(s.as_bytes()).map_err(|_| core::fmt::Error) - } -} - -impl embedded_hal::serial::Read for UsbSerialJtag<'_> { - type Error = Error; - - fn read(&mut self) -> nb::Result { - self.read_byte() - } -} - -impl embedded_hal::serial::Write for UsbSerialJtag<'_> { - type Error = Error; - - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_byte_nb(word) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.flush_tx_nb() - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_1::serial::ErrorType for UsbSerialJtag<'_> { - type Error = Error; -} - -#[cfg(feature = "eh1")] -impl embedded_hal_nb::serial::Read for UsbSerialJtag<'_> { - fn read(&mut self) -> nb::Result { - self.read_byte() - } -} - -#[cfg(feature = "eh1")] -impl embedded_hal_nb::serial::Write for UsbSerialJtag<'_> { - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_byte_nb(word) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.flush_tx_nb() - } -} diff --git a/esp-hal-procmacros/CHANGELOG.md b/esp-hal-procmacros/CHANGELOG.md new file mode 100644 index 00000000000..f3751af7fe2 --- /dev/null +++ b/esp-hal-procmacros/CHANGELOG.md @@ -0,0 +1,129 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `doc_replace` can now replace `__placeholders__` in-line (#5119) + +### Changed + +- The `handler` macro no longer generates code to check the Priority (#4996) + +### Fixed + +- Fixed the logic for determining which sections should be loaded in `load_lp_code!` macro (#4612) + + +### Removed + + +## [v0.21.0] - 2025-10-30 + +## [v0.20.0] - 2025-10-13 + +### Added + +- Added support for lifetimes and generics to `BuilderLite` derive macro (#3963) +- Added `ram(reclaimed)` as an alias for `link_section = ".dram2_uninit"` (#4245) + +### Changed + +- All `ram` proc macro options except `reclaimed` are considered `unstable` (#4309) + +### Fixed + +- Replaced `embassy_main` with `rtos_main` (intended to be called as `esp_rtos::main`) (#4172) + +## [v0.19.0] - 2025-07-16 + +### Added + +- Added simplified conditional documentation using the `#[enable_doc_switch]` macro (#3630) +- `error!` and `warning!` (moved from `esp-build`) (#3645) +- Added `doc_replace` macro (#3744) + +### Changed + +- MSRV is now 1.88.0 (#3742) +- The `handler` macro no longer accepts priority as a string (#3643) + +## [v0.18.0] - 2025-06-03 + +### Changed + +- Using the `#[handler]` macro with a priority of `None` will fail at compile time (#3304) +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) + +## [0.17.0] - 2025-02-24 + +### Added + +- Added `#[builder_lite(into)]` attribute that generates the setter with `impl Into` parameter (#3011) +- Added `#[builder_lite(skip_setter)]` and `#[builder_lite(skip_getter)]` attribute to skip generating setters or getters (#3011) +- Added `#[builder_lite(skip)]` to ignore a field completely (#3011) + +## 0.16.0 - 2025-01-15 + +### Added + +- Added the `BuilderLite` derive macro which implements the Builder Lite pattern for a struct (#2614) + +### Changed + +- Functions marked with `#[handler]` can now be referenced in `const` context. (#2559) +- Bump MSRV to 1.84 (#2951) + +### Removed + +- Removed the `enum-dispatch`, `interrupt`, and `ram` features (#2594) + +## 0.15.0 - 2024-11-20 + +### Changed + +- Remove `get_` prefix from functions (#2528) + +## 0.14.0 - 2024-10-10 + +## 0.13.0 - 2024-08-29 + +## 0.12.0 - 2024-07-15 + +## 0.11.0 - 2024-06-04 + +## 0.10.0 - 2024-04-18 + +## 0.9.0 - 2024-03-18 + +## 0.8.0 - 2023-12-12 + +## 0.7.0 - 2023-10-31 + +## 0.6.1 - 2023-09-05 + +## 0.6.0 - 2023-07-04 + +## 0.5.0 - 2023-03-27 + +## 0.4.0 - 2023-02-21 + +## 0.2.0 - 2023-01-26 + +## 0.1.0 - 2022-08-25 + +### Added + +- Initial release (#2518) + +[0.17.0]: https://github.com/esp-rs/esp-hal/releases/tag/esp-hal-procmacros-v0.17.0 +[v0.18.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-procmacros-v0.17.0...esp-hal-procmacros-v0.18.0 +[v0.19.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-procmacros-v0.18.0...esp-hal-procmacros-v0.19.0 +[v0.20.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-procmacros-v0.19.0...esp-hal-procmacros-v0.20.0 +[v0.21.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-procmacros-v0.20.0...esp-hal-procmacros-v0.21.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-hal-procmacros-v0.21.0...HEAD diff --git a/esp-hal-procmacros/Cargo.toml b/esp-hal-procmacros/Cargo.toml index a7a63697b0c..83ba0dc4588 100644 --- a/esp-hal-procmacros/Cargo.toml +++ b/esp-hal-procmacros/Cargo.toml @@ -1,38 +1,59 @@ [package] -name = "esp-hal-procmacros" -version = "0.5.0" -authors = [ - "Jesse Braham ", - "Björn Quentin ", +name = "esp-hal-procmacros" +version = "0.21.0" +edition = "2024" +rust-version = "1.88.0" +description = "Procedural macros for esp-hal" +documentation = "https://docs.espressif.com/projects/rust/esp-hal-procmacros/latest/" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +exclude = ["testdata"] + +[package.metadata.espressif] +doc-config = { features = [] } +check-configs = [ + { features = [] }, +] +clippy-configs = [ + { features = [] }, ] -edition = "2021" -rust-version = "1.65.0" -description = "Procedural macros for ESP-HAL" -repository = "https://github.com/esp-rs/esp-hal" -license = "MIT OR Apache-2.0" [package.metadata.docs.rs] -features = ["esp32c3", "interrupt", "riscv"] +features = ["has-ulp-core", "interrupt", "ram", "is-ulp-core"] [lib] proc-macro = true [dependencies] -darling = "0.14.4" -proc-macro-crate = "1.3.1" -proc-macro-error = "1.0.4" -proc-macro2 = "1.0.53" -quote = "1.0.26" -syn = {version = "1.0.109", features = ["extra-traits", "full"]} +document-features = "0.2" +object = { version = "0.37", default-features = false, features = ["read_core", "elf"], optional = true } +proc-macro-crate = "3.4" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["extra-traits", "full"] } +termcolor = "1.4" [features] -interrupt = [] -riscv = [] -rtc_slow = [] -xtensa = [] -esp32 = [] -esp32c2 = [] -esp32c3 = [] -esp32c6 = [] -esp32s2 = [] -esp32s3 = [] +## Indicates the target device has RTC slow memory available. +rtc-slow = [] + +#! ### Low-power Core Feature Flags +## Indicate that the SoC contains an LP core. +has-lp-core = ["dep:object"] +## Indicate that the SoC contains a ULP core. +has-ulp-core = ["dep:object"] +## Provide an `#[entry]` macro for running applications on the ESP32-C6's +## LP core. +is-lp-core = [] +## Provide an `#[entry]` macro for running applications on the ESP32-S2/S3's +## ULP core. +is-ulp-core = [] + +# Enables `ram(reclaimed)` +__esp_idf_bootloader = [] + +[lints.rust] +# Starting with 1.85.0, the test cfg is considered to be a "userspace" config despite being also set by rustc and should be managed by the build system itself. +# CI started to fail with rustc 1.92.0-nightly (f04e3dfc8 2025-10-19), https://github.com/esp-rs/esp-hal/pull/4386#issuecomment-3491320175 +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test)'] } diff --git a/esp-hal-procmacros/README.md b/esp-hal-procmacros/README.md index d6d90093a6d..f1ba3c4ba57 100644 --- a/esp-hal-procmacros/README.md +++ b/esp-hal-procmacros/README.md @@ -1,15 +1,19 @@ # esp-hal-procmacros [![Crates.io](https://img.shields.io/crates/v/esp-hal-procmacros?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-hal-procmacros) -[![docs.rs](https://img.shields.io/docsrs/esp-hal-procmacros?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-hal-procmacros) +[![docs.rs](https://img.shields.io/docsrs/esp-hal-procmacros?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-hal-procmacros/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.88.0-blue?labelColor=1C2C2E&style=flat-square) ![Crates.io](https://img.shields.io/crates/l/esp-hal-procmacros?labelColor=1C2C2E&style=flat-square) [![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) -Procedural macros for placing statics and functions into RAM, and for marking interrupt handlers. +Procedural macros for use with `esp-hal` and other related packages. -## [Documentation] +## [Documentation](https://docs.espressif.com/projects/rust/esp-hal-procmacros/latest/) -[documentation]: https://docs.rs/esp-hal-procmacros/ + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. ## License diff --git a/esp-hal-procmacros/src/alert.rs b/esp-hal-procmacros/src/alert.rs new file mode 100644 index 00000000000..c15fcac7feb --- /dev/null +++ b/esp-hal-procmacros/src/alert.rs @@ -0,0 +1,60 @@ +use std::io::Write as _; + +use proc_macro::TokenStream; +use syn::{LitStr, parse_macro_input}; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +// Adapted from: +// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L54-L93 +pub fn do_alert(color: Color, input: TokenStream) -> TokenStream { + let message = parse_macro_input!(input as LitStr).value(); + + let stderr = &mut StandardStream::stderr(ColorChoice::Auto); + let color_spec = ColorSpec::new().set_fg(Some(color)).clone(); + + let mut has_nonspace = false; + + for mut line in message.lines() { + if !has_nonspace { + let (maybe_heading, rest) = split_heading(line); + + if let Some(heading) = maybe_heading { + stderr.set_color(color_spec.clone().set_bold(true)).ok(); + write!(stderr, "\n{heading}").ok(); + has_nonspace = true; + } + + line = rest; + } + + if line.is_empty() { + writeln!(stderr).ok(); + } else { + stderr.set_color(&color_spec).ok(); + writeln!(stderr, "{line}").ok(); + + has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' '); + } + } + + stderr.reset().ok(); + writeln!(stderr).ok(); + + TokenStream::new() +} + +// Adapted from: +// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L95-L114 +fn split_heading(s: &str) -> (Option<&str>, &str) { + let mut end = 0; + while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) { + end += 1; + } + + if end >= 3 && (end == s.len() || s[end..].starts_with(':')) { + let (heading, rest) = s.split_at(end); + (Some(heading), rest) + } else { + (None, s) + } +} diff --git a/esp-hal-procmacros/src/blocking.rs b/esp-hal-procmacros/src/blocking.rs new file mode 100644 index 00000000000..b5eae0b8462 --- /dev/null +++ b/esp-hal-procmacros/src/blocking.rs @@ -0,0 +1,98 @@ +#![deny(warnings)] + +use proc_macro2::{Span, TokenStream}; +use syn::{ + ItemFn, + parse::{self}, +}; + +/// Marks the firmware entry point. +pub fn main(args: TokenStream, f: ItemFn) -> TokenStream { + if f.sig.asyncness.is_some() { + return parse::Error::new( + Span::call_site(), + "If you want to use `async` please use `esp-rtos`'s `#[esp_rtos::main]` macro instead.", + ) + .to_compile_error(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error(); + } + + let root = match proc_macro_crate::crate_name("esp-hal") { + Ok(proc_macro_crate::FoundCrate::Name(ref name)) => quote::format_ident!("{name}"), + _ => quote::format_ident!("esp_hal"), + }; + + quote::quote!( + #[doc = "The main entry point of the firmware, generated by the `#[main]` macro."] + #[#root::__macro_implementation::__entry] + #f + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic() { + let result = main( + quote::quote! {}.into(), + syn::parse2(quote::quote! { + fn main() {} + }) + .unwrap(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[doc = "The main entry point of the firmware, generated by the `#[main]` macro."] + #[esp_hal::__macro_implementation::__entry] + fn main () { } + } + .to_string() + ); + } + + #[test] + fn test_try_async() { + let result = main( + quote::quote! {}.into(), + syn::parse2(quote::quote! { + async fn main() {} + }) + .unwrap(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "If you want to use `async` please use `esp-rtos`'s `#[esp_rtos::main]` macro instead." } + } + .to_string() + ); + } + + #[test] + fn test_try_non_emptyargs() { + let result = main( + quote::quote! {non_empty}.into(), + syn::parse2(quote::quote! { + fn main() {} + }) + .unwrap(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "This attribute accepts no arguments" } + } + .to_string() + ); + } +} diff --git a/esp-hal-procmacros/src/builder.rs b/esp-hal-procmacros/src/builder.rs new file mode 100644 index 00000000000..b9187d6d07b --- /dev/null +++ b/esp-hal-procmacros/src/builder.rs @@ -0,0 +1,510 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + Attribute, + Data, + DataStruct, + GenericArgument, + Ident, + Path, + PathArguments, + PathSegment, + Token, + Type, + parse::Error as ParseError, + punctuated::Punctuated, + spanned::Spanned, +}; + +const KNOWN_HELPERS: &[&str] = &[ + // Generate the setter with `impl Into` as the argument + "into", + // Do not generate a getter or setter + "skip", + // Do not generate a setter + "skip_setter", + // Do not generate a getter + "skip_getter", + // Feature gate the generated setters and getters by the "unstable" feature + "unstable", + // Generate a by-reference getter instead of a by-value getter for non-`Copy` types + "reference", +]; + +/// A lightweight version of the `Builder` derive macro that only generates +/// setters and getters for each field of a struct. +/// +/// +pub fn builder_lite_derive(item: TokenStream) -> TokenStream { + let input: syn::DeriveInput = crate::unwrap_or_compile_error!(syn::parse2(item)); + + let span = input.span(); + let ident = input.ident; + let generics = input.generics; + + let mut fns = Vec::new(); + let Data::Struct(DataStruct { fields, .. }) = &input.data else { + return ParseError::new( + span, + "#[derive(Builder)] is only defined for structs, not for enums or unions!", + ) + .to_compile_error(); + }; + for field in fields { + let helper_attributes = match collect_helper_attrs(&field.attrs) { + Ok(attr) => attr, + Err(err) => return err.to_compile_error(), + }; + + // Ignore field if it has a `skip` helper attribute. + if helper_attributes.iter().any(|h| h == "skip") { + continue; + } + + let unstable = if helper_attributes.iter().any(|h| h == "unstable") { + quote! { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "unstable")] + } + } else { + quote! {} + }; + + let field_ident = field.ident.as_ref().unwrap(); + let field_type = &field.ty; + + let function_ident = format_ident!("{}", format!("with_{field_ident}").replace("__", "_")); + + let maybe_path_type = extract_type_path(field_type) + .and_then(|path| extract_option_segment(path)) + .and_then(|path_seg| match path_seg.arguments { + PathArguments::AngleBracketed(ref params) => params.args.first(), + _ => None, + }) + .and_then(|generic_arg| match *generic_arg { + GenericArgument::Type(ref ty) => Some(ty), + _ => None, + }); + + let (mut field_setter_type, mut field_assigns) = if let Some(inner_type) = maybe_path_type { + (quote! { #inner_type }, quote! { Some(#field_ident) }) + } else { + (quote! { #field_type }, quote! { #field_ident }) + }; + + // Wrap type and assignment with `Into` if needed. + if helper_attributes.iter().any(|h| h == "into") { + field_setter_type = quote! { impl Into<#field_setter_type> }; + field_assigns = quote! { #field_ident .into() }; + } + + if !helper_attributes.iter().any(|h| h == "skip_setter") { + fns.push(quote! { + #[doc = concat!(" Assign the given value to the `", stringify!(#field_ident) ,"` field.")] + #unstable + #[must_use] + pub fn #function_ident(mut self, #field_ident: #field_setter_type) -> Self { + self.#field_ident = #field_assigns; + self + } + }); + + if maybe_path_type.is_some() { + let function_ident = format_ident!("{}_none", function_ident); + fns.push(quote! { + #[doc = concat!(" Set the value of `", stringify!(#field_ident), "` to `None`.")] + #unstable + #[must_use] + pub fn #function_ident(mut self) -> Self { + self.#field_ident = None; + self + } + }); + } + } + + if !helper_attributes.iter().any(|h| h == "skip_getter") { + let docs = field.attrs.iter().filter_map(|attr| { + let syn::Meta::NameValue(ref attr) = attr.meta else { + return None; + }; + + if attr.path.is_ident("doc") { + let docstr = &attr.value; + Some(quote! { #[doc = #docstr] }) + } else { + None + } + }); + + let is_non_copy = helper_attributes.iter().any(|h| h == "reference"); + + if is_non_copy { + // Generate a by-reference getter for non-`Copy` types + fns.push(quote! { + #(#docs)* + #unstable + pub fn #field_ident(&self) -> &#field_type { + &self.#field_ident + } + }); + } else { + // Generate a by-value getter for `Copy` types (the default) + fns.push(quote! { + #(#docs)* + #unstable + pub fn #field_ident(&self) -> #field_type { + self.#field_ident + } + }); + } + } + } + + let implementation = quote! { + #[automatically_derived] + impl #generics #ident #generics { + #(#fns)* + } + }; + + implementation +} + +// https://stackoverflow.com/a/56264023 +fn extract_type_path(ty: &Type) -> Option<&Path> { + match *ty { + Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), + _ => None, + } +} + +// https://stackoverflow.com/a/56264023 +fn extract_option_segment(path: &Path) -> Option<&PathSegment> { + let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| { + acc.push_str(&v.ident.to_string()); + acc.push('|'); + acc + }); + + vec!["Option|", "std|option|Option|", "core|option|Option|"] + .into_iter() + .find(|s| idents_of_path == *s) + .and_then(|_| path.segments.last()) +} + +fn collect_helper_attrs(attrs: &[Attribute]) -> Result, syn::Error> { + let mut helper_attributes = Vec::new(); + for attr in attrs + .iter() + .filter(|attr| attr.path().is_ident("builder_lite")) + { + for helper in attr.parse_args_with(|input: syn::parse::ParseStream| { + Punctuated::::parse_terminated(input) + })? { + if !KNOWN_HELPERS.iter().any(|known| helper == *known) { + return Err(ParseError::new( + helper.span(), + format!( + "Unknown helper attribute `{}`. Only the following are allowed: {}", + helper, + KNOWN_HELPERS.join(", ") + ), + )); + } + + helper_attributes.push(helper); + } + } + + Ok(helper_attributes) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wrong_item() { + let result = builder_lite_derive( + quote::quote! { + fn main() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{"expected one of: `struct`, `enum`, `union`"} + } + .to_string() + ); + } + + #[test] + fn test_wrong_item2() { + let result = builder_lite_derive( + quote::quote! { + enum Foo {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{"#[derive(Builder)] is only defined for structs, not for enums or unions!"} + } + .to_string() + ); + } + + #[test] + fn test_wrong_item_attr() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + #[builder_lite(foo)] + foo: u32, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{"Unknown helper attribute `foo`. Only the following are allowed: into, skip, skip_setter, skip_getter, unstable, reference"} + } + .to_string() + ); + } + + #[test] + fn test_basic() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + #[doc = "keep docs"] + bar: u32, + baz: bool, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[automatically_derived] + impl Foo { + #[doc = concat!(" Assign the given value to the `" , stringify ! (bar) , "` field.")] + #[must_use] + pub fn with_bar(mut self , bar : u32) -> Self { + self.bar = bar; + self + } + + #[doc = "keep docs"] + pub fn bar(&self) -> u32 { + self.bar + } + + #[doc = concat!(" Assign the given value to the `" , stringify ! (baz) , "` field.")] + #[must_use] + pub fn with_baz(mut self, baz: bool) -> Self { + self.baz = baz; + self + } + + pub fn baz (& self) -> bool { + self.baz + } + } + } + .to_string() + ); + } + + #[test] + fn test_option_field() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + bar: Option, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[automatically_derived] + impl Foo { + #[doc = concat!(" Assign the given value to the `" , stringify!(bar) , "` field.")] + #[must_use] + pub fn with_bar(mut self, bar: u32) -> Self { + self.bar = Some(bar); + self + } + + #[doc = concat!(" Set the value of `" , stringify ! (bar) , "` to `None`.")] + #[must_use] + pub fn with_bar_none (mut self) -> Self { + self.bar = None; + self + } + + pub fn bar(&self) -> Option { + self.bar + } + } + } + .to_string() + ); + } + + #[test] + fn test_field_attrs() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + #[builder_lite(unstable)] + bar: u32, + + #[builder_lite(skip_setter)] + baz: bool, + + #[builder_lite(reference)] + boo: String, + + #[builder_lite(into)] + bam: Foo, + + #[builder_lite(unstable)] + #[builder_lite(into)] + foo: Foo, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[automatically_derived] + impl Foo { + # [doc = concat ! (" Assign the given value to the `" , stringify ! (bar) , "` field.")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "unstable")] + #[must_use] + pub fn with_bar(mut self, bar: u32) -> Self { + self.bar = bar; + self + } + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "unstable")] + pub fn bar(&self) -> u32 { + self.bar + } + pub fn baz(&self) -> bool { + self.baz + } + # [doc = concat ! (" Assign the given value to the `" , stringify ! (boo) , "` field.")] + #[must_use] + pub fn with_boo(mut self, boo: String) -> Self { + self.boo = boo; + self + } + pub fn boo(&self) -> &String { + &self.boo + } + # [doc = concat ! (" Assign the given value to the `" , stringify ! (bam) , "` field.")] + #[must_use] + pub fn with_bam(mut self, bam: impl Into) -> Self { + self.bam = bam.into(); + self + } + pub fn bam(&self) -> Foo { + self.bam + } + # [doc = concat ! (" Assign the given value to the `" , stringify ! (foo) , "` field.")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "unstable")] + #[must_use] + pub fn with_foo(mut self, foo: impl Into) -> Self { + self.foo = foo.into(); + self + } + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "unstable")] + pub fn foo(&self) -> Foo { + self.foo + } + } + } + .to_string() + ); + } + + #[test] + fn test_skip_getter() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + #[builder_lite(skip_getter)] + bar: Option, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[automatically_derived] + impl Foo { + #[doc = concat!(" Assign the given value to the `" , stringify!(bar) , "` field.")] + #[must_use] + pub fn with_bar(mut self, bar: u32) -> Self { + self.bar = Some(bar); + self + } + + #[doc = concat!(" Set the value of `" , stringify ! (bar) , "` to `None`.")] + #[must_use] + pub fn with_bar_none (mut self) -> Self { + self.bar = None; + self + } + } + } + .to_string() + ); + } + + #[test] + fn test_skip() { + let result = builder_lite_derive( + quote::quote! { + struct Foo { + #[builder_lite(skip)] + bar: Option, + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[automatically_derived] + impl Foo { + } + } + .to_string() + ); + } +} diff --git a/esp-hal-procmacros/src/doc_replace.rs b/esp-hal-procmacros/src/doc_replace.rs new file mode 100644 index 00000000000..7133f17781f --- /dev/null +++ b/esp-hal-procmacros/src/doc_replace.rs @@ -0,0 +1,703 @@ +use std::{collections::HashMap, str::FromStr}; + +use proc_macro2::{TokenStream, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + AttrStyle, + Attribute, + Expr, + ExprLit, + Item, + Lit, + LitStr, + Meta, + MetaNameValue, + Token, + braced, + parse::Parse, + punctuated::Punctuated, + spanned::Spanned, + token, +}; + +struct Replacements { + // Placeholder => [attribute contents] + // + // Replaces `# {tag}` placeholders with the attribute contents. Replaces the entire line. + line_replacements: HashMap>, + + // Placeholder => [(condition, string contents)], may be unconditional. + // + // Replaces `__tag__` placeholders with the string contents. Replaces only the placeholder + // inside the line, and applies the condition to the entire line if present. + inline_replacements: HashMap, String)>>, +} + +impl Replacements { + fn get(&self, lines: &str, outer: bool, span: proc_macro2::Span) -> Vec { + let mut attributes = vec![]; + for line in lines.split('\n') { + let mut attrs = vec![]; + let trimmed = line.trim(); + if let Some(lines) = self.line_replacements.get(trimmed) { + for line in lines { + if outer { + attrs.push(syn::parse_quote_spanned! { span => #[ #line ] }); + } else { + attrs.push(syn::parse_quote_spanned! { span => #![ #line ] }); + } + } + } else if let Some((placeholder, replacements)) = self + .inline_replacements + .iter() + .find(|(k, _v)| trimmed.contains(k.as_str())) + { + for (cfg, replacement) in replacements.iter() { + let line = line.replace(placeholder, replacement); + let line = create_raw_string(&line); + let attr_inner = if let Some(condition) = cfg { + quote! { cfg_attr(#condition, doc = #line) } + } else { + quote! { doc = #line } + }; + if outer { + attrs.push(syn::parse_quote_spanned! { span => #[ #attr_inner ] }); + } else { + attrs.push(syn::parse_quote_spanned! { span => #![ #attr_inner ] }); + } + } + } else { + // Just append the line, in the expected format (`doc = r" Foobar"`) + let line = create_raw_string(line); + if outer { + attrs.push(syn::parse_quote_spanned! { span => #[doc = #line] }); + } else { + attrs.push(syn::parse_quote_spanned! { span => #![doc = #line] }); + } + } + attributes.extend(attrs); + } + + attributes + } +} + +fn create_raw_string(line: &str) -> TokenStream2 { + let hash = if line.contains("#\"") { + "##" + } else if line.contains('"') { + "#" + } else { + "" + }; + + TokenStream2::from_str(&format!("r{hash}\"{line}\"{hash}")).unwrap() +} + +impl Parse for Replacements { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut line_replacements = HashMap::new(); + let mut inline_replacements = HashMap::new(); + + let mut add_line_replacement = |placeholder: &str, replacement: Vec| { + line_replacements.insert(format!("# {{{placeholder}}}"), replacement); + }; + let mut add_inline_replacement = + |placeholder: &str, replacement: Vec<(Option, String)>| { + // The placeholder must be a valid Rust identifier to keep rustfmt happy + inline_replacements.insert(format!("__{placeholder}__"), replacement); + }; + + if !input.is_empty() { + let args = Punctuated::::parse_terminated(input)?; + for arg in args { + match arg.replacement { + ReplacementKind::Literal(expr) => { + if let Expr::Lit(ExprLit { + lit: Lit::Str(ref lit_str), + .. + }) = expr + { + add_inline_replacement(&arg.placeholder, vec![(None, lit_str.value())]); + } + + add_line_replacement( + &arg.placeholder, + vec![quote! { + doc = #expr + }], + ); + } + ReplacementKind::Choice(items) => { + let mut conditions = vec![]; + let mut bodies = vec![]; + let mut lit_strs = vec![]; + let mut cfgs = vec![]; + + for branch in items { + let body = branch.body; + + if let Expr::Lit(ExprLit { + lit: Lit::Str(ref lit_str), + .. + }) = body + { + lit_strs.push(lit_str.value()); + } + + match branch.condition { + Some(Meta::List(cfg)) if cfg.path.is_ident("cfg") => { + let condition = cfg.tokens; + + cfgs.push(condition.clone()); + conditions.push(condition); + bodies.push(body); + } + None => { + conditions.push(quote! { not(any( #(#cfgs),*) ) }); + bodies.push(body); + } + _ => { + return Err(syn::Error::new( + branch.condition.span(), + "Expected a cfg condition or catch-all condition using `_`", + )); + } + } + } + + let branches = conditions + .iter() + .zip(bodies.iter()) + .map(|(condition, body)| { + quote! { + cfg_attr(#condition, doc = #body) + } + }) + .collect::>(); + add_line_replacement(&arg.placeholder, branches); + + if lit_strs.len() == bodies.len() { + let branches = conditions + .into_iter() + .map(Some) + .zip(lit_strs.into_iter()) + .collect::>(); + add_inline_replacement(&arg.placeholder, branches); + } + } + } + } + } + + Ok(Self { + line_replacements, + inline_replacements, + }) + } +} + +struct Replacement { + placeholder: String, + replacement: ReplacementKind, +} + +impl Parse for Replacement { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let placeholder: LitStr = input.parse()?; + let _arrow: Token![=>] = input.parse()?; + let replacement: ReplacementKind = input.parse()?; + + Ok(Self { + placeholder: placeholder.value(), + replacement, + }) + } +} + +enum ReplacementKind { + Literal(Expr), + Choice(Punctuated), +} + +impl Parse for ReplacementKind { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if !input.peek(token::Brace) { + return input.parse().map(Self::Literal); + } + + let choices; + braced!(choices in input); + + let branches = Punctuated::::parse_terminated(&choices)?; + + Ok(Self::Choice(branches)) + } +} + +struct Branch { + condition: Option, + body: Expr, +} + +impl Parse for Branch { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let condition: Option = if input.parse::().is_ok() { + None + } else { + Some(input.parse()?) + }; + + input.parse::]>()?; + + let body: Expr = input.parse()?; + + Ok(Branch { condition, body }) + } +} + +pub(crate) fn replace(attr: TokenStream, input: TokenStream) -> TokenStream { + let mut replacements: Replacements = match syn::parse2(attr) { + Ok(replacements) => replacements, + Err(e) => return e.into_compile_error(), + }; + + replacements.line_replacements.insert( + "# {before_snippet}".to_string(), + vec![quote! { doc = crate::before_snippet!() }], + ); + replacements.line_replacements.insert( + "# {after_snippet}".to_string(), + vec![quote! { doc = crate::after_snippet!() }], + ); + + let mut item: Item = crate::unwrap_or_compile_error!(syn::parse2(input)); + + let mut replacement_attrs = Vec::new(); + + let attrs = item.attrs_mut(); + for attr in attrs { + if let Meta::NameValue(MetaNameValue { path, value, .. }) = &attr.meta + && let Some(ident) = path.get_ident() + && ident == "doc" + && let Expr::Lit(lit) = value + && let Lit::Str(doc) = &lit.lit + { + let replacement = replacements.get( + doc.value().as_str(), + attr.style == AttrStyle::Outer, + attr.span(), + ); + replacement_attrs.extend(replacement); + } else { + replacement_attrs.push(attr.clone()); + } + } + + *item.attrs_mut() = replacement_attrs; + + quote! { #item } +} + +trait ItemLike { + fn attrs_mut(&mut self) -> &mut Vec; +} + +impl ItemLike for Item { + fn attrs_mut(&mut self) -> &mut Vec { + match self { + Item::Fn(item_fn) => &mut item_fn.attrs, + Item::Struct(item_struct) => &mut item_struct.attrs, + Item::Enum(item_enum) => &mut item_enum.attrs, + Item::Trait(item_trait) => &mut item_trait.attrs, + Item::Mod(module) => &mut module.attrs, + Item::Macro(item_macro) => &mut item_macro.attrs, + _ => panic!("Unsupported item type for switch macro"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic() { + let result = replace( + quote! {}.into(), + quote! { + /// # Configuration + /// + /// ## Overview + /// + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## Examples + /// ### Default initialization + /// ```rust, no_run + /// # {before_snippet} + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + /// # {after_snippet} + /// ``` + struct Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// # Configuration + /// + /// ## Overview + /// + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## Examples + /// ### Default initialization + /// ```rust, no_run + #[doc = crate::before_snippet!()] + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + #[doc = crate::after_snippet!()] + /// ``` + struct Foo {} + } + .to_string() + ); + } + + #[test] + fn test_one_doc_attr() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = r#" # Configuration + ## Overview + This module contains the initial configuration for the system. + ## Configuration + In the [`esp_hal::init()`][crate::init] method, we can configure different + parameters for the system: + - CPU clock configuration. + - Watchdog configuration. + ## Examples + ### Default initialization + ```rust, no_run + # {before_snippet} + let peripherals = esp_hal::init(esp_hal::Config::default()); + # {after_snippet} + ```"#] + struct Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// # Configuration + /// ## Overview + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## Examples + /// ### Default initialization + /// ```rust, no_run + #[doc = crate::before_snippet!()] + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + #[doc = crate::after_snippet!()] + /// ``` + struct Foo {} + } + .to_string() + ); + } + + #[test] + fn test_custom_replacements() { + let result = replace( + quote! { + "freq" => { + cfg(esp32h2) => "let freq = Rate::from_mhz(32);", + _ => "let freq = Rate::from_mhz(80);" + }, + "other" => "replacement" + }.into(), + quote! { + #[doc = " # Configuration"] + #[doc = " ## Overview"] + #[doc = " This module contains the initial configuration for the system."] + #[doc = " ## Configuration"] + #[doc = " In the [`esp_hal::init()`][crate::init] method, we can configure different"] + #[doc = " parameters for the system:"] + #[doc = " - CPU clock configuration."] + #[doc = " - Watchdog configuration."] + #[doc = " ## Examples"] + #[doc = " ### Default initialization"] + #[doc = " ```rust, no_run"] + #[doc = " # {freq}"] + #[doc = " # {before_snippet}"] + #[doc = " let peripherals = esp_hal::init(esp_hal::Config::default());"] + #[doc = " # {after_snippet}"] + #[doc = " ```"] + struct Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// # Configuration + /// ## Overview + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## Examples + /// ### Default initialization + /// ```rust, no_run + #[cfg_attr (esp32h2 , doc = "let freq = Rate::from_mhz(32);")] + #[cfg_attr (not (any (esp32h2)) , doc = "let freq = Rate::from_mhz(80);")] + #[doc = crate::before_snippet!()] + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + #[doc = crate::after_snippet!()] + /// ``` + struct Foo {} + } + .to_string() + ); + } + + #[test] + fn test_custom_inline_replacements() { + let result = replace( + quote! { + "freq" => { + cfg(esp32h2) => "32", + _ => "80" + }, + "other" => "Replacement" + } + .into(), + quote! { + /// # Configuration + /// ## Overview + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## __other__ Examples + /// ### Default initialization + /// ```rust, no_run + /// let freq = Rate::from_mhz(__freq__); + /// # {before_snippet} + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + /// # {after_snippet} + /// ``` + struct Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// # Configuration + /// ## Overview + /// This module contains the initial configuration for the system. + /// ## Configuration + /// In the [`esp_hal::init()`][crate::init] method, we can configure different + /// parameters for the system: + /// - CPU clock configuration. + /// - Watchdog configuration. + /// ## Replacement Examples + /// ### Default initialization + /// ```rust, no_run + #[cfg_attr (esp32h2 , doc = r" let freq = Rate::from_mhz(32);")] + #[cfg_attr (not (any (esp32h2)) , doc = r" let freq = Rate::from_mhz(80);")] + #[doc = crate::before_snippet!()] + /// let peripherals = esp_hal::init(esp_hal::Config::default()); + #[doc = crate::after_snippet!()] + /// ``` + struct Foo {} + } + .to_string() + ); + } + + #[test] + fn test_custom_fail() { + let result = replace( + quote! { + "freq" => { + abc(esp32h2) => "let freq = Rate::from_mhz(32);", + }, + } + .into(), + quote! {}.into(), + ); + + assert_eq!(result.to_string(), quote! { + ::core::compile_error!{ "Expected a cfg condition or catch-all condition using `_`" } + }.to_string()); + } + + #[test] + fn test_basic_fn() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + fn foo() { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// docs + #[doc = crate::before_snippet!()] + fn foo () { } + } + .to_string() + ); + } + + #[test] + fn test_basic_enum() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + enum Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// docs + #[doc = crate::before_snippet!()] + enum Foo { } + } + .to_string() + ); + } + + #[test] + fn test_basic_trait() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + trait Foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// docs + #[doc = crate::before_snippet!()] + trait Foo { } + } + .to_string() + ); + } + + #[test] + fn test_basic_mod() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + mod foo { + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// docs + #[doc = crate::before_snippet!()] + mod foo { } + } + .to_string() + ); + } + + #[test] + fn test_basic_macro() { + let result = replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + macro_rules! foo { + () => { + }; + } + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote! { + /// docs + #[doc = crate::before_snippet!()] + macro_rules! foo { + () => { + }; + } + } + .to_string() + ); + } + + // TODO panicking is not the nicest way to handle this + #[test] + #[should_panic] + fn test_basic_fail_wrong_item() { + replace( + quote! {}.into(), + quote! { + #[doc = " docs"] + #[doc = " # {before_snippet}"] + static FOO: u32 = 0u32; + } + .into(), + ); + } +} diff --git a/esp-hal-procmacros/src/interrupt.rs b/esp-hal-procmacros/src/interrupt.rs new file mode 100644 index 00000000000..11ee7236f0d --- /dev/null +++ b/esp-hal-procmacros/src/interrupt.rs @@ -0,0 +1,342 @@ +use proc_macro_crate::{FoundCrate, crate_name}; +use proc_macro2::{Ident, Span, TokenStream}; +use syn::{ + AttrStyle, + Attribute, + ItemFn, + Meta, + ReturnType, + Token, + Type, + parse::{Error as SynError, Parser}, + punctuated::Punctuated, + spanned::Spanned, +}; + +pub enum WhiteListCaller { + Interrupt, +} + +pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f: ItemFn = crate::unwrap_or_compile_error!(syn::parse2(input)); + let original_span = f.span(); + + let attr_args = match Punctuated::::parse_terminated.parse2(args) { + Ok(v) => v, + Err(e) => return e.into_compile_error(), + }; + + let mut priority = None; + + for arg in attr_args { + match arg { + Meta::NameValue(meta_name_value) => { + if meta_name_value.path.is_ident("priority") { + if priority.is_some() { + return SynError::new( + meta_name_value.span(), + "duplicate `priority` attribute", + ) + .into_compile_error(); + } + priority = Some(meta_name_value.value); + } else { + return SynError::new(meta_name_value.span(), "expected `priority = `") + .into_compile_error(); + } + } + other => { + return SynError::new(other.span(), "expected `priority = `") + .into_compile_error(); + } + } + } + + let root = Ident::new( + match crate_name("esp-hal") { + Ok(FoundCrate::Name(ref name)) => name, + _ => "crate", + }, + Span::call_site(), + ); + + let priority = match priority { + Some(ref priority) => quote::quote! { #priority }, + _ => quote::quote! { #root::interrupt::Priority::min() }, + }; + + // XXX should we blacklist other attributes? + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { + return error; + } + + let valid_signature = f.sig.constness.is_none() + && f.sig.abi.is_none() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + } + && f.sig.inputs.len() <= 1; + + if !valid_signature { + return SynError::new( + f.span(), + "`#[handler]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`", + ) + .to_compile_error(); + } + + f.sig.abi = syn::parse_quote_spanned!(original_span => extern "C"); + let orig = f.sig.ident; + let vis = f.vis.clone(); + f.sig.ident = Ident::new( + &format!("__esp_hal_internal_{orig}"), + proc_macro2::Span::call_site(), + ); + let new = f.sig.ident.clone(); + + quote::quote_spanned!(original_span => + #f + + #[allow(non_upper_case_globals)] + #vis const #orig: #root::interrupt::InterruptHandler = #root::interrupt::InterruptHandler::new(#new, #priority); + ) +} + +pub fn check_attr_whitelist( + attrs: &[Attribute], + caller: WhiteListCaller, +) -> Result<(), TokenStream> { + let whitelist = &[ + "doc", + "link_section", + "cfg", + "allow", + "warn", + "deny", + "forbid", + "cold", + "ram", + "inline", + ]; + + 'o: for attr in attrs { + if let Some(attr_name) = get_attr_name(attr) + && whitelist.contains(&attr_name.as_str()) + { + continue 'o; + } + + let err_str = match caller { + WhiteListCaller::Interrupt => { + "this attribute is not allowed on an interrupt handler controlled by esp-hal" + } + }; + + return Err(SynError::new(attr.span(), err_str).to_compile_error()); + } + + Ok(()) +} + +/// Extracts the base name of an attribute, including unwrapping of `#[unsafe(...)]`. +fn get_attr_name(attr: &Attribute) -> Option { + if !matches!(attr.style, AttrStyle::Outer) { + return None; + } + + let name = attr.path().get_ident().map(|x| x.to_string()); + + match &name { + Some(name) if name == "unsafe" => { + // Try to parse the inner meta of #[unsafe(...)] + if let Ok(inner_meta) = attr.parse_args::() { + inner_meta.path().get_ident().map(|x| x.to_string()) + } else { + None + } + } + _ => name, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic() { + let result = handler( + quote::quote! {}.into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!(result.to_string(), quote::quote! { + extern "C" fn __esp_hal_internal_foo() {} + #[allow(non_upper_case_globals)] + const foo: crate::interrupt::InterruptHandler = crate::interrupt::InterruptHandler::new( + __esp_hal_internal_foo, + crate::interrupt::Priority::min() + ); + }.to_string()); + } + + #[test] + fn test_priority() { + let result = handler( + quote::quote! { + priority = esp_hal::interrupt::Priority::Priority2 + } + .into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + extern "C" fn __esp_hal_internal_foo() {} + #[allow(non_upper_case_globals)] + const foo: crate::interrupt::InterruptHandler = + crate::interrupt::InterruptHandler::new( + __esp_hal_internal_foo, + esp_hal::interrupt::Priority::Priority2 + ); + } + .to_string() + ); + } + + #[test] + fn test_forbidden_attr() { + let result = handler( + quote::quote! {}.into(), + quote::quote! { + #[forbidden] + fn foo(){} + } + .into(), + ); + + assert_eq!(result.to_string(), quote::quote! { + ::core::compile_error!{ "this attribute is not allowed on an interrupt handler controlled by esp-hal" } + }.to_string()); + } + + #[test] + fn test_duplicate_priority() { + let result = handler( + quote::quote! { + priority = esp_hal::interrupt::Priority::Priority2, + priority = esp_hal::interrupt::Priority::Priority1, + } + .into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "duplicate `priority` attribute" } + } + .to_string() + ); + } + + #[test] + fn test_wrong_args() { + let result = handler( + quote::quote! { + true + } + .into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "expected identifier, found keyword `true`" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg() { + let result = handler( + quote::quote! { + not_allowed = true, + } + .into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "expected `priority = `" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg2() { + let result = handler( + quote::quote! { + A,B + } + .into(), + quote::quote! { + fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "expected `priority = `" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_sig() { + let result = handler( + quote::quote! {}.into(), + quote::quote! { + fn foo() -> u32 {} + } + .into(), + ); + + assert_eq!(result.to_string(), quote::quote! { + ::core::compile_error!{ "`#[handler]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`" } + }.to_string()); + } +} diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index 246e38bbc02..81784350813 100644 --- a/esp-hal-procmacros/src/lib.rs +++ b/esp-hal-procmacros/src/lib.rs @@ -1,464 +1,341 @@ -//! Procedural macros for placing statics and functions into RAM, and for -//! marking interrupt handlers. - +//! ## Overview +//! +//! Procedural macros for use with the `esp-hal` family of HAL packages. In +//! general, you should not need to depend on this package directly, as the +//! relevant procmacros are re-exported by the various HAL packages. +//! +//! Provides macros for: +//! +//! - Placing statics and functions into RAM +//! - Marking interrupt handlers +//! - Blocking and Async `#[main]` macros +//! +//! These macros offer developers a convenient way to control memory placement +//! and define interrupt handlers in their embedded applications, allowing for +//! optimized memory usage and precise handling of hardware interrupts. +//! +//! Key Components: +//! - [`handler`](macro@handler) - Attribute macro for marking interrupt handlers. Interrupt +//! handlers are used to handle specific hardware interrupts generated by peripherals. +//! +//! - [`ram`](macro@ram) - Attribute macro for placing statics and functions into specific memory +//! sections, such as SRAM or RTC RAM (slow or fast) with different initialization options. See +//! its documentation for details. +//! +//! - [`esp_rtos::main`](macro@rtos_main) - Creates a new instance of `esp_rtos::embassy::Executor` +//! and declares an application entry point spawning the corresponding function body as an async +//! task. +//! +//! ## Examples +//! +//! #### `main` macro +//! +//! Requires the `embassy` feature to be enabled. +//! +//! ```rust,ignore +//! #[main] +//! async fn main(spawner: Spawner) { +//! // Your application's entry point +//! } +//! ``` +//! +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] -use darling::FromMeta; -use proc_macro::{self, Span, TokenStream}; -use proc_macro_error::{abort, proc_macro_error}; -use quote::quote; -#[cfg(feature = "interrupt")] -use syn::{ - parse, - spanned::Spanned, - AttrStyle, - Attribute, - Ident, - ItemFn, - Meta::Path, - ReturnType, - Type, - Visibility, -}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - AttributeArgs, -}; - -#[derive(Debug, Default, FromMeta)] -#[darling(default)] -struct RamArgs { - rtc_fast: bool, - rtc_slow: bool, - uninitialized: bool, - zeroed: bool, -} - -/// This attribute allows placing statics and functions into ram. +use proc_macro::TokenStream; + +mod alert; +mod blocking; +mod builder; +mod doc_replace; +mod interrupt; +#[cfg(any( + feature = "is-lp-core", + feature = "is-ulp-core", + feature = "has-lp-core", + feature = "has-ulp-core" +))] +mod lp_core; +mod ram; +mod rtos_main; + +/// Sets which segment of RAM to use for a function or static and how it should +/// be initialized. +/// +/// # Options +/// +/// - `rtc_fast`: Use RTC fast RAM. +/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets. +/// - `persistent`: Persist the contents of the `static` across resets. See [the section +/// below](#persistent) for details. +/// - `zeroed`: Initialize the memory of the `static` to zero. The initializer expression will be +/// discarded. Types used must implement [`bytemuck::Zeroable`]. +/// - `reclaimed`: Memory reclaimed from the esp-idf bootloader. +/// +/// Using both `rtc_fast` and `rtc_slow` or `persistent` and `zeroed` together +/// is an error. +/// +/// ## `persistent` +/// +/// Initialize the memory to zero after the initial boot. Thereafter, +/// initialization is skipped to allow communication across `software_reset()`, +/// deep sleep, watchdog timeouts, etc. /// -/// Options that can be specified are rtc_slow or rtc_fast to use the -/// RTC slow or RTC fast ram instead of the normal SRAM. +/// Types used must implement [`bytemuck::AnyBitPattern`]. /// -/// The uninitialized option will skip initialization of the memory -/// (e.g. to persist it across resets or deep sleep mode for the RTC RAM) +/// ### Warnings /// -/// Not all targets support RTC slow ram. +/// - A system-level or lesser reset occurring before the ram has been zeroed *could* skip +/// initialization and start the application with the static filled with random bytes. +/// - There is no way to keep some kinds of resets from happening while updating a persistent +/// static—not even a critical section. +/// +/// If these are issues for your application, consider adding a checksum +/// alongside the data. +/// +/// # Examples +/// +/// ```rust, ignore +/// #[ram(unstable(rtc_fast))] +/// static mut SOME_INITED_DATA: [u8; 2] = [0xaa, 0xbb]; +/// +/// #[ram(unstable(rtc_fast, persistent))] +/// static mut SOME_PERSISTENT_DATA: [u8; 2] = [0; 2]; +/// +/// #[ram(unstable(rtc_fast, zeroed))] +/// static mut SOME_ZEROED_DATA: [u8; 8] = [0; 8]; +/// ``` +/// +/// See the `ram` example in the qa-test folder of the esp-hal repository for a full usage example. +/// +/// [`bytemuck::AnyBitPattern`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.AnyBitPattern.html +/// [`bytemuck::Zeroable`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.Zeroable.html #[proc_macro_attribute] -#[proc_macro_error] pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream { - let attr_args = parse_macro_input!(args as AttributeArgs); - - let RamArgs { - rtc_fast, - rtc_slow, - uninitialized, - zeroed, - } = match FromMeta::from_list(&attr_args) { - Ok(v) => v, - Err(e) => { - return e.write_errors().into(); - } - }; - - let item: syn::Item = syn::parse(input).expect("failed to parse input"); - - #[cfg(not(feature = "rtc_slow"))] - if rtc_slow { - abort!( - Span::call_site(), - "rtc_slow is not available for this target" - ); - } - - let is_fn = matches!(item, syn::Item::Fn(_)); - let section_name = match (is_fn, rtc_fast, rtc_slow, uninitialized, zeroed) { - (true, false, false, false, false) => Ok(".rwtext"), - (true, true, false, false, false) => Ok(".rtc_fast.text"), - (true, false, true, false, false) => Ok(".rtc_slow.text"), - - (false, false, false, false, false) => Ok(".data"), - - (false, true, false, false, false) => Ok(".rtc_fast.data"), - (false, true, false, true, false) => Ok(".rtc_fast.noinit"), - (false, true, false, false, true) => Ok(".rtc_fast.bss"), - - (false, false, true, false, false) => Ok(".rtc_slow.data"), - (false, false, true, true, false) => Ok(".rtc_slow.noinit"), - (false, false, true, false, true) => Ok(".rtc_slow.bss"), - - _ => Err(()), - }; - - let section = match (is_fn, section_name) { - (true, Ok(section_name)) => quote! { - #[link_section = #section_name] - #[inline(never)] // make certain function is not inlined - }, - (false, Ok(section_name)) => quote! { - #[link_section = #section_name] - }, - (_, Err(_)) => { - abort!(Span::call_site(), "Invalid combination of ram arguments"); - } - }; - - let output = quote! { - #section - #item - }; - output.into() + ram::ram(args.into(), input.into()).into() } -/// Marks a function as an interrupt handler +/// Replaces placeholders in rustdoc doc comments. /// -/// Used to handle on of the [interrupts](enum.Interrupt.html). +/// The purpose of this macro is to enable us to extract boilerplate, while at +/// the same time let rustfmt format code blocks. This macro rewrites the whole +/// documentation of the annotated item. /// -/// When specified between braces (`#[interrupt(example)]`) that interrupt will -/// be used and the function can have an arbitrary name. Otherwise the name of -/// the function must be the name of the interrupt. +/// Replacements can be placed in the documentation as `# {placeholder}`. Each +/// replacement must be its own line. The `before_snippet` and `after_snippet` placeholders are +/// expanded to the `esp_hal::before_snippet!()` and `esp_hal::after_snippet!()` macros, and are +/// expected to be used in example code blocks. /// -/// Example usage: +/// In-line replacements can be placed in the middle of a line as `__placeholder__`. Currently, +/// only literal strings can be substituted into in-line placeholders, and only one placeholder +/// can be used per line. /// -/// ```rust -/// #[interrupt] -/// fn GPIO() { -/// // code -/// } -/// ``` +/// You can also define custom replacements in the attribute. A replacement can be +/// an unconditional literal (i.e. a string that is always substituted into the doc comment), +/// or a conditional. /// -/// The interrupt context can also be supplied by adding a argument to the -/// interrupt function for example, on Xtensa based chips: +/// ## Examples /// -/// ```rust -/// fn GPIO(context: &mut xtensa_lx_rt::exception::Context) { -/// // code -/// } +/// ```rust, ignore +/// #[doc_replace( +/// "literal_placeholder" => "literal value", +/// "conditional_placeholder" => { +/// cfg(condition1) => "value 1", +/// cfg(condition2) => "value 2", +/// _ => "neither value 1 nor value 2", +/// } +/// )] +/// /// Here comes the documentation. +/// /// +/// /// The replacements are interpreted outside of code blocks, too: +/// /// # {literal_placeholder} +/// /// +/// /// ```rust, no run +/// /// // here is some code +/// /// # {literal_placeholder} +/// /// // here is some more code +/// /// # {conditional_placeholder} +/// /// +/// /// The macro even supports __conditional_placeholder__ replacements in-line. +/// /// ``` +/// fn my_function() {} /// ``` -#[cfg(feature = "interrupt")] #[proc_macro_attribute] -pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { - use proc_macro_crate::{crate_name, FoundCrate}; - - let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); - - let attr_args = parse_macro_input!(args as AttributeArgs); - - if attr_args.len() > 1 { - abort!( - Span::call_site(), - "This attribute accepts zero or 1 arguments" - ) - } - - let ident = f.sig.ident.clone(); - let mut ident_s = &ident.clone(); - - if attr_args.len() == 1 { - match &attr_args[0] { - syn::NestedMeta::Meta(Path(x)) => { - ident_s = x.get_ident().unwrap(); - } - _ => { - abort!( - Span::call_site(), - format!( - "This attribute accepts a string attribute {:?}", - attr_args[0] - ) - ) - } - } - } - - // XXX should we blacklist other attributes? - - if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { - return error; - } - - let valid_signature = f.sig.constness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => match **ty { - Type::Tuple(ref tuple) => tuple.elems.is_empty(), - Type::Never(..) => true, - _ => false, - }, - } - && f.sig.inputs.len() <= 1; - - if !valid_signature { - return parse::Error::new( - f.span(), - "`#[interrupt]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`", - ) - .to_compile_error() - .into(); - } - - f.sig.ident = Ident::new( - &format!("__esp_hal_internal_{}", f.sig.ident), - proc_macro2::Span::call_site(), - ); - - #[cfg(feature = "esp32")] - let hal_crate = crate_name("esp32-hal"); - #[cfg(feature = "esp32s2")] - let hal_crate = crate_name("esp32s2-hal"); - #[cfg(feature = "esp32s3")] - let hal_crate = crate_name("esp32s3-hal"); - #[cfg(feature = "esp32c2")] - let hal_crate = crate_name("esp32c2-hal"); - #[cfg(feature = "esp32c3")] - let hal_crate = crate_name("esp32c3-hal"); - #[cfg(feature = "esp32c6")] - let hal_crate = crate_name("esp32c6-hal"); - - #[cfg(feature = "esp32")] - let hal_crate_name = Ident::new("esp32_hal", Span::call_site().into()); - #[cfg(feature = "esp32s2")] - let hal_crate_name = Ident::new("esp32s2_hal", Span::call_site().into()); - #[cfg(feature = "esp32s3")] - let hal_crate_name = Ident::new("esp32s3_hal", Span::call_site().into()); - #[cfg(feature = "esp32c2")] - let hal_crate_name = Ident::new("esp32c2_hal", Span::call_site().into()); - #[cfg(feature = "esp32c3")] - let hal_crate_name = Ident::new("esp32c3_hal", Span::call_site().into()); - #[cfg(feature = "esp32c6")] - let hal_crate_name = Ident::new("esp32c6_hal", Span::call_site().into()); - - let interrupt_in_hal_crate = match hal_crate { - Ok(FoundCrate::Itself) => { - quote!( #hal_crate_name::peripherals::Interrupt::#ident_s ) - } - Ok(FoundCrate::Name(ref name)) => { - let ident = Ident::new(&name, Span::call_site().into()); - quote!( #ident::peripherals::Interrupt::#ident_s ) - } - Err(_) => { - quote!( crate::peripherals::Interrupt::#ident_s ) - } - }; - - f.block.stmts.extend(std::iter::once( - syn::parse2(quote! {{ - // Check that this interrupt actually exists - #interrupt_in_hal_crate; - }}) - .unwrap(), - )); - - let tramp_ident = Ident::new( - &format!("{}_trampoline", f.sig.ident), - proc_macro2::Span::call_site(), - ); - let ident = &f.sig.ident; - - let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); - - let export_name = ident_s.to_string(); - - let trap_frame_in_hal_crate = match hal_crate { - Ok(FoundCrate::Itself) => { - quote!(#hal_crate_name::trapframe::TrapFrame) - } - Ok(FoundCrate::Name(ref name)) => { - let ident = Ident::new(&name, Span::call_site().into()); - quote!( #ident::trapframe::TrapFrame ) - } - Err(_) => { - quote!(crate::trapframe::TrapFrame) - } - }; - - let context_call = - (f.sig.inputs.len() == 1).then(|| Ident::new("context", proc_macro2::Span::call_site())); - - quote!( - macro_rules! foo { - () => { - }; - } - foo!(); - - #(#cfgs)* - #(#attrs)* - #[doc(hidden)] - #[export_name = #export_name] - pub unsafe extern "C" fn #tramp_ident(context: &mut #trap_frame_in_hal_crate) { - #ident( - #context_call - ) - } - - #[inline(always)] - #f - ) - .into() +pub fn doc_replace(args: TokenStream, input: TokenStream) -> TokenStream { + doc_replace::replace(args.into(), input.into()).into() } -#[cfg(feature = "interrupt")] -enum WhiteListCaller { - Interrupt, +/// Mark a function as an interrupt handler. +/// +/// Optionally a priority can be specified, e.g. `#[handler(priority = +/// esp_hal::interrupt::Priority::Priority2)]`. +/// +/// If no priority is given, `Priority::min()` is assumed +#[proc_macro_attribute] +pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream { + interrupt::handler(args.into(), input.into()).into() } -#[cfg(feature = "interrupt")] -fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> { - let whitelist = &[ - "doc", - "link_section", - "cfg", - "allow", - "warn", - "deny", - "forbid", - "cold", - "ram", - "inline", - ]; - - 'o: for attr in attrs { - for val in whitelist { - if eq(&attr, &val) { - continue 'o; - } - } - - let err_str = match caller { - WhiteListCaller::Interrupt => { - "this attribute is not allowed on an interrupt handler controlled by esp-hal" - } - }; - - return Err(parse::Error::new(attr.span(), &err_str) - .to_compile_error() - .into()); - } - - Ok(()) +/// Load code to be run on the LP/ULP core. +/// +/// ## Example +/// ```rust, ignore +/// let lp_core_code = load_lp_code!("path.elf"); +/// lp_core_code.run(&mut lp_core, lp_core::LpCoreWakeupSource::HpCpu, lp_pin); +/// ```` +#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] +#[proc_macro] +pub fn load_lp_code(input: TokenStream) -> TokenStream { + lp_core::load_lp_code(input.into(), lp_core::RealFilesystem).into() } -/// Returns `true` if `attr.path` matches `name` -#[cfg(feature = "interrupt")] -fn eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.is_ident(name) +/// Marks the entry function of a LP core / ULP program. +#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))] +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + lp_core::entry(args.into(), input.into()).into() } -#[cfg(feature = "interrupt")] -fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { - let mut cfgs = vec![]; - let mut not_cfgs = vec![]; - - for attr in attrs { - if eq(&attr, "cfg") { - cfgs.push(attr); - } else { - not_cfgs.push(attr); - } - } - - (cfgs, not_cfgs) +/// Creates a new instance of `esp_rtos::embassy::Executor` and declares an application entry point +/// spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it +/// can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ```rust,ignore +/// #[esp_rtos::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn rtos_main(args: TokenStream, item: TokenStream) -> TokenStream { + rtos_main::main(args.into(), item.into()).into() } -#[derive(Debug)] -struct MakeGpioEnumDispatchMacro { - name: String, - filter: Vec, - elements: Vec<(String, usize)>, +/// Attribute to declare the entry point of the program +/// +/// The specified function will be called by the reset handler *after* RAM has +/// been initialized. If present, the FPU will also be enabled before the +/// function is called. +/// +/// The type of the specified function must be `[unsafe] fn() -> !` (never +/// ending function) +/// +/// # Properties +/// +/// The entry point will be called by the reset handler. The program can't +/// reference to the entry point, much less invoke it. +/// +/// # Examples +/// +/// - Simple entry point +/// +/// ```ignore +/// #[main] +/// fn main() -> ! { +/// loop { /* .. */ } +/// } +/// ``` +#[proc_macro_attribute] +pub fn blocking_main(args: TokenStream, input: TokenStream) -> TokenStream { + let f = syn::parse_macro_input!(input as syn::ItemFn); + blocking::main(args.into(), f).into() } -impl Parse for MakeGpioEnumDispatchMacro { - fn parse(input: ParseStream) -> syn::parse::Result { - let name = input.parse::()?.to_string(); - let filter = input - .parse::()? - .stream() - .into_iter() - .map(|v| match v { - proc_macro2::TokenTree::Group(_) => String::new(), - proc_macro2::TokenTree::Ident(ident) => ident.to_string(), - proc_macro2::TokenTree::Punct(_) => String::new(), - proc_macro2::TokenTree::Literal(_) => String::new(), - }) - .filter(|p| !p.is_empty()) - .collect(); - - let mut stream = input.parse::()?.stream().into_iter(); - - let mut elements = vec![]; - - let mut element_name = String::new(); - loop { - match stream.next() { - Some(v) => match v { - proc_macro2::TokenTree::Ident(ident) => { - element_name = ident.to_string(); - } - proc_macro2::TokenTree::Literal(lit) => { - let index = lit.to_string().parse().unwrap(); - elements.push((element_name.clone(), index)); - } - _ => (), - }, - None => break, - } - } - - Ok(MakeGpioEnumDispatchMacro { - name, - filter, - elements, - }) - } +/// Automatically implement the [Builder Lite] pattern for a struct. +/// +/// This will create an `impl` which contains methods for each field of a +/// struct, allowing users to easily set the values. The generated methods will +/// be the field name prefixed with `with_`, and calls to these methods can be +/// chained as needed. +/// +/// ## Example +/// +/// ```rust, ignore +/// #[derive(Default)] +/// enum MyEnum { +/// #[default] +/// A, +/// B, +/// } +/// +/// #[derive(Default, BuilderLite)] +/// #[non_exhaustive] +/// struct MyStruct { +/// enum_field: MyEnum, +/// bool_field: bool, +/// option_field: Option, +/// } +/// +/// MyStruct::default() +/// .with_enum_field(MyEnum::B) +/// .with_bool_field(true) +/// .with_option_field(-5); +/// ``` +/// +/// [Builder Lite]: https://matklad.github.io/2022/05/29/builder-lite.html +#[proc_macro_derive(BuilderLite, attributes(builder_lite))] +pub fn builder_lite_derive(item: TokenStream) -> TokenStream { + builder::builder_lite_derive(item.into()).into() } -/// Create an enum for erased GPIO pins, using the enum-dispatch pattern +/// Print a build error and terminate the process. +/// +/// It should be noted that the error will be printed BEFORE the main function +/// is called, and as such this should NOT be thought analogous to `println!` or +/// similar utilities. +/// +/// ## Example +/// +/// ```rust, ignore +/// esp_hal_procmacros::error! {" +/// ERROR: something really bad has happened! +/// "} +/// // Process exits with exit code 1 +/// ``` #[proc_macro] -pub fn make_gpio_enum_dispatch_macro(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as MakeGpioEnumDispatchMacro); - - let mut arms = Vec::new(); - - for (gpio_type, num) in input.elements { - let enum_name = quote::format_ident!("ErasedPin"); - let variant_name = quote::format_ident!("Gpio{}", num); - - if input.filter.contains(&gpio_type) { - let arm = { - quote! { #enum_name::#variant_name($target) => $body } - }; - arms.push(arm); - } else { - let arm = { - quote! { - #[allow(unused)] - #enum_name::#variant_name($target) => { panic!("Unsupported") } - } - }; - arms.push(arm); - } - } +pub fn error(input: TokenStream) -> TokenStream { + alert::do_alert(termcolor::Color::Red, input); + panic!("Build failed"); +} - let macro_name = quote::format_ident!("{}", input.name); +/// Print a build warning. +/// +/// It should be noted that the warning will be printed BEFORE the main function +/// is called, and as such this should NOT be thought analogous to `println!` or +/// similar utilities. +/// +/// ## Example +/// +/// ```rust,no_run +/// esp_hal_procmacros::warning! {" +/// WARNING: something unpleasant has happened! +/// "}; +/// ``` +#[proc_macro] +pub fn warning(input: TokenStream) -> TokenStream { + alert::do_alert(termcolor::Color::Yellow, input) +} - quote! { - #[doc(hidden)] - #[macro_export] - macro_rules! #macro_name { - ($m:ident, $target:ident, $body:block) => { - match $m { - #(#arms)* - } +macro_rules! unwrap_or_compile_error { + ($($x:tt)*) => { + match $($x)* { + Ok(x) => x, + Err(e) => { + return e.into_compile_error() } } - - pub(crate) use #macro_name; - } - .into() + }; } + +pub(crate) use unwrap_or_compile_error; diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs new file mode 100644 index 00000000000..c06cabea4d3 --- /dev/null +++ b/esp-hal-procmacros/src/lp_core.rs @@ -0,0 +1,604 @@ +#[allow(unused)] +use proc_macro2::TokenStream; +use quote::quote; + +#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + use proc_macro_crate::FoundCrate; + #[cfg(not(test))] + use proc_macro_crate::crate_name; + use proc_macro2::{Ident, Span}; + use quote::format_ident; + use syn::{ + FnArg, + GenericArgument, + ItemFn, + PatType, + PathArguments, + Type, + parse::Error, + spanned::Spanned, + }; + + pub(crate) fn make_magic_symbol_name(args: &Vec<&PatType>) -> String { + let mut res = String::from("__ULP_MAGIC_"); + for &a in args { + let t = &a.ty; + let quoted = to_string(t); + res.push_str("ed); + res.push('$'); + } + + res + } + + pub(crate) fn simplename(t: &Type) -> String { + match t { + Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(), + _ => String::new(), + } + } + + pub(crate) fn extract_pin(ty: &Type) -> u8 { + let mut res = 255u8; + if let Type::Path(p) = ty { + let segment = p.path.segments.last().unwrap(); + if let PathArguments::AngleBracketed(g) = &segment.arguments { + for arg in &g.args { + match arg { + GenericArgument::Type(t) => { + res = extract_pin(t); + } + GenericArgument::Const(c) => { + res = quote! { #c }.to_string().parse().unwrap(); + } + _ => (), + } + } + } + } + + res + } + + // This is a specialized implementation - won't fit other use-cases + fn to_string(ty: &Type) -> String { + let mut res = String::new(); + if let Type::Path(p) = ty { + let segment = p.path.segments.last().unwrap(); + res.push_str(&segment.ident.to_string()); + + if let PathArguments::AngleBracketed(g) = &segment.arguments { + res.push('<'); + let mut pushed = false; + for arg in &g.args { + if pushed { + res.push(','); + } + + match arg { + GenericArgument::Type(t) => { + pushed = true; + res.push_str(&to_string(t)); + } + GenericArgument::Const(c) => { + pushed = true; + res.push_str("e! { #c }.to_string()); + } + _ => (), + } + } + res.push('>'); + } + } + + res + } + + #[cfg(not(test))] + let found_crate = crate_name("esp-lp-hal").expect("esp-lp-hal is present in `Cargo.toml`"); + + #[cfg(test)] + let found_crate = FoundCrate::Itself; + + let hal_crate = match found_crate { + FoundCrate::Itself => quote!(esp_lp_hal), + FoundCrate::Name(name) => { + let ident = Ident::new(&name, Span::call_site()); + quote!( #ident ) + } + }; + + if !args.is_empty() { + return Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error(); + } + + let f: ItemFn = crate::unwrap_or_compile_error!(syn::parse2(input)); + + let mut argument_types = Vec::new(); + let mut create_peripheral = Vec::new(); + + let mut used_pins: Vec = Vec::new(); + + for (num, arg) in f.sig.inputs.iter().enumerate() { + let param_name = format_ident!("param{}", num); + match arg { + FnArg::Receiver(_) => { + return Error::new(arg.span(), "invalid argument").to_compile_error(); + } + FnArg::Typed(t) => { + match simplename(&t.ty).as_str() { + "Output" => { + let pin = extract_pin(&t.ty); + if used_pins.contains(&pin) { + return Error::new(arg.span(), "duplicate pin").to_compile_error(); + } + used_pins.push(pin); + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::gpio::conjure_output().unwrap() }; + )); + } + "Input" => { + let pin = extract_pin(&t.ty); + if used_pins.contains(&pin) { + return Error::new(arg.span(), "duplicate pin").to_compile_error(); + } + used_pins.push(pin); + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::gpio::conjure_input().unwrap() }; + )); + } + "LpUart" => { + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::uart::conjure() }; + )); + } + "LpI2c" => { + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::i2c::conjure() }; + )); + } + _ => { + return Error::new(arg.span(), "invalid argument to main") + .to_compile_error(); + } + } + argument_types.push(t); + } + } + } + + let magic_symbol_name = make_magic_symbol_name(&argument_types); + + let param_names: Vec = argument_types + .into_iter() + .enumerate() + .map(|(num, _)| format_ident!("param{}", num)) + .collect(); + + quote!( + #[allow(non_snake_case)] + #[unsafe(export_name = "main")] + pub fn __risc_v_rt__main() -> ! { + #[unsafe(export_name = #magic_symbol_name)] + static ULP_MAGIC: [u32; 0] = [0u32; 0]; + + unsafe { ULP_MAGIC.as_ptr().read_volatile(); } + + use #hal_crate as the_hal; + #( + #create_peripheral + )* + + main(#(#param_names),*); + } + + #f + ) +} + +#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] +pub fn load_lp_code(input: TokenStream, fs: impl Filesystem) -> TokenStream { + use object::{File, Object, ObjectSection, ObjectSymbol, Section, SectionFlags}; + use parse::Error; + use proc_macro_crate::{FoundCrate, crate_name}; + use proc_macro2::Span; + use syn::{Ident, LitStr, parse}; + + let hal_crate = if cfg!(any(feature = "is-lp-core", feature = "is-ulp-core")) { + crate_name("esp-lp-hal") + } else { + crate_name("esp-hal") + }; + + let hal_crate = if let Ok(FoundCrate::Name(ref name)) = hal_crate { + let ident = Ident::new(name, proc_macro2::Span::call_site()); + quote!( #ident ) + } else { + quote!(crate) + }; + + let lit: LitStr = match syn::parse2(input) { + Ok(lit) => lit, + Err(e) => return e.into_compile_error(), + }; + + let elf_file = lit.value(); + + if !fs.exists(&elf_file) { + return Error::new(Span::call_site(), "File not found").to_compile_error(); + } + + let bin_data = fs.read(&elf_file).unwrap(); + let obj_file = match File::parse(&*bin_data) { + Ok(obj_file) => obj_file, + Err(e) => { + return Error::new(Span::call_site(), format!("Error: {}", e)).to_compile_error(); + } + }; + let sections = obj_file.sections(); + + let mut sections: Vec

    = sections + .into_iter() + .filter(|section| match section.flags() { + SectionFlags::Elf { sh_flags: sh } => (sh & u64::from(object::elf::SHF_ALLOC)) != 0, + _ => false, + }) + .collect(); + sections.sort_by(|a, b| a.address().partial_cmp(&b.address()).unwrap()); + + let mut binary: Vec = Vec::new(); + let mut last_address = if cfg!(feature = "has-lp-core") { + 0x5000_0000 + } else { + 0x0 + }; + + if sections.is_empty() { + return Error::new( + Span::call_site(), + "Given file doesn't seem to have any allocatable sections.", + ) + .to_compile_error(); + } else if sections[0].address() < last_address { + return Error::new( + Span::call_site(), + format!( + "First section address is below expected base address (expected >= 0x{:x}, got 0x{:x})", + last_address, + sections[0].address() + ), + ) + .to_compile_error(); + } + + for section in sections { + if section.address() > last_address { + let fill = section.address() - last_address; + binary.extend(std::iter::repeat_n(0, fill as usize)); + } + + binary.extend_from_slice(section.data().unwrap()); + last_address = section.address() + section.size(); + } + + let magic_symbol = obj_file + .symbols() + .find(|s| s.name().unwrap().starts_with("__ULP_MAGIC_")); + + let magic_symbol = if let Some(magic_symbol) = magic_symbol { + magic_symbol.name().unwrap() + } else { + return Error::new( + Span::call_site().into(), + "Given file doesn't seem to be an LP/ULP core application.", + ) + .to_compile_error(); + }; + + let magic_symbol = magic_symbol.trim_start_matches("__ULP_MAGIC_"); + let args: Vec = magic_symbol + .split("$") + .map(|t| { + let t = if t.contains("OutputOpenDrain") { + t.replace("OutputOpenDrain", "LowPowerOutputOpenDrain") + } else { + t.replace("Output", "LowPowerOutput") + }; + let t = t.replace("Input", "LowPowerInput"); + t.parse().unwrap() + }) + .filter(|v: &proc_macro2::TokenStream| !v.is_empty()) + .collect(); + + #[cfg(feature = "has-lp-core")] + let imports = quote! { + use #hal_crate::lp_core::LpCore; + use #hal_crate::lp_core::LpCoreWakeupSource; + use #hal_crate::gpio::lp_io::LowPowerOutput; + use #hal_crate::gpio::*; + use #hal_crate::uart::lp_uart::LpUart; + use #hal_crate::i2c::lp_i2c::LpI2c; + }; + #[cfg(feature = "has-ulp-core")] + let imports = quote! { + use #hal_crate::ulp_core::UlpCore as LpCore; + use #hal_crate::ulp_core::UlpCoreWakeupSource as LpCoreWakeupSource; + use #hal_crate::gpio::*; + }; + + #[cfg(feature = "has-lp-core")] + let rtc_code_start = quote! { _rtc_fast_data_start }; + #[cfg(feature = "has-ulp-core")] + let rtc_code_start = quote! { _rtc_slow_data_start }; + + quote! { + { + #imports + + struct LpCoreCode {} + + static LP_CODE: &[u8] = &[#(#binary),*]; + + unsafe extern "C" { + static #rtc_code_start: u32; + } + + unsafe { + core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len()); + } + + impl LpCoreCode { + pub fn run( + &self, + lp_core: &mut LpCore, + wakeup_source: LpCoreWakeupSource, + #(_: #args),* + ) { + lp_core.run(wakeup_source); + } + } + + LpCoreCode {} + } + } +} + +#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] +pub(crate) trait Filesystem { + fn read(&self, path: &str) -> std::io::Result>; + + fn exists(&self, path: &str) -> bool; +} + +#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] +pub(crate) struct RealFilesystem; + +#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] +impl Filesystem for RealFilesystem { + fn read(&self, path: &str) -> std::io::Result> { + std::fs::read(path) + } + + fn exists(&self, path: &str) -> bool { + std::path::Path::new(path).exists() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] + struct TestFilesystem; + + #[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] + impl Filesystem for TestFilesystem { + fn read(&self, path: &str) -> std::io::Result> { + if path == "doesnt_exist.elf" { + Err(std::io::ErrorKind::NotFound.into()) + } else if path == "bad.elf" { + Ok(Vec::from(&[1, 2, 3, 4])) + } else { + std::fs::read("./testdata/ulp_code.elf") + } + } + + fn exists(&self, path: &str) -> bool { + if path == "doesnt_exist.elf" { + false + } else { + true + } + } + } + + #[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] + #[test] + fn test_load_lp_code_file_not_found() { + let result = load_lp_code(quote::quote! {"doesnt_exist.elf"}.into(), TestFilesystem); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error! { "File not found" } + } + .to_string() + ); + } + + #[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] + #[test] + fn test_load_lp_code_bad() { + let result = load_lp_code(quote::quote! {"bad.elf"}.into(), TestFilesystem); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error! { "Error: Could not read file magic" } + } + .to_string() + ); + } + + #[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))] + #[test] + fn test_load_lp_code_basic() { + let result = load_lp_code(quote::quote! {"good.elf"}.into(), TestFilesystem); + + assert_eq!( + result.to_string(), + quote::quote! { + { + use crate::lp_core::LpCore; + use crate::lp_core::LpCoreWakeupSource; + use crate::gpio::lp_io::LowPowerOutput; + use crate::gpio::*; + use crate::uart::lp_uart::LpUart; + use crate::i2c::lp_i2c::LpI2c; + + struct LpCoreCode {} + static LP_CODE: &[u8] = &[ + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, 19u8, 0u8, 0u8, 0u8, + 9u8, 160u8, 23u8, 65u8, 0u8, 0u8, 19u8, 1u8, 225u8, 247u8, 151u8, 0u8, 0u8, 0u8, 231u8, + 128u8, 160u8, 0u8, 1u8, 160u8, 55u8, 5u8, 11u8, 96u8, 3u8, 37u8, 5u8, 64u8, 17u8, + 137u8, 9u8, 201u8, 55u8, 5u8, 0u8, 80u8, 183u8, 53u8, 49u8, 1u8, 147u8, 133u8, 5u8, + 208u8, 35u8, 46u8, 181u8, 22u8, 151u8, 0u8, 0u8, 0u8, 231u8, 128u8, 192u8, 11u8, 129u8, + 67u8, 183u8, 40u8, 11u8, 96u8, 55u8, 40u8, 0u8, 80u8, 137u8, 66u8, 55u8, 3u8, 0u8, + 80u8, 133u8, 3u8, 35u8, 32u8, 120u8, 0u8, 35u8, 162u8, 88u8, 0u8, 131u8, 39u8, 195u8, + 23u8, 243u8, 37u8, 0u8, 184u8, 243u8, 38u8, 0u8, 176u8, 115u8, 38u8, 0u8, 184u8, 227u8, + 154u8, 197u8, 254u8, 133u8, 131u8, 51u8, 6u8, 208u8, 64u8, 179u8, 54u8, 208u8, 0u8, + 179u8, 5u8, 176u8, 64u8, 149u8, 141u8, 243u8, 38u8, 0u8, 184u8, 115u8, 39u8, 0u8, + 176u8, 115u8, 37u8, 0u8, 184u8, 227u8, 154u8, 166u8, 254u8, 50u8, 151u8, 174u8, 150u8, + 51u8, 53u8, 199u8, 0u8, 54u8, 149u8, 179u8, 182u8, 231u8, 0u8, 51u8, 53u8, 160u8, 0u8, + 85u8, 141u8, 113u8, 221u8, 35u8, 164u8, 88u8, 0u8, 3u8, 38u8, 195u8, 23u8, 243u8, 37u8, + 0u8, 184u8, 243u8, 38u8, 0u8, 176u8, 115u8, 37u8, 0u8, 184u8, 227u8, 154u8, 165u8, + 254u8, 5u8, 130u8, 179u8, 7u8, 208u8, 64u8, 51u8, 53u8, 208u8, 0u8, 179u8, 5u8, 176u8, + 64u8, 137u8, 141u8, 243u8, 38u8, 0u8, 184u8, 115u8, 39u8, 0u8, 176u8, 115u8, 37u8, 0u8, + 184u8, 227u8, 154u8, 166u8, 254u8, 62u8, 151u8, 174u8, 150u8, 51u8, 53u8, 247u8, 0u8, + 54u8, 149u8, 179u8, 54u8, 230u8, 0u8, 51u8, 53u8, 160u8, 0u8, 85u8, 141u8, 113u8, + 221u8, 185u8, 191u8, 55u8, 5u8, 0u8, 80u8, 3u8, 32u8, 197u8, 23u8, 151u8, 0u8, 0u8, + 0u8, 231u8, 128u8, 64u8, 244u8, 0u8, 36u8, 244u8, 0u8 + ]; + unsafe extern "C" { + static _rtc_fast_data_start: u32; + } + unsafe { + core::ptr::copy_nonoverlapping( + LP_CODE as *const _ as *const u8, + &_rtc_fast_data_start as *const u32 as *mut u8, + LP_CODE.len() + ); + } + impl LpCoreCode { + pub fn run( + &self, + lp_core: &mut LpCore, + wakeup_source: LpCoreWakeupSource, + _: LowPowerOutput<1> + ) { + lp_core.run(wakeup_source); + } + } + LpCoreCode {} + } + } + .to_string() + ); + } + + #[cfg(any(feature = "is-lp-core"))] + #[test] + fn test_lp_entry_basic() { + let result = entry( + quote::quote! {}.into(), + quote::quote! { + fn main(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[allow(non_snake_case)] + #[unsafe(export_name = "main")] + pub fn __risc_v_rt__main () -> ! { + #[unsafe(export_name = "__ULP_MAGIC_")] + static ULP_MAGIC: [u32;0] = [0u32;0]; + unsafe { + ULP_MAGIC.as_ptr().read_volatile (); + } + + use esp_lp_hal as the_hal; + main (); + } + + fn main () { } + } + .to_string() + ); + } + + #[cfg(any(feature = "is-lp-core"))] + #[test] + fn test_lp_entry_with_params() { + let result = entry( + quote::quote! {}.into(), + quote::quote! { + fn main(mut gpio1: Output<1>, mut i2c: LpI2c, mut uart: LpUart){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[allow(non_snake_case)] + #[unsafe(export_name = "main")] + pub fn __risc_v_rt__main () -> ! { + # [unsafe (export_name = "__ULP_MAGIC_Output<1>$LpI2c$LpUart$")] + static ULP_MAGIC: [u32;0] = [0u32;0]; + unsafe { ULP_MAGIC.as_ptr().read_volatile(); + } + + use esp_lp_hal as the_hal ; + let mut param0 = unsafe { the_hal::gpio::conjure_output().unwrap() }; + let mut param1 = unsafe { the_hal::i2c::conjure() }; + let mut param2 = unsafe { the_hal::uart::conjure() }; + main (param0 , param1 , param2) ; } + + fn main (mut gpio1 : Output < 1 > , mut i2c : LpI2c , mut uart : LpUart) { } + } + .to_string() + ); + } + + #[cfg(any(feature = "is-lp-core"))] + #[test] + fn test_lp_entry_non_empty_args() { + let result = entry( + quote::quote! { foo }.into(), + quote::quote! { + fn main(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "This attribute accepts no arguments" } + } + .to_string() + ); + } +} diff --git a/esp-hal-procmacros/src/ram.rs b/esp-hal-procmacros/src/ram.rs new file mode 100644 index 00000000000..052ad93a710 --- /dev/null +++ b/esp-hal-procmacros/src/ram.rs @@ -0,0 +1,638 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use syn::{Item, Token, parse::Parser, parse2, punctuated::Punctuated, spanned::Spanned}; + +pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = match Punctuated::::parse_terminated.parse2(args) { + Ok(v) => v, + Err(e) => return e.to_compile_error(), + }; + + let mut rtc_fast = false; + let mut rtc_slow = false; + let mut dram2_uninit = false; + let mut persistent = false; + let mut zeroed = false; + + for attr_arg in &attr_args { + match attr_arg { + syn::Meta::List(list) if list.path.is_ident("unstable") => { + let nested = &list.tokens; + let nested_args = match Punctuated::::parse_terminated + .parse2(nested.clone()) + { + Ok(v) => v, + Err(e) => return e.to_compile_error(), + }; + + for meta in nested_args { + match meta { + syn::Meta::Path(path) => { + let Some(ident) = path.get_ident() else { + return syn::Error::new( + path.span(), + "Expected identifier inside `unstable(...)`", + ) + .into_compile_error(); + }; + let arg = match ident { + i if i == "rtc_fast" => &mut rtc_fast, + i if i == "rtc_slow" => &mut rtc_slow, + i if i == "persistent" => &mut persistent, + i if i == "zeroed" => &mut zeroed, + i => { + return syn::Error::new( + i.span(), + format!("Unknown unstable argument `{i}`"), + ) + .into_compile_error(); + } + }; + + if *arg { + return syn::Error::new( + ident.span(), + format!("Argument `{ident}` is already set"), + ) + .into_compile_error(); + } + + *arg = true; + } + _ => { + return syn::Error::new( + list.span(), + "Expected identifiers inside `unstable(...)`", + ) + .into_compile_error(); + } + } + } + } + + syn::Meta::Path(path) => { + let Some(ident) = path.get_ident() else { + return syn::Error::new(path.span(), "Expected identifier") + .into_compile_error(); + }; + let arg = match ident { + i if i == "reclaimed" => { + if !cfg!(test) && !cfg!(feature = "__esp_idf_bootloader") { + return syn::Error::new( + ident.span(), + "`ram(reclaimed)` requires the esp-idf bootloader", + ) + .into_compile_error(); + } + + &mut dram2_uninit + } + _ => { + return syn::Error::new( + ident.span(), + format!("`{ident}` must be wrapped in `unstable(...)`"), + ) + .into_compile_error(); + } + }; + + if *arg { + return syn::Error::new( + ident.span(), + format!("Argument `{ident}` is already set"), + ) + .into_compile_error(); + } + *arg = true; + } + + _ => { + return syn::Error::new(attr_arg.span(), "Unsupported attribute syntax for `ram`") + .into_compile_error(); + } + } + } + + let item: Item = crate::unwrap_or_compile_error!(parse2(input)); + + #[cfg(not(feature = "rtc-slow"))] + if rtc_slow { + return syn::Error::new( + Span::call_site(), + "rtc_slow is not available for this target", + ) + .into_compile_error(); + } + + let is_fn = matches!(item, Item::Fn(_)); + let section_name = match (is_fn, rtc_fast, rtc_slow, dram2_uninit, persistent, zeroed) { + (true, false, false, false, false, false) => Ok(".rwtext"), + (true, true, false, false, false, false) => Ok(".rtc_fast.text"), + (true, false, true, false, false, false) => Ok(".rtc_slow.text"), + + (false, false, false, false, false, false) => Ok(".data"), + (false, false, false, true, false, false) => Ok(".dram2_uninit"), + + (false, true, false, false, false, false) => Ok(".rtc_fast.data"), + (false, true, false, false, true, false) => Ok(".rtc_fast.persistent"), + (false, true, false, false, false, true) => Ok(".rtc_fast.bss"), + + (false, false, true, false, false, false) => Ok(".rtc_slow.data"), + (false, false, true, false, true, false) => Ok(".rtc_slow.persistent"), + (false, false, true, false, false, true) => Ok(".rtc_slow.bss"), + + _ => Err(()), + }; + + let section = match (is_fn, section_name) { + (true, Ok(section_name)) => quote::quote! { + #[unsafe(link_section = #section_name)] + #[inline(never)] // make certain function is not inlined + }, + (false, Ok(section_name)) => quote::quote! { + #[unsafe(link_section = #section_name)] + }, + (_, Err(_)) => { + return syn::Error::new(Span::call_site(), "Invalid combination of ram arguments") + .into_compile_error(); + } + }; + + let trait_check = if zeroed { + Some("zeroable") + } else if persistent { + Some("persistable") + } else if dram2_uninit { + Some("uninit") + } else { + None + }; + let trait_check = trait_check.map(|name| { + use proc_macro_crate::{FoundCrate, crate_name}; + + let hal = Ident::new( + match crate_name("esp-hal") { + Ok(FoundCrate::Name(ref name)) => name, + _ => "crate", + }, + Span::call_site(), + ); + + let assertion = quote::format_ident!("assert_is_{name}"); + let Item::Static(ref item) = item else { + return syn::Error::new(Span::call_site(), "Expected a `static`").into_compile_error(); + }; + let ty = &item.ty; + quote::quote! { + const _: () = #hal::__macro_implementation::#assertion::<#ty>(); + } + }); + + quote::quote! { + #section + #item + #trait_check + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rwtext() { + let result = ram( + quote::quote! {}.into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rwtext")] + #[inline (never)] + fn foo () { } + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_text() { + let result = ram( + quote::quote! { + unstable(rtc_fast) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_fast.text")] + #[inline (never)] + fn foo () { } + } + .to_string() + ); + } + + #[cfg(feature = "rtc-slow")] + #[test] + fn test_rtc_slow_text() { + let result = ram( + quote::quote! { + unstable(rtc_slow) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_slow.text")] + #[inline (never)] + fn foo () { } + } + .to_string() + ); + } + + #[test] + fn test_data() { + let result = ram( + quote::quote! {}.into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".data")] + static mut FOO:[u8;10] = [0;10]; + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_data() { + let result = ram( + quote::quote! { + unstable(rtc_fast) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_fast.data")] + static mut FOO:[u8;10] = [0;10]; + } + .to_string() + ); + } + + #[cfg(feature = "rtc-slow")] + #[test] + fn test_rtc_slow_data() { + let result = ram( + quote::quote! { + unstable(rtc_slow) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_slow.data")] + static mut FOO:[u8;10] = [0;10]; + } + .to_string() + ); + } + + #[test] + fn test_reclaimed() { + let result = ram( + quote::quote! { + reclaimed + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe(link_section = ".dram2_uninit")] + static mut FOO: [u8;10] = [0;10]; + const _ : () = crate::__macro_implementation::assert_is_uninit::<[u8;10]>(); + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_data_zeroed() { + let result = ram( + quote::quote! { + unstable(rtc_fast,zeroed) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_fast.bss")] + static mut FOO:[u8;10] = [0;10]; + const _: () = crate::__macro_implementation::assert_is_zeroable::<[u8; 10]>(); + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_data_persistent() { + let result = ram( + quote::quote! { + unstable(rtc_fast,persistent) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_fast.persistent")] + static mut FOO:[u8;10] = [0;10]; + const _: () = crate::__macro_implementation::assert_is_persistable::<[u8; 10]>(); + } + .to_string() + ); + } + + #[cfg(feature = "rtc-slow")] + #[test] + fn test_rtc_slow_data_zeroed() { + let result = ram( + quote::quote! { + unstable(rtc_slow,zeroed) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_slow.bss")] + static mut FOO:[u8;10] = [0;10]; + const _: () = crate::__macro_implementation::assert_is_zeroable::<[u8; 10]>(); + } + .to_string() + ); + } + + #[cfg(feature = "rtc-slow")] + #[test] + fn test_rtc_slow_data_persistent() { + let result = ram( + quote::quote! { + unstable(rtc_slow,persistent) + } + .into(), + quote::quote! { + static mut FOO: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe (link_section = ".rtc_slow.persistent")] + static mut FOO:[u8;10] = [0;10]; + const _: () = crate::__macro_implementation::assert_is_persistable::<[u8; 10]>(); + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg() { + let result = ram( + quote::quote! { + test() + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "Unsupported attribute syntax for `ram`" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg2() { + let result = ram( + quote::quote! { + unstable(unstable(unstable)) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "Expected identifiers inside `unstable(...)`" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg3() { + let result = ram( + quote::quote! { + unstable(unknown) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "Unknown unstable argument `unknown`" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg4() { + let result = ram( + quote::quote! { + unstable(rtc_fast,rtc_fast) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "Argument `rtc_fast` is already set" } + } + .to_string() + ); + } + + #[cfg(feature = "rtc-slow")] + #[test] + fn test_illegal_arg5() { + let result = ram( + quote::quote! { + unstable(rtc_slow,rtc_fast) + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "Invalid combination of ram arguments" } + } + .to_string() + ); + } + + #[test] + fn test_illegal_arg6() { + let result = ram( + quote::quote! { + rtc_fast + } + .into(), + quote::quote! { + fn foo() {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "`rtc_fast` must be wrapped in `unstable(...)`" } + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_data_persistent_on_local_var() { + let result = ram( + quote::quote! { + unstable(rtc_fast,persistent) + } + .into(), + quote::quote! { + mut foo: [u8; 10] = [0; 10]; + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "expected one of: `fn`, `extern`, `use`, `static`, `const`, `unsafe`, `mod`, `type`, `struct`, `enum`, `union`, `trait`, `auto`, `impl`, `default`, `macro`, identifier, `self`, `super`, `crate`, `::`" } + } + .to_string() + ); + } + + #[test] + fn test_rtc_fast_data_persistent_on_non_static() { + let result = ram( + quote::quote! { + unstable(rtc_fast,persistent) + } + .into(), + quote::quote! { + struct Foo {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[unsafe(link_section = ".rtc_fast.persistent")] + struct Foo { } + ::core::compile_error!{ "Expected a `static`" } + } + .to_string() + ); + } +} diff --git a/esp-hal-procmacros/src/rtos_main.rs b/esp-hal-procmacros/src/rtos_main.rs new file mode 100644 index 00000000000..d2a90a9acec --- /dev/null +++ b/esp-hal-procmacros/src/rtos_main.rs @@ -0,0 +1,492 @@ +use std::{cell::RefCell, fmt::Display, thread}; + +use proc_macro2::{TokenStream, TokenStream as TokenStream2}; +use quote::{ToTokens, quote}; +use syn::{ + Attribute, + Meta, + ReturnType, + Token, + Type, + parse::{Parse, ParseBuffer}, + punctuated::Punctuated, +}; + +/// Parsed arguments for the `main` macro. +pub struct Args { + pub(crate) meta: Vec, +} + +impl Parse for Args { + fn parse(input: &ParseBuffer) -> syn::Result { + let meta = Punctuated::::parse_terminated(input)?; + Ok(Args { + meta: meta.into_iter().collect(), + }) + } +} + +/// Procedural macro entry point for the async `main` function. +pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { + let args: Args = crate::unwrap_or_compile_error!(syn::parse2(args)); + let f: syn::ItemFn = crate::unwrap_or_compile_error!(syn::parse2(item)); + + run(&args.meta, f, main_fn()).unwrap_or_else(|x| x) +} + +/// Expands and validates the async `main` function into a task entry point. +pub fn run( + _args: &[Meta], + f: syn::ItemFn, + main: TokenStream2, +) -> Result { + let fargs = f.sig.inputs.clone(); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "main function must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "main function must not be generic"); + } + if f.sig.generics.where_clause.is_some() { + ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); + } + if f.sig.abi.is_some() { + ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); + } + if f.sig.variadic.is_some() { + ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => ctxt.error_spanned_by( + &f.sig, + "main function must either not return a value, return `()` or return `!`", + ), + }, + } + + if fargs.len() != 1 { + ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); + } + + let fattrs = f.attrs; + let lint_attrs: Vec = fattrs + .clone() + .into_iter() + .filter(|item| { + item.path().is_ident("deny") + || item.path().is_ident("allow") + || item.path().is_ident("warn") + }) + .collect(); + + ctxt.check()?; + + let f_body = f.block; + let out = &f.sig.output; + + let result = quote! { + #(#lint_attrs)* + pub(crate) mod __main { + use super::*; + + #[doc(hidden)] + #(#fattrs)* + #[::embassy_executor::task()] + async fn __embassy_main(#fargs) #out { + #f_body + } + + #[doc(hidden)] + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + #(#fattrs)* + #main + } + }; + + Ok(result) +} + +/// A type to collect errors together and format them. +/// +/// Dropping this object will cause a panic. It must be consumed using +/// `check`. +/// +/// References can be shared since this type uses run-time exclusive mut +/// checking. +#[derive(Default)] +pub struct Ctxt { + // The contents will be set to `None` during checking. This is so that checking can be + // enforced. + errors: RefCell>>, +} + +impl Ctxt { + /// Create a new context object. + /// + /// This object contains no errors, but will still trigger a panic if it + /// is not `check`ed. + pub fn new() -> Self { + Ctxt { + errors: RefCell::new(Some(Vec::new())), + } + } + + /// Add an error to the context object with a tokenenizable object. + /// + /// The object is used for spanning in error messages. + pub fn error_spanned_by(&self, obj: A, msg: T) { + self.errors + .borrow_mut() + .as_mut() + .unwrap() + // Curb monomorphization from generating too many identical methods. + .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); + } + + /// Consume this object, producing a formatted error string if there are + /// errors. + pub fn check(self) -> Result<(), TokenStream2> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(to_compile_errors(errors)), + } + } +} + +fn to_compile_errors(errors: Vec) -> TokenStream2 { + let compile_errors = errors.iter().map(syn::Error::to_compile_error); + quote!(#(#compile_errors)*) +} + +impl Drop for Ctxt { + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); + } + } +} + +/// Generates the `main` function that initializes and runs the async executor. +pub fn main_fn() -> TokenStream2 { + let root = match proc_macro_crate::crate_name("esp-hal") { + Ok(proc_macro_crate::FoundCrate::Name(ref name)) => quote::format_ident!("{name}"), + _ => quote::format_ident!("esp_hal"), + }; + + quote! { + #[#root::main] + fn main() -> ! { + let mut executor = ::esp_rtos::embassy::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: Spawner){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + pub (crate) mod __main { + use super::*; + #[doc(hidden)] + #[::embassy_executor::task()] + async fn __embassy_main (spawner : Spawner) { + { } + } + + #[doc(hidden)] + unsafe fn __make_static < T > (t : & mut T) -> & 'static mut T { + ::core::mem::transmute(t) + } + + #[esp_hal::main] + fn main () -> ! { + let mut executor = ::esp_rtos::embassy::Executor::new(); + let executor = unsafe { __make_static (& mut executor) }; + executor . run (| spawner | { + spawner.must_spawn(__embassy_main (spawner)); + }) + } + } + } + .to_string() + ); + } + + #[test] + fn test_non_async_fn() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + fn foo(spawner: Spawner){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must be async" } + } + .to_string() + ); + } + + #[test] + fn test_no_arg() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must have 1 argument: the spawner." } + } + .to_string() + ); + } + + #[test] + fn test_not_generic() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: S){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must not be generic" } + } + .to_string() + ); + } + + #[test] + fn test_not_abi() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async extern "C" fn foo(spawner: Spawner){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must not have an ABI qualifier" } + } + .to_string() + ); + } + + #[test] + fn test_not_variadic() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: ...){} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must not be variadic" } + ::core::compile_error!{ "main function must have 1 argument: the spawner." } + } + .to_string() + ); + } + + #[test] + fn test_not_return_value() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: Spawner) -> u32 {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + ::core::compile_error!{ "main function must either not return a value, return `()` or return `!`" } + } + .to_string() + ); + } + + #[test] + fn test_basic_return_never() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: Spawner) -> ! {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + pub (crate) mod __main { + use super::*; + #[doc(hidden)] + #[::embassy_executor::task()] + async fn __embassy_main (spawner : Spawner) -> ! { + { } + } + + #[doc(hidden)] + unsafe fn __make_static < T > (t : & mut T) -> & 'static mut T { + ::core::mem::transmute(t) + } + + # [esp_hal::main] + fn main () -> ! { + let mut executor = ::esp_rtos::embassy::Executor::new(); + let executor = unsafe { __make_static (& mut executor) }; + executor.run (| spawner | { + spawner.must_spawn(__embassy_main (spawner)); + }) + } + } + } + .to_string() + ); + } + + #[test] + fn test_basic_return_tuple() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + async fn foo(spawner: Spawner) -> () {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + pub (crate) mod __main { + use super::*; + #[doc(hidden)] + #[::embassy_executor::task()] + async fn __embassy_main (spawner : Spawner) -> () { + { } + } + + #[doc(hidden)] + unsafe fn __make_static < T > (t : & mut T) -> & 'static mut T { + ::core::mem::transmute(t) + } + + #[esp_hal::main] + fn main () -> ! { + let mut executor = ::esp_rtos::embassy::Executor::new(); + let executor = unsafe { __make_static (& mut executor) }; + executor.run (| spawner | { + spawner.must_spawn(__embassy_main (spawner)); + }) + } + } + } + .to_string() + ); + } + + #[test] + fn test_basic_propagate_lint_attrs() { + let result = main( + quote::quote! {}.into(), + quote::quote! { + #[allow(allowed)] + #[deny(denied)] + #[warn(warning)] + #[ram] + async fn foo(spawner: Spawner) -> () {} + } + .into(), + ); + + assert_eq!( + result.to_string(), + quote::quote! { + #[allow(allowed)] + #[deny(denied)] + #[warn (warning)] + pub (crate) mod __main { + use super::*; + #[doc(hidden)] + #[allow(allowed)] + #[deny(denied)] + #[warn (warning)] + #[ram] + #[::embassy_executor::task()] + async fn __embassy_main (spawner : Spawner) -> () { + { } + } + + #[doc(hidden)] + unsafe fn __make_static < T > (t : & mut T) -> & 'static mut T { + ::core::mem::transmute(t) + } + + #[allow(allowed)] + #[deny(denied)] + #[warn(warning)] + #[ram] + #[esp_hal::main] + fn main () -> ! { + let mut executor = ::esp_rtos::embassy::Executor::new(); + let executor = unsafe { __make_static (& mut executor) }; + executor.run (| spawner | { + spawner.must_spawn(__embassy_main (spawner)); + }) + } + } + } + .to_string() + ); + } +} diff --git a/esp-hal-procmacros/testdata/ulp_code.elf b/esp-hal-procmacros/testdata/ulp_code.elf new file mode 100644 index 00000000000..29d18005abf Binary files /dev/null and b/esp-hal-procmacros/testdata/ulp_code.elf differ diff --git a/esp-hal-smartled/Cargo.toml b/esp-hal-smartled/Cargo.toml deleted file mode 100644 index 274ba5bdd1c..00000000000 --- a/esp-hal-smartled/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "esp-hal-smartled" -version = "0.2.0" -edition = "2021" -description = "RMT adapter for smartleds" -repository = "https://github.com/esp-rs/esp-hal" -license = "MIT OR Apache-2.0" - -[package.metadata.docs.rs] -features = ["esp32c3"] - -[dependencies] -esp-hal-common = { version = "0.9.0", path = "../esp-hal-common" } -fugit = "0.3.6" -smart-leds-trait = "0.2.1" - -[features] -esp32 = ["esp-hal-common/esp32"] -esp32c3 = ["esp-hal-common/esp32c3"] -esp32c6 = ["esp-hal-common/esp32c6"] -esp32s2 = ["esp-hal-common/esp32s2"] -esp32s3 = ["esp-hal-common/esp32s3"] - -esp32_26mhz = ["esp-hal-common/esp32_26mhz"] -esp32_40mhz = ["esp-hal-common/esp32_40mhz"] diff --git a/esp-hal-smartled/README.md b/esp-hal-smartled/README.md deleted file mode 100644 index b0f15cc37af..00000000000 --- a/esp-hal-smartled/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# esp-hal-smartled - -[![Crates.io](https://img.shields.io/crates/v/esp-hal-smartled?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-hal-smartled) -[![docs.rs](https://img.shields.io/docsrs/esp-hal-smartled?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-hal-smartled) -![Crates.io](https://img.shields.io/crates/l/esp-hal-smartled?labelColor=1C2C2E&style=flat-square) -[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) - -This adapter allows for the use of an RMT output channel to easily interact with RGB LEDs and use the convenience functions of the [`smart-leds`](https://crates.io/crates/smart-leds) crate. - -## [Documentation] - -[documentation]: https://docs.rs/esp-hal-smartled/ - -## License - -Licensed under either of: - -- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in -the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without -any additional terms or conditions. diff --git a/esp-hal-smartled/src/lib.rs b/esp-hal-smartled/src/lib.rs deleted file mode 100644 index 171af7192fa..00000000000 --- a/esp-hal-smartled/src/lib.rs +++ /dev/null @@ -1,224 +0,0 @@ -//! This adapter allows for the use of an RMT output channel to easily interact -//! with RGB LEDs and use the convenience functions of the -//! [`smart-leds`](https://crates.io/crates/smart-leds) crate. -//! -//! _This is a simple implementation where every LED is adressed in an -//! individual RMT operation. This is working perfectly fine in blocking mode, -//! but in case this is used in combination with interrupts that might disturb -//! the sequential sending, an alternative implementation (addressing the LEDs -//! in a sequence in a single RMT send operation) might be required!_ -//! -//! ## Example -//! -//! ```rust,ignore -//! let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); -//! let pulse = PulseControl::new( -//! peripherals.RMT, -//! &mut system.peripheral_clock_control, -//! ClockSource::APB, -//! 0, -//! 0, -//! 0, -//! ) -//! .unwrap(); -//! -//! let led = ::new(pulse.channel0, io.pins.gpio0); -//! ``` - -#![no_std] -#![deny(missing_docs)] -#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] - -use core::slice::IterMut; - -#[cfg(any(feature = "esp32", feature = "esp32s2"))] -use esp_hal_common::pulse_control::ClockSource; -use esp_hal_common::{ - gpio::OutputPin, - peripheral::Peripheral, - pulse_control::{ConfiguredChannel, OutputChannel, PulseCode, RepeatMode, TransmissionError}, -}; -use fugit::NanosDuration; -use smart_leds_trait::{SmartLedsWrite, RGB8}; - -// Specifies what clock frequency we're using for the RMT peripheral (if -// properly configured) -// -// TODO: Factor in clock configuration, this needs to be revisited once #24 and -// #44 have been addressed. -#[cfg(feature = "esp32")] -const SOURCE_CLK_FREQ: u32 = 40_000_000; -#[cfg(feature = "esp32c3")] -const SOURCE_CLK_FREQ: u32 = 40_000_000; -#[cfg(feature = "esp32c6")] -const SOURCE_CLK_FREQ: u32 = 40_000_000; -#[cfg(feature = "esp32s2")] -const SOURCE_CLK_FREQ: u32 = 40_000_000; -#[cfg(feature = "esp32s3")] -const SOURCE_CLK_FREQ: u32 = 40_000_000; - -const SK68XX_CODE_PERIOD: u32 = 1200; -const SK68XX_T0H_NS: u32 = 320; -const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS; -const SK68XX_T1H_NS: u32 = 640; -const SK68XX_T1L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T1H_NS; - -const SK68XX_T0H_CYCLES: NanosDuration = - NanosDuration::::from_ticks((SK68XX_T0H_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500); -const SK68XX_T0L_CYCLES: NanosDuration = - NanosDuration::::from_ticks((SK68XX_T0L_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500); -const SK68XX_T1H_CYCLES: NanosDuration = - NanosDuration::::from_ticks((SK68XX_T1H_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500); -const SK68XX_T1L_CYCLES: NanosDuration = - NanosDuration::::from_ticks((SK68XX_T1L_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500); - -/// All types of errors that can happen during the conversion and transmission -/// of LED commands -#[derive(Debug)] -pub enum LedAdapterError { - /// Raised in the event that the provided data container is not large enough - BufferSizeExceeded, - /// Raised if something goes wrong in the transmission, - TransmissionError(TransmissionError), -} - -/// Macro to generate adapters with an arbitrary buffer size fitting for a -/// specific number of `$buffer_size` LEDs to be addressed. -/// -/// Attempting to use more LEDs that the buffer is configured for will result in -/// an `LedAdapterError:BufferSizeExceeded` error. -#[macro_export] -macro_rules! smartLedAdapter { - ( $buffer_size: literal ) => { - // The size we're assigning here is calculated as following - // ( - // Nr. of LEDs - // * channels (r,g,b -> 3) - // * pulses per channel 8) - // ) + 1 additional pulse for the end delimiter - SmartLedsAdapter::<_, { $buffer_size * 24 + 1 }> - }; -} - -/// Adapter taking an RMT channel and a specific pin and providing RGB LED -/// interaction functionality using the `smart-leds` crate -pub struct SmartLedsAdapter { - channel: CHANNEL, - rmt_buffer: [u32; BUFFER_SIZE], -} - -impl<'d, CHANNEL, const BUFFER_SIZE: usize> SmartLedsAdapter -where - CHANNEL: ConfiguredChannel, -{ - /// Create a new adapter object that drives the pin using the RMT channel. - pub fn new( - mut channel: UnconfiguredChannel, - pin: impl Peripheral

    + 'd, - ) -> SmartLedsAdapter - where - UnconfiguredChannel: OutputChannel = CHANNEL>, - { - #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] - channel - .set_idle_output_level(false) - .set_carrier_modulation(false) - .set_channel_divider(1) - .set_idle_output(true); - - #[cfg(any(feature = "esp32", feature = "esp32s2"))] - channel - .set_idle_output_level(false) - .set_carrier_modulation(false) - .set_channel_divider(1) - .set_idle_output(true) - .set_clock_source(ClockSource::APB); - - let channel = channel.assign_pin(pin); - - Self { - channel, - rmt_buffer: [0; BUFFER_SIZE], - } - } - - fn convert_rgb_to_pulse( - value: RGB8, - mut_iter: &mut IterMut, - ) -> Result<(), LedAdapterError> { - SmartLedsAdapter::::convert_rgb_channel_to_pulses(value.g, mut_iter)?; - SmartLedsAdapter::::convert_rgb_channel_to_pulses(value.r, mut_iter)?; - SmartLedsAdapter::::convert_rgb_channel_to_pulses(value.b, mut_iter)?; - - Ok(()) - } - - fn convert_rgb_channel_to_pulses( - channel_value: u8, - mut_iter: &mut IterMut, - ) -> Result<(), LedAdapterError> { - for position in [128, 64, 32, 16, 8, 4, 2, 1] { - *mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = - match channel_value & position { - 0 => PulseCode { - level1: true, - length1: SK68XX_T0H_CYCLES, - level2: false, - length2: SK68XX_T0L_CYCLES, - } - .into(), - _ => PulseCode { - level1: true, - length1: SK68XX_T1H_CYCLES, - level2: false, - length2: SK68XX_T1L_CYCLES, - } - .into(), - } - } - - Ok(()) - } -} - -impl SmartLedsWrite for SmartLedsAdapter -where - CHANNEL: ConfiguredChannel, -{ - type Error = LedAdapterError; - type Color = RGB8; - - /// Convert all RGB8 items of the iterator to the RMT format and - /// add them to internal buffer, then start a singular RMT operation - /// based on that buffer. - fn write(&mut self, iterator: T) -> Result<(), Self::Error> - where - T: Iterator, - I: Into, - { - // We always start from the beginning of the buffer - let mut seq_iter = self.rmt_buffer.iter_mut(); - - // Add all converted iterator items to the buffer. - // This will result in an `BufferSizeExceeded` error in case - // the iterator provides more elements than the buffer can take. - for item in iterator { - SmartLedsAdapter::::convert_rgb_to_pulse( - item.into(), - &mut seq_iter, - )?; - } - - // Finally, add an end element. - *seq_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = 0; - - // Perform the actual RMT operation. We use the u32 values here right away. - match self - .channel - .send_pulse_sequence_raw(RepeatMode::SingleShot, &self.rmt_buffer) - { - Ok(_) => Ok(()), - Err(x) => Err(LedAdapterError::TransmissionError(x)), - } - } -} diff --git a/esp-hal.code-workspace b/esp-hal.code-workspace deleted file mode 100644 index ff6498aacff..00000000000 --- a/esp-hal.code-workspace +++ /dev/null @@ -1,40 +0,0 @@ -{ - "folders": [ - { - "path": "esp-hal-common" - }, - { - "path": "esp-hal-procmacros" - }, - { - "path": "esp-hal-smartled" - }, - { - "path": "esp32-hal" - }, - { - "path": "esp32c2-hal" - }, - { - "path": "esp32c3-hal" - }, - { - "path": "esp32c6-hal" - }, - { - "path": "esp32s2-hal" - }, - { - "path": "esp32s3-hal" - } - ], - "settings": { - "editor.formatOnSave": true, - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.imports.granularity.enforce": true, - "rust-analyzer.imports.granularity.group": "crate", - "rust-analyzer.cargo.buildScripts.enable": true, - "rust-analyzer.procMacro.attributes.enable": true, - "rust-analyzer.procMacro.enable": true - } -} diff --git a/esp-hal/.clippy.toml b/esp-hal/.clippy.toml new file mode 100644 index 00000000000..cda8d17eed4 --- /dev/null +++ b/esp-hal/.clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md new file mode 100644 index 00000000000..9752fe8c7ec --- /dev/null +++ b/esp-hal/CHANGELOG.md @@ -0,0 +1,1563 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- RMT: All public types now derive `Debug` and `defmt::Format`. (#4302) +- RMT: `Channel::apply_config` has been added. (#4302) +- Added blocking `send_break`, `wait_for_break` and `wait_for_break_with_timeout` for sending and detecting software breaks with the UART driver (#4284) +- Added support for `RxBreakDetected` interrupt and `wait_for_break_async` for detecting software breaks asynchronously to the UART driver (#4284) +- Unsafely expose GPIO pins that are only available on certain chip/module variants (#4520) +- ESP32-H2: light sleep and deep sleep support with timer and EXT1 wakeup sources (#4587, #4641) +- Unstable detailed clock configuration options (#4660, #4674) +- `RsaContext`, `AesContext` now derive `Clone`. (#4709) +- `ShaContext` now derive `Clone`, except on ESP32. (#4709) +- Dedicated GPIO implementation (#4699, #4819) +- `esp_hal::interrupt::wait_for_interrupt`, which enters `wfi` (RISC-V) or `waiti 0` (Xtensa) when it would not prevent a debugger from reading memory (#4782) +- Initial ESP32-C5 support (#4859, #4866, #4871, #4872, #4873, #4877, #4879, #4883, #4884) +- New configuration option: `ESP_HAL_CONFIG_MIN_CHIP_REVISION` (#4875) +- `Cpu::all` to iterate over all CPUs (#4890) +- C5: Add initial GPIO support (#4899, #4928, #4935) +- C5: Add PCNT support (#4934) +- C5: Initial UART support (#4948, #4967) +- C5: Add SPI support (#4943) +- C5: Add RMT support (#4964) +- Support ESP32-H2 rev 1.2 (#4949, #4969) +- C5: Add DMA support (#4959) +- C5: Add I2C support (#4975) +- C5: Add basic RNG support (#4978) +- C5: Add SHA, RSA support (#4979) +- C5: Add ECC support (#4983, #5071) +- C5: Add AES support (#4983) +- C5: Add USB Serial/JTAG support (#5008) +- C5: Add PARL_IO support (#5042) +- `esp_hal::interrupt::RunLevel` (#4996, #5108) +- MAC addresses for radio interfaces getter: `esp_hal::efuse::interface_mac_address(InterfaceMacAddress::)`. (#5002) +- `ShaXContext` objects now implement `digest::core_api::BlockSizeUser` (and thus they can be used with the `hmac` crate) (#5050) +- C5: Add ASSIST_DEBUG support (#5058) +- `Ecc::apply_config` and `esp_hal::ecc::Config` (#5073) +- Added experimental low-level clock control functionality via `esp_hal::clock::ll` (#5092) +- Work queue support for ECC operations (#5084) +- A mechanism to hook into linker scripts (#5116) + +### Changed + +- The `Efuse` struct has been replaced by free-standing functions in the `efuse` module. `efuse::interface_mac_address` and `efuse::MacAddress` have been stabilized. (#5104) +- `efuse::read_base_mac_address()` has been renamed to `efuse::base_mac_address()` (#5104) +- Renamed `efuse::set_mac_address` to `efuse::override_mac_address` (#5104) +- UART: `read_ready` and `write_ready` are now stable (#4600) +- RMT: `SingleShotTxTransaction` has been renamed to `TxTransaction`. (#4302) +- RMT: `ChannelCreator::configure_tx` and `ChannelCreator::configure_rx` now take the configuration by reference. (#4302) +- RMT: `ChannelCreator::configure_tx` and `ChannelCreator::configure_rx` don't take a pin anymore, instead `Channel::with_pin` has been added. (#4302) +- RMT: Configuration errors have been split out of `rmt::Error` into the new `rmt::ConfigError` enum. (#4494) +- RMT: `Rmt::new()` now returns `Error::UnreachableTargetFrequency` instead of panicking when requesting 0 Hz. (#4509) +- `AtomicWaker::wake` is now placed in IRAM (#4627) +- Internal clock configuration rework (#4501, #4517, #4527, #4553, #4595, #4610, #4633) +- RMT: Support for `Into` and `From` has been removed from Tx and Rx methods, respectively, in favor of requiring `PulseCode` directly. (#4616) +- RMT: Tx handling has been revised: Some errors will now be returned by `TxTransaction::wait()` instead of `Channel::transmit`. `Channel::transmit_continuously()` can now also report `Error::EndMarkerMissing`. (#4617) +- `Rtc::time_since_boot()` has been renamed to `Rtc::time_since_power_up()` (#4630) +- `LP_UART` now has its own configuration structure (#4667) +- The `MEM2MEM` peripheral singletons have been re-numbered from 0-8 (#4944) +- The `DmaPeripheral::Mem2MemX` variants have been renamed to `Mem2memX` and re-numbered from 0-8 (#4944) +- `esp_hal::interrupt::status` has been replaced by `esp_hal::interrupt::InterruptStatus::current()` (#4997) +- `esp_hal::interrupt::bind_interrupt` and `enable` have been merged into `bind_handler` which is now safe and infallible (#4996) +- `esp_hal::peripherals::Peripherals.WIFI` and `esp_hal::peripherals::WIFI` are now stable (#5026) +- RISC-V: `esp_hal::interrupt::enable_direct` now takes a `DirectBindableCpuInterrupt` and is infallible (#5037) +- The `ecc` driver now takes the curve by value (#5055) +- `EllipticCurve` now implements common traits (#5055) +- The `interrupt` module, `esp_hal::interrupt::Priority`, `PERI::enable_X_interrupt` and `PERI::disable_X_interrupt_on_all_cores` have been stabilized (#5040) +- The `Ecc` driver no longer expects some operations to receive big-endian input (#5061) +- `Ecc::mod_operations` has been replaced by `modular_addition`, `modular_subtraction`, `modular_multiplication` and `modular_division` (#5061) +- `Ecc` methods now return a result handle that can be used to retrieve the result of the operation. (#5061) +- `Ecc` modular arithmetic methods now take the modulus as an argument (#5073) +- `Ecc::new` now takes a configuration parameter (#5073) +- It's no longer possible to pass `esp_hal::gpio::Output` to bidirectional peripheral signals (half-duplex SPI, I2C) (#5093) +- S3: SPI1 is no longer initialized if PSRAM is not correctly detected. The warning message now includes PSRAM mode config (#5122) + +### Fixed + +- SHA: Fixed potential unsoundness in `ShaDigest` by requiring exclusive access to the peripheral (#4837) +- ESP32: ADC1 readings are no longer inverted (#4423) +- RMT: All blocking methods now return the channel on failure. (#4302) +- RMT: the `place_rmt_driver_in_ram` option now also places the async interrupt handler in RAM. (#4302) +- RMT: When dropping a Tx channel, the driver now disconnects the output pin from the peripheral. (#4302) +- I2C: avoid potential infinite loop while checking for command completion (#4519) +- UART: correct documentation of `read` which incorrectly stated that it would never block (#4586) +- Fixed System Timer timestamp inaccuracy when using uncommon crystal frequencies (#4634) +- `SystemTimer::ticks_per_second()` now correctly returns the number of ticks per second. (#4634) +- The interrupt request set by `SoftwareInterrupt::raise()` should now take effect before returning. (#4706) +- Fixed an issue in `ShaBackend` that resulted in incorrect hash calculation (#4722) +- The `Peripherals` struct is now marked as `#[non_exhaustive]`. This is a breaking change. (#4729) +- All GPIOs are now available without unsafe code. The potentially reserved GPIOs are now documented. (#4728) +- Make sure we added all relevant section to `.rwtext.wifi` (#4808) +- ESP32-S3: Fixed startup code to prevent a linker error (#4815) +- Fixed a situation where the ELF might make tooling emit more than two segments in the image which will make the bootloader refuse to boot (#4844) +- ESP32-S3/ESP32-C2: WiFi will work after light-sleep with default settings (#4826) +- ESP32-S2: Fixed an issue where enabling TRNG can prevent WiFi from working (#4856) +- Fixed an issue that caused the stack guard to overwrite data moved to the second core (#4914) +- PCNT: Fixed some potential data race issues (#4932) +- Fixed PLL reconfiguration causing garbled serial output (#5067) +- ESP32, ESP32-S2: Fixed RC_FAST divider enable polarity (#5067) +- ESP32-C6: RtcClock clock calibration for v0.1 (#5109) +- Improve LP timer accuracy (#5105, #5115) +- Increase the size of `irom_seg`/`drom_seg` from 4 MB to 32 MB for the ESP32-S3 (#5121) + +### Removed + +- The `Efuse` struct has been removed. Use free-standing functions in the `efuse` module instead. (#5104) +- The `ESP_HAL_CONFIG_XTAL_FREQUENCY` configuration option has been removed (#4517) +- `Clocks::{i2c_clock, pwm_clock, crypto_clock}` fields (#4636, #4647) +- `RtcClock::xtal_freq()` and the `XtalClock` enum (#4724) +- `Rtc::estimate_xtal_frequency()` (#4851) +- `RtcFastClock`, `RtcSlowClock` (#4851) +- `esp_hal::interrupt::enable_direct` from ESP32, ESP32-S2 and ESP32-S3 (#5007) +- `esp_hal::interrupt::RESERVED_INTERRUPTS` (#5007, #5037) +- `esp_hal::interrupt::map` (#4996, #5007) +- `InterruptHandler::new_not_nested` (#5000) +- `esp_hal::interrupt::Priority::None` (#4996) + +## [v1.0.0] - 2025-10-30 + +### Added + +- Added the `float-save-restore` feature (enabled by default) for Xtensa MCUs. (#4394) + +### Changed + +- `work_queue` is no longer public (#4357) +- UART memory is now powered down when the driver is no longer in use. (#4354) + + +### Removed + +- The `WatchdogConfig` enum and watchdog timer configuration from `esp_hal::init` (#4377) + +## [v1.0.0-rc.1] - 2025-10-13 + +### Added + +- A reimplementation of the `assign_resources!` macro (#3809) +- `TrngSource` to manage random number generator entropy (#3829) +- On RISC-V you can opt-out of nested interrupts for an interrupt handler by using `new_not_nested` (#3875) +- A new default feature `exception-handler` was added (#3887) +- `AesBackend, AesContext`: Work-queue based AES driver (#3880) +- `aes::cipher_modes`, `aes::CipherModeState` for constructing `AesContext`s (#3895) +- `DmaTxBuffer` and `DmaRxBuffer` now have a `Final` associated type. (#3923) +- `RsaBackend, RsaContext`: Work-queue based RSA driver (#3910) +- `aes::{AesBackend, AesContext, dma::AesDmaBackend}`: Work-queue based AES driver (#3880, #3897) +- `aes::cipher_modes`, `aes::CipherState` for constructing `AesContext`s (#3895) +- `aes::dma::DmaCipherState` so that `AesDma` can properly support cipher modes that require state (IV, nonce, etc.) (#3897) +- `uart::Uhci`: for UART with DMA using the UHCI peripheral (#3871, #4008, #4011) +- Align `I8080` driver pin configurations with latest guidelines (#3997) +- Expose cache line configuration (#3946) +- ESP32: Expose `psram_vaddr_mode` via `PsramConfig` (#3990) +- ESP32-S3: Expose more `Camera` config options (#3996) +- Functions to query chip revision for every chip (#3892) +- ESP32-S3: Add RtcI2c driver (#4016) +- `ShaBackend, ShaContext`: Work-queue based SHA driver (#4013) +- I2S: `i2s::master::Config` with support for more TDM mode standards (#3985) +- ESP32: support outputting the main I2S clock signal (#4128) +- ESP32 and S3 has `is_running()` function from `esp-storage (#4188) +- `Cpu::other()` is now marked as public (#4188) +- The ESP_HAL_CONFIG_STACK_GUARD_MONITORING (enabled by default) enables a data watchpoint on the stack guard value to protect the main stack (#4207, #4289) +- `start_app_core_with_stack_guard_offset` (#4207) +- Chip version Efuse accessors for ESP32, ESP32-C2, ESP32-C3 (#4248) +- The `rmt::PulseCode::MAX_LEN` constant was added. (#4246) +- `rmt::Error` now implements `core::error::Error` (#4247) +- `ram(reclaimed)` as an alias for `link_section = ".dram2_uninit"` (#4245) +- `rmt::MAX_TX_LOOPCOUNT` and `rmt::MAX_RX_IDLE_THRESHOLD` constants have been added (#4276) +- Added support for `embedded-io 0.7` (#4280) +- RMT: Wrapping the hardware buffer is now supported for rx/tx and blocking/async channels (#4049) +- `rmt::CHANNEL_RAM_SIZE` and `rmt::HAS_RX_WRAP` constants have been added (#4049) +- A new option `ESP_HAL_CONFIG_WRITE_VEC_TABLE_MONITORING` (disabled by default) to check that no unintentional writes to a very vital memory area are made. (Only RISC-V) (#4225) +- ESP32-C2: Added `Attenuation::_2p5dB` and `Attenuation::_6dB` options (#4324) + +### Changed + +- The `rng` module has been rewritten (#3829) +- Update `embassy-usb` to v0.5.0 (#3848) +- `aes::Key` variants have been renamed from bytes to bits (e.g. `Key16 -> Key128`) (#3845) +- `aes::Mode` has been replaced by `Operation`. The key length is now solely determined by the key. (#3882) +- `AesDma::process` now takes `DmaCipherState` instead of `CipherMode`. (#3897) +- `Aes::process` has been split into `Aes::encrypt` and `Aes::decrypt` (#3882) +- Blocking RMT transactions can now be `poll`ed without blocking, returning whether they have completed. (#3716) +- RISC-V: Interrupt handler don't get a TrapFrame passed in anymore (#3903) +- ISR callbacks are now wrapped in `IsrCallback` (#3885) +- The RMT `PulseCode` is now a newtype wrapping `u32` with `const fn` methods and implementing `defmt::Format` and `core::fmt::Debug`. (#3884) +- RMT transmit and receive methods accept `impl Into` and `impl From`, respectively. (#3884) +- The `Rsa::read` function has been removed. The constructor now blocks until the peripheral's memory has been cleared (#3900) +- `Rsa::enable_constant_time_acceleration` has been renamed to `Rsa::disable_constant_time` (#3900) +- `Rsa::enable_search_acceleration` has been renamed to `Rsa::search_acceleration` (#3900) +- `DmaTxBuffer::from_view` and `DmaRxBuffer::from_view` now return an object with type `DmaTx/RxBuffer::Final`. (#3923) +- `i2c::master::Config::timeout` has been de-stabilized, and `i2c::master::Config::software_timeout`. (#3926) +- The default values of `i2c::master::Config` timeouts have been changed to their maximum possible values. (#3926) +- Adc CHANNEL constant moved to trait function. (#3942) +- `ShaDigest::finish` has been reimplemented to be properly non-blocking (#3948) +- Replace Timer's `pub fn enable_interrupt(&mut self, enable: bool)` with `pub fn listen(&mut self)` and `pub fn unlisten(&mut self)` (#3933) +- ESP32-S3: `PsramConfig::core_clock` is now an `Option` (#3974) +- `RtcSlowClock::RtcFastClock8m` has been renamed to `RtcFastClock::RtcFastClockRcFast` (#3993) +- `RtcSlowClock::RtcSlowClockRtc` has been renamed to `RtcSlowClock::RtcSlowClockRcSlow` (#3993) +- The `Raw: RawChannelAccess` of `rmt::Channel` has been erased; channel numbers are always dynamic now. (#3980) +- ESP32-S2: `i2s::master::DataFormat` now includes 8-bit and 24-bit data widths (#3985) +- Bump `embassy-embedded-hal` to v0.5.0 (#4075) +- Make `uart::Uhci` configurable after splitting, improve DMA channel handling (#4039) +- `RtcClock`, `RtcFastClock`, and `RtcSlowClock` moved to `clock` module (#4089) +- Resolved enum variant naming violations in `RtcFastClock` and `RtcSlowClock` enums (#4089) +- The `rmt::Channel::transmit_continuously` and `rmt::Channel::transmit_continuously_with_loopcount` methods have been merged (#4100) +- Introduced `Error::FrequencyUnset` in `ledc::timer::Error` (#4214) +- `ESP_HAL_CONFIG_STACK_GUARD_OFFSET` is now considered stable (#4220) +- `rmt::Channel` and `rmt::ChannelCreator` now carry a lifetime and can be reborrowed. (#4174) +- RMT transactions and futures are marked as `#[must_use]` and implement `Drop`. (#4174) +- The behavior of `rmt::PulseCode` constructors (`new`, `new_clamped`, `try_new`) has been reworked to be more convenient and clear. (#4246) +- RMT: `Channel::transmit_continuously` now takes an additional `LoopStop` argument. (#4260) +- RMT: `LoopCount::Finite` and `ContinuousTxTransaction::is_tx_loopcount_interrupt_set` are only defined when the hardware supports it (all except ESP32). (#4260) +- RMT: `Channel::transmit_continuously` now verifies that loop counts don't exceed the hardware limit (#4276) +- RMT: Receive operations read only received codes instead of the entire buffer and return the number of codes read (#4049) +- `esp_hal::clock::{RtcFastClock, RtcSlowClock, RtcClock}`, `esp_hal::gpio::Input::wait_for` and `esp_hal::gpio::Event` have been marked unstable (#4293, #4326) +- All `ram` proc macro options except `#[ram(reclaimed)]` are considered `unstable` (#4309) +- ESP32: Stripped prefix from `esp_hal::adc::Resolution` variants (`ResolutionXBit -> _XBit`) (#4324) + +### Fixed + +- PSRAM on ESP32-S2 (#3811) +- WDT now allows configuring longer timeouts (#3816) +- `ADC2` now cannot be used simultaneously with `radio` on ESP32 (#3876) +- Switched GPIO32 and GPIO33 ADC channel numbers (#3908, #3911) +- Calling `Input::unlisten` in a GPIO interrupt handler no longer panics (#3913) +- ESP32, ESP32-S2: Fixed I2C bus clearing algorithm (#3926) +- Check serial instead of jtag fifo status in UsbSerialJtag's async flush function (#3957) +- ESP32: Enable up to 4M of PSRAM (#3990) +- I2C error recovery logic issues (#4000) +- I2S: Fixed RX half-sample bits configuration bug causing microphone noise (#4109) +- RISC-V: Direct interrupt vectoring (#4171) +- TWAI: Fixed unnecessary transmission abortions (#4227) +- TWAI: Fixed receive_async returning corrupt frames (#4243) +- TWAI: Fixed receive_async erroneously returning RX FIFO overrun errors (#4244) +- Subtracting Instant values with large difference no longer panics (#4249) +- ADC: Fixed integer overflow in curve calibration polynomial evaluation (#4240) +- ADC: Fixed bug where ADC1 would use ADC2 efuse calibration data (#4286) +- RMT: `Channel::transmit_continuously` also triggers the loopcount interrupt when only using a single repetition. (#4260) +- I2C: Fix position of SDA sampling point (#4268) + +### Removed + +- `Trng::new` (replaced by `Trng::try_new`) (#3829) +- `AesDma::{write_key, write_block}` have been removed. (#3880, #3882) +- `AesFlavour` trait and `AesX` structs have been removed. (#3880) +- `Xtal::Other` has been removed (#3983) +- ESP32-C3/S3: removed the UHCI1 peripheral singleton (#4007) +- `i2s::master::Standard` has been removed (#3985) +- `ledc::channel::PinConfig` has been removed and used `DriveMode` instead (#4214) +- ESP32: removed integrated SPI-connected pins (6 to 11 both included) (#4202) +- ESP32H2: removed pins 6 and 7 (#4202) +- ESP32C3 and ESP32C2: removed pins 11 to 17 both included (#4202) +- ESP32C6: removed pins 24, 25, 26, 28, 29 and 30 (#4202) +- ESP32S2 and ESP32S3: removed pins 26 to 32 both included (#4202) +- `adc::Resolution` has been removed from chips other than ESP32 (#4324) + +## [v1.0.0-rc.0] - 2025-07-16 + +### Added + +- `i2c::master::BusTimeout::Disabled` for ESP32-S2 (#3591) +- The `const CHANNEL: u8` parameter of RMT channels can now be erased via `Channel::degrade()`. (#3505) +- ESP32-C6: GPIO6 now implements `AnalogPin` (#3668) +- SPI master: Expose octal SPI-specific `with_sio` functions (#3702) +- The functions of the `RadioClockController` have been split up to the modem peripheral structs. The clock management is now provided by the `ModemClockController`. (#3687) +- Added GPIO11-GPIO17 to ESP32-C2. (#3726) +- Added the feature `requires-unstable` (#3772) +- `AnyPin::downcast`/`AnyPeripheral::downcast` to allow retrieving the original GPIO/peripheral type (#3783, #3784) +- Add `ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM` configuration option to pin the RMT driver in RAM (#3778) +- The `rt` feature (#3706) + +### Changed + +- MSRV is now 1.88.0 (#3742) +- `AnyI2c` has been moved from `esp_hal::i2c` to `esp_hal::i2c::master` (#3627) +- `AnySpi` has been moved from `esp_hal::spi` to `esp_hal::spi::master` and `esp_hal::spi::slave` (#3627) +- `DataMode` has been moved from `esp_hal::spi` to `esp_hal::spi::master` (#3627) +- The `handler` macro (reexported from `esp-hal-procmacros`) no longer accepts priority as a string (#3643) +- Generic parameters of RMT `Channel`s have changed in preparation for type-erased channels. (#3505) +- RMT `TxChannelCreator` and `RxChannelCreator` now have a `DriverMode` generic parameter; `TxChannelCreatorAsync` and `RxChannelCreatorAsync` have been removed. (#3505) +- RMT `ChannelCreator` methods have been renamed from `configure` to `configure_tx` and `configure_rx` to avoid trait disambiguation issues. (#3505) +- The RMT `Error` type has been marked `non_exhaustive` (#3701) +- Increase ESP32 DRAM memory region by 16K (#3703) +- The I2C async interrupt handler is now placed into IRAM (#3722) +- Adjusted ESP32-S2 and ESP-S3 memory region lengths to reflect those defined in ESP-IDF. (#3709) +- Changed the various `ConfigError` variant names to use a consistent word order. (#3782) +- Adjusted ESP32-S2 deep-sleep to hibernate for the Ext1WakeupSource (#3785) +- Libraries depending on esp-hal should now disable default features, so that only the final binary crate enables the `rt` feature (#3706) +- Changed `interrupt::RESERVED_INTERRUPTS` from `&[usize]` to `&[u32]` (#3798) + +### Fixed + +- Fixed a typo in the ESP32-C3 memory linker script, causing ICACHE to not be defined (#3613) +- Prevent bootloops when DRAM is close to being full. (#3635) +- Fix PSRAM mapping on ESP32-S3 when the bootloader used the last page to access flash (#3637) +- `ESP_HAL_CONFIG_STACK_GUARD_OFFSET` and `ESP_HAL_CONFIG_STACK_GUARD_VALUE` are now unstable config options (#3711) +- Fixed MCPWM output when using USB pins (#3795) + +### Removed + +- `InterruptHandler` no longer implements `PartialEq`, `Eq` or `Hash`. (#3650) +- `gpio::NUM_PINS` (#3658) +- `RADIO_CLK` and `RadioClockController` have been removed (#3687) +- Removed GPIO24 from ESP32. (#3726) +- Removed GPIO15-GPIO21 from ESP32-H2. (#3726) +- ESP32-S3: `AnalogPin` is no longer implemented for GPIO0 and GPIO21 (#3781) + +## [v1.0.0-beta.1] - 2025-06-03 + +### Added + +- RMT channel creator `steal` function (#3496) +- Support for RMT extended memory (#3182) +- Support for `rand_core` 0.9 (#3211) +- `ESP_HAL_CONFIG_STACK_GUARD_OFFSET` and `ESP_HAL_CONFIG_STACK_GUARD_VALUE` to configure Rust's [Stack smashing protection](https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection) (#3203) +- Experimental metadata in the output `.elf` (#3276) +- `PeripheralInput::connect_input_to_peripheral` and `PeripheralOuptut::{connect_peripheral_to_output, disconnect_from_peripheral_output}` (#3302) +- `ESP_HAL_CONFIG_CRITICAL_SECTION_IMPL` to allow opting out of the default `critical-section` implementation (#3293) +- All peripheral singletons (`GpioPin<...>`, `SPIn`, ...) now have a lifetime, as well as `steal`, `reborrow` and `clone_unchecked` methods (#3305) +- `i2c::master::Operation` now implements `defmt::Format` (#3348) +- ESP32-S2: Support for light-/deep-sleep (#3341) +- Add DMA memcpy support to the S2 (#3352) +- Some config options can now only be set when the `unstable` feature in enabled (#3365) +- Added `Flex::enable_output` (#3387) +- Added `Flex::set_output_enable` (#3387) +- Added `{Uart, UartRx}::read_ready` (#3423) +- Added `{Uart, UartTx}::write_ready` (#3423) +- Implemented `embedded_io::ReadReady` for `Uart` and `UartRx` (#3423) +- Implemented `embedded_io::WriteReady` for `Uart` and `UartTx` (#3423) +- ESP32-H2: Support for ADC calibration (#3414) +- Expose ADC asynchronous functionalities where applicable (#3443) +- Added `UartInterrupt::RxTimeout` support (#3493) +- UART: Added HW and SW flow control config option (#3435) +- I2C master: `SoftwareTimeout` and `Config::with_software_timeout`. (#3577) +- `esp_hal::time::{Instant, Duration}` now implement `Hash` (#3577) + +### Changed + +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) +- Replaced `chrono::NaiveDateTime` on the RTC API by raw `u64` timestamps (#3200) +- `esp_hal::i2s::master::AnyI2s` has been moved to `esp_hal::i2s::AnyI2s` (#3226) +- `esp_hal::i2c::master::AnyI2c` has been moved to `esp_hal::i2c::AnyI2c` (#3226) +- `SpiDmaBus` no longer adjusts the DMA buffer length for each transfer (#3263) +- `SpiDma` now uses the SPI interrupt (instead of DMA) to wait for completion (#3303) +- I2S driver now takes `DmaDescriptor`s later in construction (#3324) +- The `gpio::interconnect` module has been rewritten. For details, refer to the Migration guide (#3302, #3395) +- Make `ParlIo` driver construction more consistent (#3345) +- `ParlIo` driver now uses a config struct (#3359) +- The `critical-section` implementation is now gated behind the `critical-section-impl` feature (#3293) +- `Trace` is no longer generic (#3305) +- Migrate SPI slave driver to newer DMA API (#3326) +- Migrate DMA memcpy driver to newer DMA API (#3327) +- Moved numbered GPIO pin types from `esp_hal::gpio::GpioPin` to `esp_hal::peripherals::GPION<'_>` (#3349) +- Moved DMA channel types from `esp_hal::dma::DmaChannelN`/`esp_hal::dma::XYDmaChannel` to `esp_hal::peripherals::DMA_XY` (#3372) +- `ParlIoFullDuplex`, `ParlIoTxOnly` and `ParlIoRxOnly` have been merged into `ParlIo` (#3366) +- I2C checks ST_TOUT / MAIN_ST_TOUT where available (#3333) +- All `Camera` pins are now configured using `with_*()` methods (#3237) +- The `ESP_HAL_CONFIG_PLACE_SPI_DRIVER_IN_RAM` configuration option has been renamed to `ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM`. (#3402) +- Made the `ParlIo` traits for `TxPins`, `RxPins`, `ConfigurePins` public (#3398) +- Renamed `Flex::enable_input` to `set_input_enable` (#3387) +- Make `esp_hal::interrupt::current_runlevel` public under the unstable feature (#3403) +- Update `defmt` to 1.0 (#3416) +- `spi::master::Spi::transfer` no longer returns the received data as a slice (#3417) +- esp-hal no longer clears the GPIO interrupt status bits by default. (#3408) +- eFuse field definitions have been updated/corrected (#3440) +- `spi::master::Spi::transfer` no longer returns the received data as a slice (#3417) +- The `log` feature has been replaced by `log-04`. (#3425) +- Multiple feature flags have been replaced by `unstable`. (#3425) +- The `debug` feature has been removed. (#3425) +- The `usb_otg` and `bluetooth` features are now considered private and have been renamed accordingly. (#3425) +- Include `.uninit` in the `noinit` section (#3558) +- `SoftwareInterruptControl::software_interrupt2` is no longer available when using `esp-wifi/builtin-scheduler` (#3576) + +### Fixed + +- RMT: Return an error when trying create a channel with `memsize: 0` (#3477) +- RMT: fix a potential hang on transmitting data with an embedded stop code (#3477) +- RMT channel drop implementation bugfix where the channel was not released properly (#3496) +- RMT now uses correct max filter threshold of 255 instead of 127 (#3192) +- Full-duplex SPI works when mixed with half-duplex SPI (#3176) +- `Uart::flush_async` should no longer return prematurely (#3186) +- Detecting a UART overflow now clears the RX FIFO. (#3190) +- ESP32-S2: Fixed PSRAM initialization (#3196) +- `Uart::{with_tx, with_rx}` can now be called on the async driver as well (#3212) +- ESP32: Fixed SPI3 QSPI signals (#3201) +- ESP32-C6/H2: The `flip_link` feature should no longer crash (#3203) +- SPI: `Spi::transfer_in_place_async` now stops the transfer when cancelled (#3242) +- ESP32/ESP32-S2: Avoid running into timeouts with reads/writes larger than the FIFO (#3199) +- ESP32: Enforce required pointer alignments in DMA buffers (#3296) +- ESP32-C6: Keep ADC enabled to improve radio signal strength (#3249) +- Fix off-by-one in the allowed range of the spi clock calculations (#3266) +- Fix PCNT counter not keeping the peripheral enabled (#3334) +- Fixed an issue where inverting a pin via the interconnect matrix was ineffective (#3312) +- The half-duplex SPI APIs should accept more valid line width combinations (#3325) +- Async I2C is doesn't do blocking reads anymore (#3344) +- Passing an invalid seven bit I2C address is now rejected (#3343) +- PARL_IO: Use correct max transfer size (#3346) +- `OneShot` timer now returns an InvalidTimeout from `schedule` instead of panicking (#3433) +- GPIO interrupt handling no longer causes infinite looping if a task at higher priority is awaiting on a pin event (#3408) +- `esp_hal::gpio::Input::is_interrupt_set` can now return true (#3408) +- `Uart::write_str` (both core::fmt and uWrite implementations) no longer stops writing when the internal buffer fills up (#3452) +- Fixed I2C `Timeout` errors experienced during high CPU load (#3458, #3555) +- Fix a problem where reading/writing flash didn't work when using PSRAM on ESP32 (#3524) +- Fixed `esp_hal::time::Instant::duration_since_epoch` (#3582) +- Improve PSRAM size detection for the case when no PSRAM is present or unusable (#3554) +- ESP32-S2: I2C operations will now time out if the SCL line is kept low. This timeout is controlled by `Config::software_timeout` (#3571, #3577) +- Asynchronous I2C operations are now cancelled if the Future is dropped (#3572) +- The I2C driver will clear the bus after an error, if necessary (#3570) + +### Removed + +- The `Peripheral` trait and `PeripheralRef` struct have been removed (#3302, #3305) +- Removed the inherent `degrade` method from peripheral singletons. (#3305) +- Removed the `FullDuplex` trait from the PARL_IO driver. (#3339) +- Removed `Flex::{set_as_input, set_as_output, set_drive_strength, set_as_open_drain, pull_direction}` functions (#3387) +- The `Efuse::read_field_be` function has been removed (#3440) +- The config option (`ESP_HAL_CONFIG_FLIP_LINK`) got removed, `ESP_HAL_CONFIG_WRITE_VEC_TABLE_MONITORING` will yield equally good results (#4295) + +## [v1.0.0-beta.0] - 2025-02-24 + +### Added + +- SPI: Added support for 3-wire SPI (#2919) +- UART: Add separate config for Rx and Tx (#2965) +- UART: `read_exact_async` (unstable) (#3142) +- UART: `TxConfig::fifo_empty_threshold` (#3142) +- Added accessor methods to config structs (#3011) +- `esp_hal::time::{Rate, Duration, Instant}` (#3083) +- Async support for ADC oneshot reads for ESP32C2, ESP32C3, ESP32C6 and ESP32H2 (#2925, #3082) +- `ESP_HAL_CONFIG_XTAL_FREQUENCY` configuration. For now, chips other than ESP32 and ESP32-C2 have a single option only. (#3054) +- Added more validation to UART and SPI. The user can now specify the baudrate tolerance of UART config (#3074) +- Add auto-writeback support to DMA buffers (#3107) + +### Changed + +- LEDC: Derive `Clone` and `Copy` for ledc speed types to make `ledc::channel::Config` derive them too. (#3139) +- The `unstable` feature is no longer enabled by default (#3136) +- RMT: `TxChannelConfig` and `RxChannelConfig` now support the builder-lite pattern (#2978) +- RMT: Some fields of `TxChannelConfig` and `RxChannelConfig` are now `gpio::Level`-valued instead of `bool` (#2989) +- RMT: The `PulseCode` trait now uses `gpio::Level` to specify output levels instead of `bool` (#2989) +- Removed `embedded-hal-nb` traits (#2882) +- `timer::wait` is now blocking (#2882) +- By default, set `tx_idle_num` to 0 so that bytes written to TX FIFO are always immediately transmitted. (#2859) +- `Rng` and `Trng` now implement `Peripheral

    ` (#2992) +- SPI, UART, I2C: `with_` functions of peripheral drivers now disconnect the previously assigned pins from the peripheral. (#3012) +- SPI, UART, I2C: Dropping a driver now disconnects pins from their peripherals. (#3012) +- TWAI: Async transmission future resolves after successful transmission and can be aborted by dropping the future. (#3132) +- Migrate PARL_IO driver to DMA move API (#3033) +- `Async` drivers are no longer `Send` (#2980) +- GPIO drivers now take configuration structs (#2990, #3029) +- `flip-link` feature is now a config option (`ESP_HAL_CONFIG_FLIP_LINK`) (#3001) +- Migrate AES driver to DMA move API (#3084) +- Removed features `psram-quad` and `psram-octal` - replaced by `psram` and the `ESP_HAL_CONFIG_PSRAM_MODE` (`quad`/`octal`) (#3001) +- The `esp_hal::time` module no longer reexports `fugit` types (#3083) +- The `system::RadioClockController` trait has been replaced by the `clock::RadioClockController` struct. (#3100) +- The `Cpu` struct and contents of the `reset` and `cpu_control` modules have been moved into `cpu`. (#3099) +- The `software_reset_cpu` now takes which CPU to reset as parameter. (#3099) +- `read_bytes` and `write_bytes` methods on drivers have been renamed to `read` and `write` (#3137) +- `Uart::write` and `Uart::read` are now blocking and return the number of bytes written/read (#2882) +- `Uart::flush` is now blocking (#2882) +- `Uart::split` and the respective split halves have been marked as unstable (#3137) +- Uart errors have been split into `RxError` and `TxError`. A combined `IoError` has been created for embedded-io. (#3138) +- `{Uart, UartTx}::flush()` is now fallible. (#3138) +- `Uart::{read_async, write_async}` are now cancellation-safe (#3142) +- I2C: Async functions are postfixed with `_async`, non-async functions are available in async-mode (#3056) +- ESP32-H2/ESP32-C6: Don't rely on the bootloader to deconfigure permission control (#3150) + +### Fixed + +- `DmaDescriptor` is now `#[repr(C)]` (#2988) +- Fixed an issue that caused LCD_CAM drivers to turn off their clocks unexpectedly (#3007) +- Fixed an issue where DMA-driver peripherals started transferring before the data was ready (#3003) +- Fixed an issue on ESP32 and S2 where short asynchronous Timer delays would never resolve (#3093) +- Fixed an issue setting higher UART baud rates (#3104) +- ESP32-S2: Fixed linker script (#3096) +- Fix auto writeback on Crypto DMA (#3108) +- `Uart::flush()` now correctly blocks until the TX FIFO is empty (#3151) + +### Removed + +- Removed `Pin`, `RtcPin` and `RtcPinWithResistors` implementations from `Flex` (#2938) +- OutputOpenDrain has been removed (#3029) +- The fields of config structs are no longer public (#3011) +- Removed the dysfunctional `DmaChannel::set_priority` function (#3088) +- `esp_hal::time::now()`, which has been replaced by `esp_hal::time::Instant::now()` (#3083) +- `peripherals::Interrupts` (#3152) + +## [0.23.1] - 2025-01-15 + +### Fixed + +- Fixed `PriorityLock` being ineffective with `Priority::max()` on RISC-V CPUs (#2964) + +## [0.23.0] - 2025-01-15 + +### Added + +- ESP32-S3: Added SDMMC signals (#2556) +- Added `set_priority` to the `DmaChannel` trait on GDMA devices (#2403, #2526) +- Added `into_async` and `into_blocking` functions for `ParlIoTxOnly`, `ParlIoRxOnly` (#2526) +- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526, #2532) +- DMA: `PeripheralDmaChannel` type aliases and `DmaChannelFor` traits to improve usability. (#2532) +- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403) +- `esp_hal::asynch::AtomicWaker` that does not hold a global critical section (#2555) +- `esp_hal::sync::RawMutex` for embassy-sync. (#2555) +- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526) +- Added PSRAM configuration to `esp_hal::Config` if `quad-psram` or `octal-psram` is enabled (#2546) +- Added `esp_hal::psram::psram_raw_parts` (#2546) +- The timer drivers `OneShotTimer` & `PeriodicTimer` have `into_async` and `new_typed` methods (#2586) +- `timer::Timer` trait has three new methods, `wait`, `async_interrupt_handler` and `peripheral_interrupt` (#2586) +- Configuration structs in the I2C, SPI, and UART drivers now implement the Builder Lite pattern (#2614) +- Added `I8080::apply_config`, `DPI::apply_config` and `Camera::apply_config` (#2610) +- Introduced the `unstable` feature which will be used to restrict stable APIs to a subset of esp-hal. (#2628) +- HAL configuration structs now implement the Builder Lite pattern (#2645) +- Added `OutputOpenDrain::unlisten` (#2625) +- Added `{Input, Flex}::wait_for` (#2625) +- Peripheral singletons now implement `Debug` and `defmt::Format` (#2682, #2834) +- `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543) +- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543) +- Added `SpiDmaBus::split` for moving between manual & automatic DMA buffers (#2824) +- ESP32-S2: DMA support for AES (#2699) +- Added `transfer_in_place_async` and embedded-hal-async implementation to `Spi` (#2691) +- `InterruptHandler` now implements `Hash` and `defmt::Format` (#2830) +- `uart::ConfigError` now implements `Eq` (#2825) +- `i2c::master::Error` now implements `Eq` and `Hash` (#2825) +- `i2c::master::Operation` now implements `Debug`, `PartialEq`, `Eq`, `Hash`, and `Display` (#2825) +- `i2c::master::Config` now implements `PartialEq`, `Eq`, and `Hash` (#2825) +- `i2c::master::I2c` now implements `Debug`, `PartialEq`, and `Eq` (#2825) +- `i2c::master::Info` now implements `Debug` (#2825) +- `spi::master::Config` now implements `Hash` (#2823) +- `spi::master` drivers now implement `Debug` and `defmt::Format` (#2823) +- `DmaRxBuf`, `DmaTxBuf` and `DmaRxTxBuf` now implement `Debug` and `defmt::Format` (#2823) +- DMA channels (`AnyGdmaChannel`, `SpiDmaChannel`, `I2sDmaChannel`, `CryptoDmaChannel`) and their RX/TX halves now implement `Debug` and `defmt::Format` (#2823) +- `DmaDescriptor` and `DmaDescriptorFlags` now implement `PartialEq` and `Eq` (#2823) +- `gpio::{Event, WakeEvent, GpioRegisterAccess}` now implement `Debug`, `Eq`, `PartialEq` and `Hash` (#2842) +- `gpio::{Level, Pull, AlternateFunction, RtcFunction}` now implement `Hash` (#2842) +- `gpio::{GpioPin, AnyPin, Io, Output, OutputOpenDrain, Input, Flex}` now implement `Debug`, `defmt::Format` (#2842) +- More interrupts are available in `esp_hal::spi::master::SpiInterrupt`, add `enable_listen`,`interrupts` and `clear_interrupts` for ESP32/ESP32-S2 (#2833) +- The `ExtU64` and `RateExtU32` traits have been added to `esp_hal::time` (#2845) +- Added `AnyPin::steal(pin_number)` (#2854) +- `adc::{AdcCalSource, Attenuation, Resolution}` now implement `Hash` and `defmt::Format` (#2840) +- `rtc_cntl::{RtcFastClock, RtcSlowClock, RtcCalSel}` now implement `PartialEq`, `Eq`, `Hash` and `defmt::Format` (#2840) +- Added `tsens::TemperatureSensor` peripheral for ESP32C6 and ESP32C3 (#2875) +- Added `with_rx()` and `with_tx()` methods to Uart, UartRx, and UartTx (#2904) +- ESP32-S2: Made Wi-Fi peripheral non virtual. (#2942) +- `UartRx::check_for_errors`, `Uart::check_for_rx_errors`, `{Uart, UartRx}::read_buffered_bytes` (#2935) +- Added `i2c` interrupt API (#2944) + +### Changed + +- In addition to taking by value, peripheral drivers can now mutably borrow DMA channel objects. (#2526) +- DMA channel objects are no longer wrapped in `Channel`. The `Channel` drivers are now managed by DMA enabled peripheral drivers. (#2526) +- The `Dpi` driver and `DpiTransfer` now have a `Mode` type parameter. The driver's asyncness is determined by the asyncness of the `Lcd` used to create it. (#2526) +- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403) +- `SystemTimer::set_unit_value` & `SystemTimer::configure_unit` (#2576) +- `SystemTimer` no longer uses peripheral ref (#2576) +- `TIMGX` no longer uses peripheral ref (#2581) +- `SystemTimer::now` has been renamed `SystemTimer::unit_value(Unit)` (#2576) +- `SpiDma` transfers now explicitly take a length along with the DMA buffer object (#2587) +- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403) +- `SystemTimer`s `Alarm`s are now type erased (#2576) +- `TimerGroup` `Timer`s are now type erased (#2581) +- PSRAM is now initialized automatically if `quad-psram` or `octal-psram` is enabled (#2546) +- DMA channels are now available via the `Peripherals` struct, and have been renamed accordingly. (#2545) +- Moved interrupt related items from lib.rs, moved to the `interrupt` module (#2613) +- The timer drivers `OneShotTimer` & `PeriodicTimer` now have a `Mode` parameter and type erase the underlying driver by default (#2586) +- `timer::Timer` has new trait requirements of `Into`, `'static` and `InterruptConfigurable` (#2586) +- `systimer::etm::Event` no longer borrows the alarm indefinitely (#2586) +- A number of public enums and structs in the I2C, SPI, and UART drivers have been marked with `#[non_exhaustive]` (#2614) +- Interrupt handling related functions are only provided for Blocking UART. (#2610) +- Changed how `Spi`, (split or unsplit) `Uart`, `LpUart`, `I8080`, `Camera`, `DPI` and `I2C` drivers are constructed (#2610) +- I8080, camera, DPI: The various standalone configuration options have been merged into `Config` (#2610) +- Dropped GPIO futures stop listening for interrupts (#2625) +- UART driver's `StopBits` enum variants now correctly use UpperCamelCase (#2669) +- The `PeripheralInput` and `PeripheralOutput` traits are now sealed (#2690) +- `esp_hal::sync::Lock` has been renamed to RawMutex (#2684) +- Updated `esp-pacs` with support for Wi-Fi on the ESP32 and made the peripheral non virtual (#2822) +- `SpiBitOrder`, `SpiDataMode`, `SpiMode` were renamed to `BitOder`, `DataMode` and `Mode` (#2828) +- `crate::Mode` was renamed to `crate::DriverMode` (#2828) +- `Spi::with_miso` has been overloaded into `Spi::with_miso` and `Spi::with_sio1` (#2557) +- Renamed some I2C error variants (#2844) +- I2C: Replaced potential panics with errors. (#2831) +- UART: Make `AtCmdConfig` and `ConfigError` non-exhaustive (#2851) +- UART: Make `AtCmdConfig` use builder-lite pattern (#2851) +- UART: Fix naming violations for `DataBits`, `Parity`, and `StopBits` enum variants (#2893) +- UART: Remove blocking version of `read_bytes` and rename `drain_fifo` to `read_bytes` instead (#2895) +- Renamed variants of `CpuClock`, made the enum non-exhaustive (#2899) +- SPI: Fix naming violations for `Mode` enum variants (#2902) +- SPI: Fix naming violations for `Address` and `Command` enum variants (#2906) +- `ClockSource` enums are now `#[non_exhaustive]` (#2912) +- `macros` module is now private (#2900) +- `gpio::{Input, Flex}::wakeup_enable` now returns an error instead of panicking. (#2916) +- I2C: Have a dedicated enum to specify the timeout (#2864) +- Removed the `I` prefix from `DriveStrength` enum variants. (#2922) +- Removed the `Attenuation` prefix from `Attenuation` enum variants. (#2922) +- Renamed / changed some I2C error variants (#2844, #2862) +- The `entry` macro is replaced by the `main` macro (#2941) +- `{Uart, UartRx}::read_bytes` now blocks until the buffer is filled. (#2935) +- Bump MSRV to 1.84 (#2951) + +### Fixed + +- Xtensa devices now correctly enable the `esp-hal-procmacros/rtc-slow` feature (#2594) +- User-bound GPIO interrupt handlers should no longer interfere with async pins. (#2625) +- `spi::master::Spi::{into_async, into_blocking}` are now correctly available on the typed driver, too. (#2674) +- It is no longer possible to safely conjure `GpioPin` instances (#2688) +- UART: Public API follows `C-WORD_ORDER` Rust API standard (`VerbObject` order) (#2851) +- `DmaRxStreamBuf` now correctly resets the descriptors the next time it's used (#2890) +- i2s: fix pin offset logic for parallel output on i2s1 (#2886) + +### Removed + +- Remove more examples. Update doctests. (#2547) +- The `configure` and `configure_for_async` DMA channel functions has been removed (#2403) +- The DMA channel objects no longer have `tx` and `rx` fields. (#2526) +- `SysTimerAlarms` has been removed, alarms are now part of the `SystemTimer` struct (#2576) +- `FrozenUnit`, `AnyUnit`, `SpecificUnit`, `SpecificComparator`, `AnyComparator` have been removed from `systimer` (#2576) +- Remove Dma[Rx|Tx]Buffer::length (#2587) +- `esp_hal::psram::psram_range` (#2546) +- The `Dma` structure has been removed. (#2545) +- Removed `embedded-hal 0.2.x` impls and deps from `esp-hal` (#2593) +- Removed `Camera::set_` functions (#2610) +- `DmaTxBuf::{compute_chunk_size, compute_descriptor_count, new_with_block_size}` (#2543) +- The `prelude` module has been removed (#2845) +- SPI: Removed `pub fn read_byte` and `pub fn write_byte` (#2915) +- Removed all peripheral instance type parameters and `new_typed` constructors (#2907) + +## [0.22.0] - 2024-11-20 + +### Added + +- A new config option `PLACE_SWITCH_TABLES_IN_RAM` to improve performance (especially for interrupts) at the cost of slightly more RAM usage (#2331) +- A new config option `PLACE_ANON_IN_RAM` to improve performance (especially for interrupts) at the cost of RAM usage (#2331) +- Add burst transfer support to DMA buffers (#2336) +- `AnyPin` now implements `From>`. (#2326) +- Added `AnySpi` and `AnySpiDmaChannel`. (#2334) +- Added `AnyI2s` and `AnyI2sDmaChannel`. (#2367) +- Added `AnyTwai`. (#2359) +- Added `AnyUart`. (#2381) +- `Pins::steal()` to unsafely obtain GPIO. (#2335) +- `I2c::with_timeout` (#2361) +- `Spi::half_duplex_read` and `Spi::half_duplex_write` (#2373) +- Add RGB/DPI driver (#2415) +- Add `DmaLoopBuf` (#2415) +- `Cpu::COUNT` and `Cpu::current()` (#2411) +- `UartInterrupt` and related functions (#2406) +- I2S Parallel output driver for ESP32. (#2348, #2436, #2472) +- Add an option to configure `WDT` action (#2330) +- `DmaDescriptor` is now `Send` (#2456) +- `into_async` and `into_blocking` functions for most peripherals (#2430, #2461) +- API mode type parameter (currently always `Blocking`) to `master::Spi` and `slave::Spi` (#2430) +- `gpio::{GpioPin, AnyPin, Flex, Output, OutputOpenDrain}::split()` to obtain peripheral interconnect signals. (#2418) +- `gpio::Input::{split(), into_peripheral_output()}` when used with output pins. (#2418) +- `gpio::Output::peripheral_input()` (#2418) +- `{Uart, UartRx, UartTx}::apply_config()` (#2449) +- `{Uart, UartRx, UartTx}` now implement `embassy_embedded_hal::SetConfig` (#2449) +- GPIO ETM tasks and events now accept `InputSignal` and `OutputSignal` (#2427) +- `spi::master::Config` and `{Spi, SpiDma, SpiDmaBus}::apply_config` (#2448) +- `embassy_embedded_hal::SetConfig` is now implemented for `spi::master::{Spi, SpiDma, SpiDmaBus}`, `i2c::master::I2c` (#2448, #2477) +- `slave::Spi::{with_mosi(), with_miso(), with_sclk(), with_cs()}` functions (#2485) +- I8080: Added `set_8bits_order()` to set the byte order in 8-bit mode (#2487) +- `I2c::{apply_config(), with_sda(), with_scl()}` (#2477) +- ESP32-S2: Added missing GPIO alternate functions (#2512) + +### Changed + +- Peripheral type erasure for SPI (#2334) +- Peripheral type erasure for I2S (#2367) +- Peripheral type erasure for I2C (#2361) +- Peripheral type erasure for TWAI (#2359) +- The SPI driver has been rewritten to allow using half-duplex and full-duplex functionality on the same bus. See the migration guide for details. (#2373) +- Renamed `SpiDma` functions: `dma_transfer` to `transfer`, `dma_write` to `write`, `dma_read` to `read`. (#2373) +- Peripheral type erasure for UART (#2381) +- Changed listening for UART events (#2406) +- Circular DMA transfers now correctly error, `available` returns `Result` now (#2409) +- Interrupt listen/unlisten/clear functions now accept any type that converts into `EnumSet` (i.e. single interrupt flags). (#2442) +- SPI interrupt listening is now only available in Blocking mode. The `set_interrupt_handler` is available via `InterruptConfigurable` (#2442) +- Allow users to create DMA `Preparation`s (#2455) +- The `rmt::asynch::RxChannelAsync` and `rmt::asynch::TxChannelAsync` traits have been moved to `rmt` (#2430) +- Calling `AnyPin::output_signals` on an input-only pin (ESP32 GPIO 34-39) will now result in a panic. (#2418) +- UART configuration types have been moved to `esp_hal::uart` (#2449) +- `spi::master::Spi::new()` no longer takes `frequency` and `mode` as a parameter. (#2448) +- Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419) +- The I2S driver has been moved to `i2s::master` (#2472) +- `slave::Spi` constructors no longer take pins (#2485) +- The `I2c` master driver has been moved from `esp_hal::i2c` to `esp_hal::i2c::master`. (#2476) +- `I2c` SCL timeout is now defined in bus clock cycles. (#2477) +- Trying to send a single-shot RMT transmission will result in an error now, `RMT` deals with `u32` now, `PulseCode` is a convenience trait now (#2463) +- Removed `get_` prefixes from functions (#2528) +- The `Camera` and `I8080` drivers' constructors now only accepts blocking-mode DMA channels. (#2519) +- Many peripherals are now disabled by default and also get disabled when the driver is dropped (#2544) +- Updated embassy-time to v0.4 (#2701) +- Config: Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848) +- UART: `read_byte` and `write_byte` made private. (#2915) + +### Fixed + +- Fix conflict between `RtcClock::get_xtal_freq` and `Rtc::disable_rom_message_printing` (#2360) +- Fixed an issue where interrupts enabled before `esp_hal::init` were disabled. This issue caused the executor created by `#[esp_hal_embassy::main]` to behave incorrectly in multi-core applications. (#2377) +- Fixed `TWAI::transmit_async`: bus-off state is not reached when CANH and CANL are shorted. (#2421) +- ESP32: added UART-specific workaround for (#2441) +- Fixed some SysTimer race conditions and panics (#2451) +- TWAI: accept all messages by default (#2467) +- I8080: `set_byte_order()` now works correctly in 16-bit mode (#2487) +- ESP32-C6/ESP32-H2: Make higher LEDC frequencies work (#2520) + +### Removed + +- The `i2s::{I2sWrite, I2sWriteDma, I2sRead, I2sReadDma, I2sWriteDmaAsync, I2sReadDmaAsync}` traits have been removed. (#2316) +- The `ledc::ChannelHW` trait is no longer generic. (#2387) +- The `I2c::new_with_timeout` constructors have been removed (#2361) +- `I2c::new()` no longer takes `frequency` and pins as parameters. (#2477) +- The `spi::master::HalfDuplexReadWrite` trait has been removed. (#2373) +- The `Spi::with_pins` methods have been removed. (#2373) +- The `Spi::new_half_duplex` constructor have been removed. (#2373) +- The `HalfDuplexMode` and `FullDuplexMode` parameters have been removed from `Spi`. (#2373) +- Removed the output pin type parameter from `ledc::{Channel, ChannelIFace}` (#2388) +- Removed the output pin type parameter from `mcpwm::operator::{PwmPin, LinkedPins}` (#2388) +- Removed the output pin type parameter from `parl_io::{ClkOutPin, ClkInPin, RxClkInPin}` (#2388) +- Removed the valid pin type parameter from `parl_io::{TxPinConfigWithValidPin, RxPinConfigWithValidPin}` (#2388) +- Removed the pin type parameters from `parl_io::{TxOneBit, TxTwoBits, TxFourBits, TxEightBits, TxSixteenBits}` (#2388) +- Removed the pin type parameters from `parl_io::{RxOneBit, RxTwoBits, RxFourBits, RxEightBits, RxSixteenBits}` (#2388) +- Removed the pin type parameters from `lcd_cam::lcd::i8080::{TxEightBits, TxSixteenBits}` (#2388) +- Removed the pin type parameters from `lcd_cam::cam::{RxEightBits, RxSixteenBits}` (#2388) +- Most of the async-specific constructors (`new_async`, `new_async_no_transceiver`) have been removed. (#2430) +- The `configure_for_async` DMA functions have been removed (#2430) +- The `Uart::{change_baud, change_stop_bits}` functions have been removed (#2449) +- `gpio::{Input, Output, OutputOpenDrain, Flex, GpioPin}::{peripheral_input, into_peripheral_output}` have been removed. (#2418) +- The `GpioEtm` prefix has been removed from `gpio::etm` types (#2427) +- The `TimerEtm` prefix has been removed from `timer::timg::etm` types (#2427) +- The `SysTimerEtm` prefix has been removed from `timer::systimer::etm` types (#2427) +- The `GpioEtmEventRising`, `GpioEtmEventFalling`, `GpioEtmEventAny` types have been replaced with `Event` (#2427) +- The `TaskSet`, `TaskClear`, `TaskToggle` types have been replaced with `Task` (#2427) +- `{Spi, SpiDma, SpiDmaBus}` configuration methods (#2448) +- `Io::new_with_priority` and `Io::new_no_bind_interrupt`. (#2486) +- `parl_io::{no_clk_pin(), NoClkPin}` (#2531) +- Removed `get_core` function in favour of `Cpu::current` (#2533) +- Removed `uart::Config` setters and `symbol_length`. (#2847) + +## [0.21.1] + +### Fixed + +- Restored blocking `embedded_hal` compatibility for async I2C driver (#2343) +- I2c::transaction is now able to transmit data of arbitrary length (#2481) + +## [0.21.0] + +### Added + +- Introduce traits for the DMA buffer objects (#1976, #2213) +- Implement `embedded-hal` output pin traits for `NoPin` (#2019, #2133) +- Added `esp_hal::init` to simplify HAL initialisation (#1970, #1999) +- Added GpioPin::degrade to create ErasePins easily. Same for AnyPin by accident. (#2075) +- Added missing functions to `Flex`: `unlisten`, `is_interrupt_set`, `wakeup_enable`, `wait_for_high`, `wait_for_low`, `wait_for_rising_edge`, `wait_for_falling_edge`, `wait_for_any_edge`. (#2075) +- `Flex` now implements `Wait`. (#2075) +- Added sleep and wakeup support for esp32c2 (#1922) +- `Input`, `Output`, `OutputOpenDrain` and `Flex` now implement `Peripheral`. (#2094) +- Previously unavailable memory is available via `.dram2_uninit` section (#2079) +- You can now use `Input`, `Output`, `OutputOpenDrain` and `Flex` pins as EXTI and RTCIO wakeup sources (#2095) +- Added `Rtc::set_current_time` to allow setting RTC time, and `Rtc::current_time` to getting RTC time while taking into account boot time (#1883) +- Added APIs to allow connecting signals through the GPIO matrix. (#2128) +- Allow I8080 transfers to be cancelled on the spot (#2191) +- Implement `TryFrom` for `ledc::timer::config::Duty` (#1984) +- Expose `RtcClock::get_xtal_freq` and `RtcClock::get_slow_freq` publically for all chips (#2183) +- TWAI support for ESP32-H2 (#2199) +- Make `DmaDescriptor` methods public (#2237) +- Added a way to configure watchdogs in `esp_hal::init` (#2180) +- Introduce `DmaRxStreamBuf` (#2242) +- Implement `embedded_hal_async::delay::DelayNs` for `TIMGx` timers (#2084) +- Added `Efuse::read_bit` (#2259) +- Limited SPI slave support for ESP32 (Modes 1 and 3 only) (#2278) +- Added `Rtc::disable_rom_message_printing` (S3 and H2 only) (#2280) +- Added `esp_hal::time::{Duration, Instant}` (#2304) + +### Changed + +- Make saving and restoring SHA digest state an explicit operation (#2049) +- Reordered RX-TX pairs in all APIs to be consistent (#2074) +- Make saving and restoring SHA digest state an explicit operation (#2049) +- `Delay::new()` is now a `const` function (#1999) +- `Input`, `Output`, `OutputOpenDrain` and `Flex` are now type-erased by default. Use the new `new_typed` constructor to keep using the ZST pin types. (#2075) +- To avoid confusion with the `Rtc::current_time` wall clock time APIs, we've renamed `esp_hal::time::current_time` to `esp_hal::time::now`. (#2091) +- Renamed `touch::Continous` to `touch::Continuous`. (#2094) +- Faster SHA (#2112) +- The (previously undocumented) `ErasedPin` enum has been replaced with the `ErasedPin` struct. (#2094) +- Renamed and merged `Rtc::get_time_us` and `Rtc::get_time_ms` into `Rtc::time_since_boot` (#1883) +- ESP32: Added support for touch sensing on GPIO32 and 33 (#2109) +- Removed gpio pin generics from I8080 driver type. (#2171) +- I8080 driver now decides bus width at transfer time rather than construction time. (#2171) +- Migrate the I8080 driver to a move based API (#2191) +- Replaced `AnyPin` with `InputSignal` and `OutputSignal` and renamed `ErasedPin` to `AnyPin` (#2128) +- Replaced the `ErasedTimer` enum with the `AnyTimer` struct. (#2144) +- `Camera` and `AesDma` now support erasing the DMA channel type (#2258) +- Changed the parameters of `Spi::with_pins` to no longer be optional (#2133) +- Renamed `DummyPin` to `NoPin` and removed all internal logic from it. (#2133) +- The `NO_PIN` constant has been removed. (#2133) +- MSRV bump to 1.79 (#2156) +- Allow handling interrupts while trying to lock critical section on multi-core chips. (#2197) +- Migrate `Camera` to a move based API (#2242) +- Removed the PS-RAM related features, replaced by `quad-psram`/`octal-psram`, `init_psram` takes a configuration parameter, it's now possible to auto-detect PS-RAM size (#2178) +- `EspTwaiFrame` constructors now accept any type that converts into `esp_hal::twai::Id` (#2207) +- Change `DmaTxBuf` to support PSRAM on `esp32s3` (#2161) +- I2c `transaction` is now also available as a inherent function, lift size limit on `write`,`read` and `write_read` (#2262) +- SPI transactions are now cancelled if the transfer object (or async Future) is dropped. (#2216) +- The DMA channel types have been removed from peripherals (#2261) +- `I2C` driver renamed to `I2c` (#2320) +- The GPIO pins are now accessible via `Peripherals` and are no longer part of the `Io` struct (#2508) +- `dma::{ChannelRx, ChannelTx}` now have a `Mode` type parameter (#2519) + +### Fixed + +- SHA driver can now be safely used in multiple contexts concurrently (#2049) +- Fixed an issue with DMA transfers potentially not waking up the correct async task (#2065) +- Fixed an issue with LCD_CAM i8080 where it would send double the clocks in 16bit mode (#2085) +- Fix i2c embedded-hal transaction (#2028) +- Fix some inconsistencies in DMA interrupt bits (#2169) +- Fix SPI DMA alternating `write` and `read` for ESP32 and ESP32-S2 (#2131) +- Fix I2C ending up in a state when only re-creating the peripheral makes it useable again (#2141) +- Fix `SpiBus::transfer` transferring data twice in some cases (#2159) +- Fixed UART freezing when using `RcFast` clock source on ESP32-C2/C3 (#2170) +- I2S: on ESP32 and ESP32-S2 data is now output to the right (WS=1) channel first. (#2194) +- SPI: Fixed an issue where unexpected data was written outside of the read buffer (#2179) +- SPI: Fixed an issue where `wait` has returned before the DMA has finished writing the memory (#2179) +- SPI: Fixed an issue where repeated calls to `dma_transfer` may end up looping indefinitely (#2179) +- SPI: Fixed an issue that prevented correctly reading the first byte in a transaction (#2179) +- SPI: ESP32: Send address with correct data mode even when no data is sent. (#2231) +- SPI: ESP32: Allow using QSPI mode on SPI3. (#2245) +- PARL_IO: Fixed an issue that caused garbage to be output at the start of some requests (#2211) +- TWAI on ESP32 (#2207) +- TWAI should no longer panic when receiving a non-compliant frame (#2255) +- OneShotTimer: fixed `delay_nanos` behaviour (#2256) +- Fixed unsoundness around `Efuse` (#2259) +- Empty I2C writes to unknown addresses now correctly fail with `AckCheckFailed`. (#2506) + +### Removed + +- Removed `digest::Digest` implementation from SHA (#2049) +- Removed `NoPinType` in favour of `DummyPin`. (#2068) +- Removed the `async`, `embedded-hal-02`, `embedded-hal`, `embedded-io`, `embedded-io-async`, and `ufmt` features (#2070) +- Removed the `GpioN` type aliasses. Use `GpioPin` instead. (#2073) +- Removed `Peripherals::take`. Use `esp_hal::init` to obtain `Peripherals` (#1999) +- Removed `AnyInputOnlyPin` in favour of `AnyPin`. (#2071) +- Removed the following functions from `GpioPin`: `is_high`, `is_low`, `set_high`, `set_low`, `set_state`, `is_set_high`, `is_set_low`, `toggle`. (#2094) +- Removed `Rtc::get_time_raw` (#1883) +- Removed `_with_default_pins` UART constructors (#2132) +- Removed transfer methods `send`, `send_dma` and `send_dma_async` from `I8080` (#2191) +- Removed `uart::{DefaultRxPin, DefaultTxPin}` (#2132) +- Removed `PcntSource` and `PcntInputConfig`. (#2134) +- Removed the `place-spi-driver-in-ram` feature, this is now enabled via [esp-config](https://docs.rs/esp-config) (#2156) +- Removed `esp_hal::spi::slave::prelude` (#2260) +- Removed `esp_hal::spi::slave::WithDmaSpiN` traits (#2260) +- The `WithDmaAes` trait has been removed (#2261) +- The `I2s::new_i2s1` constructor has been removed (#2261) +- `Peripherals.GPIO` has been removed (#2508) + +## [0.20.1] - 2024-08-30 + +### Fixed + +- A build issue when including doc comment prelude (#2040) + +## [0.20.0] - 2024-08-29 + +### Added + +- Introduce DMA buffer objects (#1856, #1985) +- Added new `Io::new_no_bind_interrupt` constructor (#1861) +- Added touch pad support for esp32 (#1873, #1956) +- Allow configuration of period updating method for MCPWM timers (#1898) +- Add self-testing mode for TWAI peripheral. (#1929) +- Added a `PeripheralClockControl::reset` to the driver constructors where missing (#1893) +- Added `digest::Digest` implementation to SHA (#1908) +- Added `debugger::debugger_connected`. (#1961) +- DMA: don't require `Sealed` to implement `ReadBuffer` and `WriteBuffer` (#1921) +- Allow DMA to/from psram for esp32s3 (#1827) +- Added missing methods to `SpiDmaBus` (#2016) +- PARL_IO use ReadBuffer and WriteBuffer for Async DMA (#1996) + +### Changed + +- Peripheral driver constructors don't take `InterruptHandler`s anymore. Use `set_interrupt_handler` to explicitly set the interrupt handler now. (#1819) +- Migrate SPI driver to use DMA buffer objects (#1856, #1985) +- Use the peripheral ref pattern for `OneShotTimer` and `PeriodicTimer` (#1855) +- Improve SYSTIMER API (#1871) +- SHA driver now use specific structs for the hashing algorithm instead of a parameter. (#1908) +- Remove `fn free(self)` in HMAC which goes against esp-hal API guidelines (#1972) +- `AnyPin`, `AnyInputOnyPin` and `DummyPin` are now accessible from `gpio` module (#1918) +- Changed the RSA modular multiplication API to be consistent across devices (#2002) + +### Fixed + +- Improve error detection in the I2C driver (#1847) +- Fix I2S async-tx (#1833) +- Fix PARL_IO async-rx (#1851) +- SPI: Clear DMA interrupts before (not after) DMA starts (#1859) +- SPI: disable and re-enable MISO and MOSI in `start_transfer_dma`, `start_read_bytes_dma` and `start_write_bytes_dma` accordingly (#1894) +- TWAI: GPIO pins are not configured as input and output (#1906) +- ESP32C6: Make ADC usable after TRNG deinicialization (#1945) +- We should no longer generate 1GB .elf files for ESP32C2 and ESP32C3 (#1962) +- Reset peripherals in driver constructors where missing (#1893, #1961) +- Fixed ESP32-S2 systimer interrupts (#1979) +- Software interrupt 3 is no longer available when it is required by `esp-hal-embassy`. (#2011) +- ESP32: Fixed async RSA (#2002) + +### Removed + +- This package no longer re-exports the `esp_hal_procmacros::main` macro (#1828) +- The `AesFlavour` trait no longer has the `ENCRYPT_MODE`/`DECRYPT_MODE` associated constants (#1849) +- Removed `FlashSafeDma` (#1856) +- Remove redundant WithDmaSpi traits (#1975) +- `IsFullDuplex` and `IsHalfDuplex` traits (#1985) + +## [0.19.0] - 2024-07-15 + +### Added + +- uart: Added `with_cts`/`with_rts`s methods to configure CTS, and RTS pins (#1592) +- uart: Constructors now require TX and RX pins (#1592) +- uart: Added `Uart::new_with_default_pins` constructor (#1592) +- uart: Added `UartTx` and `UartRx` constructors (#1592) +- Add Flex / AnyFlex GPIO pin driver (#1659) +- Add new `DmaError::UnsupportedMemoryRegion` - used memory regions are checked when preparing a transfer now (#1670) +- Add DmaTransactionTxOwned, DmaTransactionRxOwned, DmaTransactionTxRxOwned, functions to do owning transfers added to SPI half-duplex (#1672) +- uart: Implement `embedded_io::ReadReady` for `Uart` and `UartRx` (#1702) +- ESP32-S3: Expose optional HSYNC input in LCD_CAM (#1707) +- ESP32-S3: Add async support to the LCD_CAM I8080 driver (#1834) +- ESP32-C6: Support lp-core as wake-up source (#1723) +- Add support for GPIO wake-up source (#1724) +- gpio: add DummyPin (#1769) +- dma: add Mem2Mem to support memory to memory transfer (#1738) +- Add `uart` wake source (#1727) +- `#[ram(persistent)]` option to replace the unsound `uninitialized` option (#1677) +- uart: Make `rx_timeout` optional in Config struct (#1759) +- Add interrupt related functions to `PeriodicTimer`/`OneShotTimer`, added `ErasedTimer` (#1753) +- Added blocking `read_bytes` method to `Uart` and `UartRx` (#1784) +- Add method to expose `InputPin::is_interrupt_set` in `Input` for use in interrupt handlers (#1829) + +### Fixed + +- ESP32-S3: Fix DMA waiting check in LCD_CAM (#1707) +- TIMG: Fix interrupt handler setup (#1714) +- Fix `sleep_light` for ESP32-C6 (#1720) +- ROM Functions: Fix address of `ets_update_cpu_frequency_rom` (#1722) +- Fix `regi2c_*` functions for `esp32h2` (#1737) +- Improved `#[ram(zeroed)]` soundness by adding a `bytemuck::Zeroable` type bound (#1677) +- ESP32-S2 / ESP32-S3: Fix UsbDm and UsbDp for Gpio19 and Gpio20 (#1764) +- Fix reading/writing small buffers via SPI master async dma (#1760) +- Remove unnecessary delay in rtc_ctnl (#1794) + +### Changed + +- Refactor `Dac1`/`Dac2` drivers into a single `Dac` driver (#1661) +- esp-hal-embassy: make executor code optional (but default) again (#1683) +- Improved interrupt latency on RISC-V based chips (#1679) +- `esp_wifi::initialize` no longer requires running maximum CPU clock, instead check it runs above 80MHz. (#1688) +- Move DMA descriptors from DMA Channel to each individual peripheral driver. (#1719) +- Allow users to easily name DMA channels (#1770) +- Support DMA chunk sizes other than the default 4092 (#1758) +- Improved interrupt latency on Xtensa based chips (#1735) +- Improve PCNT api (#1765) + +### Removed + +- uart: Removed `configure_pins` methods (#1592) +- Removed `DmaError::Exhausted` error by improving the implementation of the `pop` function (#1664) +- Unsound `#[ram(uninitialized)]` option in favor of the new `persistent` option (#1677) + +## [0.18.0] - 2024-06-04 + +### Added + +- i2c: implement `I2C:transaction` for `embedded-hal` and `embedded-hal-async` (#1505) +- spi: implement `with_bit_order` (#1537) +- ESP32-PICO-V3-02: Initial support (#1155) +- `time::current_time` API (#1503) +- ESP32-S3: Add LCD_CAM Camera driver (#1483) +- `embassy-usb` support (#1517) +- SPI Slave support for ESP32-S2 (#1562) +- Add new generic `OneShotTimer` and `PeriodicTimer` drivers, plus new `Timer` trait which is implemented for `TIMGx` and `SYSTIMER` (#1570) +- Feature: correct `TRNG` mechanism (#1804) + +### Fixed + +- i2c: i2c1_handler used I2C0 register block by mistake (#1487) +- Removed ESP32 specific code for resolutions > 16 bit in ledc embedded_hal::pwm max_duty_cycle function. (#1441) +- Fixed division by zero in ledc embedded_hal::pwm set_duty_cycle function and converted to set_duty_hw instead of set_duty to eliminate loss of granularity. (#1441) +- Embassy examples now build on stable (#1485) +- Fix delay on esp32h2 (#1535) +- spi: fix dma wrong mode when using eh1 blocking api (#1541) +- uart: make `uart::UartRx::read_byte` public (#1547) +- Fix async serial-usb-jtag (#1561) +- Feeding `RWDT` now actually works (#1645) + +### Changed + +- Removed unneeded generic parameters on `Usb` (#1469) +- Created virtual peripherals for CPU control and radio clocks, rather than splitting them from `SYSTEM` (#1428) +- `IO`, `ADC`, `DAC`, `RTC*`, `LEDC`, `PWM` and `PCNT` drivers have been converted to camel case format (#1473) +- RNG is no longer TRNG, the `CryptoRng` implementation has been removed. To track this being re-added see #1499 (#1498) +- Make software interrupts shareable (#1500) +- The `SystemParts` struct has been renamed to `SystemControl`, and now has a constructor which takes the `SYSTEM` peripheral (#1495) +- Timer abstraction: refactor `systimer` and `timer` modules into a common `timer` module (#1527) +- Removed the `embassy-executor-thread` and `embassy-executor-interrupt` features, they are now enabled by default when `embassy` is enabled. (#1485) +- Software interrupt 3 is now used instead of software interrupt 0 on the thread aware executor on multicore systems (#1485) +- Timer abstraction: refactor `systimer` and `timer` modules into a common `timer` module (#1527) +- Refactoring of GPIO module, have drivers for Input,Output,OutputOpenDrain, all drivers setup their GPIOs correctly (#1542) +- DMA transactions are now found in the `dma` module (#1550) +- Remove unnecessary generics from PARL_IO driver (#1545) +- Use `Level enum` in GPIO constructors instead of plain bools (#1574) +- rmt: make ChannelCreator public (#1597) + +### Removed + +- Removed the `SystemExt` trait (#1495) +- Removed the `GpioExt` trait (#1496) +- Embassy support (and all related features) has been removed, now available in the `esp-hal-embassy` package instead (#1595) + +## [0.17.0] - 2024-04-18 + +### Added + +- Add `ADC::read_blocking` to xtensa chips (#1293) +- ESP32-C6 / ESP32-H2: Implement `ETM` for general purpose timers (#1274) +- `interrupt::enable` now has a direct CPU enable counter part, `interrupt::enable_direct` (#1310) +- `Delay::delay(time: fugit::MicrosDurationU64)` (#1298) +- Added async support for TWAI (#1320) +- Add TWAI support for ESP32-C6 (#1323) +- `GpioPin::steal` unsafe API (#1363) +- Inherent implementions of GPIO pin `set_low`, `is_low`, etc. (#1284) +- Warn users when attempting to build using the `dev` profile (#1420) +- Async uart now reports interrupt errors(overflow, glitch, frame error, parity) back to user of read/write. uart clock decimal part configured for c2,c3,s3 (#1168, #1445) +- Add mechanism to configure UART source clock (#1416) +- `GpioPin` got a function `set_state(bool)` (#1462) +- Add definitions of external USB PHY peripheral I/O signals (#1463) +- Expose e-hal ErrorKind::NoAcknowledge in I2C driver (#1454) +- Add remaining peripheral signals for LCD_CAM (#1466) + +### Fixed + +- Reserve `esp32` ROM stacks to prevent the trashing of dram2 section (#1289) +- Fixing `esp-wifi` + `TRNG` issue on `ESP32-S2` (#1272) +- Fixed core1 startup using the wrong stack on the esp32 and esp32s3 (#1286) +- ESP32: Apply fix for Errata 3.6 in all the places necessary. (#1315) +- ESP32 & ESP32-S2: Fix I²C frequency (#1306) +- UART's TX/RX FIFOs are now cleared during initialization (#1344) +- Fixed `LCD_CAM I8080` driver potentially sending garbage to display (#1301) +- The TWAI driver can now be used without requiring the `embedded-hal` traits (#1355) +- USB pullup/pulldown now gets properly cleared and does not interfere anymore on esp32c3 and esp32s3 (#1244) +- Fixed GPIO counts so that using async code with the higher GPIO number should no longer panic (#1361, #1362) +- ESP32/ESP32-S2: Wait for I2S getting out of TX_IDLE when starting a transfer (#1375) +- Fixed writes to SPI not flushing before attempting to write, causing corrupted writes (#1381) +- fix AdcConfig::adc_calibrate for xtensa targets (#1379) +- Fixed a divide by zero panic when setting the LEDC duty cycle to 0 with `SetDutyCycle::set_duty_cycle` (#1403) +- Support 192 and 256-bit keys for AES (#1316) +- Fixed MCPWM DeadTimeCfg bit values (#1378) +- ESP32 LEDC `set_duty_cycle` used HighSpeedChannel for LowSpeedChannel (#1457) + +### Changed + +- TIMG: Allow use without the embedded-hal-02 traits in scope (#1367) +- DMA: use channel clusters (#1330) +- Remove `Ext32` and `RateExtU64` from prelude (#1298) +- Prefer mutable references over moving for DMA transactions (#1238) +- Support runtime interrupt binding, adapt GPIO driver (#1231) +- Renamed `eh1` feature to `embedded-hal`, feature-gated `embedded-hal@0.2.x` trait implementations (#1273) +- Enable `embedded-hal` feature by default, instead of the `embedded-hal-02` feature (#1313) +- `Uart` structs now take a `Mode` parameter which defines how the driver is initialized (#1294) +- `Rmt` can be created in async or blocking mode. The blocking constructor takes an optional interrupt handler argument. (#1341) +- All `Instance` traits are now sealed, and can no longer be implemented for arbitrary types (#1346) +- DMA channels can/have to be explicitly created for async or blocking drivers, added `set_interrupt_handler` to DMA channels, SPI, I2S, PARL_IO, don't enable interrupts on startup for DMA, I2S, PARL_IO, GPIO (#1300) +- UART: Rework `change_baud` so it is possible to set baud rate even after instantiation (#1350) +- Runtime ISR binding for SHA,ECC and RSA (#1354) +- Runtime ISR binding for I2C (#1376) +- `UsbSerialJtag` can be created in async or blocking mode. The blocking constructor takes an optional interrupt handler argument (#1377) +- SYSTIMER and TIMG instances can now be created in async or blocking mode (#1348) +- Runtime ISR binding for TWAI (#1384) +- ESP32-C6: The `gpio::lp_gpio` module has been renamed to `gpio::lp_io` to match the peripheral name (#1397) +- Runtime ISR binding for assist_debug (#1395) +- Runtime ISR binding for software interrupts, software interrupts are split now, interrupt-executor takes the software interrupt to use, interrupt-executor is easier to use (#1398) +- PCNT: Runtime ISR binding (#1396) +- Runtime ISR binding for RTC (#1405) +- Improve MCPWM DeadTimeCfg API (#1378) +- `SystemTimer`'s `Alarm` methods now require `&mut self` (#1455) + +### Removed + +- Remove package-level type exports (#1275) +- Removed `direct-vectoring` & `interrupt-preemption` features, as they are now enabled by default (#1310) +- Removed the `rt` and `vectored` features (#1380) +- Remove partial support for the ESP32-P4 (#1461) + +## [0.16.1] - 2024-03-12 + +### Fixed + +- Resolved an issue with the `defmt` dependency/feature (#1264) + +### Changed + +- Use ROM `memcpy` over compiler builtins (#1255) +- Do not ensure randomness or implement the `CryptoRng` trait for ESP32-P4/S2 (#1267) + +## [0.16.0] - 2024-03-08 + +### Added + +- Add initial support for the ESP32-P4 (#1101) +- Implement `embedded_hal::pwm::SetDutyCycle` trait for `ledc::channel::Channel` (#1097) +- ESP32-P4: Add initial GPIO support (#1109) +- ESP32-P4: Add initial support for interrupts (#1112) +- ESP32-P4: Add efuse reading support (#1114) +- ESP32-S3: Added LCD_CAM I8080 driver (#1086) +- Allow for splitting of the USB Serial JTAG peripheral into tx/rx components (#1024) +- `RngCore` trait is implemented (#1122) +- Support Rust's `stack-protector` feature (#1135) +- Adding clock support for `ESP32-P4` (#1145) +- Implementation OutputPin and InputPin for AnyPin (#1067) +- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174) +- A way to push into I2S DMA buffer via a closure (#1189) +- Added basic `LP-I2C` driver for C6 (#1185) +- Ensuring that the random number generator is TRNG. (#1200) +- ESP32-C6: Add timer wakeup source for deepsleep (#1201) +- Introduce `InterruptExecutor::spawner()` (#1211) +- Add `InterruptHandler` struct, which couples interrupt handlers and their priority together (#1299) + +### Fixed + +- Fix embassy-time tick rate not set when using systick as the embassy timebase (#1124) +- Fix `get_raw_core` on Xtensa (#1126) +- Fix docs.rs documentation builds (#1129) +- Fix circular DMA (#1144) +- Fix `hello_rgb` example for ESP32 (#1173) +- Fixed the multicore critical section on Xtensa (#1175) +- Fix timer `now` for esp32c3 and esp32c6 (#1178) +- Wait for registers to get synced before reading the timer count for all chips (#1183) +- Fix I2C error handling (#1184) +- Fix circular DMA (#1189) +- Fix esp32c3 uart initialization (#1156) +- Fix ESP32-S2 I2C read (#1214) +- Reset/init UART if it's not the console UART (#1213) + +### Changed + +- DmaDescriptor struct to better model the hardware (#1054) +- DMA descriptor count no longer needs to be multiplied by 3 (#1054) +- RMT channels no longer take the channel number as a generic param (#959) +- The `esp-hal-common` package is now called `esp-hal` (#1131) +- Refactor the `Trace` driver to be generic around its peripheral (#1140) +- Auto detect crystal frequency based on `RtcClock::estimate_xtal_frequency()` (#1165) +- ESP32-S3: Configure 32k ICACHE (#1169) +- Lift the minimal buffer size requirement for I2S (#1189) +- Replaced `SystemTimer::TICKS_PER_SEC` with `SystemTimer::ticks_per_sec()` (#1981) +- `ADC` and `DAC` drivers now take virtual peripherals in their constructors, instead of splitting `APB_SARADC`/`SENS` (#1100) +- The `DAC` driver's constructor is now `new` instead of `dac`, to be more consistent with other APIs (#1100) +- The DMA peripheral is now called `Dma` for devices with both PDMA and GDMA controllers (#1125) +- The `ADC` driver's constructor is now `new` instead of `adc`, to be more consistent with other APIs (#1133) +- `embassy-executor`'s `integrated-timers` is no longer enabled by default. (#1196) +- Renamed `embassy-time-systick` to `embassy-time-systick-16mhz` for use with all chips with a systimer, except `esp32s2`. Added `embassy-time-systick-80mhz` specifically for the `esp32s2`. (#1247) + +### Removed + +- Remove `xtal-26mhz` and `xtal-40mhz` features (#1165) +- All chip-specific HAL packages have been removed (#1196) + +## [0.15.0] - 2024-01-19 + +### Added + +- ESP32-C6: Properly initialize PMU (#974) +- Implement overriding base mac address (#1044) +- Add `rt-riscv` and `rt-xtensa` features to enable/disable runtime support (#1057) +- ESP32-C6: Implement deep sleep (#918) +- Add `embedded-io` feature to each chip-specific HAL (#1072) +- Add `embassy-time-driver` to `esp-hal-common` due to updating `embassy-time` to `v0.3.0` (#1075) +- ESP32-S3: Added support for 80Mhz PSRAM (#1069) +- ESP32-C3/S3: Add workaround for USB pin exchange on usb-serial-jtag (#1104) +- ESP32C6: Added LP_UART initialization (#1113) +- Add `place-spi-driver-in-ram` feature to `esp-hal-common` (#1096) + +### Changed + +- Set up interrupts for the DMA and async enabled peripherals only when `async` feature is provided (#1042) +- Update to `1.0.0` releases of the `embedded-hal-*` packages (#1068) +- Update `embassy-time` to `0.3.0` and embassy-executor to `0.5.0` release due to the release of the `embedded-hal-*` packages (#1075) +- No longer depend on `embassy-time` (#1092) +- Update to latest `smart-leds-trait` and `smart-leds` packages (#1094) +- Unify the low-power peripheral names (`RTC_CNTL` and `LP_CLKRST` to `LPWR`) (#1064) + +### Fixed + +- ESP32: correct gpio 32/33 in errata36() (#1053) +- ESP32: make gpio 4 usable as analog pin (#1078) +- Fix double &mut for the `SetDutyCycle` impl on `PwmPin` (#1033) +- ESP32/ESP32-S3: Fix stack-top calculation for app-core (#1081) +- ESP32/ESP32-S2/ESP32-S3: Fix embassy-time-timg0 driver (#1091) +- ESP32: ADC readings are no longer inverted (#1093) + +## [0.14.1] - 2023-12-13 + +### Fixed + +- Fix SHA for all targets (#1021) + +## [0.14.0] - 2023-12-12 + +### Added + +- ESP32-C6: LP core clock is configurable (#907) +- Derive `Clone` and `Copy` for `EspTwaiFrame` (#914) +- A way to configure inverted pins (#912) +- Added API to check a GPIO-pin's interrupt status bit (#929) +- A `embedded_io_async::Read` implementation for `UsbSerialJtag` (#889) +- `RtcClock::get_xtal_freq`, `RtcClock::get_slow_freq` (#957) +- Added Rx Timeout functionality to async Uart (#911) +- RISC-V: Thread-mode and interrupt-mode executors, `#[main]` macro (#947) +- A macro to make it easier to create DMA buffers and descriptors (#935) +- I2C timeout is configurable (#1011) +- ESP32-C6/ESP32-H2: `flip-link` feature gives zero-cost stack overflow protection (#1008) + +### Changed + +- Improve DMA documentation & clean up module (#915) +- Only allow a single version of `esp-hal-common` to be present in an application (#934) +- ESP32-C3/C6 and ESP32-H2 can now use the `zero-rtc-bss` feature to enable `esp-hal-common/rv-zero-rtc-bss` (#867) +- Reuse `ieee802154_clock_enable/disable()` functions for BLE and rename `ble_ieee802154_clock_enable()` (#953) +- The `embedded-io` trait implementations are now gated behind the `embedded-io` feature (#964) +- Simplifed RMT channels and channel creators (#958) +- Reworked construction of I2S driver instances (#983) +- ESP32-S2/S3: Don't require GPIO 18 to create a USB peripheral driver instance (#990) +- Updated to latest release candidate (`1.0.0-rc.2`) for `embedded-hal{-async,-nb}` (#994) +- Explicit panic when hitting the `DefaultHandler` (#1005) +- Relevant interrupts are now auto enabled in `embassy::init` (#1014) +- `Spi::new`/`Spi::new_half_duplex` takes no gpio pin now, instead you need to call `with_pins` to setup those (#901) +- ESP32-C2, ESP32-C3, ESP32-S2: atomic emulation trap has been removed. (#904, #985) + +### Fixed + +- ESP32-C2/C3 examples: fix build error (#899) +- ESP32-S3: Fix GPIO interrupt handler crashing when using GPIO48. (#898) +- Fixed short wait times in embassy causing hangs (#906) +- Make sure to clear LP/RTC RAM before loading code (#916) +- Async RMT channels can be used concurrently (#925) +- Xtensa: Allow using `embassy-executor`'s thread-mode executor if neither `embassy-executor-thread`, nor `embassy-executor-interrupt` is enabled. (#937) +- Uart Async: Improve interrupt handling and irq <--> future communication (#977) +- RISC-V: Fix stack allocation (#988) +- ESP32-C6: Fix used RAM (#997) +- ESP32-H2: Fix used RAM (#1003) +- Fix SPI slave DMA dma_read and dma_write (#1013) +- ESP32-C6/H2: Fix disabling of interrupts (#1040) + +### Removed + +- Direct boot support has been removed (#903) +- Removed the `mcu-boot` feature from `esp32c3-hal` (#938) +- Removed SpiBusController and SpiBusDevice in favour of embedded-hal-bus and embassy-embedded-hal implementations. (#978) + +## [0.13.1] - 2023-11-02 + +### Fixed + +- ESP32-C3: Make sure BLE and WiFi are not powered down when esp-wifi needs them (#891) +- ESP32-C6/H2: Fix setting UART baud rate (#893) + +## [0.13.0] - 2023-10-31 + +### Added + +- Implement SetFrequencyCycle and PwmPin from embedded_hal for PwmPin of MCPWM. (#880) +- Added `embassy-time-systick` to ESP32-S2 (#827) +- Implement enabling/disabling BLE clock on ESP32-C6 (#784) +- Async support for RMT (#787) +- Implement `defmt::Format` for more types (#786) +- Add new_no_miso to Spi FullDuplexMode (#794) +- Add UART support for splitting into TX and RX (#754) +- Async support for I2S (#801) +- Async support for PARL_IO (#807) +- ETM driver, GPIO ETM (#819) +- (G)DMA AES support (#821) +- SYSTIMER ETM functionality (#828) +- Adding async support for RSA peripheral(doesn't work properly for `esp32` chip - issue will be created) (#790) +- Added sleep support for ESP32-C3 with timer and GPIO wakeups (#795) +- Support for ULP-RISCV including Delay and GPIO (#840, #845) +- Add bare-bones SPI slave support, DMA only (#580, #843) +- Embassy `#[main]` convenience macro (#841) +- Add a `defmt` feature to the `esp-hal-smartled` package (#846) +- Support 16MB octal PS-RAM for ESP32-S3 (#858) +- RISCV TRACE Encoder driver for ESP32-C6 / ESP32-H2 (#864) +- `embedded_hal` 1 `InputPin` and `embedded_hal_async` `Wait` impls for open drain outputs (#905) + +### Changed + +- Bumped MSRV to 1.67 (#798) +- Optimised multi-core critical section implementation (#797) +- Changed linear- and curve-calibrated ADC to provide readings in mV (#836) +- `Uart::new` now takes the `&Clocks` struct to ensure baudrate is correct for CPU/APB speed. (#808) +- `Uart::new_with_config` takes an `Config` instead of `Option`. (#808) +- `Alarm::set_period` takes a period (duration) instead of a frequency (#812) +- `Alarm::interrupt_clear` is now `Alarm::clear_interrupt` to be consistent (#812) +- The `PeripheralClockControl` struct is no longer public, drivers no longer take this as a parameter (#817) +- Unify the system peripheral, `SYSTEM`, `DPORT` and `PCR` are now all exposed as `SYSTEM` (#832) +- Unified the ESP32's and ESP32-C2's xtal frequency features (#831) +- Replace any underscores in feature names with dashes (#833) +- The `spi` and `spi_slave` modules have been refactored into the `spi`, `spi::master`, and `spi::slave` modules (#843) +- The `WithDmaSpi2`/`WithDmaSpi3` structs are no longer generic around the inner peripheral type (#853) +- The `SarAdcExt`/`SensExt` traits are now collectively named `AnalogExt` instead (#857) +- Replace the `radio` module with peripheral singleton structs (#852) +- The SPI traits are no longer re-exported in the main prelude, but from preludes in `spi::master`/`spi::slave` instead (#860) +- The `embedded-hal-1` and `embedded-hal-async` traits are no longer re-exported in the prelude (#860) + +### Fixed + +- S3: Allow powering down RC_FAST_CLK (#796) +- UART/ESP32: fix calculating FIFO counter with `get_rx_fifo_count()` (#804) +- Xtensa targets: Use ESP32Reset - not Reset (#823) +- Examples should now work with the `defmt` feature (#810) +- Fixed a race condition causing SpiDma to stop working unexpectedly (#869) +- Fixed async uart serial, and updated the embassy_serial examples (#871) +- Fix ESP32-S3 direct-boot (#873) +- Fix ESP32-C6 ADC (#876) +- Fix ADC Calibration not being used on ESP32-S2 and ESP32-S3 (#1000) + +### Removed + +- `Pin::is_pcore_interrupt_set` (#793) +- `Pin::is_pcore_non_maskable_interrupt_set` (#793) +- `Pin::is_acore_interrupt_set` (#793) +- `Pin::is_acore_non_maskable_interrupt_set` (#793) +- `Pin::enable_hold` (#793) +- Removed the generic return type for ADC reads (#792) + +## [0.12.0] - 2023-09-05 + +### Added + +- Implement RTCIO pullup, pulldown and hold control for Xtensa MCUs (#684) +- S3: Implement RTCIO wakeup source (#690) +- Add PARL_IO driver for ESP32-C6 / ESP32-H2 (#733, #760) +- Implement `ufmt_write::uWrite` trait for USB Serial JTAG (#751) +- Add HMAC peripheral support (#755) +- Add multicore-aware embassy executor for Xtensa MCUs (#723, #756) +- Add interrupt-executor for Xtensa MCUs (#723, #756) +- Add missing `Into>` conversion (#764) +- Updated `clock` module documentation (#774) +- Add `log` feature to enable log output (#773) +- Add `defmt` feature to enable log output (#773) +- A new macro to load LP core code on ESP32-C6 (#779) +- Add `ECC`` peripheral driver (#785) +- Initial LLD support for Xtensa chips (#861) + +### Changed + +- Update the `embedded-hal-*` packages to `1.0.0-rc.1` and implement traits from `embedded-io` and `embedded-io-async` (#747) +- Moved AlignmentHelper to its own module (#753) +- Disable all watchdog timers by default at startup (#763) +- `log` crate is now opt-in (#773) +- `CpuControl::start_app_core()` now takes an `FnOnce` closure (#739) + +### Fixed + +- Fix `psram` availability lookup in `esp-hal-common` build script (#718) +- Fix wrong `dram_seg` length in `esp32s2-hal` linker script (#732) +- Fix setting alarm when a timer group is used as the alarm source. (#730) +- Fix `Instant::now()` not counting in some cases when using TIMG0 as the timebase (#737) +- Fix number of ADC attenuations for ESP32-C6 (#771) +- Fix SHA registers access (#805) + +## [0.11.0] - 2023-08-10 + +### Added + +- Add initial LP-IO support for ESP32-C6 (#639) +- Implement sleep with some wakeup methods for `esp32` (#574) +- Add a new RMT driver (#653, #667, #695) +- Implemented calibrated ADC API for ESP32-S3 (#641) +- Add MCPWM DeadTime configuration (#406) +- Implement sleep with some wakeup methods for `esp32-s3` (#660, #689, #696) +- Add feature enabling directly hooking the interrupt vector table (#621) +- Add `ClockControl::max` helper for all chips (#701) +- Added module-level documentation for all peripherals (#680) +- Implement sleep with some wakeup methods for `esp32-s3` (#660) +- Add `FlashSafeDma` wrapper for eh traits which ensure correct DMA transfer from source data in flash (ROM) (#678) + +### Changed + +- Update `embedded-hal-*` alpha packages to their latest versions (#640) +- Implement the `Clone` and `Copy` traits for the `Rng` driver (#650) +- Use all remaining memory as core-0's stack (#716) +- `DmaTransfer::wait` and `I2sReadDmaTransfer::wait_receive` now return `Result` (#665) +- `gpio::Pin` is now object-safe (#687) + +### Fixed + +- Fixed Async Uart `read` when `set_at_cmd` is not used (#652) +- USB device support is working again (#656) +- Add missing interrupt status read for esp32s3, which fixes USB-SERIAL-JTAG interrupts (#664) +- GPIO interrupt status bits are now properly cleared (#670) +- Increase frequency resolution in `set_periodic` (#686) +- Fixed ESP32-S2, ESP32-S3, ESP32-C2, ESP32-C3 radio clock gating (#679, #681) +- Partially fix ESP32 radio clocks (#709) +- Fixed "ESP32/ESP32-S2 RMT transmission with with data.len() > RMT_CHANNEL_RAM_SIZE results in TransmissionError" #707 (#710) + +### Removed + +- Remove the `allow-opt-level-z` feature from `esp32c3-hal` (#654) +- Remove the old `pulse_control` driver (#694) + +## [0.10.0] - 2023-06-04 + +### Added + +- Add `WithDmaSpi3` to prelude for ESP32S3 (#623) +- Add bare-bones PSRAM support for ESP32 (#506) +- Add initial support for the ESP32-H2 (#513, #526, #527, #528, #530, #538, #544, #548, #551, #556, #560, #566, #549, #564, #569, #576, #577, #589, #591, #597) +- Add bare-bones PSRAM support for ESP32-S3 (#517) +- Add async support to the I2C driver (#519) +- Implement Copy and Eq for EspTwaiError (#540) +- Add LEDC hardware fade support (#475) +- Added support for multicore async GPIO (#542) +- Add a fn to poll DMA transfers (#559) +- Add unified field-based efuse access (#567) +- Move `esp-riscv-rt` into esp-hal (#578) +- Add CRC functions from ESP ROM (#587) +- Add a `debug` feature to enable the PACs' `impl-register-debug` feature (#596) +- Add initial support for `I2S` in ESP32-H2 (#597) +- Add octal PSRAM support for ESP32-S3 (#610) +- Add MD5 functions from ESP ROM (#618) +- Add embassy async `read` support for `uart` (#620) +- Add bare-bones support to run code on ULP-RISCV / LP core (#631) +- Add ADC calibration implementation for a riscv chips (#555) +- Add `async` implementation for `USB Serial/JTAG` (#632) + +### Changed + +- Simplify the `Delay` driver, derive `Clone` and `Copy` (#568) +- DMA types can no longer be constructed by the user (#625) +- Move core interrupt handling from Flash to RAM for RISC-V chips (ESP32-H2, ESP32-C2, ESP32-C3, ESP32-C6) (#541) +- Change LED pin to GPIO2 in ESP32 blinky example (#581) +- Update ESP32-H2 and ESP32-C6 clocks and remove `i2c_clock` for all chips but ESP32 (#592) +- Use both timers in `TIMG0` for embassy time driver when able (#609) +- Re-work `RadioExt` implementations, add support for ESP32-H2 (#627) +- Improve examples documentation (#533) +- esp32h2-hal: added README (#585) +- Update `esp-hal-procmacros` package dependencies and features (#628) +- Simplified user-facing SpiDma and I2s types (#626) +- Significantly simplified user-facing GPIO pin types. (#553) +- No longer re-export the `soc` module and the contents of the `interrupt` module at the package level (#607) + +### Fixed + +- Corrected the expected DMA descriptor counts (#622, #625) +- DMA is supported for SPI3 on ESP32-S3 (#507) +- `change_bus_frequency` is now available on `SpiDma` (#529) +- Fixed a bug where a GPIO interrupt could erroneously fire again causing the next `await` on that pin to instantly return `Poll::Ok` (#537) +- Set `vecbase` on core 1 (ESP32, ESP32-S3) (#536) +- ESP32-S3: Move PSRAM related function to RAM (#546) +- ADC driver will now apply attenuation values to the correct ADC's channels. (#554) +- Sometimes half-duplex non-DMA SPI reads were reading garbage in non-release mode (#552) +- ESP32-C3: Fix GPIO5 ADC channel id (#562) +- ESP32-H2: Fix direct-boot feature (#570) +- Fix Async GPIO not disabling interrupts on chips with multiple banks (#572) +- ESP32-C6: Support FOSC CLK calibration for ECO1+ chip revisions (#593) +- Fixed CI by pinning the log crate to 0.4.18 (#600) +- ESP32-S3: Fix calculation of PSRAM start address (#601) +- Fixed wrong variable access (FOSC CLK calibration for ESP32-C6) (#593) +- Fixed [trap location in ram](https://github.com/esp-rs/esp-hal/pull/605#issuecomment-1604039683) (#605) +- Fix rom::crc docs (#611) +- Fixed a possible overlap of `.data` and `.rwtext` (#616) +- Avoid SDA/SCL being low while configuring pins for I2C (#619) + +## [0.9.0] - 2023-05-02 + +### Added + +- Add bare-bones PSRAM support for ESP32-S2 (#493) +- Add `DEBUG_ASSIST` functionality (#484) +- Add RSA peripheral support (#467) +- Add PeripheralClockControl argument to `timg`, `wdt`, `sha`, `usb-serial-jtag` and `uart` constructors (#463) +- Added API to raise and reset software interrupts (#426) +- Implement `embedded_hal_nb::serial::*` traits for `UsbSerialJtag` (#498) + +### Fixed + +- Fix `get_wakeup_cause` comparison error (#472) +- Use 192 as mclk_multiple for 24-bit I2S (#471) +- Fix `CpuControl::start_app_core` signature (#466) +- Move `rwtext` after other RAM data sections (#464) +- ESP32-C3: Disable `usb_pad_enable` when setting GPIO18/19 to input/output (#461) +- Fix 802.15.4 clock enabling (ESP32-C6) (#458) +- ESP32-S3: Disable usb_pad_enable when setting GPIO19/20 to input/output (#645) + +### Changed + +- Update `embedded-hal-async` and `embassy-*` dependencies (#488) +- Update to `embedded-hal@1.0.0-alpha.10` and `embedded-hal-nb@1.0.0-alpha.2` (#487) +- Let users configure the LEDC output pin as open-drain (#474) +- Use bitflags to decode wakeup cause (#473) +- Minor linker script additions (#470) +- Minor documentation improvements (#460) + +### Removed + +- Remove unnecessary generic from `UsbSerialJtag` driver (#492) +- Remove `#[doc(inline)]` from esp-hal-common re-exports (#490) + +## [0.8.0] - 2023-03-27 + +## [0.7.1] - 2023-02-22 + +## [0.7.0] - 2023-02-21 + +## [0.5.0] - 2023-01-26 + +## [0.4.0] - 2022-12-12 + +## [0.3.0] - 2022-11-17 + +## [0.2.0] - 2022-09-13 + +## [0.1.0] - 2022-08-05 + +[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0 +[0.2.0]: https://github.com/esp-rs/esp-hal/compare/v0.1.0...v0.2.0 +[0.3.0]: https://github.com/esp-rs/esp-hal/compare/v0.2.0...v0.3.0 +[0.4.0]: https://github.com/esp-rs/esp-hal/compare/v0.3.0...v0.4.0 +[0.5.0]: https://github.com/esp-rs/esp-hal/compare/v0.4.0...v0.5.0 +[0.7.0]: https://github.com/esp-rs/esp-hal/compare/v0.5.0...v0.7.0 +[0.7.1]: https://github.com/esp-rs/esp-hal/compare/v0.7.0...v0.7.1 +[0.8.0]: https://github.com/esp-rs/esp-hal/compare/v0.7.1...v0.8.0 +[0.9.0]: https://github.com/esp-rs/esp-hal/compare/v0.8.0...v0.9.0 +[0.10.0]: https://github.com/esp-rs/esp-hal/compare/v0.9.0...v0.10.0 +[0.11.0]: https://github.com/esp-rs/esp-hal/compare/v0.10.0...v0.11.0 +[0.12.0]: https://github.com/esp-rs/esp-hal/compare/v0.11.0...v0.12.0 +[0.13.0]: https://github.com/esp-rs/esp-hal/compare/v0.12.0...v0.13.0 +[0.13.1]: https://github.com/esp-rs/esp-hal/compare/v0.13.0...v0.13.1 +[0.14.0]: https://github.com/esp-rs/esp-hal/compare/v0.13.1...v0.14.0 +[0.14.1]: https://github.com/esp-rs/esp-hal/compare/v0.14.0...v0.14.1 +[0.15.0]: https://github.com/esp-rs/esp-hal/compare/v0.14.1...v0.15.0 +[0.16.0]: https://github.com/esp-rs/esp-hal/compare/v0.15.0...v0.16.0 +[0.16.1]: https://github.com/esp-rs/esp-hal/compare/v0.16.0...v0.16.1 +[0.17.0]: https://github.com/esp-rs/esp-hal/compare/v0.16.1...v0.17.0 +[0.18.0]: https://github.com/esp-rs/esp-hal/compare/v0.17.0...v0.18.0 +[0.19.0]: https://github.com/esp-rs/esp-hal/compare/v0.18.0...v0.19.0 +[0.20.0]: https://github.com/esp-rs/esp-hal/compare/v0.19.0...v0.20.0 +[0.20.1]: https://github.com/esp-rs/esp-hal/compare/v0.20.0...v0.20.1 +[0.21.0]: https://github.com/esp-rs/esp-hal/compare/v0.20.1...v0.21.0 +[0.21.1]: https://github.com/esp-rs/esp-hal/compare/v0.21.0...v0.21.1 +[0.22.0]: https://github.com/esp-rs/esp-hal/compare/v0.21.1...v0.22.0 +[0.23.0]: https://github.com/esp-rs/esp-hal/compare/v0.22.0...v0.23.0 +[0.23.1]: https://github.com/esp-rs/esp-hal/compare/v0.23.0...v0.23.1 +[v1.0.0-beta.0]: https://github.com/esp-rs/esp-hal/compare/v0.23.1...esp-hal-v1.0.0-beta.0 +[v1.0.0-beta.1]: https://github.com/esp-rs/esp-hal/compare/esp-hal-v1.0.0-beta.0...esp-hal-v1.0.0-beta.1 +[v1.0.0-rc.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-v1.0.0-beta.1...esp-hal-v1.0.0-rc.0 +[v1.0.0-rc.1]: https://github.com/esp-rs/esp-hal/compare/esp-hal-v1.0.0-rc.0...esp-hal-v1.0.0-rc.1 +[v1.0.0]: https://github.com/esp-rs/esp-hal/compare/esp-hal-v1.0.0-rc.1...esp-hal-v1.0.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-hal-v1.0.0...HEAD diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml new file mode 100644 index 00000000000..7cdf4783e07 --- /dev/null +++ b/esp-hal/Cargo.toml @@ -0,0 +1,328 @@ +[package] +name = "esp-hal" +version = "1.0.0" +edition = "2024" +rust-version = "1.88.0" +description = "Bare-metal HAL for Espressif devices" +documentation = "https://docs.espressif.com/projects/rust/esp-hal/latest/" +keywords = ["embedded", "embedded-hal", "esp32", "espressif", "hal"] +categories = ["embedded", "hardware-support", "no-std"] +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" +exclude = [ "api-baseline", "MIGRATING-*", "CHANGELOG.md" ] + +[package.metadata.espressif] +semver-checked = true +doc-config = { features = ["unstable", "rt"], append = [ + { if = 'chip_has("psram")', features = ["psram"] }, + { if = 'chip_has("usb_otg_driver_supported")', features = ["__usb_otg"] }, + { if = 'chip_has("bt_driver_supported")', features = ["__bluetooth"] }, +] } +check-configs = [ + { features = [] }, + { features = ["rt"] }, + { features = ["unstable", "rt"] }, + { features = ["unstable", "rt", "psram"], if = 'chip_has("psram")' }, + { features = ["unstable", "rt", "__usb_otg"], if = 'chip_has("usb_otg_driver_supported")' }, + { features = ["unstable", "rt", "__bluetooth"], if = 'chip_has("bt_driver_supported")' }, +] +# Prefer fewer, but more complex clippy rules. A clippy run should cover as much code as possible. +clippy-configs = [ + { features = ["unstable", "rt"], append = [ + { if = 'chip_has("psram")', features = ["psram"] }, + { if = 'chip_has("usb_otg_driver_supported")', features = ["__usb_otg"] }, + { if = 'chip_has("bt_driver_supported")', features = ["__bluetooth"] }, + ] }, +] + +[package.metadata.docs.rs] +default-target = "riscv32imac-unknown-none-elf" +features = ["esp32c6", "unstable"] +rustdoc-args = ["--cfg", "docsrs"] + +[lib] +bench = false +test = false + +[dependencies] +bitflags = "2.9" +bytemuck = "1.24" +cfg-if = "1" +critical-section = { version = "1", features = ["restore-state-u32"], optional = true } +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +enumset = "1.1" +paste = "1.0.15" +portable-atomic = { version = "1.11", default-features = false } + +esp-rom-sys = { version = "0.1.3", path = "../esp-rom-sys" } + +# Unstable dependencies that are not (strictly) part of the public API +bitfield = "0.19" +delegate = "0.13" +document-features = "0.2" +embassy-futures = "0.1" +embassy-sync = "0.7" +fugit = "0.3.7" +instability = "0.3.9" +strum = { version = "0.27.1", default-features = false, features = ["derive"] } + +esp-config = { version = "0.6.1", path = "../esp-config" } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } +esp-sync = { version = "0.1.1", path = "../esp-sync" } +procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } + +# Dependencies that are optional because they are used by unstable drivers. +# They are needed when using the `unstable` feature. +digest = { version = "0.10.7", default-features = false, features = ["core-api"], optional = true } +embassy-usb-driver = { version = "0.2", optional = true } +embassy-usb-synopsys-otg = { version = "0.3", optional = true } +embedded-can = { version = "0.4.1", optional = true } +esp-synopsys-usb-otg = { version = "0.4.2", optional = true } +nb = { version = "1.1", optional = true } + +# Logging interfaces, they are mutually exclusive so they need to be behind separate features. +defmt = { version = "1.0.1", optional = true } +log-04 = { package = "log", version = "0.4", optional = true } + +# ESP32-only fallback SHA algorithms +sha1 = { version = "0.10", default-features = false, optional = true } +sha2 = { version = "0.10", default-features = false, optional = true } + +# Optional dependencies that enable ecosystem support. +# We could support individually enabling them, but there is no big downside to just +# enabling them all via the `unstable` feature. +embassy-embedded-hal = { version = "0.5", optional = true } +embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true } +embedded-io-async-06 = { package = "embedded-io-async", version = "0.6", optional = true } +embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } +embedded-io-async-07 = { package = "embedded-io-async", version = "0.7", optional = true } +rand_core-06 = { package = "rand_core", version = "0.6", optional = true } +rand_core-09 = { package = "rand_core", version = "0.9", optional = true } +ufmt-write = { version = "0.1", optional = true } + +# IMPORTANT: +# Each supported device MUST have its PAC included below along with a +# corresponding feature. +esp32 = { version = "0.39", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32c2 = { version = "0.28", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32c3 = { version = "0.31", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32c5 = { version = "0.1", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32c6 = { version = "0.22", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32h2 = { version = "0.18", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32s2 = { version = "0.30", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } +esp32s3 = { version = "0.34", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "b6e370c" } + +[target.'cfg(target_arch = "riscv32")'.dependencies] +riscv = { version = "0.15.0" } +esp-riscv-rt = { version = "0.13.0", path = "../esp-riscv-rt", optional = true } + +[target.'cfg(target_arch = "xtensa")'.dependencies] +xtensa-lx = { version = "0.13.0", path = "../xtensa-lx" } +xtensa-lx-rt = { version = "0.21.0", path = "../xtensa-lx-rt", optional = true } + +[build-dependencies] +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"] } +esp-config = { version = "0.6.1", path = "../esp-config", features = ["build"] } + +[dev-dependencies] +crypto-bigint = { version = "0.5.5", default-features = false } +jiff = { version = "0.2", default-features = false, features = ["static"] } + +[features] +default = ["rt", "exception-handler", "float-save-restore"] + +# These features are considered private and unstable. They are not covered by +# semver guarantees and may change or be removed without notice. +__bluetooth = [] +__usb_otg = [ + "dep:embassy-usb-driver", + "dep:embassy-usb-synopsys-otg", + "dep:esp-synopsys-usb-otg", + "esp-synopsys-usb-otg/esp32sx", + "esp-synopsys-usb-otg/fs", +] + +#! ### Runtime support +#! These features are meant to be enabled by firmware projects. If you are writing a library that +#! depends on `esp-hal`, you should *not* enable these features under any circumstance. + +## Enable code necessary to run the firmware. +rt = [ + "dep:xtensa-lx-rt", + "dep:esp-riscv-rt", + "esp32?/rt", + "esp32c2?/rt", + "esp32c3?/rt", + "esp32c6?/rt", + "esp32h2?/rt", + "esp32s2?/rt", + "esp32s3?/rt", + "critical-section", +] + +## Enable a simple exception handler turning exceptions into panics. +exception-handler = [] + +## Save and restore the floating point co-processor context. +## +## This feature enables the floating-point coprocessor in interrupt contexts, and adds code to save and restore the relevant registers. +## +##

    +## If you intend to do floating-point calculations in multiple contexts, or in interrupt/exception handlers, you will need to enable this feature. +##
    +## +## Note that this feature is only effective on ESP32 and ESP32-S3. Other chips don't have a hardware floating-point coprocessor. +## +## ⚠️ This feature is considered unstable. +float-save-restore = ["xtensa-lx-rt/float-save-restore"] + +#! ### Chip selection +#! One of the following features must be enabled to select the target chip: + +## +esp32 = [ + "dep:esp32", + "procmacros/rtc-slow", + "esp-rom-sys/esp32", + "esp-sync/esp32", + "esp-metadata-generated/esp32", + "dep:sha1", + "dep:sha2" +] +## +esp32c2 = [ + "dep:esp32c2", + "esp-riscv-rt/no-mie-mip", + "portable-atomic/unsafe-assume-single-core", + "esp-rom-sys/esp32c2", + "esp-sync/esp32c2", + "esp-metadata-generated/esp32c2", +] +## +esp32c3 = [ + "dep:esp32c3", + "esp-riscv-rt/no-mie-mip", + "esp-riscv-rt/rtc-ram", + "portable-atomic/unsafe-assume-single-core", + "esp-rom-sys/esp32c3", + "esp-sync/esp32c3", + "esp-metadata-generated/esp32c3", +] +## +esp32c5 = [ + "dep:esp32c5", + "esp-riscv-rt/rtc-ram", + "esp-riscv-rt/clic-48", + "procmacros/has-lp-core", + "esp-rom-sys/esp32c5", + "esp-sync/esp32c5", + "esp-metadata-generated/esp32c5", +] +## +esp32c6 = [ + "dep:esp32c6", + "esp-riscv-rt/rtc-ram", + "procmacros/has-lp-core", + "esp-rom-sys/esp32c6", + "esp-sync/esp32c6", + "esp-metadata-generated/esp32c6", +] +## +esp32h2 = [ + "dep:esp32h2", + "esp-riscv-rt/rtc-ram", + "esp-rom-sys/esp32h2", + "esp-sync/esp32h2", + "esp-metadata-generated/esp32h2", +] +## +esp32s2 = [ + "dep:esp32s2", + "portable-atomic/unsafe-assume-single-core", + "procmacros/has-ulp-core", + "procmacros/rtc-slow", + "__usb_otg", + "esp-rom-sys/esp32s2", + "esp-sync/esp32s2", + "esp-metadata-generated/esp32s2", +] +## +esp32s3 = [ + "dep:esp32s3", + "procmacros/has-ulp-core", + "procmacros/rtc-slow", + "__usb_otg", + "esp-rom-sys/esp32s3", + "esp-sync/esp32s3", + "esp-metadata-generated/esp32s3", +] + +#! ### Logging Feature Flags +## Enable logging output using version 0.4 of the `log` crate. +log-04 = ["dep:log-04"] + +## Enable logging output using `defmt` and implement `defmt::Format` on certain types. +defmt = [ + "dep:defmt", + "embassy-futures/defmt", + "embassy-sync/defmt", + "embedded-io-06?/defmt-03", + "embedded-io-async-06?/defmt-03", + "embedded-io-07?/defmt", + "embedded-io-async-07?/defmt", + "enumset/defmt", + "esp32?/defmt", + "esp32c2?/defmt", + "esp32c3?/defmt", + "esp32c5?/defmt", + "esp32c6?/defmt", + "esp32h2?/defmt", + "esp32s2?/defmt", + "esp32s3?/defmt", + "fugit/defmt", + "esp-riscv-rt?/defmt", + "xtensa-lx-rt?/defmt", + "esp-sync/defmt" +] + +#DOC_IF has("psram") +#! ### PSRAM Feature Flags + +## Use externally connected PSRAM (`quad` by default, can be configured to `octal` via ESP_HAL_CONFIG_PSRAM_MODE) +psram = [] + +#DOC_ENDIF + +#! ### Unstable APIs +#! Unstable APIs are drivers and features that are not yet ready for general use. +#! They may be incomplete, have bugs, or be subject to change without notice. +#! Unstable APIs are not covered by semver guarantees. + +## Enables APIs that are not stable and thus come with no stability guarantees. +## Never enable this feature in a library crate using esp-hal. +unstable = [ + "dep:digest", + "dep:embassy-embedded-hal", + "dep:embedded-can", + "dep:embedded-io-06", + "dep:embedded-io-async-06", + "dep:embedded-io-07", + "dep:embedded-io-async-07", + "dep:rand_core-06", + "dep:rand_core-09", + "dep:nb", + "dep:ufmt-write", +] + +## Libraries that depend on `esp-hal` should enable this feature to indicate their use of unstable APIs. +## However, they must **not** enable the `unstable` feature themselves. +## +## For development you can enable the `unstable` and the chip feature by adding esp-hal as a dev-dependency. +requires-unstable = [] + +[lints.clippy] +mixed_attributes_style = "allow" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(host_os, values("windows"))'] } diff --git a/esp-hal/MIGRATING-1.0.0-rc.1.md b/esp-hal/MIGRATING-1.0.0-rc.1.md new file mode 100644 index 00000000000..81f8c48a7d2 --- /dev/null +++ b/esp-hal/MIGRATING-1.0.0-rc.1.md @@ -0,0 +1 @@ +# Migration Guide from 1.0.0-rc.1 to 1.0.0 diff --git a/esp-hal/MIGRATING-1.0.0.md b/esp-hal/MIGRATING-1.0.0.md new file mode 100644 index 00000000000..5c99e360a43 --- /dev/null +++ b/esp-hal/MIGRATING-1.0.0.md @@ -0,0 +1,164 @@ +# Migration Guide from 1.0.0 to {{currentVersion}} + +## RMT Changes + +`ChannelCreator::configure_tx` and `ChannelCreator::configure_rx` have changed in a few ways: +- both methods now take the configuration by reference, +- the pin argument has been removed in favor of `Channel::with_pin`, which is infallible and avoids consuming the pin on error. + +```diff + let tx_config = TxChannelConfig::default(); + let rx_config = RxChannelConfig::default(); + + let tx = rmt.channel0 +- .configure_tx(tx_pin, tx_config) +- .unwrap(); ++ .configure_tx(&tx_config) ++ .unwrap() ++ .with_pin(tx_pin); + + let rx = rmt.channel2 +- .configure_rx(rx_pin, rx_config) +- .unwrap(); ++ .configure_rx(&rx_config) ++ .unwrap() ++ .with_pin(rx_pin); +``` + +`SingleShotTxTransaction` has been renamed to `TxTransaction`: + +```diff +-let txn: SingleShotTxTransaction<'_, '_, PulseCode> = tx_channel.transmit(&data)?; ++let txn: TxTransaction<'_, '_, PulseCode> = tx_channel.transmit(&data)?; +``` + +Some blocking `Channel` methods that previously consumed the channel on error now return it in all cases: + +```diff + let mut channel = todo!("set up tx channel"); + + channel = match tx_channel.transmit(&data) { + Ok(txn) => { + match txn.wait() { + Ok(c) => c, + Err((e, c)) => { + // TODO: Handle the error + c + } + } + } +- Err(e) => { +- // Channel cannot be re-used here (if it had the 'static lifetime) +- panic!(); ++ Err((e, c)) => { ++ // TODO: Handle the error ++ c + } + } +``` + +This applies to +- `Channel<'_, Blocking, Tx>::transmit`, +- `Channel<'_, Blocking, Tx>::transmit_continuously`, +- `Channel<'_, Blocking, Rx>::receive`. + +Configuration methods +- `Rmt::new`, +- `ChannelCreator::configure_tx`, +- `ChannelCreator::configure_rx`, +now return `ConfigError` instead of `Error`. +Corresponding enum variants have been removed from `Error`, and some variants +that are now part of `ConfigError` have been renamed. + +### RMT data type changes + +Support for `Into` and `From` has been removed from Tx and Rx methods, respectively. +Instead, buffers must now contain `PulseCode` directly. +The corresponding generic argument has also been removed from `TxTransaction` and `RxTransaction`: + +```diff +-let tx_data: [u32; 8] = todo!(); +-let mut rx_data: [u32; 8] = [0u32; 8]; +-let tx_transaction: TxTransaction<'_, '_, u32> = tx_channel.transmit(&tx_data)?; +-let rx_transaction: RxTransaction<'_, '_, u32> = rx_channel.receive(&mut rx_data)?; ++let tx_data: [PulseCode; 8] = todo!(); ++let mut rx_data: [PulseCode; 8] = [PulseCode::default(); 8]; ++let tx_transaction: TxTransaction<'_, '_> = tx_channel.transmit(&tx_data)?; ++let rx_transaction: RxTransaction<'_, '_> = rx_channel.receive(&mut rx_data)?; +``` + +## SHA Changes + +The `ShaDigest` type now requires exclusive access to the SHA peripheral to prevent unsound concurrent access. If you were manually constructing `ShaDigest` instances (not using `Sha::start()` or `Sha::start_owned()`), you need to use a mutable reference: + +```diff + let mut sha = Sha::new(peripherals.SHA); +-let sha_ref = &sha; ++let sha_ref = &mut sha; + let digest = ShaDigest::::new(sha_ref); +``` + +The recommended `Sha::start()` and `Sha::start_owned()` methods already require `&mut self`, so typical usage is unaffected. + +## Clock changes + +The `RtcClock::xtal_freq()` function and the `XtalClock` enum have been removed. The currently recommended way to access the crystal clock frequency is through the `Clocks` struct, which returns a `Rate` value: + +```rust +let xtal_clock = esp_hal::clock::Clocks::get().xtal_clock; +``` + +## Interrupt handling changes + +### Direct binding interrupt handlers + +On Xtensa MCUs (ESP32, ESP32-S2, ESP32-S3) the direct binding option has been removed. + +On RISC-V MCUs, the `interrupt::enable_direct` function now takes a `DirectBindableCpuInterrupt` and is infallible: + +```diff + interrupt::enable_direct( + peripheral_interrupt, + interrupt_priority, +- CpuInterrupt::Interrupt25, ++ DirectBindableCpuInterrupt::Interrupt0, + handler_function, +-).unwrap(); ++); +``` + +`DirectBindableCpuInterrupt` is numbered from 0, and corresponds to CPU interrupt numbers that are not disabled or reserved for vectoring. + +## ECC changes + +- The `Ecc::new` constructor now takes a configuration parameter. +- All `Ecc` methods now expect little endian input, and produce little endian output. +- `Ecc` methods now return a result handle instead of writing data back directly. These handles can be used to retrieve the result of the operation. Modular arithmetic methods now take the modulus as an argument. + +```diff +-let mut ecc = Ecc::new(peripherals.ECC); ++let mut ecc = Ecc::new(peripherals.ECC, Config::default()); +-ecc.mod_operations(EllipticCurve::P256, &mut a, &mut b, WorkMode::ModAdd).unwrap(); ++let result = ecc.modular_addition(EllipticCurve::P256, EccModBase::OrderOfCurve, &a, &b).unwrap(); ++result.read_scalar_result(&mut a).unwrap(); +``` + +The method that performs the operation now only returns an error if the parameters are of incorrect +length. Point verification errors are now returned by the result handle. If a particular operation +only does point verification and does not perform any other operations, the verification result +can be read using the `success` method. + +## eFuse Changes + +The `Efuse` struct has been removed. All methods are now free-standing functions +in the `efuse` module. Some functions have been renamed: + +- `Efuse::mac_address()` → `efuse::base_mac_address()` +- `Efuse::set_mac_address(mac)` → `efuse::override_mac_address(mac)` +- `Efuse::interface_mac_address(kind)` → `efuse::interface_mac_address(kind)` +- `Efuse::read_field_le(field)` → `efuse::read_field_le(field)` +- `Efuse::read_bit(field)` → `efuse::read_bit(field)` +- `Efuse::chip_revision()` → `efuse::chip_revision()` + +`Efuse::read_base_mac_address()` has been removed; use `efuse::base_mac_address()` instead. +`MacAddress::as_bytes_mut()` has been removed; use `MacAddress::as_bytes()` for read access. diff --git a/esp-hal/README.md b/esp-hal/README.md new file mode 100644 index 00000000000..0457db8f85a --- /dev/null +++ b/esp-hal/README.md @@ -0,0 +1,140 @@ +# esp-hal + +[![Crates.io](https://img.shields.io/crates/v/esp-hal?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-hal) +[![docs.rs](https://img.shields.io/docsrs/esp-hal?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-hal/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.88.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-hal?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +Bare-metal (`no_std`) hardware abstraction layer for Espressif devices. + +Implements a number of blocking and, where applicable, async traits from the various packages in the [embedded-hal] repository. + +For help getting started with this HAL, please refer to [The Rust on ESP Book] and the [documentation]. + +[embedded-hal]: https://github.com/rust-embedded/embedded-hal +[the rust on esp book]: https://docs.espressif.com/projects/rust/book/ + +## [Documentation] + +[documentation]: https://docs.espressif.com/projects/rust/ + +## Supported Devices + +| Chip | Datasheet | Technical Reference Manual | Target | +| :------: | :----------------------: | :------------------------: | :----------------------------: | +| ESP32 | [ESP32][32-datasheet] | [ESP32][32-trm] | `xtensa-esp32-none-elf` | +| ESP32-C2 | [ESP32-C2][c2-datasheet] | [ESP32-C2][c2-trm] | `riscv32imc-unknown-none-elf` | +| ESP32-C3 | [ESP32-C3][c3-datasheet] | [ESP32-C3][c3-trm] | `riscv32imc-unknown-none-elf` | +| ESP32-C5 | [ESP32-C5][c5-datasheet] | [ESP32-C5][c5-trm] | `riscv32imac-unknown-none-elf` | +| ESP32-C6 | [ESP32-C6][c6-datasheet] | [ESP32-C6][c6-trm] | `riscv32imac-unknown-none-elf` | +| ESP32-H2 | [ESP32-H2][h2-datasheet] | [ESP32-H2][h2-trm] | `riscv32imac-unknown-none-elf` | +| ESP32-S2 | [ESP32-S2][s2-datasheet] | [ESP32-S2][s2-trm] | `xtensa-esp32s2-none-elf` | +| ESP32-S3 | [ESP32-S3][s3-datasheet] | [ESP32-S3][s3-trm] | `xtensa-esp32s3-none-elf` | + +[32-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf +[c2-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp8684_datasheet_en.pdf +[c3-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf +[c5-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-c5_datasheet_en.pdf +[c6-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf +[h2-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf +[s2-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf +[s3-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf +[32-trm]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf +[c2-trm]: https://www.espressif.com/sites/default/files/documentation/esp8684_technical_reference_manual_en.pdf +[c3-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf +[c5-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-c5_technical_reference_manual_en.pdf +[c6-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf +[h2-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-h2_technical_reference_manual_en.pdf +[s2-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf +[s3-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf + +## Peripheral support + + + +| Driver | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ------------------------- |:-----:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:| +| ADC | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| AES | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| ASSIST_DEBUG | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | +| Analog Voltage Comparator | | | | ❌ | | | | | +| Bit Scrambler | | | | ❌ | | | | | +| Bluetooth | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | +| Camera interface | ❌ | | | | | | ❌ | ⚒️ | +| DAC | ⚒️ | | | | | | ⚒️ | | +| Dedicated GPIO | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| DMA | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| DS | | | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| ECC | | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | | | +| Ethernet | ❌ | | | | | | | | +| ETM | | | | ❌ | ⚒️ | ⚒️ | | | +| GPIO | ✔️ | ✔️ | ✔️ | ⚒️ | ✔️ | ✔️ | ✔️ | ✔️ | +| HMAC | | | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| I2C master | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| I2C slave | ❌ | | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| I2S | ⚒️ | | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| IEEE 802.15.4 | | | | ⚒️ | ⚒️ | ⚒️ | | | +| Interrupts | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| IOMUX | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| Key Manager | | | | ❌ | | | | | +| LEDC | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| LP I2C master | | | | ⚒️ | ⚒️ | | | | +| LP UART | | | | ❌ | ⚒️ | | | | +| MCPWM | ⚒️ | | | ❌ | ⚒️ | ⚒️ | | ⚒️ | +| PARL_IO | | | | ⚒️ | ⚒️ | ⚒️ | | | +| PCNT | ⚒️ | | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| PHY | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| PSRAM | ⚒️ | | | ❌ | | | ⚒️ | ⚒️ | +| RGB display | ⚒️ | | | | | | ❌ | ⚒️ | +| RMT | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| RNG | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| RSA | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| RTC Timekeeping | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| SDIO host | ⚒️ | | | | | | | ⚒️ | +| SDIO slave | ⚒️ | | | ❌ | ⚒️ | | | | +| SHA | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| Light/deep sleep | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| SPI master | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| SPI slave | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| SYSTIMER | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| Temperature sensor | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| Timers | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| Touch | ⚒️ | | | ❌ | | | ❌ | ❌ | +| TWAI / CAN / CANFD | ⚒️ | | ⚒️ | ❌ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| UART | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| UHCI | ❌ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | +| ULP (FSM) | ⚒️ | | | | | | ⚒️ | ⚒️ | +| ULP (RISC-V) | | | | ❌ | ⚒️ | | ⚒️ | ⚒️ | +| USB OTG FS | | | | | | | ⚒️ | ⚒️ | +| USB Serial/JTAG | | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | +| WIFI | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | ⚒️ | + + * Empty cell: Not available + * ❌: Not supported + * ⚒️: Partial support + * ✔️: Supported + + +## `unstable` feature + +The stable feature set is designed to remain consistent and reliable. Other parts guarded by the `unstable` feature, however, are still under active development and may undergo breaking changes and are disabled by default. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-hal/build.rs b/esp-hal/build.rs new file mode 100644 index 00000000000..246062cdb16 --- /dev/null +++ b/esp-hal/build.rs @@ -0,0 +1,284 @@ +use std::error::Error; +#[cfg(feature = "rt")] +use std::{ + collections::HashMap, + env, + fs::{self, File}, + io::{BufRead, Write}, + path::{Path, PathBuf}, +}; + +use esp_config::{Value, generate_config_from_yaml_definition}; +use esp_metadata_generated::assert_unique_features; + +fn main() -> Result<(), Box> { + // if using '"rust-analyzer.cargo.buildScripts.useRustcWrapper": true' we can detect this + let suppress_panics = std::env::var("RUSTC_WRAPPER") + .unwrap_or_default() + .contains("rust-analyzer"); + + println!("cargo:rustc-check-cfg=cfg(is_debug_build)"); + if let Ok(level) = std::env::var("OPT_LEVEL") + && (level == "0" || level == "1") + { + println!("cargo:rustc-cfg=is_debug_build"); + } + + // If some library required unstable make sure unstable is actually enabled. + if !suppress_panics && cfg!(feature = "requires-unstable") && !cfg!(feature = "unstable") { + panic!( + "\n\nThe `unstable` feature is required by a dependent crate but is not enabled.\n\n" + ); + } + + // Log and defmt are mutually exclusive features. The main technical reason is + // that allowing both would make the exact panicking behaviour a fragile + // implementation detail. + assert_unique_features!("log-04", "defmt"); + + // Ensure that exactly one chip has been specified: + let chip = esp_metadata_generated::Chip::from_cargo_feature()?; + + if !suppress_panics && chip.target() != std::env::var("TARGET").unwrap_or_default().as_str() { + panic!(" + Seems you are building for an unsupported or wrong target (e.g. the host environment). + Maybe you are missing the `target` in `.cargo/config.toml` or you have configs overriding it? + + See https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure + "); + } + + // Define all necessary configuration symbols for the configured device: + chip.define_cfgs(); + + // emit config + println!("cargo:rerun-if-changed=./esp_config.yml"); + let cfg_yaml = std::fs::read_to_string("./esp_config.yml") + .expect("Failed to read esp_config.yml for esp-hal"); + let cfg = generate_config_from_yaml_definition(&cfg_yaml, true, true, Some(chip)).unwrap(); + + // RISC-V and Xtensa devices each require some special handling and processing + // of linker scripts: + + let mut config_symbols: Vec = + chip.all_symbols().iter().map(|c| c.to_string()).collect(); + + for (key, value) in &cfg { + match value { + Value::Bool(true) => { + config_symbols.push(key.clone()); + } + Value::String(v) => { + config_symbols.push(format!("{key}_{v}")); + } + _ => {} + } + } + + // Only emit linker directives if the `rt` feature is enabled + #[cfg(feature = "rt")] + { + // Place all linker scripts in `OUT_DIR`, and instruct Cargo how to find these + // files: + let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + println!("cargo:rustc-link-search={}", out.display()); + + if chip.is_xtensa() { + #[cfg(any(feature = "esp32", feature = "esp32s2"))] + File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?; + + let (irtc, drtc) = if cfg!(feature = "esp32s3") { + ("rtc_fast_seg", "rtc_fast_seg") + } else { + ("rtc_fast_iram_seg", "rtc_fast_dram_seg") + }; + + let alias = format!( + r#" + REGION_ALIAS("ROTEXT", irom_seg); + REGION_ALIAS("RWTEXT", iram_seg); + REGION_ALIAS("RODATA", drom_seg); + REGION_ALIAS("RWDATA", dram_seg); + REGION_ALIAS("RTC_FAST_RWTEXT", {irtc}); + REGION_ALIAS("RTC_FAST_RWDATA", {drtc}); + "# + ); + + fs::write(out.join("alias.x"), alias)?; + fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?; + } else { + // RISC-V devices: + + preprocess_file( + &config_symbols, + &cfg, + "ld/riscv/asserts.x", + out.join("asserts.x"), + )?; + preprocess_file( + &config_symbols, + &cfg, + "ld/riscv/hal-defaults.x", + out.join("hal-defaults.x"), + )?; + } + + // With the architecture-specific linker scripts taken care of, we can copy all + // remaining linker scripts which are common to all devices: + copy_dir_all(&config_symbols, &cfg, "ld/sections", &out)?; + copy_dir_all(&config_symbols, &cfg, format!("ld/{}", chip.name()), &out)?; + } + + Ok(()) +} + +// ---------------------------------------------------------------------------- +// Helper Functions +#[cfg(feature = "rt")] +fn copy_dir_all( + config_symbols: &[String], + cfg: &HashMap, + src: impl AsRef, + dst: impl AsRef, +) -> std::io::Result<()> { + fs::create_dir_all(&dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all( + config_symbols, + cfg, + entry.path(), + dst.as_ref().join(entry.file_name()), + )?; + } else { + preprocess_file( + config_symbols, + cfg, + entry.path(), + dst.as_ref().join(entry.file_name()), + )?; + } + } + Ok(()) +} + +/// A naive pre-processor for linker scripts +#[cfg(feature = "rt")] +fn preprocess_file( + config: &[String], + cfg: &HashMap, + src: impl AsRef, + dst: impl AsRef, +) -> std::io::Result<()> { + println!("cargo:rerun-if-changed={}", src.as_ref().display()); + + let file = File::open(src)?; + let mut out_file = File::create(dst)?; + + let mut take = Vec::new(); + take.push(true); + + for line in std::io::BufReader::new(file).lines() { + let line = substitute_config(cfg, &line?); + let trimmed = line.trim(); + + if let Some(condition) = trimmed.strip_prefix("#IF ") { + let should_take = take.iter().all(|v| *v); + let should_take = should_take && config.iter().any(|c| c == condition); + take.push(should_take); + continue; + } else if trimmed == "#ELSE" { + let taken = take.pop().unwrap(); + let should_take = take.iter().all(|v| *v); + let should_take = should_take && !taken; + take.push(should_take); + continue; + } else if trimmed == "#ENDIF" { + take.pop(); + continue; + } + + if *take.last().unwrap() { + out_file.write_all(line.as_bytes())?; + let _ = out_file.write(b"\n")?; + } + } + Ok(()) +} + +#[cfg(feature = "rt")] +fn substitute_config(cfg: &HashMap, line: &str) -> String { + let mut result = String::new(); + let mut chars = line.chars().peekable(); + + while let Some(c) = chars.next() { + if c != '$' { + result.push(c); + continue; + } + + let Some('{') = chars.peek() else { + result.push(c); + continue; + }; + chars.next(); + + let mut key = String::new(); + for c in chars.by_ref() { + if c == '}' { + break; + } + key.push(c); + } + match cfg + .get(&key) + .unwrap_or_else(|| panic!("missing config key: {key}")) + { + Value::Bool(true) => result.push('1'), + Value::Bool(false) => result.push('0'), + Value::Integer(value) => result.push_str(&value.to_string()), + Value::String(value) => result.push_str(value), + } + } + + result +} + +#[cfg(all(feature = "esp32", feature = "rt"))] +fn generate_memory_extras() -> Vec { + let reserve_dram = if cfg!(feature = "__bluetooth") { + "0x10000" + } else { + "0x0" + }; + + format!( + " + /* reserved at the start of DRAM for e.g. the BT stack */ + RESERVE_DRAM = {reserve_dram}; + " + ) + .as_bytes() + .to_vec() +} + +#[cfg(all(feature = "esp32s2", feature = "rt"))] +fn generate_memory_extras() -> Vec { + let reserved_cache = if cfg!(feature = "psram") { + "0x4000" + } else { + "0x2000" + }; + + format!( + " + /* reserved at the start of DRAM/IRAM */ + RESERVE_CACHES = {reserved_cache}; + " + ) + .as_bytes() + .to_vec() +} diff --git a/esp-hal/esp_config.yml b/esp-hal/esp_config.yml new file mode 100644 index 00000000000..f7fa7a88895 --- /dev/null +++ b/esp-hal/esp_config.yml @@ -0,0 +1,182 @@ +crate: esp-hal + +options: + - name: place-spi-master-driver-in-ram + description: Places the SPI master driver in RAM for better performance + default: + - value: false + + - name: place-switch-tables-in-ram + description: "Places switch-tables, some lookup tables and constants related to + interrupt handling into RAM - resulting in better performance but slightly more + RAM consumption." + default: + - value: true + stability: !Stable "1.0.0" + + - name: place-anon-in-ram + description: "Places anonymous symbols into RAM - resulting in better performance + at the cost of significant more RAM consumption. Best to be combined with + `place-switch-tables-in-ram`." + default: + - value: false + stability: !Stable "1.0.0" + + - name: place-rmt-driver-in-ram + description: Places the RMT driver in RAM for better performance + default: + - value: false + + - name: spi-address-workaround + description: "Enables a workaround for the issue where SPI in + half-duplex mode incorrectly transmits the address on a single line if the + data buffer is empty." + default: + - value: true + active: 'chip == "esp32"' + + - name: psram-mode + description: SPIRAM chip mode + default: + - value: '"quad"' + constraints: + - if: 'feature("octal_psram")' + type: + validator: enumeration + value: + - "quad" + - "octal" + - if: '!feature("octal_psram")' + type: + validator: enumeration + value: + - "quad" + active: 'feature("psram")' + + - name: stack-guard-offset + description: The stack guard variable will be placed this many bytes from the stack's end. Needs to be a multiple of 4. + default: + - value: 60 + active: "true" + stability: !Stable "1.0.0" + + - name: stack-guard-value + description: The value to be written to the stack guard variable. + default: + - value: 3740121773 + display_hint: Hex + + - name: stack-guard-monitoring + description: Use a data watchpoint to check if the stack guard was overwritten. + default: + - value: true + + - name: write-vec-table-monitoring + description: Use a data watchpoint to check that the vector table was not unintentionally overwritten. + default: + - value: false + active: 'chip == "esp32c2" || chip == "esp32c3" || chip == "esp32c6" || chip == "esp32h2"' + + - name: stack-guard-monitoring-with-debugger-connected + description: Enable the stack guard also with a debugger connected. Also applies to `write-vec-table-monitoring`. + default: + - value: true + + - name: impl-critical-section + description: "Provide a `critical-section` implementation. Note that if disabled, + you will need to provide a `critical-section` implementation which is + using `restore-state-u32`." + default: + - value: true + + - name: instruction-cache-size + description: Instruction cache size to be set on application startup. + default: + - value: '"32KB"' + constraints: + - type: + validator: enumeration + value: + - "16KB" + - "32KB" + active: 'chip == "esp32s3"' + + - name: instruction-cache-line-size + description: Instruction cache line size to be set on application startup. + default: + - value: '"32B"' + constraints: + - type: + validator: enumeration + value: + - "16B" + - "32B" + active: 'chip == "esp32s3"' + + - name: icache-associated-ways + description: Instruction cache associated ways to be set on application startup. + default: + - value: '"8"' + constraints: + - type: + validator: enumeration + value: + - "4" + - "8" + active: 'chip == "esp32s3"' + + - name: data-cache-size + description: Data cache size to be set on application startup. + default: + - value: '"32KB"' + constraints: + - type: + validator: enumeration + value: + - "16KB" + - "32KB" + - "64KB" + active: 'chip == "esp32s3"' + + - name: data-cache-line-size + description: Data cache line size to be set on application startup. + default: + - value: '"32B"' + constraints: + - type: + validator: enumeration + value: + - "16B" + - "32B" + - "64B" + active: 'chip == "esp32s3"' + + - name: dcache-associated-ways + description: Data cache associated ways to be set on application startup. + default: + - value: '"8"' + constraints: + - type: + validator: enumeration + value: + - "4" + - "8" + active: 'chip == "esp32s3"' + + - name: min-chip-revision + description: "The minimum chip revision required for the application to run, in format: major * 100 + minor." + default: + - value: 0 + + - name: use_rwdata_ld_hook + description: Include 'rwdata_hook.x' + default: + - value: false + + - name: use_rwtext_ld_hook + description: Include 'rwtext_hook.x' + default: + - value: false + +checks: + - "ESP_HAL_CONFIG_STACK_GUARD_OFFSET % 4 == 0" diff --git a/esp-hal/ld/README.md b/esp-hal/ld/README.md new file mode 100644 index 00000000000..767bc1e9157 --- /dev/null +++ b/esp-hal/ld/README.md @@ -0,0 +1,8 @@ +# ROM functions + +Files in the `rom` subdirectories are taken from esp-idf + +- DON'T include any `*newlib*` functions +- systimer, wdt and mbedtls shouldn't be included +- make sure to align the version you take the files from with esp-wifi-sys - NEVER randomly sync the files with other versions +- some additional functions are needed from ROM - see `additional.ld` (these are usually defined in the `*newlib*` files) diff --git a/esp-hal/ld/esp32/esp32.x b/esp-hal/ld/esp32/esp32.x new file mode 100644 index 00000000000..4e68ac1d838 --- /dev/null +++ b/esp-hal/ld/esp32/esp32.x @@ -0,0 +1,20 @@ +INCLUDE exception.x + +/* ESP32 fixups */ +INCLUDE "fixups/rtc_fast_rwdata_dummy.x" +/* END ESP32 fixups */ + +/* Shared sections - ordering matters */ +SECTIONS { + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" +} +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "rtc_slow.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections */ diff --git a/esp-hal/ld/esp32/linkall.x b/esp-hal/ld/esp32/linkall.x new file mode 100644 index 00000000000..b83c9e93b61 --- /dev/null +++ b/esp-hal/ld/esp32/linkall.x @@ -0,0 +1,5 @@ +INCLUDE "memory.x" +INCLUDE "alias.x" +INCLUDE "esp32.x" +INCLUDE "hal-defaults.x" + diff --git a/esp-hal/ld/esp32/memory.x b/esp-hal/ld/esp32/memory.x new file mode 100644 index 00000000000..74dc01267db --- /dev/null +++ b/esp-hal/ld/esp32/memory.x @@ -0,0 +1,58 @@ +/* This memory map assumes the flash cache is on; + the blocks used are excluded from the various memory ranges + + see: https://github.com/espressif/esp-idf/blob/5b1189570025ba027f2ff6c2d91f6ffff3809cc2/components/heap/port/esp32/memory_layout.c + for details + */ + +INCLUDE "memory_extras.x" + +/* Specify main memory areas */ +MEMORY +{ + reserved_cache_seg : ORIGIN = 0x40070000, len = 64k /* SRAM0; reserved for usage as flash cache*/ + vectors_seg ( RX ) : ORIGIN = 0x40080000, len = 1k /* SRAM0 */ + iram_seg ( RX ) : ORIGIN = 0x40080400, len = 128k-0x400 /* SRAM0 */ + + /* 8K reserved for usage by the ROM */ + /* first 64kB used by BT if enable */ + dram_seg ( RW ) : ORIGIN = 0x3FFAE000 + 8K + RESERVE_DRAM, len = 192K - RESERVE_DRAM + + /* + * The following values come from the heap allocator in esp-idf: https://github.com/espressif/esp-idf/blob/ab63aaa4a24a05904da2862d627f3987ecbeafd0/components/heap/port/esp32/memory_layout.c#L137-L157 + * The segment dram2_seg after the rom data space is not mentioned in the esp32 linker scripts in esp-idf, instead the space after is used as heap space. + * It seems not all rom data space is reserved, but only "core"/"important" ROM functions that may be called after booting from ROM. + */ + reserved_rom_data_pro : ORIGIN = 0x3ffe0000, len = 1088 + reserved_rom_data_app : ORIGIN = 0x3ffe3f20, len = 1072 + + /* + * The following values are derived from the __stack and _stack_sentry values from ROM. + * They represent the stacks used for each core setup by ROM code. In theory both of these + * can be reclaimed once both cores are running, but for now we play it safe and reserve them both. + */ + reserved_rom_stack_pro : ORIGIN = 0x3ffe1320, len = 11264 + reserved_rom_stack_app : ORIGIN = 0x3ffe5230, len = 11264 + + dram2_seg : ORIGIN = 0x3ffe7e30, len = 98768 /* the rest of DRAM after the rom data segments and rom stacks in the middle */ + + /* external flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + irom_seg ( RX ) : ORIGIN = 0x400D0020, len = 3M - 0x20 + drom_seg ( R ) : ORIGIN = 0x3F400020, len = 4M - 0x20 + + + /* RTC fast memory (executable). Persists over deep sleep. Only for core 0 (PRO_CPU) */ + rtc_fast_iram_seg(RWX) : ORIGIN = 0x400C0000, len = 8k + + /* RTC fast memory (same block as above), viewed from data bus. Only for core 0 (PRO_CPU) */ + rtc_fast_dram_seg(RW) : ORIGIN = 0x3FF80000, len = 8k + + /* RTC slow memory (data accessible). Persists over deep sleep. */ + rtc_slow_seg(RW) : ORIGIN = 0x50000000, len = 8k +} diff --git a/esp-hal/ld/esp32c2/esp32c2.x b/esp-hal/ld/esp32c2/esp32c2.x new file mode 100644 index 00000000000..3efea014a58 --- /dev/null +++ b/esp-hal/ld/esp32c2/esp32c2.x @@ -0,0 +1,57 @@ +/* esp32c2 fixups */ + +SECTIONS { + .rotext_dummy (NOLOAD) : + { + /* This dummy section represents the .rodata section within ROTEXT. + * Since the same physical memory is mapped to both DROM and IROM, + * we need to make sure the .rodata and .text sections don't overlap. + * We skip the amount of memory taken by .rodata* in .text + */ + + /* Start at the same alignment constraint than .flash.text */ + + . = ALIGN(ALIGNOF(.rodata)); + . = ALIGN(ALIGNOF(.rodata.wifi)); + + /* Create an empty gap as big as .text section */ + + . = . + SIZEOF(.flash.appdesc); + . = . + SIZEOF(.rodata); + . = . + SIZEOF(.rodata.wifi); + + /* Prepare the alignment of the section above. Few bytes (0x20) must be + * added for the mapping header. + */ + + . = ALIGN(0x10000) + 0x20; + _rotext_reserved_start = .; + } > ROTEXT +} +INSERT BEFORE .text; + +/* Similar to .rotext_dummy this represents .rwtext but in .data */ +SECTIONS { + .rwdata_dummy (NOLOAD) : ALIGN(4) + { + . = . + SIZEOF(.rwtext) + SIZEOF(.rwtext.wifi) + SIZEOF(.trap); + } > RWDATA +} +INSERT BEFORE .data; + +/* end of esp32c2 fixups */ + +/* Shared sections - ordering matters */ +SECTIONS { + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" +} +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections */ + +_dram_data_start = ORIGIN( DRAM ) + SIZEOF(.trap) + SIZEOF(.rwtext); diff --git a/esp-hal/ld/esp32c2/linkall.x b/esp-hal/ld/esp32c2/linkall.x new file mode 100644 index 00000000000..ec0917813ba --- /dev/null +++ b/esp-hal/ld/esp32c2/linkall.x @@ -0,0 +1,10 @@ +INCLUDE "memory.x" + +REGION_ALIAS("ROTEXT", IROM); +REGION_ALIAS("RODATA", DROM); + +REGION_ALIAS("RWDATA", DRAM); +REGION_ALIAS("RWTEXT", IRAM); + +INCLUDE "esp32c2.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32c2/memory.x b/esp-hal/ld/esp32c2/memory.x new file mode 100644 index 00000000000..91fa6416b4a --- /dev/null +++ b/esp-hal/ld/esp32c2/memory.x @@ -0,0 +1,37 @@ +MEMORY +{ + /* + https://github.com/espressif/esptool/blob/10828527038d143e049790d330ac4de76ce987d6/esptool/targets/esp32c2.py#L53-L62 + MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"], + [0x3C000000, 0x3C400000, "DROM"], + [0x3FCA0000, 0x3FCE0000, "DRAM"], + [0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"], + [0x3FF00000, 0x3FF50000, "DROM_MASK"], + [0x40000000, 0x40090000, "IROM_MASK"], + [0x42000000, 0x42400000, "IROM"], + [0x4037C000, 0x403C0000, "IRAM"]] + */ + + ICACHE : ORIGIN = 0x4037C000, LENGTH = 16K + /* Instruction RAM */ + IRAM : ORIGIN = 0x4037C000 + LENGTH(ICACHE), LENGTH = 186k + /* Data RAM */ + DRAM : ORIGIN = 0x3FCA0000, LENGTH = 186k + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(DRAM) + LENGTH(DRAM), len = 0x3fcdeb70 - (ORIGIN(DRAM) + LENGTH(DRAM)) + + /* External flash + + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + + /* Instruction ROM */ + IROM : ORIGIN = 0x42000000 + 0x20, LENGTH = 0x200000 - 0x20 + /* Data ROM */ + DROM : ORIGIN = 0x3C000000 + 0x20, LENGTH = 0x200000 - 0x20 +} \ No newline at end of file diff --git a/esp-hal/ld/esp32c3/esp32c3.x b/esp-hal/ld/esp32c3/esp32c3.x new file mode 100644 index 00000000000..b3509e4e841 --- /dev/null +++ b/esp-hal/ld/esp32c3/esp32c3.x @@ -0,0 +1,58 @@ +/* esp32c3 fixups */ + +SECTIONS { + .rotext_dummy (NOLOAD) : + { + /* This dummy section represents the .rodata section within ROTEXT. + * Since the same physical memory is mapped to both DROM and IROM, + * we need to make sure the .rodata and .text sections don't overlap. + * We skip the amount of memory taken by .rodata* in .text + */ + + /* Start at the same alignment constraint than .flash.text */ + + . = ALIGN(ALIGNOF(.rodata)); + . = ALIGN(ALIGNOF(.rodata.wifi)); + + /* Create an empty gap as big as .text section */ + + . = . + SIZEOF(.flash.appdesc); + . = . + SIZEOF(.rodata); + . = . + SIZEOF(.rodata.wifi); + + /* Prepare the alignment of the section above. Few bytes (0x20) must be + * added for the mapping header. + */ + + . = ALIGN(0x10000) + 0x20; + _rotext_reserved_start = .; + } > ROTEXT +} +INSERT BEFORE .text; + +/* Similar to .rotext_dummy this represents .rwtext but in .data */ +SECTIONS { + .rwdata_dummy (NOLOAD) : ALIGN(4) + { + . = . + SIZEOF(.rwtext) + SIZEOF(.rwtext.wifi) + SIZEOF(.trap); + } > RWDATA +} +INSERT BEFORE .data; + +/* end of esp32c3 fixups */ + +/* Shared sections - ordering matters */ +SECTIONS { + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" +} +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections */ + +_dram_data_start = ORIGIN( DRAM ) + SIZEOF(.trap) + SIZEOF(.rwtext); diff --git a/esp-hal/ld/esp32c3/linkall.x b/esp-hal/ld/esp32c3/linkall.x new file mode 100644 index 00000000000..cef88740393 --- /dev/null +++ b/esp-hal/ld/esp32c3/linkall.x @@ -0,0 +1,13 @@ +INCLUDE "memory.x" + +REGION_ALIAS("ROTEXT", IROM); +REGION_ALIAS("RODATA", DROM); + +REGION_ALIAS("RWDATA", DRAM); +REGION_ALIAS("RWTEXT", IRAM); + +REGION_ALIAS("RTC_FAST_RWTEXT", RTC_FAST); +REGION_ALIAS("RTC_FAST_RWDATA", RTC_FAST); + +INCLUDE "esp32c3.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32c3/memory.x b/esp-hal/ld/esp32c3/memory.x new file mode 100644 index 00000000000..f4c1806945c --- /dev/null +++ b/esp-hal/ld/esp32c3/memory.x @@ -0,0 +1,43 @@ +MEMORY +{ + /* + https://github.com/espressif/esptool/blob/ed64d20b051d05f3f522bacc6a786098b562d4b8/esptool/targets/esp32c3.py#L78-L90 + MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"], + [0x3C000000, 0x3C800000, "DROM"], + [0x3FC80000, 0x3FCE0000, "DRAM"], + [0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"], + [0x3FF00000, 0x3FF20000, "DROM_MASK"], + [0x40000000, 0x40060000, "IROM_MASK"], + [0x42000000, 0x42800000, "IROM"], + [0x4037C000, 0x403E0000, "IRAM"], + [0x50000000, 0x50002000, "RTC_IRAM"], + [0x50000000, 0x50002000, "RTC_DRAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"]] + */ + + ICACHE : ORIGIN = 0x4037C000, LENGTH = 0x4000 + /* Instruction RAM */ + IRAM : ORIGIN = 0x4037C000 + 0x4000, LENGTH = 313K - 0x4000 + /* Data RAM */ + DRAM : ORIGIN = 0x3FC80000, LENGTH = 313K + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(DRAM) + LENGTH(DRAM), len = 0x3fcde710 - (ORIGIN(DRAM) + LENGTH(DRAM)) + + /* External flash + + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + + /* Instruction ROM */ + IROM : ORIGIN = 0x42000000 + 0x20, LENGTH = 0x400000 - 0x20 + /* Data ROM */ + DROM : ORIGIN = 0x3C000000 + 0x20, LENGTH = 0x400000 - 0x20 + + /* RTC fast memory (executable). Persists over deep sleep. */ + RTC_FAST : ORIGIN = 0x50000000, LENGTH = 0x2000 /*- ESP_BOOTLOADER_RESERVE_RTC*/ +} diff --git a/esp-hal/ld/esp32c5/esp32c5.x b/esp-hal/ld/esp32c5/esp32c5.x new file mode 100644 index 00000000000..55ad31c5f8f --- /dev/null +++ b/esp-hal/ld/esp32c5/esp32c5.x @@ -0,0 +1,37 @@ +/* esp32c5 fixups */ +/* The ESP32-C2 and ESP32-C3 have interrupt IDs 1-31, while the ESP32-C5 has + IDs 0-31, so we must define the handler for the one additional interrupt + ID: */ +PROVIDE(interrupt0 = DefaultHandler); + +SECTIONS { + /* Shared sections - ordering matters */ + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" + /* End of Shared sections */ +} + +SECTIONS { + /** + * Bootloader really wants to have separate segments for ROTEXT and RODATA + * Thus, we need to force a gap here. + */ + .text_gap (NOLOAD): { + . = . + 4; + . = ALIGN(4) + 0x20; + } > ROM +} +INSERT BEFORE .text; +/* end of esp32c5 fixups */ + +/* Shared sections #2 - ordering matters */ +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections #2 */ + +_dram_data_start = ORIGIN( RAM ) + SIZEOF(.trap) + SIZEOF(.rwtext); diff --git a/esp-hal/ld/esp32c5/linkall.x b/esp-hal/ld/esp32c5/linkall.x new file mode 100644 index 00000000000..034835cf077 --- /dev/null +++ b/esp-hal/ld/esp32c5/linkall.x @@ -0,0 +1,13 @@ +INCLUDE "memory.x" + +REGION_ALIAS("ROTEXT", ROM); +REGION_ALIAS("RODATA", ROM); + +REGION_ALIAS("RWTEXT", RAM); +REGION_ALIAS("RWDATA", RAM); + +REGION_ALIAS("RTC_FAST_RWTEXT", RTC_FAST); +REGION_ALIAS("RTC_FAST_RWDATA", RTC_FAST); + +INCLUDE "esp32c5.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32c5/memory.x b/esp-hal/ld/esp32c5/memory.x new file mode 100644 index 00000000000..d318a858f19 --- /dev/null +++ b/esp-hal/ld/esp32c5/memory.x @@ -0,0 +1,40 @@ +MEMORY +{ + /* MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x42000000, 0x44000000, "DROM"], + [0x40800000, 0x40860000, "DRAM"], + [0x40800000, 0x40860000, "BYTE_ACCESSIBLE"], + [0x4003A000, 0x40040000, "DROM_MASK"], + [0x40000000, 0x4003A000, "IROM_MASK"], + [0x42000000, 0x44000000, "IROM"], + [0x40800000, 0x40860000, "IRAM"], + [0x50000000, 0x50004000, "RTC_IRAM"], + [0x50000000, 0x50004000, "RTC_DRAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"], + ] */ + + /* 512K of on soc RAM, 32K reserved for cache */ + /* Instruction and Data RAM + 0x4084e5a0 = 2nd stage bootloader iram_loader_seg start address + see https://github.com/espressif/esp-idf/blob/03414a15508036c8fc0f51642aed7a264e9527df/components/esp_system/ld/esp32c5/memory.ld.in#L26 + */ + RAM : ORIGIN = 0x40800000 , LENGTH = 0x4E5A0 + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), len = 0x4085e5a0 - (ORIGIN(RAM) + LENGTH(RAM)) + + /* External flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + + /* Instruction and Data ROM */ + ROM : ORIGIN = 0x42000000 + 0x20, LENGTH = 0x400000 - 0x20 + + /* RTC fast memory (executable). Persists over deep sleep. */ + RTC_FAST : ORIGIN = 0x50000000, LENGTH = 16K /*- ESP_BOOTLOADER_RESERVE_RTC*/ +} diff --git a/esp-hal/ld/esp32c6/esp32c6.x b/esp-hal/ld/esp32c6/esp32c6.x new file mode 100644 index 00000000000..3f5a8bbb0bd --- /dev/null +++ b/esp-hal/ld/esp32c6/esp32c6.x @@ -0,0 +1,37 @@ +/* esp32c6 fixups */ +/* The ESP32-C2 and ESP32-C3 have interrupt IDs 1-31, while the ESP32-C6 has + IDs 0-31, so we much define the handler for the one additional interrupt + ID: */ +PROVIDE(interrupt0 = DefaultHandler); + +SECTIONS { + /* Shared sections - ordering matters */ + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" + /* End of Shared sections */ +} + +SECTIONS { + /** + * Bootloader really wants to have separate segments for ROTEXT and RODATA + * Thus, we need to force a gap here. + */ + .text_gap (NOLOAD): { + . = . + 8; + . = ALIGN(0x10000) + 0x20; + } > ROM +} +INSERT BEFORE .text; +/* end of esp32c6 fixups */ + +/* Shared sections #2 - ordering matters */ +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections #2 */ + +_dram_data_start = ORIGIN( RAM ) + SIZEOF(.trap) + SIZEOF(.rwtext); diff --git a/esp-hal/ld/esp32c6/linkall.x b/esp-hal/ld/esp32c6/linkall.x new file mode 100644 index 00000000000..e0b42581f35 --- /dev/null +++ b/esp-hal/ld/esp32c6/linkall.x @@ -0,0 +1,13 @@ +INCLUDE "memory.x" + +REGION_ALIAS("ROTEXT", ROM); +REGION_ALIAS("RODATA", ROM); + +REGION_ALIAS("RWTEXT", RAM); +REGION_ALIAS("RWDATA", RAM); + +REGION_ALIAS("RTC_FAST_RWTEXT", RTC_FAST); +REGION_ALIAS("RTC_FAST_RWDATA", RTC_FAST); + +INCLUDE "esp32c6.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32c6/memory.x b/esp-hal/ld/esp32c6/memory.x new file mode 100644 index 00000000000..ed5bd023599 --- /dev/null +++ b/esp-hal/ld/esp32c6/memory.x @@ -0,0 +1,40 @@ +MEMORY +{ + /* MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x42800000, 0x43000000, "DROM"], + [0x40800000, 0x40880000, "RAM"], + [0x40800000, 0x40880000, "BYTE_ACCESSIBLE"], + [0x4004AC00, 0x40050000, "DROM_MASK"], + [0x40000000, 0x4004AC00, "ROM_MASK"], + [0x42000000, 0x42800000, "ROM"], + [0x40800000, 0x40880000, "RAM"], + [0x50000000, 0x50004000, "RTC_RAM"], + [0x50000000, 0x50004000, "RTC_RAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"], + ] */ + + /* 512K of on soc RAM, 32K reserved for cache */ + /* Instruction and Data RAM + 0x4086E610 = 2nd stage bootloader iram_loader_seg start address + see https://github.com/espressif/esp-idf/blob/03414a15508036c8fc0f51642aed7a264e9527df/components/esp_system/ld/esp32c6/memory.ld.in#L26 + */ + RAM : ORIGIN = 0x40800000 , LENGTH = 0x6E610 + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), len = 0x4087e610 - (ORIGIN(RAM) + LENGTH(RAM)) + + /* External flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + + /* Instruction and Data ROM */ + ROM : ORIGIN = 0x42000000 + 0x20, LENGTH = 0x400000 - 0x20 + + /* RTC fast memory (executable). Persists over deep sleep. */ + RTC_FAST : ORIGIN = 0x50000000, LENGTH = 16K /*- ESP_BOOTLOADER_RESERVE_RTC*/ +} diff --git a/esp-hal/ld/esp32h2/esp32h2.x b/esp-hal/ld/esp32h2/esp32h2.x new file mode 100644 index 00000000000..180d4a211b4 --- /dev/null +++ b/esp-hal/ld/esp32h2/esp32h2.x @@ -0,0 +1,35 @@ +/* The ESP32-C2 and ESP32-C3 have interrupt IDs 1-31, while the ESP32-C6 and ESP32-H2 have + IDs 0-31, so we much define the handler for the one additional interrupt + ID: */ +PROVIDE(interrupt0 = DefaultHandler); + +SECTIONS { + /* Shared sections - ordering matters */ + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" + /* End of Shared sections */ +} + +SECTIONS { + /** + * Bootloader really wants to have separate segments for ROTEXT and RODATA + * Thus, we need to force a gap here. + */ + .text_gap (NOLOAD): { + . = . + 8; + . = ALIGN(0x10000) + 0x20; + } > ROM +} +INSERT BEFORE .text; + +/* Shared sections #2 - ordering matters */ +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections #2 */ + +_dram_data_start = ORIGIN( RAM ) + SIZEOF(.trap) + SIZEOF(.rwtext); diff --git a/esp-hal/ld/esp32h2/linkall.x b/esp-hal/ld/esp32h2/linkall.x new file mode 100644 index 00000000000..3d1c461e121 --- /dev/null +++ b/esp-hal/ld/esp32h2/linkall.x @@ -0,0 +1,13 @@ +INCLUDE "memory.x" + +REGION_ALIAS("ROTEXT", ROM); +REGION_ALIAS("RODATA", ROM); + +REGION_ALIAS("RWTEXT", RAM); +REGION_ALIAS("RWDATA", RAM); + +REGION_ALIAS("RTC_FAST_RWTEXT", RTC_FAST); +REGION_ALIAS("RTC_FAST_RWDATA", RTC_FAST); + +INCLUDE "esp32h2.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32h2/memory.x b/esp-hal/ld/esp32h2/memory.x new file mode 100644 index 00000000000..e9872e2e980 --- /dev/null +++ b/esp-hal/ld/esp32h2/memory.x @@ -0,0 +1,42 @@ +MEMORY +{ + /* MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x42800000, 0x43000000, "DROM"], + [0x40800000, 0x40850000, "RAM"], + [0x40800000, 0x40850000, "BYTE_ACCESSIBLE"], + [0x4001С400, 0x40020000, "DROM_MASK"], + [0x40000000, 0x4001С400, "ROM_MASK"], + [0x42000000, 0x42800000, "ROM"], + [0x40800000, 0x40850000, "RAM"], + [0x50000000, 0x50001000, "RTC_RAM"], + [0x50000000, 0x50001000, "RTC_RAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"], + ] */ + + + /* 320K of on soc RAM, 16K reserved for cache */ + /* Instruction and Data RAM + 0x4083EFD0 = 2nd stage bootloader iram_loader_seg start address + see https://github.com/espressif/esp-idf/blob/03414a15508036c8fc0f51642aed7a264e9527df/components/esp_system/ld/esp32h2/memory.ld.in#L26 + */ + RAM : ORIGIN = 0x40800000, LENGTH = 0x3EFD0 + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), len = 0x4084fee0 - (ORIGIN(RAM) + LENGTH(RAM)) + + /* External flash + + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + + /* Instruction and Data ROM */ + ROM : ORIGIN = 0x42000000 + 0x20, LENGTH = 0x400000 - 0x20 + + /* RTC fast memory (executable). Persists over deep sleep. */ + RTC_FAST : ORIGIN = 0x50000000, LENGTH = 16K /*- ESP_BOOTLOADER_RESERVE_RTC*/ +} diff --git a/esp-hal/ld/esp32s2/esp32s2.x b/esp-hal/ld/esp32s2/esp32s2.x new file mode 100644 index 00000000000..b7bca457cc5 --- /dev/null +++ b/esp-hal/ld/esp32s2/esp32s2.x @@ -0,0 +1,28 @@ +INCLUDE exception.x + +/* This represents .rwtext but in .data */ +SECTIONS { + .rwdata_dummy (NOLOAD) : ALIGN(4) + { + . = . + SIZEOF(.rwtext) + SIZEOF(.rwtext.wifi); + } > RWDATA +} +INSERT BEFORE .data; + +INCLUDE "fixups/rtc_fast_rwdata_dummy.x" +/* End of fixups for esp32s2 */ + +/* Shared sections - ordering matters */ +SECTIONS { + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" +} +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "rtc_slow.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections */ diff --git a/esp-hal/ld/esp32s2/linkall.x b/esp-hal/ld/esp32s2/linkall.x new file mode 100644 index 00000000000..8ca2c75e9e3 --- /dev/null +++ b/esp-hal/ld/esp32s2/linkall.x @@ -0,0 +1,4 @@ +INCLUDE "memory.x" +INCLUDE "alias.x" +INCLUDE "esp32s2.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32s2/memory.x b/esp-hal/ld/esp32s2/memory.x new file mode 100644 index 00000000000..71fc818a753 --- /dev/null +++ b/esp-hal/ld/esp32s2/memory.x @@ -0,0 +1,41 @@ +/* This memory map assumes the flash cache is on; + the blocks used are excluded from the various memory ranges + + see: https://github.com/espressif/esp-idf/blob/5b1189570025ba027f2ff6c2d91f6ffff3809cc2/components/heap/port/esp32s2/memory_layout.c + for details + */ + +INCLUDE "memory_extras.x" + +VECTORS_SIZE = 0x400; + +/* Specify main memory areas */ +MEMORY +{ + vectors_seg ( RX ) : ORIGIN = 0x40020000 + RESERVE_CACHES, len = VECTORS_SIZE + iram_seg ( RX ) : ORIGIN = 0x40020000 + RESERVE_CACHES + VECTORS_SIZE, len = 184k - RESERVE_CACHES - VECTORS_SIZE + + dram_seg ( RW ) : ORIGIN = 0x3FFB0000 + RESERVE_CACHES + VECTORS_SIZE, len = 184k - RESERVE_CACHES - VECTORS_SIZE + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = ORIGIN(dram_seg) + LENGTH(dram_seg), len = 136K + + /* external flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + irom_seg ( RX ) : ORIGIN = 0x40080020, len = 3M - 0x20 + drom_seg ( R ) : ORIGIN = 0x3F000020, len = 4M - 0x20 + + /* RTC fast memory (executable). Persists over deep sleep. Only for core 0 (PRO_CPU) */ + rtc_fast_iram_seg(RWX) : ORIGIN = 0x40070000, len = 8k + + /* RTC fast memory (same block as above), viewed from data bus. Only for core 0 (PRO_CPU) */ + rtc_fast_dram_seg(RW) : ORIGIN = 0x3ff9e000, len = 8k + + /* RTC slow memory (data accessible). Persists over deep sleep. */ + rtc_slow_seg(RW) : ORIGIN = 0x50000000, len = 8k +} diff --git a/esp-hal/ld/esp32s3/esp32s3.x b/esp-hal/ld/esp32s3/esp32s3.x new file mode 100644 index 00000000000..07126e16c33 --- /dev/null +++ b/esp-hal/ld/esp32s3/esp32s3.x @@ -0,0 +1,55 @@ +INCLUDE exception.x + +SECTIONS { + .rotext_dummy (NOLOAD) : + { + /* This dummy section represents the .rodata section within ROTEXT. + * Since the same physical memory is mapped to both DROM and IROM, + * we need to make sure the .rodata and .text sections don't overlap. + * We skip the amount of memory taken by .rodata* in .text + */ + + /* Start at the same alignment constraint than .flash.text */ + + . = ALIGN(ALIGNOF(.rodata)); + . = ALIGN(ALIGNOF(.rodata.wifi)); + + /* Create an empty gap as big as .text section */ + + . = . + SIZEOF(.flash.appdesc); + . = . + SIZEOF(.rodata); + . = . + SIZEOF(.rodata.wifi); + + /* Prepare the alignment of the section above. Few bytes (0x20) must be + * added for the mapping header. + */ + + . = ALIGN(0x10000) + 0x20; + _rotext_reserved_start = .; + } > ROTEXT +} +INSERT BEFORE .text; + +/* Similar to .rotext_dummy this represents .rwtext but in .data */ +SECTIONS { + .rwdata_dummy (NOLOAD) : ALIGN(4) + { + . = . + SIZEOF(.rwtext) + SIZEOF(.rwtext.wifi) + SIZEOF(.vectors); + } > RWDATA +} +INSERT BEFORE .data; + +/* Shared sections - ordering matters */ +SECTIONS { + INCLUDE "rwtext.x" + INCLUDE "rwdata.x" +} +INCLUDE "rodata.x" +INCLUDE "text.x" +INCLUDE "rtc_fast.x" +INCLUDE "rtc_slow.x" +INCLUDE "stack.x" +INCLUDE "dram2.x" +INCLUDE "metadata.x" +INCLUDE "eh_frame.x" +/* End of Shared sections */ diff --git a/esp-hal/ld/esp32s3/linkall.x b/esp-hal/ld/esp32s3/linkall.x new file mode 100644 index 00000000000..54747f5d417 --- /dev/null +++ b/esp-hal/ld/esp32s3/linkall.x @@ -0,0 +1,4 @@ +INCLUDE "memory.x" +INCLUDE "alias.x" +INCLUDE "esp32s3.x" +INCLUDE "hal-defaults.x" diff --git a/esp-hal/ld/esp32s3/memory.x b/esp-hal/ld/esp32s3/memory.x new file mode 100644 index 00000000000..e0d706f11ff --- /dev/null +++ b/esp-hal/ld/esp32s3/memory.x @@ -0,0 +1,50 @@ +#IF ESP_HAL_CONFIG_INSTRUCTION_CACHE_SIZE_32KB +/* reserved for ICACHE */ +RESERVE_ICACHE = 0x8000; +#ENDIF + +#IF ESP_HAL_CONFIG_INSTRUCTION_CACHE_SIZE_16KB +/* reserved for ICACHE */ +RESERVE_ICACHE = 0x4000; +#ENDIF + +VECTORS_SIZE = 0x400; + +/* Specify main memory areas + + 40370000 <- IRAM/Icache -> 40378000 <- D/IRAM (I) -> 403E0000 + 3FC88000 <- D/IRAM (D) -> 3FCF0000 <- DRAM/DCache -> 3FD00000 + + Startup code uses the IRAM from 0x403B9000 to 0x403E0000, which is not available for static + memory, but can only be used after app starts. + + D cache use the memory from high address, so when it's configured to 16K/32K, the region + 0x3FCF0000 ~ (3FD00000 - DATA_CACHE_SIZE) should be available. This region is not used as + static memory, leaving to the heap. +*/ +MEMORY +{ + vectors_seg ( RX ) : ORIGIN = 0x40370000 + RESERVE_ICACHE, len = VECTORS_SIZE + iram_seg ( RX ) : ORIGIN = 0x40370000 + RESERVE_ICACHE + VECTORS_SIZE, len = 328k - VECTORS_SIZE - RESERVE_ICACHE + + /* memory available after the 2nd stage bootloader is finished */ + dram2_seg ( RW ) : ORIGIN = 0x3FCDB700, len = 0x3FCED710 - 0x3FCDB700 + dram_seg ( RW ) : ORIGIN = 0x3FC88000 , len = ORIGIN(dram2_seg) - 0x3FC88000 + + /* external flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + irom_seg ( RX ) : ORIGIN = 0x42000020, len = 32M - 0x20 + drom_seg ( R ) : ORIGIN = 0x3C000020, len = 32M - 0x20 + + + /* RTC fast memory (executable). Persists over deep sleep. Only for core 0 (PRO_CPU) */ + rtc_fast_seg(RWX) : ORIGIN = 0x600fe000, len = 8k + + /* RTC slow memory (data accessible). Persists over deep sleep. */ + rtc_slow_seg(RW) : ORIGIN = 0x50000000, len = 8k +} diff --git a/esp-hal-common/ld/riscv/asserts.x b/esp-hal/ld/riscv/asserts.x similarity index 80% rename from esp-hal-common/ld/riscv/asserts.x rename to esp-hal/ld/riscv/asserts.x index dd805886740..7b1b21170d9 100644 --- a/esp-hal-common/ld/riscv/asserts.x +++ b/esp-hal/ld/riscv/asserts.x @@ -10,9 +10,6 @@ ERROR(riscv-rt): the start of the RODATA must be 4-byte aligned"); ASSERT(ORIGIN(REGION_DATA) % 4 == 0, " ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned"); -ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " -ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); - ASSERT(ORIGIN(ROTEXT) % 4 == 0, " ERROR(riscv-rt): the start of the ROTEXT must be 4-byte aligned"); @@ -31,17 +28,10 @@ BUG(riscv-rt): the LMA of .data is not 4-byte aligned"); ASSERT(_bss_start % 4 == 0 && _bss_end % 4 == 0, " BUG(riscv-rt): .bss is not 4-byte aligned"); -ASSERT(_sheap % 4 == 0, " -BUG(riscv-rt): start of .heap is not 4-byte aligned"); - ASSERT(_stext + SIZEOF(.text) < ORIGIN(ROTEXT) + LENGTH(ROTEXT), " ERROR(riscv-rt): The .text section must be placed inside the ROTEXT region. Set _stext to an address smaller than 'ORIGIN(ROTEXT) + LENGTH(ROTEXT)'"); -ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " -ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. -Consider changing `_max_hart_id` or `_hart_stack_size`."); - ASSERT(SIZEOF(.got) == 0, " .got section detected in the input files. Dynamic relocations are not supported. If you are linking to C code compiled using the `gcc` crate diff --git a/esp-hal/ld/riscv/hal-defaults.x b/esp-hal/ld/riscv/hal-defaults.x new file mode 100644 index 00000000000..0760b630385 --- /dev/null +++ b/esp-hal/ld/riscv/hal-defaults.x @@ -0,0 +1,95 @@ +ENTRY(_start) + +/* +We don't use the linker scripts defined in riscv-rt (since we have special needs) but we need to define some symbols to satisfy riscv-rt +*/ + +PROVIDE(_stext = ORIGIN(ROTEXT)); +PROVIDE(__global_pointer$ = ALIGN(_data_start, 4) + 0x800); + +/* Default abort entry point. If no abort symbol is provided, then abort maps to _default_abort. */ +EXTERN(_default_abort); +PROVIDE(abort = _default_abort); + +/* Trap for exceptions triggered during initialization. If the execution reaches this point, it + means that there is a bug in the boot code. If no _pre_init_trap symbol is provided, then + _pre_init_trap defaults to _default_abort. Note that _pre_init_trap must be 4-byte aligned */ +PROVIDE(_pre_init_trap = _default_abort); + +/* Default trap entry point. If not _start_trap symbol is provided, then _start_trap maps to + _default_start_trap, which saves caller saved registers, calls _start_trap_rust, restores + caller saved registers and then returns. Note that _start_trap must be 4-byte aligned */ +EXTERN(_default_start_trap); +PROVIDE(_start_trap = _default_start_trap); + +/* Default interrupt setup entry point. If not _setup_interrupts symbol is provided, then + _setup_interrupts maps to _default_setup_interrupts, which in direct mode sets the value + of the xtvec register to _start_trap and, in vectored mode, sets its value to + _vector_table and enables vectored mode. */ +EXTERN(_default_setup_interrupts); +PROVIDE(_setup_interrupts = _default_setup_interrupts); + +/* Default main routine. If no hal_main symbol is provided, then hal_main maps to main, which + is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main + instead of main directly allow HALs to inject code before jumping to user main. */ +PROVIDE(hal_main = main); + +/* Default exception handler. By default, the exception handler is abort. + Users can override this alias by defining the symbol themselves */ +PROVIDE(ExceptionHandler = abort); + +/* Default interrupt trap entry point. When vectored trap mode is enabled, + the riscv-rt crate provides an implementation of this function, which saves caller saved + registers, calls the the DefaultHandler ISR, restores caller saved registers and returns. + Note, however, that this provided implementation cannot be overwritten. We use PROVIDE + to avoid compilation errors in direct mode, not to allow users to overwrite the symbol. */ +PROVIDE(_start_DefaultHandler_trap = _start_trap); + +/* Default interrupt handler. */ +PROVIDE(DefaultHandler = EspDefaultHandler); + +PROVIDE(_max_hart_id = 0); + +/* don't init data - expect the bootloader to do it */ +__sdata = 0; +__edata = 0; +__sidata = 0; + +/* alias bss start + end as expected by riscv-rt */ +__sbss = _bss_start; +__ebss = _bss_end; + +PROVIDE(interrupt1 = DefaultHandler); +PROVIDE(interrupt2 = DefaultHandler); +PROVIDE(interrupt3 = DefaultHandler); +PROVIDE(interrupt4 = DefaultHandler); +PROVIDE(interrupt5 = DefaultHandler); +PROVIDE(interrupt6 = DefaultHandler); +PROVIDE(interrupt7 = DefaultHandler); +PROVIDE(interrupt8 = DefaultHandler); +PROVIDE(interrupt9 = DefaultHandler); +PROVIDE(interrupt10 = DefaultHandler); +PROVIDE(interrupt11 = DefaultHandler); +PROVIDE(interrupt12 = DefaultHandler); +PROVIDE(interrupt13 = DefaultHandler); +PROVIDE(interrupt14 = DefaultHandler); +PROVIDE(interrupt15 = DefaultHandler); +PROVIDE(interrupt16 = DefaultHandler); +PROVIDE(interrupt17 = DefaultHandler); +PROVIDE(interrupt18 = DefaultHandler); +PROVIDE(interrupt19 = DefaultHandler); +PROVIDE(interrupt20 = DefaultHandler); +PROVIDE(interrupt21 = DefaultHandler); +PROVIDE(interrupt22 = DefaultHandler); +PROVIDE(interrupt23 = DefaultHandler); +PROVIDE(interrupt24 = DefaultHandler); +PROVIDE(interrupt25 = DefaultHandler); +PROVIDE(interrupt26 = DefaultHandler); +PROVIDE(interrupt27 = DefaultHandler); +PROVIDE(interrupt28 = DefaultHandler); +PROVIDE(interrupt29 = DefaultHandler); +PROVIDE(interrupt30 = DefaultHandler); +PROVIDE(interrupt31 = DefaultHandler); + +/* external interrupts (from PAC) */ +INCLUDE "device.x" diff --git a/esp-hal/ld/sections/dram2.x b/esp-hal/ld/sections/dram2.x new file mode 100644 index 00000000000..1289f781fe9 --- /dev/null +++ b/esp-hal/ld/sections/dram2.x @@ -0,0 +1,6 @@ +/* an uninitialized section of RAM otherwise not useable */ +SECTIONS { + .dram2_uninit (NOLOAD) : ALIGN(4) { + *(.dram2_uninit) + } > dram2_seg +} diff --git a/esp-hal/ld/sections/eh_frame.x b/esp-hal/ld/sections/eh_frame.x new file mode 100644 index 00000000000..e503e75144a --- /dev/null +++ b/esp-hal/ld/sections/eh_frame.x @@ -0,0 +1,6 @@ +SECTIONS { + .eh_frame 0 (INFO) : + { + KEEP(*(.eh_frame)); + } +} diff --git a/esp-hal-common/ld/sections/fixups/rtc_fast_rwdata_dummy.x b/esp-hal/ld/sections/fixups/rtc_fast_rwdata_dummy.x similarity index 92% rename from esp-hal-common/ld/sections/fixups/rtc_fast_rwdata_dummy.x rename to esp-hal/ld/sections/fixups/rtc_fast_rwdata_dummy.x index 7409e536f14..4729f728aac 100644 --- a/esp-hal-common/ld/sections/fixups/rtc_fast_rwdata_dummy.x +++ b/esp-hal/ld/sections/fixups/rtc_fast_rwdata_dummy.x @@ -7,7 +7,7 @@ SECTIONS { .rtc_fast.dummy (NOLOAD) : { _rtc_dummy_start = ABSOLUTE(.); /* needed to make section proper size */ - . = SIZEOF(.rtc_fast.text); + . = . + SIZEOF(.rtc_fast.text); _rtc_dummy_end = ABSOLUTE(.); /* needed to make section proper size */ } > RTC_FAST_RWDATA } diff --git a/esp-hal/ld/sections/metadata.x b/esp-hal/ld/sections/metadata.x new file mode 100644 index 00000000000..d1c881aedb3 --- /dev/null +++ b/esp-hal/ld/sections/metadata.x @@ -0,0 +1,6 @@ +SECTIONS { + .espressif.metadata 0 (INFO) : + { + KEEP(*(.espressif.metadata)); + } +} diff --git a/esp-hal/ld/sections/rodata.x b/esp-hal/ld/sections/rodata.x new file mode 100644 index 00000000000..32ebc48128d --- /dev/null +++ b/esp-hal/ld/sections/rodata.x @@ -0,0 +1,33 @@ +SECTIONS { + /* For ESP App Description, must be placed first in image */ + .flash.appdesc : ALIGN(4) + { + KEEP(*(.flash.appdesc)); + KEEP(*(.flash.appdesc.*)); + } > RODATA + + /* + Depending on the input sections which go into .rodata the alignment might get adjusted to >4. + Make sure we have a "merge-section" here to make the tooling NOT emit more than two sections which go into flash. + */ + .rodata_merge : ALIGN (4) { + . = ALIGN(ALIGNOF(.rodata)); + } > RODATA + + .rodata : ALIGN(4) + { + . = ALIGN (4); + _rodata_start = ABSOLUTE(.); + *(.rodata .rodata.*) + *(.srodata .srodata.*) + . = ALIGN(4); + _rodata_end = ABSOLUTE(.); + } > RODATA + + .rodata.wifi : ALIGN(4) + { + . = ALIGN(4); + *( .rodata_wlog_*.* ) + . = ALIGN(4); + } > RODATA +} diff --git a/esp-hal/ld/sections/rtc_fast.x b/esp-hal/ld/sections/rtc_fast.x new file mode 100644 index 00000000000..1b81c865a8d --- /dev/null +++ b/esp-hal/ld/sections/rtc_fast.x @@ -0,0 +1,39 @@ + + +SECTIONS { + .rtc_fast.text : { + . = ALIGN(4); + *(.rtc_fast.literal .rtc_fast.text .rtc_fast.literal.* .rtc_fast.text.*) + . = ALIGN(4); + } > RTC_FAST_RWTEXT AT > RODATA + + .rtc_fast.data : + { + . = ALIGN(4); + _rtc_fast_data_start = ABSOLUTE(.); + *(.rtc_fast.data .rtc_fast.data.*) + _rtc_fast_data_end = ABSOLUTE(.); + . = ALIGN(4); + } > RTC_FAST_RWDATA AT > RODATA + + /* LMA of .data */ + _rtc_fast_sidata = LOADADDR(.rtc_fast.data); + + .rtc_fast.bss (NOLOAD) : + { + . = ALIGN(4); + _rtc_fast_bss_start = ABSOLUTE(.); + *(.rtc_fast.bss .rtc_fast.bss.*) + _rtc_fast_bss_end = ABSOLUTE(.); + . = ALIGN(4); + } > RTC_FAST_RWDATA + + .rtc_fast.persistent (NOLOAD) : + { + . = ALIGN(4); + _rtc_fast_persistent_start = ABSOLUTE(.); + *(.rtc_fast.persistent .rtc_fast.persistent.*) + _rtc_fast_persistent_end = ABSOLUTE(.); + . = ALIGN(4); + } > RTC_FAST_RWDATA +} diff --git a/esp-hal/ld/sections/rtc_slow.x b/esp-hal/ld/sections/rtc_slow.x new file mode 100644 index 00000000000..0610512cd49 --- /dev/null +++ b/esp-hal/ld/sections/rtc_slow.x @@ -0,0 +1,39 @@ + + +SECTIONS { + .rtc_slow.text : { + . = ALIGN(4); + *(.rtc_slow.literal .rtc_slow.text .rtc_slow.literal.* .rtc_slow.text.*) + . = ALIGN(4); + } > rtc_slow_seg AT > RODATA + + .rtc_slow.data : + { + . = ALIGN(4); + _rtc_slow_data_start = ABSOLUTE(.); + *(.rtc_slow.data .rtc_slow.data.*) + _rtc_slow_data_end = ABSOLUTE(.); + . = ALIGN(4); + } > rtc_slow_seg AT > RODATA + + /* LMA of .data */ + _rtc_slow_sidata = LOADADDR(.rtc_slow.data); + + .rtc_slow.bss (NOLOAD) : + { + . = ALIGN(4); + _rtc_slow_bss_start = ABSOLUTE(.); + *(.rtc_slow.bss .rtc_slow.bss.*) + _rtc_slow_bss_end = ABSOLUTE(.); + . = ALIGN(4); + } > rtc_slow_seg + + .rtc_slow.persistent (NOLOAD) : + { + . = ALIGN(4); + _rtc_slow_persistent_start = ABSOLUTE(.); + *(.rtc_slow.persistent .rtc_slow.persistent.*) + _rtc_slow_persistent_end = ABSOLUTE(.); + . = ALIGN(4); + } > rtc_slow_seg +} diff --git a/esp-hal/ld/sections/rwdata.x b/esp-hal/ld/sections/rwdata.x new file mode 100644 index 00000000000..2302d8c578d --- /dev/null +++ b/esp-hal/ld/sections/rwdata.x @@ -0,0 +1,64 @@ +.data : ALIGN(4) +{ + _data_start = ABSOLUTE(.); + . = ALIGN (4); + + #IF ESP_HAL_CONFIG_PLACE_SWITCH_TABLES_IN_RAM + *(.rodata.*_esp_hal_internal_handler*) + *(.rodata..Lswitch.table.*) + *(.rodata.cst*) + #ENDIF + + #IF ESP_HAL_CONFIG_PLACE_ANON_IN_RAM + *(.rodata..Lanon .rodata..Lanon.*) + #ENDIF + + #IF ESP_HAL_CONFIG_USE_RWDATA_LD_HOOK + INCLUDE "rwdata_hook.x" + #ENDIF + + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + *(.data1) + _data_end = ABSOLUTE(.); + . = ALIGN(4); +} > RWDATA + +/* LMA of .data */ +_sidata = LOADADDR(.data); + +.data.wifi : +{ + . = ALIGN(4); + *( .dram1 .dram1.*) + . = ALIGN(4); +} > RWDATA + +.bss (NOLOAD) : ALIGN(4) +{ + _bss_start = ABSOLUTE(.); + . = ALIGN (4); + *(.dynsbss) + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.sbss2) + *(.sbss2.*) + *(.gnu.linkonce.sb2.*) + *(.dynbss) + *(.sbss .sbss.* .bss .bss.*); + *(.share.mem) + *(.gnu.linkonce.b.*) + *(COMMON) + _bss_end = ABSOLUTE(.); + . = ALIGN(4); +} > RWDATA + +.noinit (NOLOAD) : ALIGN(4) +{ + . = ALIGN(4); + *(.noinit .noinit.*) + *(.uninit .uninit.*) + . = ALIGN(4); +} > RWDATA diff --git a/esp-hal/ld/sections/rwtext.x b/esp-hal/ld/sections/rwtext.x new file mode 100644 index 00000000000..fabf8f3e80d --- /dev/null +++ b/esp-hal/ld/sections/rwtext.x @@ -0,0 +1,44 @@ +#IF riscv +.trap : ALIGN(4) +{ + _trap_section_origin = .; + KEEP(*(.trap)); + *(.trap.*); +} > RWTEXT +#ENDIF + +.rwtext : ALIGN(4) +{ + . = ALIGN (4); + *(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*) + /* unconditionally add patched SPI-flash ROM functions (from esp-rom-sys) - the linker is still happy if there are none */ + *:esp_rom_spiflash.*(.literal .literal.* .text .text.*) + + #IF ESP_HAL_CONFIG_USE_RWTEXT_LD_HOOK + INCLUDE "rwtext_hook.x" + #ENDIF + + . = ALIGN(4); +} > RWTEXT + +.rwtext.wifi : +{ + . = ALIGN(4); + *( .wifi0iram .wifi0iram.*) + *( .wifirxiram .wifirxiram.*) + *( .wifislprxiram .wifislprxiram.*) + *( .wifislpiram .wifislpiram.*) + *( .phyiram .phyiram.*) + *( .iram1 .iram1.*) + *( .wifiextrairam.* ) + *( .coexiram.* ) + *( .high_perf_code_iram* ) + *( .coexsleepiram* ) + *( .wifiorslpiram* ) + *( .isr_iram* ) + *( .conn_iram* ) + *( .sleep_iram* ) + . = ALIGN(4); + + _rwtext_len = . - ORIGIN(RWTEXT); +} > RWTEXT diff --git a/esp-hal/ld/sections/stack.x b/esp-hal/ld/sections/stack.x new file mode 100644 index 00000000000..63923d3fb8c --- /dev/null +++ b/esp-hal/ld/sections/stack.x @@ -0,0 +1,17 @@ +SECTIONS { + /* must be last segment using RWDATA */ + .stack (NOLOAD) : ALIGN(4) + { + _stack_end = ABSOLUTE(.); + _stack_end_cpu0 = ABSOLUTE(.); + + /* The stack_guard for `stack-protector` mitigation - https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection */ + __stack_chk_guard = ABSOLUTE(_stack_end) + ${ESP_HAL_CONFIG_STACK_GUARD_OFFSET}; + + . = ORIGIN(RWDATA) + LENGTH(RWDATA); + + . = ALIGN (4); + _stack_start = ABSOLUTE(.); + _stack_start_cpu0 = ABSOLUTE(.); + } > RWDATA +} diff --git a/esp-hal/ld/sections/text.x b/esp-hal/ld/sections/text.x new file mode 100644 index 00000000000..2f7c0cb4484 --- /dev/null +++ b/esp-hal/ld/sections/text.x @@ -0,0 +1,15 @@ + + +SECTIONS { + + .text : ALIGN(4) + { + #IF riscv + KEEP(*(.init)); + KEEP(*(.init.rust)); + KEEP(*(.text.abort)); + #ENDIF + *(.literal .text .literal.* .text.*) + } > ROTEXT + +} \ No newline at end of file diff --git a/esp-hal/ld/xtensa/hal-defaults.x b/esp-hal/ld/xtensa/hal-defaults.x new file mode 100644 index 00000000000..bdae33192f0 --- /dev/null +++ b/esp-hal/ld/xtensa/hal-defaults.x @@ -0,0 +1,19 @@ +ENTRY(Reset) + +PROVIDE(__zero_bss = default_mem_hook); +PROVIDE(__init_data = default_mem_hook); +PROVIDE(__init_persistent = default_mem_hook); +PROVIDE(__post_init = no_init_hook); + +PROVIDE(DefaultHandler = EspDefaultHandler); + +PROVIDE(level1_interrupt = DefaultHandler); +PROVIDE(level2_interrupt = DefaultHandler); +PROVIDE(level3_interrupt = DefaultHandler); +PROVIDE(level4_interrupt = DefaultHandler); +PROVIDE(level5_interrupt = DefaultHandler); +PROVIDE(level6_interrupt = DefaultHandler); +PROVIDE(level7_interrupt = DefaultHandler); + +/* external interrupts (from PAC) */ +INCLUDE "device.x" diff --git a/esp-hal/src/aes/cipher_modes.rs b/esp-hal/src/aes/cipher_modes.rs new file mode 100644 index 00000000000..a1f55efc23e --- /dev/null +++ b/esp-hal/src/aes/cipher_modes.rs @@ -0,0 +1,475 @@ +//! Software implementations of the supported block cipher operating modes. +//! +//! These may be used by the Typical AES operating mode, as well as by the DMA-enabled driver where +//! the DMA does not support a particular operating mode in hardware. + +use core::{marker::PhantomData, ptr::NonNull}; + +use super::{BLOCK_SIZE, Error}; + +#[derive(Clone, Copy)] +pub(crate) struct CryptoBuffers<'a> { + buffers: UnsafeCryptoBuffers, + _marker: PhantomData<&'a mut ()>, +} + +impl<'a> CryptoBuffers<'a> { + pub fn new(input: &'a [u8], output: &'a mut [u8]) -> Result { + if input.len() != output.len() { + return Err(Error::BuffersNotEqual); + } + Ok(Self { + buffers: UnsafeCryptoBuffers { + input: NonNull::from(input), + output: NonNull::from(output), + }, + _marker: PhantomData, + }) + } + + pub fn new_in_place(data: &'a mut [u8]) -> Self { + let ptr = NonNull::from(data); + Self { + buffers: UnsafeCryptoBuffers { + input: ptr, + output: ptr, + }, + _marker: PhantomData, + } + } + + pub(super) unsafe fn into_inner(self) -> UnsafeCryptoBuffers { + self.buffers + } +} + +#[derive(Clone, Copy)] +pub(super) struct UnsafeCryptoBuffers { + pub input: NonNull<[u8]>, + pub output: NonNull<[u8]>, +} +impl UnsafeCryptoBuffers { + pub fn in_place(&self) -> bool { + self.input.addr() == self.output.addr() + } + + #[cfg(aes_dma)] + pub(crate) unsafe fn byte_add(self, bytes: usize) -> Self { + UnsafeCryptoBuffers { + input: unsafe { self.input.byte_add(bytes) }, + output: unsafe { self.output.byte_add(bytes) }, + } + } +} + +/// Electronic codebook mode. +#[derive(Clone)] +pub struct Ecb; +impl Ecb { + pub(super) fn encrypt_decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + buffer.for_data_chunks(BLOCK_SIZE, |input, output, len| { + process_block( + NonNull::slice_from_raw_parts(input, len), + NonNull::slice_from_raw_parts(output, len), + ) + }); + } +} + +/// Cipher block chaining mode. +#[derive(Clone)] +pub struct Cbc { + pub(super) iv: [u8; BLOCK_SIZE], +} +impl Cbc { + /// Creates a new context object. + pub fn new(iv: [u8; BLOCK_SIZE]) -> Self { + Self { iv } + } + + pub(super) fn encrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let iv = NonNull::from(self.iv.as_mut()); + + buffer.for_data_chunks(BLOCK_SIZE, |plaintext, ciphertext, len| { + // Block input is feedback/IV mixed with plaintext. We can let this overwrite the IV + // because we'll overwrite this again with the ciphertext as the next IV. + xor_into(iv.cast(), plaintext, len); + + // Block output is ciphertext directly + process_block(iv, NonNull::slice_from_raw_parts(ciphertext, len)); + + // Feed back the ciphertext for the next iteration. + copy(iv.cast(), ciphertext, len); + }); + } + + pub(super) fn decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let iv = NonNull::from(self.iv.as_mut()).cast::(); + + if buffer.in_place() { + let mut temp_buffer = [0; 16]; + let temp = NonNull::from(&mut temp_buffer).cast::(); + buffer.for_data_chunks(BLOCK_SIZE, |ciphertext, plaintext, len| { + copy(temp, ciphertext, len); + + // Block output is plaintext, mixed with IV + process_block( + NonNull::slice_from_raw_parts(ciphertext, len), + NonNull::slice_from_raw_parts(plaintext, len), + ); + xor_into(plaintext, iv, len); + + // Next IV is the previous ciphertext + copy(iv, temp, len); + }); + } else { + buffer.for_data_chunks(BLOCK_SIZE, |ciphertext, plaintext, len| { + // Block output is plaintext, mixed with IV + process_block( + NonNull::slice_from_raw_parts(ciphertext, len), + NonNull::slice_from_raw_parts(plaintext, len), + ); + xor_into(plaintext, iv, len); + + // Next IV is the previous ciphertext + copy(iv, ciphertext, len); + }); + } + } +} + +/// Output feedback mode. +#[derive(Clone)] +pub struct Ofb { + pub(super) iv: [u8; BLOCK_SIZE], + pub(super) offset: usize, +} +impl Ofb { + /// Creates a new context object. + pub fn new(iv: [u8; BLOCK_SIZE]) -> Self { + Self { iv, offset: 0 } + } + + pub(super) fn encrypt_decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let mut offset = self.offset; + buffer.for_data_chunks(1, |input, output, _| { + if offset == 0 { + // Out of bytes, generate next key. + let iv = NonNull::from(self.iv.as_mut()); + process_block(iv, iv); + } + + // Calculate ciphertext by mixing the key with the plaintext. + unsafe { output.write(input.read() ^ self.iv[offset]) }; + offset = (offset + 1) % BLOCK_SIZE; + }); + self.offset = offset; + } + + #[cfg(aes_dma)] + pub(super) fn flush(&mut self, buffer: UnsafeCryptoBuffers) -> usize { + let mut offset = self.offset; + buffer + .first_n((BLOCK_SIZE - offset) % BLOCK_SIZE) + .for_data_chunks(1, |input, output, _| { + unsafe { output.write(input.read() ^ self.iv[offset]) }; + offset += 1; + }); + let flushed = offset - self.offset; + self.offset = offset % BLOCK_SIZE; + flushed + } +} + +/// Counter mode. +#[derive(Clone)] +pub struct Ctr { + /// The nonce + counter. Note that the security of this relies on the nonce being random. + pub(super) nonce: [u8; BLOCK_SIZE], + /// The key produced by the block cipher. + pub(super) buffer: [u8; BLOCK_SIZE], + pub(super) offset: usize, +} +impl Ctr { + /// Creates a new context object. + pub fn new(nonce: [u8; BLOCK_SIZE]) -> Self { + Self { + nonce, + buffer: [0; BLOCK_SIZE], + offset: 0, + } + } + + pub(super) fn encrypt_decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + fn increment(nonce: &mut [u8]) { + for byte in nonce.iter_mut().rev() { + *byte = byte.wrapping_add(1); + if *byte != 0 { + break; + } + } + } + + let mut offset = self.offset; + buffer.for_data_chunks(1, |plaintext, ciphertext, _| { + if offset == 0 { + let nonce = NonNull::from(self.nonce.as_mut()); + let buffer = NonNull::from(self.buffer.as_mut()); + // Block input is feedback/IV. Block output is feedback/IV. + process_block(nonce, buffer); + increment(&mut self.nonce); + } + + // Calculate ciphertext by mixing the key with the plaintext. + unsafe { ciphertext.write(plaintext.read() ^ self.buffer[offset]) }; + offset = (offset + 1) % BLOCK_SIZE; + }); + self.offset = offset; + } + + #[cfg(aes_dma)] + pub(super) fn flush(&mut self, buffer: UnsafeCryptoBuffers) -> usize { + let mut offset = self.offset; + buffer + .first_n((BLOCK_SIZE - offset) % BLOCK_SIZE) + .for_data_chunks(1, |plaintext, ciphertext, _| { + unsafe { ciphertext.write(plaintext.read() ^ self.buffer[offset]) }; + offset += 1; + }); + let flushed = offset - self.offset; + self.offset = offset % BLOCK_SIZE; + flushed + } +} + +/// Cipher feedback with 8-bit shift mode. +#[derive(Clone)] +pub struct Cfb8 { + pub(super) iv: [u8; BLOCK_SIZE], +} +impl Cfb8 { + /// Creates a new context object. + pub fn new(iv: [u8; BLOCK_SIZE]) -> Self { + Self { iv } + } + + pub(super) fn encrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let mut ov = [0; BLOCK_SIZE]; + buffer.for_data_chunks(1, |plaintext, ciphertext, _| { + process_block(NonNull::from(self.iv.as_mut()), NonNull::from(ov.as_mut())); + + unsafe { + let out = ov[0] ^ plaintext.read(); + ciphertext.write(out); + + // Shift IV by a byte. + self.iv.copy_within(1.., 0); + self.iv[BLOCK_SIZE - 1] = out; + } + }); + } + + pub(super) fn decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let mut ov = [0; BLOCK_SIZE]; + buffer.for_data_chunks(1, |ciphertext, plaintext, _| { + process_block(NonNull::from(self.iv.as_mut()), NonNull::from(ov.as_mut())); + + unsafe { + let c = ciphertext.read(); + plaintext.write(ov[0] ^ c); + + // Shift IV by a byte. + self.iv.copy_within(1.., 0); + self.iv[BLOCK_SIZE - 1] = c; + } + }); + } +} + +/// Cipher feedback with 128-bit shift mode. +#[derive(Clone)] +pub struct Cfb128 { + pub(super) iv: [u8; BLOCK_SIZE], + pub(super) offset: usize, +} +impl Cfb128 { + /// Creates a new context object. + pub fn new(iv: [u8; BLOCK_SIZE]) -> Self { + Self { iv, offset: 0 } + } + + pub(super) fn encrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let mut offset = self.offset; + buffer.for_data_chunks(1, |plaintext, ciphertext, _| { + if offset == 0 { + let iv = NonNull::from(self.iv.as_mut()); + process_block(iv, iv); + } + + unsafe { + self.iv[offset] ^= plaintext.read(); + ciphertext.write(self.iv[offset]); + } + offset = (offset + 1) % BLOCK_SIZE; + }); + self.offset = offset; + } + + pub(super) fn decrypt( + &mut self, + buffer: UnsafeCryptoBuffers, + mut process_block: impl FnMut(NonNull<[u8]>, NonNull<[u8]>), + ) { + let mut offset = self.offset; + buffer.for_data_chunks(1, |ciphertext, plaintext, _| { + if offset == 0 { + let iv = NonNull::from(self.iv.as_mut()); + process_block(iv, iv); + } + + unsafe { + let c = ciphertext.read(); + plaintext.write(self.iv[offset] ^ c); + self.iv[offset] = c; + } + offset = (offset + 1) % BLOCK_SIZE; + }); + self.offset = offset; + } + + #[cfg(aes_dma)] + pub(super) fn flush_encrypt(&mut self, buffer: UnsafeCryptoBuffers) -> usize { + let mut offset = self.offset; + buffer + .first_n((BLOCK_SIZE - offset) % BLOCK_SIZE) + .for_data_chunks(1, |plaintext, ciphertext, _| { + unsafe { + self.iv[offset] ^= plaintext.read(); + ciphertext.write(self.iv[offset]); + } + offset += 1; + }); + let flushed = offset - self.offset; + self.offset = offset % BLOCK_SIZE; + flushed + } + + #[cfg(aes_dma)] + pub(super) fn flush_decrypt(&mut self, buffer: UnsafeCryptoBuffers) -> usize { + let mut offset = self.offset; + buffer + .first_n((BLOCK_SIZE - offset) % BLOCK_SIZE) + .for_data_chunks(1, |ciphertext, plaintext, _| { + unsafe { + let c = ciphertext.read(); + plaintext.write(self.iv[offset] ^ c); + self.iv[offset] = c; + } + offset += 1; + }); + let flushed = offset - self.offset; + self.offset = offset % BLOCK_SIZE; + flushed + } +} + +// Utilities + +impl UnsafeCryptoBuffers { + fn for_data_chunks( + self, + chunk_size: usize, + mut cb: impl FnMut(NonNull, NonNull, usize), + ) { + let input = pointer_chunks(self.input, chunk_size); + let output = pointer_chunks(self.output, chunk_size); + + for (input, output, len) in input + .zip(output) + .map(|((input, len), (output, _))| (input, output, len)) + { + cb(input, output, len) + } + } + + #[cfg(aes_dma)] + fn first_n(self, n: usize) -> UnsafeCryptoBuffers { + let len = n.min(self.input.len()); + Self { + input: NonNull::slice_from_raw_parts(self.input.cast(), len), + output: NonNull::slice_from_raw_parts(self.output.cast(), len), + } + } +} + +fn pointer_chunks( + ptr: NonNull<[T]>, + chunk: usize, +) -> impl Iterator, usize)> + Clone { + let mut len = ptr.len(); + let mut ptr = ptr.cast::(); + core::iter::from_fn(move || { + let advance = if len > chunk { + chunk + } else if len > 0 { + len + } else { + return None; + }; + + let retval = (ptr, advance); + + unsafe { ptr = ptr.add(advance) }; + len -= advance; + Some(retval) + }) +} + +fn xor_into(mut out: NonNull, mut a: NonNull, len: usize) { + let end = unsafe { out.add(len) }; + while out < end { + unsafe { + out.write(out.read() ^ a.read()); + a = a.add(1); + out = out.add(1); + } + } +} + +fn copy(out: NonNull, from: NonNull, len: usize) { + unsafe { + out.copy_from(from, len); + } +} diff --git a/esp-hal/src/aes/mod.rs b/esp-hal/src/aes/mod.rs new file mode 100644 index 00000000000..a3436314ae5 --- /dev/null +++ b/esp-hal/src/aes/mod.rs @@ -0,0 +1,1677 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Advanced Encryption Standard (AES). +//! +//! ## Overview +//! +//! The AES accelerator is a hardware device that speeds up computation +//! using AES algorithm significantly, compared to AES algorithms implemented +//! solely in software. The AES accelerator has two working modes, which are +//! Typical AES and AES-DMA. +//! +//! ## Configuration +//! +//! The AES peripheral can be configured to encrypt or decrypt data using +//! different encryption/decryption modes. +//! +//! When using AES-DMA, the peripheral can be configured to use different block +//! cipher modes such as ECB, CBC, OFB, CTR, CFB8, and CFB128. +//! +//! ## Examples +//! +//! ### Encrypting and decrypting a message +//! +//! Simple example of encrypting and decrypting a message using AES-128: +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::aes::{Aes, Operation}; +//! # let keytext = b"SUp4SeCp@sSw0rd"; +//! # let plaintext = b"message"; +//! # let mut keybuf = [0_u8; 16]; +//! # keybuf[..keytext.len()].copy_from_slice(keytext); +//! # +//! let mut block = [0_u8; 16]; +//! block[..plaintext.len()].copy_from_slice(plaintext); +//! +//! let mut aes = Aes::new(peripherals.AES); +//! aes.encrypt(&mut block, keybuf); +//! +//! // The encryption happens in-place, so the ciphertext is in `block` +//! +//! aes.decrypt(&mut block, keybuf); +//! +//! // The decryption happens in-place, so the plaintext is in `block` +//! # {after_snippet} +//! ``` + +use core::ptr::NonNull; + +use crate::{ + aes::cipher_modes::{CryptoBuffers, UnsafeCryptoBuffers}, + pac, + peripherals::AES, + system::GenericPeripheralGuard, +}; + +pub mod cipher_modes; + +for_each_aes_key_length! { + ($len:literal) => { + // Implementing From for easy conversion from array to Key enum. + impl From<[u8; $len / 8]> for Key { + fn from(key: [u8; $len / 8]) -> Self { + paste::paste! { + Key::[](key) + } + } + } + }; + + (bits $( ($len:literal) ),*) => { + paste::paste! { + /// Represents the various key sizes allowed for AES encryption and decryption. + pub enum Key { + $( + #[doc = concat!(stringify!($len), "-bit AES key")] + []([u8; $len / 8]), + )* + } + + impl Key { + /// Returns a slice representation of the AES key. + fn as_slice(&self) -> &[u8] { + match self { + $( + Self::[](key) => key.as_ref(), + )* + } + } + + fn copy(&self) -> Self { + match self { + $( + Self::[](key) => Self::[](*key), + )* + } + } + } + + impl Drop for Key { + fn drop(&mut self) { + use core::mem::MaybeUninit; + unsafe { (self as *mut Self).cast::>().write_volatile(MaybeUninit::zeroed()) }; + } + } + } + }; + + (modes $(($bits:literal, $encrypt:literal, $decrypt:literal)),*) => { + paste::paste! { + /// Defines the operating modes for AES encryption and decryption. + #[repr(C)] + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + enum Mode { + $( + [] = $encrypt, + [] = $decrypt, + )* + } + + impl Key { + fn encrypt_mode(&self) -> Mode { + match self { + $(Self::[](_) => Mode::[],)* + } + } + + fn decrypt_mode(&self) -> Mode { + match self { + $(Self::[](_) => Mode::[],)* + } + } + } + } + }; +} + +/// The possible AES operations. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Operation { + /// Produce ciphertext from plaintext + Encrypt, + + /// Produce plaintext from ciphertext + Decrypt, +} + +/// AES peripheral container +pub struct Aes<'d> { + aes: AES<'d>, + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Aes as u8 }>, +} + +impl<'d> Aes<'d> { + /// Constructs a new `Aes` instance. + pub fn new(aes: AES<'d>) -> Self { + let guard = GenericPeripheralGuard::new(); + + #[cfg_attr(not(aes_dma), expect(unused_mut))] + let mut this = Self { aes, _guard: guard }; + + #[cfg(aes_dma)] + this.write_dma(false); + + this + } + + fn regs(&self) -> &pac::aes::RegisterBlock { + self.aes.register_block() + } + + /// Configures how the state matrix would be laid out + #[cfg(aes_endianness_configurable)] + pub fn write_endianness( + &mut self, + input_text_word_endianess: Endianness, + input_text_byte_endianess: Endianness, + output_text_word_endianess: Endianness, + output_text_byte_endianess: Endianness, + key_word_endianess: Endianness, + key_byte_endianess: Endianness, + ) { + let mut to_write = 0_u32; + to_write |= key_byte_endianess as u32; + to_write |= (key_word_endianess as u32) << 1; + to_write |= (input_text_byte_endianess as u32) << 2; + to_write |= (input_text_word_endianess as u32) << 3; + to_write |= (output_text_byte_endianess as u32) << 4; + to_write |= (output_text_word_endianess as u32) << 5; + self.regs().endian().write(|w| unsafe { w.bits(to_write) }); + } + + fn start(&self) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().start().write(|w| w.start().set_bit()); + } else { + self.regs().trigger().write(|w| w.trigger().set_bit()); + } + } + } + + fn is_idle(&mut self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().idle().read().idle().bit_is_set() + } else { + self.regs().state().read().state().bits() == 0 + } + } + } + + fn write_key(&mut self, input: &[u8]) { + for (i, word) in read_words(input).enumerate() { + self.regs().key(i).write(|w| unsafe { w.bits(word) }); + } + } + + fn write_block(&mut self, block: &[u8]) { + for (i, word) in read_words(block).enumerate() { + cfg_if::cfg_if! { + if #[cfg(aes_has_split_text_registers)] { + self.regs().text_in(i).write(|w| unsafe { w.bits(word) }); + } else { + self.regs().text(i).write(|w| unsafe { w.bits(word) }); + } + } + } + } + + fn read_block(&self, block: &mut [u8]) { + cfg_if::cfg_if! { + if #[cfg(aes_has_split_text_registers)] { + write_words(block, |i| self.regs().text_out(i).read().bits()); + } else { + write_words(block, |i| self.regs().text(i).read().bits()); + } + } + } + + fn write_mode(&self, mode: Mode) { + self.regs().mode().write(|w| unsafe { w.bits(mode as _) }); + } + + #[cfg(aes_dma)] + fn write_dma(&mut self, enable_dma: bool) { + self.regs() + .dma_enable() + .write(|w| w.dma_enable().bit(enable_dma)); + } + + fn process(&mut self, block: &mut [u8; 16], mode: Mode, key: Key) { + self.write_key(key.as_slice()); + self.write_mode(mode); + self.write_block(block); + self.start(); + while !(self.is_idle()) {} + self.read_block(block); + } + + /// Encrypts the given buffer with the given key. + pub fn encrypt(&mut self, block: &mut [u8; 16], key: impl Into) { + let key = key.into(); + let mode = key.encrypt_mode(); + self.process(block, mode, key) + } + + /// Decrypts the given buffer with the given key. + pub fn decrypt(&mut self, block: &mut [u8; 16], key: impl Into) { + let key = key.into(); + let mode = key.decrypt_mode(); + self.process(block, mode, key) + } + + /// Encrypts/Decrypts the given buffer based on `mode` parameter + fn process_work_item(&mut self, work_item: &mut AesOperation) { + // Note that we can't just create slices out of the input and output buffers, because they + // may alias (when encrypting/decrypting data in place). + + let slice = work_item.key.as_slice(); + self.write_key(slice); + self.write_mode(unsafe { + work_item + .cipher_mode + .as_ref() + .software_operating_mode(work_item.mode, &work_item.key) + }); + + let process_block = |input: NonNull<[u8]>, mut output: NonNull<[u8]>| { + unsafe { self.write_block(input.as_ref()) }; + self.start(); + while !self.is_idle() {} + unsafe { self.read_block(output.as_mut()) }; + }; + + // Safety: the reference to the algorithm state is only held for the duration of the + // operation. + match unsafe { work_item.cipher_mode.as_mut() } { + CipherState::Ecb(algo) => algo.encrypt_decrypt(work_item.buffers, process_block), + CipherState::Cbc(algo) => { + if work_item.mode == Operation::Encrypt { + algo.encrypt(work_item.buffers, process_block); + } else { + algo.decrypt(work_item.buffers, process_block); + } + } + CipherState::Ofb(algo) => algo.encrypt_decrypt(work_item.buffers, process_block), + CipherState::Ctr(algo) => algo.encrypt_decrypt(work_item.buffers, process_block), + CipherState::Cfb8(algo) => { + if work_item.mode == Operation::Encrypt { + algo.encrypt(work_item.buffers, process_block) + } else { + algo.decrypt(work_item.buffers, process_block) + } + } + CipherState::Cfb128(algo) => { + if work_item.mode == Operation::Encrypt { + algo.encrypt(work_item.buffers, process_block) + } else { + algo.decrypt(work_item.buffers, process_block) + } + } + } + } +} + +/// Data endianness +#[cfg(aes_endianness_configurable)] +pub enum Endianness { + /// Big endian (most-significant byte at the smallest address) + BigEndian = 1, + /// Little endian (least-significant byte at the smallest address) + LittleEndian = 0, +} + +/// Provides DMA (Direct Memory Access) support for AES operations. +/// +/// This module enhances the AES capabilities by utilizing DMA to handle data +/// transfer, which can significantly speed up operations when dealing with +/// large data volumes. It supports various cipher modes such as ECB, CBC, OFB, +/// CTR, CFB8, and CFB128. +#[cfg(aes_dma)] +pub mod dma { + use core::{mem::ManuallyDrop, ptr::NonNull}; + + use procmacros::{handler, ram}; + + #[cfg(psram_dma)] + use crate::dma::ManualWritebackBuffer; + use crate::{ + Blocking, + aes::{ + AES_WORK_QUEUE, + AesOperation, + BLOCK_SIZE, + CipherState, + Key, + Mode, + Operation, + UnsafeCryptoBuffers, + cipher_modes, + read_words, + }, + dma::{ + Channel, + DmaChannelFor, + DmaDescriptor, + DmaError, + DmaPeripheral, + DmaRxBuffer, + DmaTxBuffer, + NoBuffer, + PeripheralDmaChannel, + prepare_for_rx, + prepare_for_tx, + }, + peripherals::AES, + system::{Peripheral, PeripheralClockControl}, + work_queue::{Poll, Status, VTable, WorkQueueDriver}, + }; + + /// Specifies the block cipher modes available for AES operations. + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum CipherMode { + /// Electronic Codebook Mode + #[cfg(aes_dma_mode_ecb)] + Ecb = 0, + /// Cipher Block Chaining Mode + #[cfg(aes_dma_mode_cbc)] + Cbc = 1, + /// Output Feedback Mode + #[cfg(aes_dma_mode_ofb)] + Ofb = 2, + /// Counter Mode. + #[cfg(aes_dma_mode_ctr)] + Ctr = 3, + /// Cipher Feedback Mode with 8-bit shifting. + #[cfg(aes_dma_mode_cfb8)] + Cfb8 = 4, + /// Cipher Feedback Mode with 128-bit shifting. + #[cfg(aes_dma_mode_cfb128)] + Cfb128 = 5, + // TODO: GCM needs different handling, not supported yet + } + + // If we can process from PSRAM, we need 2 extra descriptors. One will store the unaligned head, + // one will store the unaligned tail. + const OUT_DESCR_COUNT: usize = 1 + 2 * cfg!(psram_dma) as usize; + + /// A DMA capable AES instance. + #[instability::unstable] + pub struct AesDma<'d> { + /// The underlying [`Aes`](super::Aes) driver + pub aes: super::Aes<'d>, + + channel: Channel>>, + } + + impl<'d> super::Aes<'d> { + /// Enable DMA for the current instance of the AES driver + pub fn with_dma(self, channel: impl DmaChannelFor>) -> AesDma<'d> { + let channel = Channel::new(channel.degrade()); + channel.runtime_ensure_compatible(&self.aes); + AesDma { aes: self, channel } + } + } + + impl core::fmt::Debug for AesDma<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("AesDma").finish() + } + } + + impl<'d> AesDma<'d> { + fn write_key(&mut self, key: &Key) { + let key = key.as_slice(); + self.aes.write_key(key); + } + + fn write_iv(&mut self, iv: &[u8; BLOCK_SIZE]) { + for (word, reg) in read_words(iv).zip(self.aes.regs().iv_mem_iter()) { + reg.write(|w| unsafe { w.bits(word) }); + } + } + + fn read_iv(&self, iv: &mut [u8; BLOCK_SIZE]) { + iv[0..4].copy_from_slice(&self.aes.regs().iv_mem(0).read().bits().to_le_bytes()); + iv[4..8].copy_from_slice(&self.aes.regs().iv_mem(1).read().bits().to_le_bytes()); + iv[8..12].copy_from_slice(&self.aes.regs().iv_mem(2).read().bits().to_le_bytes()); + iv[12..16].copy_from_slice(&self.aes.regs().iv_mem(3).read().bits().to_le_bytes()); + } + + fn start_dma_transfer( + mut self, + number_of_blocks: usize, + mut output: RXBUF, + mut input: TXBUF, + mode: Mode, + cipher_mode: CipherMode, + key: &Key, + ) -> Result, (DmaError, Self, RXBUF, TXBUF)> + where + TXBUF: DmaTxBuffer, + RXBUF: DmaRxBuffer, + { + let peri = self.dma_peripheral(); + + if let Err(error) = unsafe { self.channel.tx.prepare_transfer(peri, &mut input) } + .and_then(|_| unsafe { self.channel.rx.prepare_transfer(peri, &mut output) }) + // Start them together, to avoid the latter prepare discarding data from FIFOs. + .and_then(|_| self.channel.tx.start_transfer()) + .and_then(|_| self.channel.rx.start_transfer()) + { + return Err((error, self, output, input)); + } + + self.enable_dma(true); + self.listen(); + + self.aes.write_mode(mode); + self.write_key(key); + + self.set_cipher_mode(cipher_mode); + self.set_num_block(number_of_blocks as u32); + + self.start_transform(); + + Ok(AesTransfer { + aes_dma: ManuallyDrop::new(self), + rx_view: ManuallyDrop::new(output.into_view()), + tx_view: ManuallyDrop::new(input.into_view()), + }) + } + + /// Perform a DMA transfer. + /// + /// This will return a [AesTransfer]. + pub fn process( + mut self, + number_of_blocks: usize, + output: RXBUF, + input: TXBUF, + mode: Operation, + cipher_state: &DmaCipherState, + key: K, + ) -> Result, (DmaError, Self, RXBUF, TXBUF)> + where + K: Into, + TXBUF: DmaTxBuffer, + RXBUF: DmaRxBuffer, + { + // AES has to be restarted after each calculation + self.reset_aes(); + + // This unwrap is fine, the cipher state can only be constructed from supported + // modes of operation. + let cipher_mode = unwrap!(cipher_state.state.hardware_cipher_mode()); + + let key = key.into(); + cipher_state.state.write_state(&mut self); + + let mode = cipher_state.state.hardware_operating_mode(mode, &key); + + self.start_dma_transfer(number_of_blocks, output, input, mode, cipher_mode, &key) + } + + fn reset_aes(&self) { + PeripheralClockControl::reset(Peripheral::Aes); + } + + fn dma_peripheral(&self) -> DmaPeripheral { + DmaPeripheral::Aes + } + + fn enable_dma(&self, enable: bool) { + self.aes + .regs() + .dma_enable() + .write(|w| w.dma_enable().bit(enable)); + } + + fn listen(&self) { + self.aes.regs().int_ena().write(|w| w.int_ena().set_bit()); + } + + fn clear_interrupt(&self) { + self.aes.regs().int_clr().write(|w| w.int_clr().set_bit()); + } + + fn set_cipher_mode(&self, mode: CipherMode) { + self.aes + .regs() + .block_mode() + .modify(|_, w| unsafe { w.block_mode().bits(mode as u8) }); + + // FIXME + if mode == CipherMode::Ctr { + self.aes + .regs() + .inc_sel() + .modify(|_, w| w.inc_sel().clear_bit()); + } + } + + fn start_transform(&self) { + self.aes.start(); + } + + fn finish_transform(&self) { + self.aes.regs().dma_exit().write(|w| w.dma_exit().set_bit()); + self.enable_dma(false); + self.reset_aes(); + } + + fn set_num_block(&self, block: u32) { + self.aes + .regs() + .block_num() + .modify(|_, w| unsafe { w.block_num().bits(block) }); + } + + fn is_done(&self) -> bool { + const DMA_STATUS_DONE: u8 = 2; + // TODO: PAC should provide the variants + self.aes.regs().state().read().state().bits() == DMA_STATUS_DONE + } + } + + /// Represents an ongoing (or potentially stopped) transfer with the Aes. + #[instability::unstable] + pub struct AesTransfer<'d, RX: DmaRxBuffer, TX: DmaTxBuffer> { + aes_dma: ManuallyDrop>, + rx_view: ManuallyDrop, + tx_view: ManuallyDrop, + } + + impl<'d, RX: DmaRxBuffer, TX: DmaTxBuffer> AesTransfer<'d, RX, TX> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.aes_dma.is_done() + } + + /// Waits for the transfer to finish and returns the peripheral and + /// buffers. + pub fn wait(mut self) -> (AesDma<'d>, RX::Final, TX::Final) { + while !self.is_done() {} + while !self.aes_dma.channel.rx.is_done() {} + + // Stop the DMA as it doesn't know that the aes has stopped. + self.aes_dma.channel.rx.stop_transfer(); + self.aes_dma.channel.tx.stop_transfer(); + + self.aes_dma.finish_transform(); + + unsafe { + let aes_dma = ManuallyDrop::take(&mut self.aes_dma); + let rx_view = ManuallyDrop::take(&mut self.rx_view); + let tx_view = ManuallyDrop::take(&mut self.tx_view); + + core::mem::forget(self); + + (aes_dma, RX::from_view(rx_view), TX::from_view(tx_view)) + } + } + + /// Provides shared access to the DMA rx buffer view. + pub fn rx_view(&self) -> &RX::View { + &self.rx_view + } + + /// Provides exclusive access to the DMA rx buffer view. + pub fn rx_view_mut(&mut self) -> &mut RX::View { + &mut self.rx_view + } + + /// Provides shared access to the DMA tx buffer view. + pub fn tx_view(&self) -> &TX::View { + &self.tx_view + } + + /// Provides exclusive access to the DMA tx buffer view. + pub fn tx_view_mut(&mut self) -> &mut TX::View { + &mut self.tx_view + } + } + + impl Drop for AesTransfer<'_, RX, TX> { + fn drop(&mut self) { + // Stop the DMA to prevent further memory access. + self.aes_dma.channel.rx.stop_transfer(); + self.aes_dma.channel.tx.stop_transfer(); + + // SAFETY: This is Drop, we know that self.aes_dma and self.buf_view + // won't be touched again. + unsafe { + ManuallyDrop::drop(&mut self.aes_dma); + } + let rx_view = unsafe { ManuallyDrop::take(&mut self.rx_view) }; + let tx_view = unsafe { ManuallyDrop::take(&mut self.tx_view) }; + let _ = RX::from_view(rx_view); + let _ = TX::from_view(tx_view); + } + } + + const AES_DMA_VTABLE: VTable = VTable { + post: |driver, item| { + let driver = unsafe { AesDmaBackend::from_raw(driver) }; + driver.start_processing(item); + Some(Poll::Pending(false)) + }, + poll: |driver, item| { + let driver = unsafe { AesDmaBackend::from_raw(driver) }; + driver.poll_status(item) + }, + cancel: |_driver, _item| { + // We can't (shouldn't) abort an in-progress computation, or we may corrupt the block + // cipher's state. + }, + stop: |driver| { + // Drop the AES driver to conserve power when there is nothing to do (or when the driver + // was stopped). + let driver = unsafe { AesDmaBackend::from_raw(driver) }; + driver.deinitialize() + }, + }; + + enum DriverState<'d> { + None, + Idle(AesDma<'d>), + WaitingForDma { + transfer: AesTransfer<'d, NoBuffer, NoBuffer>, + remaining_bytes: usize, + }, + } + + #[procmacros::doc_replace( + "dma_channel" => { + cfg(esp32s2) => "DMA_CRYPTO", + _ => "DMA_CH0" + } + )] + /// DMA-enabled AES processing backend. + /// + /// The backend will try its best to use hardware acceleration as much as possible. It will + /// fall back to CPU-driven processing (equivalent to [`AesBackend`](super::AesBackend)) in some + /// cases, including: + /// + /// - When the block cipher is not implemented in hardware. + /// - When the data is not correctly aligned to the needs of the hardware (e.g. when using a + /// stream cipher mode, the data length is not an integer multiple of 16 bytes). + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::{AesContext, Operation, cipher_modes::Ecb, dma::AesDmaBackend}; + /// + /// let mut aes = AesDmaBackend::new(peripherals.AES, peripherals.__dma_channel__); + /// // Start the backend, which allows processing AES operations. + /// let _backend = aes.start(); + /// + /// // Create a new context with a 128-bit key. The context will use the ECB block cipher mode. + /// // The key length must be supported by the hardware. + /// let mut ecb_encrypt = AesContext::new(Ecb, Operation::Encrypt, *b"SUp4SeCp@sSw0rd\0"); + /// + /// // Process a block of data in this context. The ECB mode of operation requires that + /// // the length of the data is a multiple of the block (16 bytes) size. + /// let input_buffer: [u8; 16] = *b"message\0\0\0\0\0\0\0\0\0"; + /// let mut output_buffer: [u8; 16] = [0; 16]; + /// + /// let operation_handle = ecb_encrypt + /// .process(&input_buffer, &mut output_buffer) + /// .unwrap(); + /// operation_handle.wait_blocking(); + /// + /// // output_buffer now contains the ciphertext + /// assert_eq!( + /// output_buffer, + /// [ + /// 0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a, + /// 0x31, 0x96, + /// ] + /// ); + /// # + /// # {after_snippet} + /// ``` + pub struct AesDmaBackend<'d> { + peri: AES<'d>, + dma: PeripheralDmaChannel>, + state: DriverState<'d>, + + #[cfg(psram_dma)] + unaligned_data_buffers: [Option; 2], + input_descriptors: [DmaDescriptor; 1], + output_descriptors: [DmaDescriptor; OUT_DESCR_COUNT], + } + + // The DMA descriptors prevent auto-implementing Sync and Send, but they can be treated as Send + // and Sync because we only access them in a critical section (around the work queue + // operations), and only when the DMA is not actively using them. `unaligned_data_buffers` + // contain pointers to AesContext data, and are only accessed when the associated data is + // handled by the work queue. Other parts of the backend are Send and Sync automatically. + unsafe impl Sync for AesDmaBackend<'_> {} + unsafe impl Send for AesDmaBackend<'_> {} + + impl<'d> AesDmaBackend<'d> { + #[procmacros::doc_replace( + "dma_channel" => { + cfg(esp32s2) => "DMA_CRYPTO", + _ => "DMA_CH0" + } + )] + /// Creates a new DMA-enabled AES backend. + /// + /// The backend needs to be [`start`][Self::start]ed before it can execute AES operations. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::dma::AesDmaBackend; + /// + /// let mut aes = AesDmaBackend::new(peripherals.AES, peripherals.__dma_channel__); + /// # {after_snippet} + /// ``` + pub fn new(aes: AES<'d>, dma: impl DmaChannelFor>) -> Self { + Self { + peri: aes, + dma: dma.degrade(), + state: DriverState::None, + + #[cfg(psram_dma)] + unaligned_data_buffers: [const { None }; 2], + input_descriptors: [DmaDescriptor::EMPTY; 1], + output_descriptors: [DmaDescriptor::EMPTY; OUT_DESCR_COUNT], + } + } + + #[procmacros::doc_replace( + "dma_channel" => { + cfg(esp32s2) => "DMA_CRYPTO", + _ => "DMA_CH0" + } + )] + /// Registers the DMA-driven AES driver to process AES operations. + /// + /// The driver stops operating when the returned object is dropped. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::dma::AesDmaBackend; + /// + /// let mut aes = AesDmaBackend::new(peripherals.AES, peripherals.__dma_channel__); + /// let _handle = aes.start(); + /// # {after_snippet} + /// ``` + pub fn start(&mut self) -> AesDmaWorkQueueDriver<'_, 'd> { + AesDmaWorkQueueDriver { + _inner: WorkQueueDriver::new(self, AES_DMA_VTABLE, &super::AES_WORK_QUEUE), + } + } + + // WorkQueue callbacks. They may run in any context. + + unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self { + unsafe { ptr.cast::>().as_mut() } + } + + fn start_processing(&mut self, item: &mut AesOperation) -> Poll { + let driver = match core::mem::replace(&mut self.state, DriverState::None) { + DriverState::None => { + let peri = unsafe { self.peri.clone_unchecked() }; + let dma = unsafe { self.dma.clone_unchecked() }; + let driver = super::Aes::new(peri).with_dma(dma); + + driver.aes.aes.bind_peri_interrupt(interrupt_handler); + + driver + } + DriverState::Idle(aes_dma) => aes_dma, + _ => unreachable!(), + }; + + // There are some constraints that make us (partially) fall back to CPU-driven mode: + // - The algo isn't implemented in hardware. Not much to do here, we process the whole + // data using the CPU. + // - There is data stuck in the cipher state. + // - In this case we need to flush the data before we can start using the DMA. + // - The data alignment isn't appropriate to its location (PSRAM) + // - The DMA can read (transmit) any number of bytes from any address (in theory), but + // it can't write arbitrarily to PSRAM. We should be able to split the write + // transfer into two parts, and write the unaligned bytes into internal memory using + // the DMA, then copy the data out to PSRAM using the CPU at the end. + // - The data length (after all the above) is not a multiple of the block length + // - We process as many blocks as we can using the DMA, then use the CPU for the + // remainder. + + if unsafe { !item.cipher_mode.as_ref().implemented_in_hardware() } { + // Algo is either not implemented in hardware, or the data is not accessible by DMA. + self.process_with_cpu(driver, item); + return Poll::Ready(Status::Completed); + } + + if !crate::soc::is_valid_memory_address(item.buffers.input.addr().get()) { + unsafe { + // Safety: + // We've verified when constructing the buffer that input and output are the + // same length. The CryptoBuffers constructor also ensures that output is + // mutable and therefore is in RAM. If input is not, the pointers + // don't overlap. + // A buffer created via `CryptoBuffers::new_in_place` will not reach here + // because it takes a `&mut` buffer, which is always in RAM. + item.buffers.output.cast::().copy_from_nonoverlapping( + item.buffers.input.cast::(), + item.buffers.input.len(), + ); + } + // We've copied the data, now overwrite the pointer to make this an in-place + // operation. + item.buffers.input = item.buffers.output; + } + + // Flush available bytes: + let flushed = unsafe { item.cipher_mode.as_mut().flush(item.buffers, item.mode) }; + if flushed == item.buffers.input.len() { + // No more data to process + self.state = DriverState::Idle(driver); + return Poll::Ready(Status::Completed); + } + + // Process the remaining data with DMA: + let mut new_item = AesOperation { + mode: item.mode, + cipher_mode: item.cipher_mode, + buffers: unsafe { item.buffers.byte_add(flushed) }, + key: item.key.copy(), + }; + + // If there is enough data for the DMA, set it up: + if let Err(driver) = self.process_with_dma(driver, &mut new_item) { + // Otherwise, process the remaining data with CPU. + self.process_with_cpu(driver, &mut new_item); + return Poll::Ready(Status::Completed); + } + + Poll::Pending(false) + } + + fn poll_status(&mut self, item: &mut AesOperation) -> Poll { + match &self.state { + DriverState::WaitingForDma { transfer, .. } if !transfer.is_done() => { + Poll::Pending(false) + } + + DriverState::WaitingForDma { .. } => { + let DriverState::WaitingForDma { + transfer, + remaining_bytes, + } = core::mem::replace(&mut self.state, DriverState::None) + else { + unreachable!() + }; + + let (driver, _, _) = transfer.wait(); + unsafe { item.cipher_mode.as_mut() }.read_state(&driver); + + driver.clear_interrupt(); + + // Write out PSRAM data if needed: + #[cfg(psram_dma)] + for buffer in self.unaligned_data_buffers.iter_mut() { + // Avoid copying the write_back buffer + if let Some(buffer) = buffer.as_ref() { + buffer.write_back(); + } + *buffer = None; + } + #[cfg(psram_dma)] + if crate::psram::psram_range().contains(&item.buffers.output.addr().get()) { + unsafe { + crate::soc::cache_writeback_addr( + item.buffers.output.addr().get() as u32, + item.buffers.output.len() as u32, + ); + } + } + + // Now process the remainder: + if remaining_bytes > 0 { + let mut temp_item = AesOperation { + mode: item.mode, + cipher_mode: item.cipher_mode, + buffers: unsafe { + item.buffers + .byte_add(item.buffers.input.len() - remaining_bytes) + }, + key: item.key.copy(), + }; + + // If there is enough data for the DMA, set it up again: + if let Err(driver) = self.process_with_dma(driver, &mut temp_item) { + // Otherwise, process the remaining data with CPU. + self.process_with_cpu(driver, &mut temp_item); + } else { + return Poll::Pending(false); + } + } + + Poll::Ready(Status::Completed) + } + _ => unreachable!(), + } + } + + fn process_with_cpu(&mut self, mut driver: AesDma<'d>, work_item: &mut AesOperation) { + driver.aes.process_work_item(work_item); + self.state = DriverState::Idle(driver); + } + + fn process_with_dma( + &mut self, + mut driver: AesDma<'d>, + work_item: &mut AesOperation, + ) -> Result<(), AesDma<'d>> { + let input_len = work_item.buffers.input.len(); + let (input_buffer, data_len) = unsafe { + // This unwrap is infallible as AES-DMA devices don't have TX DMA alignment + // requirements. + unwrap!(prepare_for_tx( + &mut self.input_descriptors, + work_item.buffers.input, + BLOCK_SIZE, + )) + }; + + let number_of_blocks = data_len / BLOCK_SIZE; + if number_of_blocks == 0 { + // DMA can't do anything. + return Err(driver); + } + + let (output_buffer, rx_data_len) = unsafe { + prepare_for_rx( + &mut self.output_descriptors, + #[cfg(psram_dma)] + &mut self.unaligned_data_buffers, + // Truncate data based on how much the TX buffer can read. + NonNull::slice_from_raw_parts(work_item.buffers.output.cast::(), data_len), + ) + }; + + debug_assert_eq!(rx_data_len, data_len); + + let cipher_state = unsafe { work_item.cipher_mode.as_ref() }; + cipher_state.write_state(&mut driver); + + let mode = cipher_state.hardware_operating_mode(work_item.mode, &work_item.key); + // This unwrap is fine, this function is not called for modes of operation not supported + // by the hardware. + let cipher_mode = unwrap!(cipher_state.hardware_cipher_mode()); + + let Ok(transfer) = driver.start_dma_transfer( + number_of_blocks, + output_buffer, + input_buffer, + mode, + cipher_mode, + &work_item.key, + ) else { + panic!() + }; + + self.state = DriverState::WaitingForDma { + transfer, + remaining_bytes: input_len - data_len, + }; + + Ok(()) + } + + fn deinitialize(&mut self) { + self.state = DriverState::None; + } + } + + #[handler] + #[ram] + fn interrupt_handler() { + AES_WORK_QUEUE.process(); + } + + /// An active work queue driver. + /// + /// This object must be kept around, otherwise AES operations will never complete. + pub struct AesDmaWorkQueueDriver<'t, 'd> { + _inner: WorkQueueDriver<'t, AesDmaBackend<'d>, AesOperation>, + } + + /// The state of block ciphers that the AES hardware implements. + #[derive(Clone)] + pub struct DmaCipherState { + state: CipherState, + } + + impl DmaCipherState { + /// Saves the block cipher state in memory so that it can be restored by a later operation. + pub fn save_state(&mut self, aes_dma: &AesDma<'_>) { + self.state.read_state(aes_dma); + } + } + + #[cfg(aes_dma_mode_ecb)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Ecb) -> Self { + Self { + state: CipherState::Ecb(value), + } + } + } + + #[cfg(aes_dma_mode_cbc)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Cbc) -> Self { + Self { + state: CipherState::Cbc(value), + } + } + } + + #[cfg(aes_dma_mode_ofb)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Ofb) -> Self { + Self { + state: CipherState::Ofb(value), + } + } + } + + #[cfg(aes_dma_mode_ctr)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Ctr) -> Self { + Self { + state: CipherState::Ctr(value), + } + } + } + + #[cfg(aes_dma_mode_cfb8)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Cfb8) -> Self { + Self { + state: CipherState::Cfb8(value), + } + } + } + + #[cfg(aes_dma_mode_cfb128)] + impl From for DmaCipherState { + fn from(value: cipher_modes::Cfb128) -> Self { + Self { + state: CipherState::Cfb128(value), + } + } + } + + impl CipherState { + fn hardware_cipher_mode(&self) -> Option { + #[allow(unreachable_patterns)] + match self { + #[cfg(aes_dma_mode_ecb)] + Self::Ecb(_) => Some(CipherMode::Ecb), + #[cfg(aes_dma_mode_cbc)] + Self::Cbc(_) => Some(CipherMode::Cbc), + #[cfg(aes_dma_mode_ofb)] + Self::Ofb(_) => Some(CipherMode::Ofb), + #[cfg(aes_dma_mode_ctr)] + Self::Ctr(_) => Some(CipherMode::Ctr), + #[cfg(aes_dma_mode_cfb8)] + Self::Cfb8(_) => Some(CipherMode::Cfb8), + #[cfg(aes_dma_mode_cfb128)] + Self::Cfb128(_) => Some(CipherMode::Cfb128), + // TODO: GCM + _ => None, + } + } + + fn implemented_in_hardware(&self) -> bool { + self.hardware_cipher_mode().is_some() + } + + fn hardware_operating_mode(&self, operation: Operation, key: &Key) -> Mode { + if operation == Operation::Encrypt { + key.encrypt_mode() + } else { + key.decrypt_mode() + } + } + + fn flush(&mut self, buffers: UnsafeCryptoBuffers, mode: Operation) -> usize { + match self { + // These operate on complete blocks, nothing to flush: + Self::Ecb(_) | Self::Cbc(_) => 0, + + // CFB8 shifts bytes but has no internal state other than IV: + Self::Cfb8(_) => 0, + + // These modes may have bytes to output: + Self::Ofb(ofb) => ofb.flush(buffers), + Self::Ctr(ctr) => ctr.flush(buffers), + Self::Cfb128(cfb128) => { + if mode == Operation::Encrypt { + cfb128.flush_encrypt(buffers) + } else { + cfb128.flush_decrypt(buffers) + } + } + } + } + + fn write_state(&self, aes: &mut AesDma<'_>) { + match self { + CipherState::Ecb(_ecb) => {} + CipherState::Cbc(cbc) => aes.write_iv(&cbc.iv), + CipherState::Ofb(ofb) => aes.write_iv(&ofb.iv), + CipherState::Ctr(ctr) => aes.write_iv(&ctr.nonce), + CipherState::Cfb8(cfb8) => aes.write_iv(&cfb8.iv), + CipherState::Cfb128(cfb128) => aes.write_iv(&cfb128.iv), + } + } + + fn read_state(&mut self, aes: &AesDma<'_>) { + match self { + CipherState::Ecb(_ecb) => {} + CipherState::Cbc(cbc) => aes.read_iv(&mut cbc.iv), + CipherState::Ofb(ofb) => aes.read_iv(&mut ofb.iv), + CipherState::Ctr(ctr) => aes.read_iv(&mut ctr.nonce), + CipherState::Cfb8(cfb8) => aes.read_iv(&mut cfb8.iv), + CipherState::Cfb128(cfb128) => aes.read_iv(&mut cfb128.iv), + } + } + } +} + +use crate::work_queue::{ + Handle, + Poll, + Status, + VTable, + WorkQueue, + WorkQueueDriver, + WorkQueueFrontend, +}; + +/// The stored state of various block cipher modes. +#[derive(Clone)] +#[non_exhaustive] +pub enum CipherState { + /// Electronic Codebook Mode + Ecb(cipher_modes::Ecb), + /// Cipher Block Chaining Mode + Cbc(cipher_modes::Cbc), + /// Output Feedback Mode + Ofb(cipher_modes::Ofb), + /// Counter Mode + Ctr(cipher_modes::Ctr), + /// Cipher Feedback Mode with 8-bit shifting. + Cfb8(cipher_modes::Cfb8), + /// Cipher Feedback Mode with 128-bit shifting. + Cfb128(cipher_modes::Cfb128), + // Galois Counter Mode + // Gcm(*mut Gcm), // TODO: this is more involved +} + +impl From for CipherState { + fn from(value: cipher_modes::Ecb) -> Self { + Self::Ecb(value) + } +} + +impl From for CipherState { + fn from(value: cipher_modes::Cbc) -> Self { + Self::Cbc(value) + } +} + +impl From for CipherState { + fn from(value: cipher_modes::Ofb) -> Self { + Self::Ofb(value) + } +} + +impl From for CipherState { + fn from(value: cipher_modes::Ctr) -> Self { + Self::Ctr(value) + } +} + +impl From for CipherState { + fn from(value: cipher_modes::Cfb8) -> Self { + Self::Cfb8(value) + } +} + +impl From for CipherState { + fn from(value: cipher_modes::Cfb128) -> Self { + Self::Cfb128(value) + } +} + +impl CipherState { + fn software_operating_mode(&self, operation: Operation, key: &Key) -> Mode { + match self { + CipherState::Ecb(_) | CipherState::Cbc(_) => { + if operation == Operation::Encrypt { + key.encrypt_mode() + } else { + key.decrypt_mode() + } + } + // For these, decryption is handled in software using the hardware in ecryption mode to + // produce intermediate results. + CipherState::Ofb(_) + | CipherState::Ctr(_) + | CipherState::Cfb8(_) + | CipherState::Cfb128(_) => key.encrypt_mode(), + } + } + + /// Returns whether the mode of operation requires complete 16-byte blocks. + fn requires_blocks(&self) -> bool { + matches!( + self, + CipherState::Ecb(_) | CipherState::Ofb(_) | CipherState::Cbc(_) + ) + } +} + +const BLOCK_SIZE: usize = 16; + +struct AesOperation { + mode: Operation, + // The block cipher mode of operation. + cipher_mode: NonNull, + // The length of the key is determined by the mode of operation. Note that the pointers may + // change during AES operation. + buffers: UnsafeCryptoBuffers, + key: Key, +} + +impl Clone for AesOperation { + fn clone(&self) -> Self { + Self { + mode: self.mode, + cipher_mode: self.cipher_mode, + buffers: self.buffers, + key: self.key.copy(), + } + } +} + +// Safety: AesOperation is safe to share between threads, in the context of a WorkQueue. The +// WorkQueue ensures that only a single location can access the data. All the internals, except +// for the pointers, are Sync. The pointers are safe to share because they point at data that the +// AES driver ensures can be accessed safely and soundly. +unsafe impl Sync for AesOperation {} +// Safety: we will not hold on to the pointers when the work item leaves the queue. +unsafe impl Send for AesOperation {} + +static AES_WORK_QUEUE: WorkQueue = WorkQueue::new(); +const BLOCKING_AES_VTABLE: VTable = VTable { + post: |driver, item| { + // Fully process the work item. A single CPU-driven AES operation would complete + // faster than we could poll the queue with all the locking around it. + let driver = unsafe { AesBackend::from_raw(driver) }; + Some(driver.process(item)) + }, + poll: |_driver, _item| { + // We've processed the item completely when we received it in `post`. + unreachable!() + }, + cancel: |_driver, _item| { + // To achieve a decent performance in Typical AES mode, we run the operations in a blocking + // manner and so they can't be cancelled. + }, + stop: |driver| { + // Drop the AES driver to conserve power when there is nothing to do (or when the driver was + // stopped). + let driver = unsafe { AesBackend::from_raw(driver) }; + driver.deinitialize() + }, +}; + +#[procmacros::doc_replace] +/// CPU-driven AES processing backend. +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::aes::{AesBackend, AesContext, Operation, cipher_modes::Ecb}; +/// # +/// let mut aes = AesBackend::new(peripherals.AES); +/// // Start the backend, which allows processing AES operations. +/// let _backend = aes.start(); +/// +/// // Create a new context with a 128-bit key. The context will use the ECB block cipher mode. +/// // The key length must be supported by the hardware. +/// let mut ecb_encrypt = AesContext::new(Ecb, Operation::Encrypt, *b"SUp4SeCp@sSw0rd\0"); +/// +/// // Process a block of data in this context. The ECB mode of operation requires that +/// // the length of the data is a multiple of the block (16 bytes) size. +/// let input_buffer: [u8; 16] = *b"message\0\0\0\0\0\0\0\0\0"; +/// let mut output_buffer: [u8; 16] = [0; 16]; +/// +/// let operation_handle = ecb_encrypt +/// .process(&input_buffer, &mut output_buffer) +/// .unwrap(); +/// operation_handle.wait_blocking(); +/// +/// // output_buffer now contains the ciphertext +/// assert_eq!( +/// output_buffer, +/// [ +/// 0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a, +/// 0x31, 0x96, +/// ] +/// ); +/// # +/// # {after_snippet} +/// ``` +pub struct AesBackend<'d> { + peri: AES<'d>, + driver: Option>, +} + +impl<'d> AesBackend<'d> { + #[procmacros::doc_replace] + /// Creates a new AES backend. + /// + /// The backend needs to be [`start`][Self::start]ed before it can execute AES operations. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::AesBackend; + /// # + /// let mut aes = AesBackend::new(peripherals.AES); + /// # {after_snippet} + /// ``` + pub fn new(aes: AES<'d>) -> Self { + Self { + peri: aes, + driver: None, + } + } + + #[procmacros::doc_replace] + /// Registers the CPU-driven AES driver to process AES operations. + /// + /// The driver stops operating when the returned object is dropped. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::AesBackend; + /// # + /// let mut aes = AesBackend::new(peripherals.AES); + /// // Start the backend, which allows processing AES operations. + /// let _backend = aes.start(); + /// # {after_snippet} + /// ``` + pub fn start(&mut self) -> AesWorkQueueDriver<'_, 'd> { + AesWorkQueueDriver { + inner: WorkQueueDriver::new(self, BLOCKING_AES_VTABLE, &AES_WORK_QUEUE), + } + } + + // WorkQueue callbacks. They may run in any context. + + unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self { + unsafe { ptr.cast::>().as_mut() } + } + + fn process(&mut self, item: &mut AesOperation) -> Poll { + let driver = self.driver.get_or_insert_with(|| { + let peri = unsafe { self.peri.clone_unchecked() }; + Aes::new(peri) + }); + + driver.process_work_item(item); + + Poll::Ready(Status::Completed) + } + + fn deinitialize(&mut self) { + self.driver = None; + } +} + +/// An error related to an AES operation. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The input and output buffers have different lengths. + BuffersNotEqual, + + /// The buffer length is not appropriate for the current cipher mode. + IncorrectBufferLength, +} + +/// An active work queue driver. +/// +/// This object must be kept around, otherwise AES operations will never complete. +pub struct AesWorkQueueDriver<'t, 'd> { + inner: WorkQueueDriver<'t, AesBackend<'d>, AesOperation>, +} + +impl<'t, 'd> AesWorkQueueDriver<'t, 'd> { + /// Finishes processing the current work queue item, then stops the driver. + pub fn stop(self) -> impl Future { + self.inner.stop() + } +} + +/// An AES work queue user. +#[derive(Clone)] +pub struct AesContext { + cipher_mode: CipherState, + frontend: WorkQueueFrontend, +} + +impl AesContext { + /// Creates a new context to encrypt or decrypt data with the given block cipher mode of + /// operation. + pub fn new( + cipher_mode: impl Into, + operation: Operation, + key: impl Into, + ) -> Self { + Self { + cipher_mode: cipher_mode.into(), + frontend: WorkQueueFrontend::new(AesOperation { + mode: operation, + cipher_mode: NonNull::dangling(), + buffers: unsafe { CryptoBuffers::new_in_place(&mut []).into_inner() }, + key: key.into(), + }), + } + } + + fn post(&mut self) -> AesHandle<'_> { + AesHandle(self.frontend.post(&AES_WORK_QUEUE)) + } + + fn validate(&self, buffer: &[u8]) -> Result<(), Error> { + if self.cipher_mode.requires_blocks() && !buffer.len().is_multiple_of(BLOCK_SIZE) { + return Err(Error::IncorrectBufferLength); + } + + Ok(()) + } + + /// Starts transforming the input buffer, and writes the result into the output buffer. + /// + /// - For encryption the input is the plaintext, the output is the ciphertext. + /// - For decryption the input is the ciphertext, the output is the plaintext. + /// + /// The returned Handle must be polled until it returns `true`. Dropping the handle + /// before the operation finishes will cancel the operation. + /// + /// For an example, see the documentation of [`AesBackend`]. + /// + /// ## Errors + /// + /// - If the lengths of the input and output buffers don't match, an error is returned. + /// - The ECB and OFB cipher modes require the data length to be a multiple of the block size + /// (16), otherwise an error is returned. + pub fn process<'t>( + &'t mut self, + input: &'t [u8], + output: &'t mut [u8], + ) -> Result, Error> { + self.validate(input)?; + self.validate(output)?; + + let data = self.frontend.data_mut(); + data.cipher_mode = NonNull::from(&mut self.cipher_mode); + data.buffers = unsafe { CryptoBuffers::new(input, output)?.into_inner() }; + + Ok(self.post()) + } + + #[procmacros::doc_replace] + /// Starts transforming the buffer. + /// + /// The processed data will be written back to the `buffer`. + /// + /// The returned Handle must be polled until it returns `true`. Dropping the handle + /// before the operation finishes will cancel the operation. + /// + /// This function operates similar to [`AesContext::process`], but it overwrites the data buffer + /// with the result of the transformation. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::aes::{AesBackend, AesContext, Operation, cipher_modes::Ecb}; + /// # + /// let mut aes = AesBackend::new(peripherals.AES); + /// // Start the backend, which pins it in place and allows processing AES operations. + /// let _backend = aes.start(); + /// + /// // Create a new context with a 128-bit key. The context will use the ECB block cipher mode. + /// // The key length must be supported by the hardware. + /// let mut ecb_encrypt = AesContext::new(Ecb, Operation::Encrypt, *b"SUp4SeCp@sSw0rd\0"); + /// + /// // Process a block of data in this context, in place. The ECB mode of operation requires that + /// // the length of the data is a multiple of the block (16 bytes) size. + /// let mut buffer: [u8; 16] = *b"message\0\0\0\0\0\0\0\0\0"; + /// + /// let operation_handle = ecb_encrypt.process_in_place(&mut buffer).unwrap(); + /// operation_handle.wait_blocking(); + /// + /// // Instead of the plaintext message, buffer now contains the ciphertext. + /// assert_eq!( + /// buffer, + /// [ + /// 0xb3, 0xc8, 0xd2, 0x3b, 0xa7, 0x36, 0x5f, 0x18, 0x61, 0x70, 0x0, 0x3e, 0xd9, 0x3a, + /// 0x31, 0x96, + /// ] + /// ); + /// # + /// # {after_snippet} + /// ``` + /// + /// ## Errors + /// + /// The ECB and OFB cipher modes require the data length to be a multiple of the block size + /// (16), otherwise an error is returned. + pub fn process_in_place<'t>( + &'t mut self, + buffer: &'t mut [u8], + ) -> Result, Error> { + self.validate(buffer)?; + + let data = self.frontend.data_mut(); + + data.cipher_mode = NonNull::from(&self.cipher_mode); + data.buffers = unsafe { CryptoBuffers::new_in_place(buffer).into_inner() }; + + Ok(self.post()) + } +} + +/// The handle to the pending AES operation. +/// +/// This object is returned by [`AesContext::process`] and [`AesContext::process_in_place`]. +/// +/// Dropping this handle before the operation finishes will cancel the operation. +/// +/// For an example, see the documentation of [`AesBackend`]. +pub struct AesHandle<'t>(Handle<'t, AesOperation>); + +impl AesHandle<'_> { + /// Polls the status of the work item. + /// + /// This function returns `true` if the item has been processed. + #[inline] + pub fn poll(&mut self) -> bool { + self.0.poll() + } + + /// Polls the work item to completion, by busy-looping. + /// + /// This function returns immediately if `poll` returns `true`. + #[inline] + pub fn wait_blocking(self) -> Status { + self.0.wait_blocking() + } + + /// Waits until the work item is completed. + #[inline] + pub fn wait(&mut self) -> impl Future { + self.0.wait() + } + + /// Cancels the work item and asynchronously waits until it is removed from the work queue. + #[inline] + pub fn cancel(&mut self) -> impl Future { + self.0.cancel() + } +} + +// Utilities + +fn read_words(slice: &[u8]) -> impl Iterator { + fn bytes(slice: &[u8]) -> impl Iterator { + slice.chunks(N).map(|c| { + let mut bytes = [0; N]; + bytes[0..c.len()].copy_from_slice(c); + bytes + }) + } + + bytes::<4>(slice).map(u32::from_le_bytes) +} + +fn write_words(slice: &mut [u8], next: impl Fn(usize) -> u32) { + for (i, chunk) in slice.chunks_mut(4).enumerate() { + let bytes = next(i).to_le_bytes(); + chunk.copy_from_slice(&bytes[0..chunk.len()]); + } +} diff --git a/esp-hal/src/analog/adc/calibration/basic.rs b/esp-hal/src/analog/adc/calibration/basic.rs new file mode 100644 index 00000000000..643653d5178 --- /dev/null +++ b/esp-hal/src/analog/adc/calibration/basic.rs @@ -0,0 +1,53 @@ +use core::marker::PhantomData; + +use crate::analog::adc::{ + AdcCalEfuse, + AdcCalScheme, + AdcCalSource, + AdcConfig, + Attenuation, + CalibrationAccess, +}; + +/// Basic ADC calibration scheme +/// +/// Basic calibration sets the initial ADC bias value so that a zero voltage +/// gives a reading of zero. The correct bias value is usually stored in efuse, +/// but can also be measured at runtime by connecting the ADC input to ground +/// internally. +/// +/// Failing to apply basic calibration can substantially reduce the ADC's output +/// range because bias correction is done *before* the ADC's output is truncated +/// to 12 bits. +#[derive(Clone, Copy)] +pub struct AdcCalBasic { + /// Calibration value to set to ADC unit + cal_val: u16, + + _phantom: PhantomData, +} + +impl crate::private::Sealed for AdcCalBasic {} + +impl AdcCalScheme for AdcCalBasic +where + ADCI: AdcCalEfuse + CalibrationAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + // Try to get init code (Dout0) from efuse + // Dout0 means mean raw ADC value when zero voltage applied to input. + let cal_val = ADCI::init_code(atten).unwrap_or_else(|| { + // As a fallback try to calibrate via connecting input to ground internally. + AdcConfig::::adc_calibrate(atten, AdcCalSource::Gnd) + }); + + Self { + cal_val, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.cal_val + } +} diff --git a/esp-hal/src/analog/adc/calibration/curve.rs b/esp-hal/src/analog/adc/calibration/curve.rs new file mode 100644 index 00000000000..f2c73dcf4c8 --- /dev/null +++ b/esp-hal/src/analog/adc/calibration/curve.rs @@ -0,0 +1,279 @@ +use core::marker::PhantomData; + +use crate::analog::adc::{ + AdcCalEfuse, + AdcCalLine, + AdcCalScheme, + AdcHasLineCal, + Attenuation, + CalibrationAccess, +}; + +const COEFF_MUL: i64 = 1 << 52; + +/// Integer type for the error polynomial's coefficients. Despite +/// the type, this is a fixed-point number with 52 fractional bits. +type CurveCoeff = i64; + +/// Polynomial coefficients for specified attenuation. +pub struct CurveCoeffs { + /// Attenuation + atten: Attenuation, + /// Polynomial coefficients + coeff: &'static [CurveCoeff], +} + +type CurvesCoeffs = &'static [CurveCoeffs]; + +/// Marker trait for ADC which support curve fitting +/// +/// See also [`AdcCalCurve`]. +pub trait AdcHasCurveCal { + /// Coefficients for calculating the reading voltage error. + /// + /// A sets of coefficients for each attenuation. + const CURVES_COEFFS: CurvesCoeffs; +} + +/// Curve fitting ADC calibration scheme +/// +/// This scheme implements polynomial error correction using predefined +/// coefficient sets for each attenuation. It returns readings in mV. +/// +/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and +/// line fitting ([`AdcCalLine`]). +#[derive(Clone, Copy)] +pub struct AdcCalCurve { + line: AdcCalLine, + + /// Coefficients of the error estimation polynomial. + /// + /// The constant coefficient comes first; the error polynomial is + /// `coeff[0] + coeff[1] * x + ... + coeff[n] * x^n`. + /// + /// This calibration works by first applying linear calibration. Then + /// the error polynomial is applied to the output of linear calibration. + /// The output of the polynomial is our estimate of the error; it gets + /// subtracted from linear calibration's output to get the final reading. + coeff: &'static [CurveCoeff], + + _phantom: PhantomData, +} + +impl crate::private::Sealed for AdcCalCurve {} + +impl AdcCalScheme for AdcCalCurve +where + ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + CalibrationAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + let line = AdcCalLine::::new_cal(atten); + + let coeff = ADCI::CURVES_COEFFS + .iter() + .find(|item| item.atten == atten) + .expect("No curve coefficients for given attenuation") + .coeff; + + Self { + line, + coeff, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.line.adc_cal() + } + + fn adc_val(&self, val: u16) -> u16 { + let val = self.line.adc_val(val); + + // Calculate polynomial error using Horner's method to prevent overflow. + // Horner's evaluates: err = coeff[0] + val*(coeff[1] + val*(coeff[2] + ...)) + // This avoids computing val^n which causes overflow when multiplied by coefficients. + let err = if val == 0 || self.coeff.is_empty() { + 0 + } else { + let val_i64 = val as i64; + let mut poly = 0i64; + + // Iterate coefficients in reverse order for Horner's method + for &coeff in self.coeff.iter().rev() { + poly = poly * val_i64 + coeff; + } + + (poly / COEFF_MUL) as i32 + }; + + (val as i32 - err) as u16 + } +} + +macro_rules! coeff_tables { + ($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => { + $( + $(#[$($meta)*])* + const $name: CurvesCoeffs = &[ + $(CurveCoeffs { + atten: Attenuation::$att, + coeff: &[ + $(($val as f64 * COEFF_MUL as f64) as CurveCoeff,)* + ], + },)* + ]; + )* + }; +} + +#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))] +mod impls { + use super::*; + + impl AdcHasCurveCal for crate::peripherals::ADC1<'_> { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1; + } + + #[cfg(esp32c3)] + impl AdcHasCurveCal for crate::peripherals::ADC2<'_> { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1; + } + + #[cfg(esp32s3)] + impl AdcHasCurveCal for crate::peripherals::ADC2<'_> { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS2; + } + + coeff_tables! { + /// Error curve coefficients derived from + #[cfg(esp32c3)] + CURVES_COEFFS1 [ + _0dB => [ + -0.225966470500043, + -0.0007265418501948, + 0.0000109410402681, + ], + _2p5dB => [ + 0.4229623392600516, + -0.0000731527490903, + 0.0000088166562521, + ], + _6dB => [ + -1.017859239236435, + -0.0097159265299153, + 0.0000149794028038, + ], + _11dB => [ + -1.4912262772850453, + -0.0228549975564099, + 0.0000356391935717, + -0.0000000179964582, + 0.0000000000042046, + ], + ]; + + /// Error curve coefficients derived from + #[cfg(esp32c6)] + CURVES_COEFFS1 [ + _0dB => [ + -0.0487166399931449, + 0.0006436483033201, + 0.0000030410131806, + ], + _2p5dB => [ + -0.8665498165817785, + 0.0015239070452946, + 0.0000013818878844, + ], + _6dB => [ + -1.2277821756674387, + 0.0022275554717885, + 0.0000005924302667, + ], + _11dB => [ + -0.3801417550380255, + -0.0006020352420772, + 0.0000012442478488, + ], + ]; + + /// Error curve coefficients derived from + #[cfg(esp32h2)] + CURVES_COEFFS1 [ + _0dB => [ + -0.5081991760658888, + 0.0000007858995319, + 0, + ], + _2p5dB => [ + -0.8359230818901277, + 0.0000009025419089, + 0, + ], + _6dB => [ + -1.165668771581976, + 0.0000008294679249, + 0, + ], + _11dB => [ + -0.3637329628677273, + -0.0000196072597389, + 0.0000007871689227, + ], + ]; + + /// Error curve coefficients derived from + #[cfg(esp32s3)] + CURVES_COEFFS1 [ + _0dB => [ + -2.7856531419538344, + -0.0050871540569528, + 0.0000097982495890, + ], + _2p5dB => [ + -2.9831022915028695, + -0.0049393185868806, + 0.0000101379430548, + ], + _6dB => [ + -2.3285545746296417, + -0.0147640181047414, + 0.0000208385525314, + ], + _11dB => [ + -0.644403418269478, + -0.0644334888647536, + 0.0001297891447611, + -0.0000000707697180, + 0.0000000000135150, + ], + ]; + + /// Error curve coefficients derived from + #[cfg(esp32s3)] + CURVES_COEFFS2 [ + _0dB => [ + -2.5668651654328927, + 0.0001353548869615, + 0.0000036615265189, + ], + _2p5dB => [ + -2.3690184690298404, + -0.0066319894226185, + 0.0000118964995959, + ], + _6dB => [ + -0.9452499397020617, + -0.0200996773954387, + 0.00000259011467956, + ], + _11dB => [ + 1.2247719764336924, + -0.0755717904943462, + 0.0001478791187119, + -0.0000000796725280, + 0.0000000000150380, + ], + ]; + } +} diff --git a/esp-hal/src/analog/adc/calibration/line.rs b/esp-hal/src/analog/adc/calibration/line.rs new file mode 100644 index 00000000000..792a0ed14f5 --- /dev/null +++ b/esp-hal/src/analog/adc/calibration/line.rs @@ -0,0 +1,104 @@ +use core::marker::PhantomData; + +use crate::analog::adc::{ + AdcCalBasic, + AdcCalEfuse, + AdcCalScheme, + AdcCalSource, + AdcConfig, + Attenuation, + CalibrationAccess, +}; + +/// Marker trait for ADC units which support line fitting +/// +/// Usually it means that reference points are stored in efuse. +/// See also [`AdcCalLine`]. +pub trait AdcHasLineCal {} + +/// We store the gain as a u32, but it's really a fixed-point number. +const GAIN_SCALE: u32 = 1 << 16; + +/// Line fitting ADC calibration scheme +/// +/// This scheme implements gain correction based on reference points, and +/// returns readings in mV. +/// +/// A reference point is a pair of a reference voltage and the corresponding +/// mean raw digital ADC value. Such values are usually stored in efuse bit +/// fields for each supported attenuation. +/// +/// Also it can be measured in runtime by connecting ADC to reference voltage +/// internally but this method is not so good because actual reference voltage +/// may varies in range 1.0..=1.2 V. Currently this method is used as a fallback +/// (with 1.1 V by default) when calibration data is missing. +/// +/// This scheme also includes basic calibration ([`AdcCalBasic`]). +#[derive(Clone, Copy)] +pub struct AdcCalLine { + basic: AdcCalBasic, + + /// ADC gain. + /// + /// After being de-biased by the basic calibration, the reading is + /// multiplied by this value. Despite the type, it is a fixed-point + /// number with 16 fractional bits. + gain: u32, + + _phantom: PhantomData, +} + +impl crate::private::Sealed for AdcCalLine {} + +impl AdcCalScheme for AdcCalLine +where + ADCI: AdcCalEfuse + AdcHasLineCal + CalibrationAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + let basic = AdcCalBasic::::new_cal(atten); + + // Try get the reference point (Dout, Vin) from efuse + // Dout means mean raw ADC value when specified Vin applied to input. + let (code, mv) = ADCI::cal_code(atten) + .map(|code| (code, ADCI::cal_mv(atten))) + .unwrap_or_else(|| { + // As a fallback try to calibrate using reference voltage source. + // This method is not too good because actual reference voltage may varies + // in range 1000..=1200 mV and this value currently cannot be read from efuse. + ( + AdcConfig::::adc_calibrate(atten, AdcCalSource::Ref), + 1100, // use 1100 mV as a middle of typical reference voltage range + ) + }); + + // Estimate the (assumed) linear relationship between the measured raw value and + // the voltage with the previously done measurement when the chip was + // manufactured. + // + // Note that the constant term is zero because the basic calibration takes care + // of it already. + let gain = mv as u32 * GAIN_SCALE / code as u32; + + Self { + basic, + gain, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.basic.adc_cal() + } + + fn adc_val(&self, val: u16) -> u16 { + let val = self.basic.adc_val(val); + + (val as u32 * self.gain / GAIN_SCALE) as u16 + } +} + +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] +impl AdcHasLineCal for crate::peripherals::ADC1<'_> {} + +#[cfg(any(esp32c3, esp32s3))] +impl AdcHasLineCal for crate::peripherals::ADC2<'_> {} diff --git a/esp-hal/src/analog/adc/calibration/mod.rs b/esp-hal/src/analog/adc/calibration/mod.rs new file mode 100644 index 00000000000..1dde6b10a34 --- /dev/null +++ b/esp-hal/src/analog/adc/calibration/mod.rs @@ -0,0 +1,13 @@ +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] +pub use self::basic::AdcCalBasic; +#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))] +pub use self::curve::{AdcCalCurve, AdcHasCurveCal}; +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] +pub use self::line::{AdcCalLine, AdcHasLineCal}; + +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] +mod basic; +#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))] +mod curve; +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] +mod line; diff --git a/esp-hal/src/analog/adc/esp32.rs b/esp-hal/src/analog/adc/esp32.rs new file mode 100644 index 00000000000..d49da470ee4 --- /dev/null +++ b/esp-hal/src/analog/adc/esp32.rs @@ -0,0 +1,388 @@ +use core::{ + marker::PhantomData, + sync::atomic::{AtomicBool, Ordering}, +}; + +use super::{AdcConfig, Attenuation}; +use crate::{ + peripherals::{ADC1, ADC2, RTC_IO, SENS}, + private::{self}, +}; + +pub(super) const NUM_ATTENS: usize = 10; + +// ADC2 cannot be used with `radio` functionality on `esp32`, this global helps us to track it's +// state to prevent unexpected behaviour +static ADC2_IN_USE: AtomicBool = AtomicBool::new(false); + +/// ADC Error +#[derive(Debug)] +pub enum Error { + /// `ADC2` is used together with `radio`. + Adc2InUse, +} + +#[doc(hidden)] +/// Tries to "claim" `ADC2` peripheral and set its status +pub fn try_claim_adc2(_: private::Internal) -> Result<(), Error> { + if ADC2_IN_USE.fetch_or(true, Ordering::Relaxed) { + Err(Error::Adc2InUse) + } else { + Ok(()) + } +} + +#[doc(hidden)] +/// Resets `ADC2` usage status to `Unused` +pub fn release_adc2(_: private::Internal) { + ADC2_IN_USE.store(false, Ordering::Relaxed); +} + +/// The sampling/readout resolution of the ADC. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "unit of measurement")] +pub enum Resolution { + /// 9-bit resolution + _9Bit, + /// 10-bit resolution + _10Bit, + /// 11-bit resolution + _11Bit, + /// 12-bit resolution + #[default] + _12Bit, +} + +#[doc(hidden)] +pub trait RegisterAccess { + fn set_resolution(resolution: u8); + + fn set_attenuation(channel: usize, attenuation: u8); + + fn clear_dig_force(); + + fn set_start_force(); + + fn set_en_pad_force(); + + fn set_en_pad(channel: u8); + + fn clear_start_sar(); + + fn set_start_sar(); + + fn read_done_sar() -> bool; + + fn read_data_sar() -> u16; + + fn instance_number() -> u8; +} + +impl RegisterAccess for ADC1<'_> { + fn set_resolution(resolution: u8) { + SENS::regs() + .sar_start_force() + .modify(|_, w| unsafe { w.sar1_bit_width().bits(resolution) }); + SENS::regs() + .sar_read_ctrl() + .modify(|_, w| unsafe { w.sar1_sample_bit().bits(resolution) }); + } + + fn set_attenuation(channel: usize, attenuation: u8) { + SENS::regs().sar_atten1().modify(|r, w| { + let new_value = (r.bits() & !(0b11 << (channel * 2))) + | (((attenuation & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar1_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + SENS::regs() + .sar_read_ctrl() + .modify(|_, w| w.sar1_dig_force().clear_bit()); + } + + fn set_start_force() { + SENS::regs() + .sar_meas_start1() + .modify(|_, w| w.meas1_start_force().set_bit()); + } + + fn set_en_pad_force() { + SENS::regs() + .sar_meas_start1() + .modify(|_, w| w.sar1_en_pad_force().set_bit()); + } + + fn set_en_pad(channel: u8) { + SENS::regs() + .sar_meas_start1() + .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) }); + } + + fn clear_start_sar() { + SENS::regs() + .sar_meas_start1() + .modify(|_, w| w.meas1_start_sar().clear_bit()); + } + + fn set_start_sar() { + SENS::regs() + .sar_meas_start1() + .modify(|_, w| w.meas1_start_sar().set_bit()); + } + + fn read_done_sar() -> bool { + SENS::regs() + .sar_meas_start1() + .read() + .meas1_done_sar() + .bit_is_set() + } + + fn read_data_sar() -> u16 { + SENS::regs() + .sar_meas_start1() + .read() + .meas1_data_sar() + .bits() + } + + fn instance_number() -> u8 { + 1 + } +} + +impl RegisterAccess for ADC2<'_> { + fn set_resolution(resolution: u8) { + SENS::regs() + .sar_start_force() + .modify(|_, w| unsafe { w.sar2_bit_width().bits(resolution) }); + SENS::regs() + .sar_read_ctrl2() + .modify(|_, w| unsafe { w.sar2_sample_bit().bits(resolution) }); + } + + fn set_attenuation(channel: usize, attenuation: u8) { + SENS::regs().sar_atten2().modify(|r, w| { + let new_value = (r.bits() & !(0b11 << (channel * 2))) + | (((attenuation & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar2_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + SENS::regs() + .sar_read_ctrl2() + .modify(|_, w| w.sar2_dig_force().clear_bit()); + } + + fn set_start_force() { + SENS::regs() + .sar_meas_start2() + .modify(|_, w| w.meas2_start_force().set_bit()); + } + + fn set_en_pad_force() { + SENS::regs() + .sar_meas_start2() + .modify(|_, w| w.sar2_en_pad_force().set_bit()); + } + + fn set_en_pad(channel: u8) { + SENS::regs() + .sar_meas_start2() + .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) }); + } + + fn clear_start_sar() { + SENS::regs() + .sar_meas_start2() + .modify(|_, w| w.meas2_start_sar().clear_bit()); + } + + fn set_start_sar() { + SENS::regs() + .sar_meas_start2() + .modify(|_, w| w.meas2_start_sar().set_bit()); + } + + fn read_done_sar() -> bool { + SENS::regs() + .sar_meas_start2() + .read() + .meas2_done_sar() + .bit_is_set() + } + + fn read_data_sar() -> u16 { + SENS::regs() + .sar_meas_start2() + .read() + .meas2_data_sar() + .bits() + } + + fn instance_number() -> u8 { + 2 + } +} + +/// Analog-to-Digital Converter peripheral driver. +pub struct Adc<'d, ADC, Dm: crate::DriverMode> { + _adc: ADC, + attenuations: [Option; NUM_ATTENS], + active_channel: Option, + _phantom: PhantomData<(Dm, &'d mut ())>, +} + +impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking> +where + ADCI: RegisterAccess + 'd, +{ + /// Configure a given ADC instance using the provided configuration, and + /// initialize the ADC for use + /// + /// # Panics + /// + /// `ADC2` cannot be used simultaneously with `radio` functionalities, otherwise this function + /// will panic. + pub fn new(adc_instance: ADCI, config: AdcConfig) -> Self { + if ADCI::instance_number() == 2 && try_claim_adc2(private::Internal).is_err() { + panic!( + "ADC2 is already in use by Radio. On ESP32, ADC2 cannot be used simultaneously with Wi-Fi or Bluetooth." + ); + } + + let sensors = SENS::regs(); + + // Set reading and sampling resolution + ADCI::set_resolution(config.resolution as u8); + + // Set attenuation for pins + let attenuations = config.attenuations; + + for (channel, attentuation) in attenuations.iter().enumerate() { + if let Some(attenuation) = attentuation { + ADC1::set_attenuation(channel, *attenuation as u8); + } + } + + // Set controller to RTC + ADCI::clear_dig_force(); + ADCI::set_start_force(); + ADCI::set_en_pad_force(); + sensors.sar_touch_ctrl1().modify(|_, w| { + w.xpd_hall_force().set_bit(); + w.hall_phase_force().set_bit() + }); + + sensors.sar_meas_wait2().modify(|_, w| unsafe { + // Set power to SW power on + w.force_xpd_sar().bits(0b11); + // disable AMP + w.force_xpd_amp().bits(0b10) + }); + sensors.sar_meas_ctrl().modify(|_, w| unsafe { + w.amp_rst_fb_fsm().bits(0); + w.amp_short_ref_fsm().bits(0); + w.amp_short_ref_gnd_fsm().bits(0) + }); + sensors.sar_meas_wait1().modify(|_, w| unsafe { + w.sar_amp_wait1().bits(1); + w.sar_amp_wait2().bits(1) + }); + sensors + .sar_meas_wait2() + .modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) }); + + // Do *not* invert the output + // NOTE: This seems backwards, but was verified experimentally. + sensors + .sar_read_ctrl() + .modify(|_, w| w.sar1_data_inv().set_bit()); + sensors + .sar_read_ctrl2() + .modify(|_, w| w.sar2_data_inv().set_bit()); + + Adc { + _adc: adc_instance, + attenuations: config.attenuations, + active_channel: None, + _phantom: PhantomData, + } + } + + /// Request that the ADC begin a conversion on the specified pin + /// + /// This method takes an [AdcPin](super::AdcPin) reference, as it is + /// expected that the ADC will be able to sample whatever channel + /// underlies the pin. + pub fn read_oneshot(&mut self, pin: &mut super::AdcPin) -> nb::Result + where + PIN: super::AdcChannel, + { + if self.attenuations[pin.pin.adc_channel() as usize].is_none() { + panic!( + "Channel {} is not configured reading!", + pin.pin.adc_channel() + ); + } + + if let Some(active_channel) = self.active_channel { + // There is conversion in progress: + // - if it's for a different channel try again later + // - if it's for the given channel, go ahead and check progress + if active_channel != pin.pin.adc_channel() { + return Err(nb::Error::WouldBlock); + } + } else { + // If no conversions are in progress, start a new one for given channel + self.active_channel = Some(pin.pin.adc_channel()); + + ADCI::set_en_pad(pin.pin.adc_channel()); + + ADCI::clear_start_sar(); + ADCI::set_start_sar(); + } + + // Wait for ADC to finish conversion + let conversion_finished = ADCI::read_done_sar(); + if !conversion_finished { + return Err(nb::Error::WouldBlock); + } + + // Get converted value + let converted_value = ADCI::read_data_sar(); + + // Mark that no conversions are currently in progress + self.active_channel = None; + + Ok(converted_value) + } +} + +impl Adc<'_, ADC1, crate::Blocking> { + /// Enable the Hall sensor + pub fn enable_hall_sensor() { + RTC_IO::regs() + .hall_sens() + .modify(|_, w| w.xpd_hall().set_bit()); + } + + /// Disable the Hall sensor + pub fn disable_hall_sensor() { + RTC_IO::regs() + .hall_sens() + .modify(|_, w| w.xpd_hall().clear_bit()); + } +} + +impl Drop for ADC2<'_> { + fn drop(&mut self) { + release_adc2(private::Internal); + } +} diff --git a/esp-hal/src/analog/adc/mod.rs b/esp-hal/src/analog/adc/mod.rs new file mode 100644 index 00000000000..84aabd7aba8 --- /dev/null +++ b/esp-hal/src/analog/adc/mod.rs @@ -0,0 +1,251 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "analog_pin" => { + cfg(esp32) => "GPIO32", + cfg(any(esp32s2, esp32s3)) => "GPIO3", + cfg(not(any(esp32, esp32s2, esp32s3))) => "GPIO2" + } +))] +//! # Analog to Digital Converter (ADC) +//! +//! ## Overview +//! +//! The ADC is integrated on the chip, and is capable of measuring analog +//! signals from specific analog I/O pins. One or more ADC units are available, +//! depending on the device being used. +//! +//! ## Configuration +//! +//! The ADC can be configured to measure analog signals from specific pins. The +//! configuration includes the resolution of the ADC, the attenuation of the +//! input signal, and the pins to be measured. +//! +//! Some targets also support ADC calibration via different schemes like +//! basic calibration, curve fitting or linear interpolation. The calibration +//! schemes can be used to improve the accuracy of the ADC readings. +//! +//! ## Examples +//! +//! ### Read an analog signal from a pin +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::analog::adc::AdcConfig; +//! # use esp_hal::peripherals::ADC1; +//! # use esp_hal::analog::adc::Attenuation; +//! # use esp_hal::analog::adc::Adc; +//! # use esp_hal::delay::Delay; +//! let mut adc1_config = AdcConfig::new(); +//! let mut pin = adc1_config.enable_pin(peripherals.__analog_pin__, Attenuation::_11dB); +//! let mut adc1 = Adc::new(peripherals.ADC1, adc1_config); +//! +//! let mut delay = Delay::new(); +//! +//! loop { +//! let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut pin))?; +//! +//! delay.delay_millis(1500); +//! } +//! # } +//! ``` +//! +//! ## Implementation State +//! +//! - [ADC calibration is not implemented for all targets]. +//! +//! [ADC calibration is not implemented for all targets]: https://github.com/esp-rs/esp-hal/issues/326 +use core::marker::PhantomData; + +use crate::gpio::AnalogPin; + +#[cfg_attr(esp32, path = "esp32.rs")] +#[cfg_attr(riscv, path = "riscv.rs")] +#[cfg_attr(any(esp32s2, esp32s3), path = "xtensa.rs")] +#[cfg(feature = "unstable")] +mod implementation; + +#[cfg(feature = "unstable")] +pub use self::implementation::*; + +/// The approximate attenuation of the ADC pin. +/// +/// The effective measurement range for a given attenuation is dependent on the +/// device being targeted. Please refer to "ADC Characteristics" section of your +/// device's datasheet for more information. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "unit of measurement")] +pub enum Attenuation { + /// About 0dB attenuation. + _0dB = 0b00, + /// About 2.5dB attenuation. + _2p5dB = 0b01, + /// About 6dB attenuation. + _6dB = 0b10, + /// About 11dB attenuation. + _11dB = 0b11, +} + +/// Calibration source of the ADC. +#[cfg(not(esp32))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AdcCalSource { + /// Use Ground as the calibration source + Gnd, + /// Use Vref as the calibration source + Ref, +} + +/// An I/O pin which can be read using the ADC. +pub struct AdcPin { + /// The underlying GPIO pin + pub pin: PIN, + /// Calibration scheme used for the configured ADC pin + pub cal_scheme: CS, + _phantom: PhantomData, +} + +/// Configuration for the ADC. +#[cfg(feature = "unstable")] +pub struct AdcConfig { + #[cfg(esp32)] + resolution: Resolution, + attenuations: [Option; NUM_ATTENS], + _phantom: PhantomData, +} + +#[cfg(feature = "unstable")] +impl AdcConfig { + /// Create a new configuration struct with its default values + pub fn new() -> Self { + Self::default() + } + + /// Enable the specified pin with the given attenuation + pub fn enable_pin(&mut self, pin: PIN, attenuation: Attenuation) -> AdcPin + where + PIN: AdcChannel + AnalogPin, + { + // TODO revert this on drop + pin.set_analog(crate::private::Internal); + self.attenuations[pin.adc_channel() as usize] = Some(attenuation); + + AdcPin { + pin, + cal_scheme: AdcCalScheme::<()>::new_cal(attenuation), + _phantom: PhantomData, + } + } + + /// Enable the specified pin with the given attenuation and calibration + /// scheme + #[cfg(not(esp32))] + #[cfg(feature = "unstable")] + pub fn enable_pin_with_cal( + &mut self, + pin: PIN, + attenuation: Attenuation, + ) -> AdcPin + where + ADCI: CalibrationAccess, + PIN: AdcChannel + AnalogPin, + CS: AdcCalScheme, + { + // TODO revert this on drop + pin.set_analog(crate::private::Internal); + self.attenuations[pin.adc_channel() as usize] = Some(attenuation); + + AdcPin { + pin, + cal_scheme: CS::new_cal(attenuation), + _phantom: PhantomData, + } + } +} + +#[cfg(feature = "unstable")] +impl Default for AdcConfig { + fn default() -> Self { + Self { + #[cfg(esp32)] + resolution: Resolution::default(), + attenuations: [None; NUM_ATTENS], + _phantom: PhantomData, + } + } +} + +#[cfg(not(esp32))] +#[doc(hidden)] +#[cfg(feature = "unstable")] +pub trait CalibrationAccess: RegisterAccess { + const ADC_CAL_CNT_MAX: u16; + const ADC_CAL_CHANNEL: u16; + const ADC_VAL_MASK: u16; + + fn enable_vdef(enable: bool); + + /// Enable internal calibration voltage source + fn connect_cal(source: AdcCalSource, enable: bool); +} + +/// A helper trait to get the ADC channel of a compatible GPIO pin. +pub trait AdcChannel { + /// Channel number used by the ADC + fn adc_channel(&self) -> u8; +} + +/// A trait abstracting over calibration methods. +/// +/// The methods in this trait are mostly for internal use. To get +/// calibrated ADC reads, all you need to do is call `enable_pin_with_cal` +/// and specify some implementor of this trait. +pub trait AdcCalScheme: Sized + crate::private::Sealed { + /// Create a new calibration scheme for the given attenuation. + fn new_cal(atten: Attenuation) -> Self; + + /// Return the basic ADC bias value. + fn adc_cal(&self) -> u16 { + 0 + } + + /// Convert ADC value. + fn adc_val(&self, val: u16) -> u16 { + val + } +} + +impl crate::private::Sealed for () {} + +impl AdcCalScheme for () { + fn new_cal(_atten: Attenuation) -> Self {} +} + +/// A helper trait to get access to ADC calibration efuses. +#[cfg(not(any(esp32, esp32s2)))] +trait AdcCalEfuse { + /// Get ADC calibration init code + /// + /// Returns digital value for zero voltage for a given attenuation + fn init_code(atten: Attenuation) -> Option; + + /// Get ADC calibration reference point voltage + /// + /// Returns reference voltage (millivolts) for a given attenuation + fn cal_mv(atten: Attenuation) -> u16; + + /// Get ADC calibration reference point digital value + /// + /// Returns digital value for reference voltage for a given attenuation + fn cal_code(atten: Attenuation) -> Option; +} + +for_each_analog_function! { + (($ch_name:ident, ADCn_CHm, $adc:literal, $ch:literal), $gpio:ident) => { + impl $crate::analog::adc::AdcChannel for $crate::peripherals::$gpio<'_> { + fn adc_channel(&self) -> u8 { + $ch + } + } + }; +} diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs new file mode 100644 index 00000000000..c19d8a6d699 --- /dev/null +++ b/esp-hal/src/analog/adc/riscv.rs @@ -0,0 +1,667 @@ +use core::marker::PhantomData; + +cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + use Interrupt::APB_SARADC as InterruptSource; + } else { + use Interrupt::APB_ADC as InterruptSource; + } +} + +use core::{ + pin::Pin, + task::{Context, Poll}, +}; + +// We only have to count on devices that have multiple ADCs sharing the same interrupt +#[cfg(all(adc_adc1, adc_adc2))] +use portable_atomic::{AtomicU32, Ordering}; +use procmacros::handler; + +pub use self::calibration::*; +use super::{AdcCalSource, AdcConfig, Attenuation}; +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] +use crate::efuse::AdcCalibUnit; +use crate::{ + Async, + Blocking, + asynch::AtomicWaker, + interrupt::{InterruptConfigurable, InterruptHandler}, + peripherals::{APB_SARADC, Interrupt}, + soc::regi2c, + system::{GenericPeripheralGuard, Peripheral}, +}; + +mod calibration; + +// Constants taken from: +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c2/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c3/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c6/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h2/include/soc/regi2c_saradc.h +cfg_if::cfg_if! { + if #[cfg(adc_adc1)] { + const ADC_VAL_MASK: u16 = 0xfff; + const ADC_CAL_CNT_MAX: u16 = 32; + const ADC_CAL_CHANNEL: u16 = 15; + } +} + +// The number of analog IO pins, and in turn the number of attentuations, +// depends on which chip is being used +cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + pub(super) const NUM_ATTENS: usize = 7; + } else { + pub(super) const NUM_ATTENS: usize = 5; + } +} + +impl AdcConfig +where + ADCI: RegisterAccess, +{ + /// Calibrate ADC with specified attenuation and voltage source + pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 + where + ADCI: super::CalibrationAccess, + { + let mut adc_max: u16 = 0; + let mut adc_min: u16 = u16::MAX; + let mut adc_sum: u32 = 0; + + ADCI::enable_vdef(true); + + // Start sampling + ADCI::config_onetime_sample(ADC_CAL_CHANNEL as u8, atten as u8); + + // Connect calibration source + ADCI::connect_cal(source, true); + + ADCI::calibration_init(); + for _ in 0..ADC_CAL_CNT_MAX { + ADCI::set_init_code(0); + + // Trigger ADC sampling + ADCI::start_onetime_sample(); + + // Wait until ADC1 sampling is done + while !ADCI::is_done() {} + + let adc = ADCI::read_data() & ADC_VAL_MASK; + + ADCI::reset(); + + adc_sum += adc as u32; + adc_max = adc.max(adc_max); + adc_min = adc.min(adc_min); + } + + let cal_val = (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADC_CAL_CNT_MAX - 2); + + // Disconnect calibration source + ADCI::connect_cal(source, false); + + cal_val + } +} + +#[doc(hidden)] +pub trait RegisterAccess { + /// Configure onetime sampling parameters + fn config_onetime_sample(channel: u8, attenuation: u8); + + /// Start onetime sampling + fn start_onetime_sample(); + + /// Check if sampling is done + fn is_done() -> bool; + + /// Read sample data + fn read_data() -> u16; + + /// Reset flags + fn reset(); + + /// Set up ADC hardware for calibration + fn calibration_init(); + + /// Set calibration parameter to ADC hardware + fn set_init_code(data: u16); +} + +#[cfg(adc_adc1)] +impl RegisterAccess for crate::peripherals::ADC1<'_> { + fn config_onetime_sample(channel: u8, attenuation: u8) { + APB_SARADC::regs().onetime_sample().modify(|_, w| unsafe { + w.saradc1_onetime_sample().set_bit(); + w.onetime_channel().bits(channel); + w.onetime_atten().bits(attenuation) + }); + } + + fn start_onetime_sample() { + APB_SARADC::regs() + .onetime_sample() + .modify(|_, w| w.onetime_start().set_bit()); + } + + fn is_done() -> bool { + APB_SARADC::regs().int_raw().read().adc1_done().bit() + } + + fn read_data() -> u16 { + APB_SARADC::regs() + .sar1data_status() + .read() + .saradc1_data() + .bits() as u16 + & 0xfff + } + + fn reset() { + // Clear ADC1 sampling done interrupt bit + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc1_done().clear_bit_by_one()); + + // Disable ADC sampling + APB_SARADC::regs() + .onetime_sample() + .modify(|_, w| w.onetime_start().clear_bit()); + } + + // Currently #[cfg] covers all supported RISC-V devices, + // but, for example, esp32p4 uses the value 4 instead of 1, + // so it is not standard across all RISC-V devices. + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] + fn calibration_init() { + // e.g. + // https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/hal/esp32c3/include/hal/adc_ll.h#L702 + regi2c::ADC_SAR1_DREF.write_field(1); + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(msb); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(lsb); + } +} + +#[cfg(adc_adc1)] +impl super::CalibrationAccess for crate::peripherals::ADC1<'_> { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + regi2c::ADC_SAR1_DREF.write_field(enable as _); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => regi2c::ADC_SAR1_ENCAL_GND.write_field(enable as _), + #[cfg(not(esp32h2))] + AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_REF.write_field(enable as _), + // For the ESP32-H2 ground and internal reference voltage are mutually exclusive and + // you can toggle between them. + // + // See: + #[cfg(esp32h2)] + AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_GND.write_field(!enable as _), + } + } +} + +#[cfg(adc_adc2)] +impl RegisterAccess for crate::peripherals::ADC2<'_> { + fn config_onetime_sample(channel: u8, attenuation: u8) { + APB_SARADC::regs().onetime_sample().modify(|_, w| unsafe { + w.saradc2_onetime_sample().set_bit(); + w.onetime_channel().bits(channel); + w.onetime_atten().bits(attenuation) + }); + } + + fn start_onetime_sample() { + APB_SARADC::regs() + .onetime_sample() + .modify(|_, w| w.onetime_start().set_bit()); + } + + fn is_done() -> bool { + APB_SARADC::regs().int_raw().read().adc2_done().bit() + } + + fn read_data() -> u16 { + APB_SARADC::regs() + .sar2data_status() + .read() + .saradc2_data() + .bits() as u16 + & 0xfff + } + + fn reset() { + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc2_done().clear_bit_by_one()); + + APB_SARADC::regs() + .onetime_sample() + .modify(|_, w| w.onetime_start().clear_bit()); + } + + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] + fn calibration_init() { + regi2c::ADC_SAR2_DREF.write_field(1); + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(msb as _); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(lsb as _); + } +} + +#[cfg(adc_adc2)] +impl super::CalibrationAccess for crate::peripherals::ADC2<'_> { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + regi2c::ADC_SAR2_DREF.write_field(enable as _); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => regi2c::ADC_SAR2_ENCAL_GND.write_field(enable as _), + AdcCalSource::Ref => regi2c::ADC_SAR2_ENCAL_REF.write_field(enable as _), + } + } +} + +/// Analog-to-Digital Converter peripheral driver. +pub struct Adc<'d, ADCI, Dm: crate::DriverMode> { + _adc: ADCI, + attenuations: [Option; NUM_ATTENS], + active_channel: Option, + _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>, + _phantom: PhantomData<(Dm, &'d mut ())>, +} + +impl<'d, ADCI> Adc<'d, ADCI, Blocking> +where + ADCI: RegisterAccess + 'd, +{ + /// Configure a given ADC instance using the provided configuration, and + /// initialize the ADC for use + pub fn new(adc_instance: ADCI, config: AdcConfig) -> Self { + let guard = GenericPeripheralGuard::new(); + + APB_SARADC::regs().ctrl().modify(|_, w| unsafe { + w.start_force().set_bit(); + w.start().set_bit(); + w.sar_clk_gated().set_bit(); + w.xpd_sar_force().bits(0b11) + }); + + Adc { + _adc: adc_instance, + attenuations: config.attenuations, + active_channel: None, + _guard: guard, + _phantom: PhantomData, + } + } + + /// Reconfigures the ADC driver to operate in asynchronous mode. + pub fn into_async(mut self) -> Adc<'d, ADCI, Async> { + acquire_async_adc(); + self.set_interrupt_handler(adc_interrupt_handler); + + // Reset interrupt flags and disable oneshot reading to normalize state before + // entering async mode, otherwise there can be '0' readings, happening initially + // using ADC2 + ADCI::reset(); + + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData, + } + } + + /// Request that the ADC begin a conversion on the specified pin + /// + /// This method takes an [AdcPin](super::AdcPin) reference, as it is + /// expected that the ADC will be able to sample whatever channel + /// underlies the pin. + pub fn read_oneshot( + &mut self, + pin: &mut super::AdcPin, + ) -> nb::Result + where + PIN: super::AdcChannel, + CS: super::AdcCalScheme, + { + if self.attenuations[pin.pin.adc_channel() as usize].is_none() { + panic!( + "Channel {} is not configured reading!", + pin.pin.adc_channel() + ); + } + + if let Some(active_channel) = self.active_channel { + // There is conversion in progress: + // - if it's for a different channel try again later + // - if it's for the given channel, go ahead and check progress + if active_channel != pin.pin.adc_channel() { + return Err(nb::Error::WouldBlock); + } + } else { + // If no conversions are in progress, start a new one for given channel + self.active_channel = Some(pin.pin.adc_channel()); + + // Set ADC unit calibration according used scheme for pin + ADCI::calibration_init(); + ADCI::set_init_code(pin.cal_scheme.adc_cal()); + + let channel = self.active_channel.unwrap(); + let attenuation = self.attenuations[channel as usize].unwrap() as u8; + ADCI::config_onetime_sample(channel, attenuation); + ADCI::start_onetime_sample(); + + // see https://github.com/espressif/esp-idf/blob/b4268c874a4cf8fcf7c0c4153cffb76ad2ddda4e/components/hal/adc_oneshot_hal.c#L105-L107 + // the delay might be a bit generous but longer delay seem to not cause problems + #[cfg(esp32c6)] + { + crate::rom::ets_delay_us(40); + ADCI::start_onetime_sample(); + } + } + + // Wait for ADC to finish conversion + let conversion_finished = ADCI::is_done(); + if !conversion_finished { + return Err(nb::Error::WouldBlock); + } + + // Get converted value + let converted_value = ADCI::read_data(); + ADCI::reset(); + + // Postprocess converted value according to calibration scheme used for pin + let converted_value = pin.cal_scheme.adc_val(converted_value); + + // There is a hardware limitation. If the APB clock frequency is high, the step + // of this reg signal: ``onetime_start`` may not be captured by the + // ADC digital controller (when its clock frequency is too slow). A rough + // estimate for this step should be at least 3 ADC digital controller + // clock cycle. + // + // This limitation will be removed in hardware future versions. + // We reset ``onetime_start`` in `reset` and assume enough time has passed until + // the next sample is requested. + + // Mark that no conversions are currently in progress + self.active_channel = None; + + Ok(converted_value) + } +} + +impl crate::private::Sealed for Adc<'_, ADCI, Blocking> {} + +impl InterruptConfigurable for Adc<'_, ADCI, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, InterruptSource); + } + crate::interrupt::bind_handler(InterruptSource, handler); + } +} + +#[cfg(adc_adc1)] +impl super::AdcCalEfuse for crate::peripherals::ADC1<'_> { + fn init_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC1, atten) + } + + fn cal_mv(atten: Attenuation) -> u16 { + crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC1, atten) + } + + fn cal_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC1, atten) + } +} + +#[cfg(adc_adc2)] +impl super::AdcCalEfuse for crate::peripherals::ADC2<'_> { + fn init_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC2, atten) + } + + fn cal_mv(atten: Attenuation) -> u16 { + crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC2, atten) + } + + fn cal_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC2, atten) + } +} + +impl<'d, ADCI> Adc<'d, ADCI, Async> +where + ADCI: RegisterAccess + 'd, +{ + /// Create a new instance in [crate::Blocking] mode. + pub fn into_blocking(self) -> Adc<'d, ADCI, Blocking> { + if release_async_adc() { + // Disable ADC interrupt on all cores if the last async ADC instance is disabled + for cpu in crate::system::Cpu::all() { + crate::interrupt::disable(cpu, InterruptSource); + } + } + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData, + } + } + + /// Request that the ADC begin a conversion on the specified pin + /// + /// This method takes an [AdcPin](super::AdcPin) reference, as it is + /// expected that the ADC will be able to sample whatever channel + /// underlies the pin. + pub async fn read_oneshot(&mut self, pin: &mut super::AdcPin) -> u16 + where + ADCI: Instance, + PIN: super::AdcChannel, + CS: super::AdcCalScheme, + { + let channel = pin.pin.adc_channel(); + if self.attenuations[channel as usize].is_none() { + panic!("Channel {} is not configured reading!", channel); + } + + // Set ADC unit calibration according used scheme for pin + ADCI::calibration_init(); + ADCI::set_init_code(pin.cal_scheme.adc_cal()); + + let attenuation = self.attenuations[channel as usize].unwrap() as u8; + ADCI::config_onetime_sample(channel, attenuation); + ADCI::start_onetime_sample(); + + // Wait for ADC to finish conversion and get value + let adc_ready_future = AdcFuture::new(self); + adc_ready_future.await; + let converted_value = ADCI::read_data(); + + // There is a hardware limitation. If the APB clock frequency is high, the step + // of this reg signal: ``onetime_start`` may not be captured by the + // ADC digital controller (when its clock frequency is too slow). A rough + // estimate for this step should be at least 3 ADC digital controller + // clock cycle. + // + // This limitation will be removed in hardware future versions. + // We reset ``onetime_start`` in `reset` and assume enough time has passed until + // the next sample is requested. + + ADCI::reset(); + + // Postprocess converted value according to calibration scheme used for pin + pin.cal_scheme.adc_val(converted_value) + } +} + +#[cfg(all(adc_adc1, adc_adc2))] +static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0); + +pub(super) fn acquire_async_adc() { + #[cfg(all(adc_adc1, adc_adc2))] + ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed); +} + +pub(super) fn release_async_adc() -> bool { + cfg_if::cfg_if! { + if #[cfg(all(adc_adc1, adc_adc2))] { + ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 + } else { + true + } + } +} + +#[handler] +pub(crate) fn adc_interrupt_handler() { + let saradc = APB_SARADC::regs(); + let interrupt_status = saradc.int_st().read(); + + #[cfg(adc_adc1)] + if interrupt_status.adc1_done().bit_is_set() { + unsafe { handle_async(crate::peripherals::ADC1::steal()) } + } + + #[cfg(adc_adc2)] + if interrupt_status.adc2_done().bit_is_set() { + unsafe { handle_async(crate::peripherals::ADC2::steal()) } + } +} + +fn handle_async(_instance: ADCI) { + ADCI::waker().wake(); + ADCI::unlisten(); +} + +/// Enable asynchronous access. +pub trait Instance: crate::private::Sealed { + /// Enable the ADC interrupt + fn listen(); + + /// Disable the ADC interrupt + fn unlisten(); + + /// Clear the ADC interrupt + fn clear_interrupt(); + + /// Obtain the waker for the ADC interrupt + fn waker() -> &'static AtomicWaker; +} + +#[cfg(adc_adc1)] +impl Instance for crate::peripherals::ADC1<'_> { + fn listen() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc1_done().set_bit()); + } + + fn unlisten() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc1_done().clear_bit()); + } + + fn clear_interrupt() { + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc1_done().clear_bit_by_one()); + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } +} + +#[cfg(adc_adc2)] +impl Instance for crate::peripherals::ADC2<'_> { + fn listen() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc2_done().set_bit()); + } + + fn unlisten() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc2_done().clear_bit()); + } + + fn clear_interrupt() { + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc2_done().clear_bit_by_one()); + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub(crate) struct AdcFuture { + phantom: PhantomData, +} + +impl AdcFuture { + pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl core::future::Future for AdcFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if ADCI::is_done() { + ADCI::clear_interrupt(); + Poll::Ready(()) + } else { + ADCI::waker().register(cx.waker()); + ADCI::listen(); + Poll::Pending + } + } +} + +impl Drop for AdcFuture { + fn drop(&mut self) { + ADCI::unlisten(); + } +} diff --git a/esp-hal/src/analog/adc/xtensa.rs b/esp-hal/src/analog/adc/xtensa.rs new file mode 100644 index 00000000000..90ace0b5b45 --- /dev/null +++ b/esp-hal/src/analog/adc/xtensa.rs @@ -0,0 +1,516 @@ +use core::marker::PhantomData; + +#[cfg(esp32s3)] +pub use self::calibration::*; +use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation}; +#[cfg(esp32s3)] +use crate::efuse::AdcCalibUnit; +use crate::{ + peripherals::{APB_SARADC, SENS}, + soc::regi2c, + system::{GenericPeripheralGuard, Peripheral}, +}; + +mod calibration; + +pub(super) const NUM_ATTENS: usize = 10; + +cfg_if::cfg_if! { + if #[cfg(esp32s3)] { + const ADC_VAL_MASK: u16 = 0xfff; + const ADC_CAL_CNT_MAX: u16 = 32; + const ADC_CAL_CHANNEL: u16 = 15; + } +} + +impl AdcConfig +where + ADCI: RegisterAccess, +{ + /// Calibrate ADC with specified attenuation and voltage source + pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 + where + ADCI: super::CalibrationAccess, + { + let mut adc_max: u16 = 0; + let mut adc_min: u16 = u16::MAX; + let mut adc_sum: u32 = 0; + + ADCI::enable_vdef(true); + + // Start sampling + ADCI::set_en_pad(ADCI::ADC_CAL_CHANNEL as u8); + ADCI::set_attenuation(ADCI::ADC_CAL_CHANNEL as usize, atten as u8); + + // Connect calibration source + ADCI::connect_cal(source, true); + + ADCI::calibration_init(); + ADCI::set_init_code(0); + + for _ in 0..ADCI::ADC_CAL_CNT_MAX { + // Trigger ADC sampling + ADCI::start_sample(); + + // Wait until ADC1 sampling is done + while !ADCI::is_done() {} + + let adc = ADCI::read_data() & ADCI::ADC_VAL_MASK; + + ADCI::reset(); + + adc_sum += adc as u32; + adc_max = adc.max(adc_max); + adc_min = adc.min(adc_min); + } + + let cal_val = + (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADCI::ADC_CAL_CNT_MAX - 2); + + // Disconnect calibration source + ADCI::connect_cal(source, false); + + cal_val + } +} + +#[doc(hidden)] +pub trait RegisterAccess { + fn set_attenuation(channel: usize, attenuation: u8); + + fn clear_dig_force(); + + fn set_start_force(); + + fn set_en_pad_force(); + + fn set_en_pad(channel: u8); + + fn clear_start_sample(); + + fn start_sample(); + + /// Check if sampling is done + fn is_done() -> bool; + + /// Read sample data + fn read_data() -> u16; + + /// Set up ADC hardware for calibration + fn calibration_init(); + + /// Set calibration parameter to ADC hardware + fn set_init_code(data: u16); + + /// Reset flags + fn reset(); +} + +impl RegisterAccess for crate::peripherals::ADC1<'_> { + fn set_attenuation(channel: usize, attenuation: u8) { + SENS::regs().sar_atten1().modify(|r, w| { + let new_value = (r.bits() & !(0b11 << (channel * 2))) + | (((attenuation & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar1_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + SENS::regs() + .sar_meas1_mux() + .modify(|_, w| w.sar1_dig_force().clear_bit()); + } + + fn set_start_force() { + SENS::regs() + .sar_meas1_ctrl2() + .modify(|_, w| w.meas1_start_force().set_bit()); + } + + fn set_en_pad_force() { + SENS::regs() + .sar_meas1_ctrl2() + .modify(|_, w| w.sar1_en_pad_force().set_bit()); + } + + fn set_en_pad(channel: u8) { + SENS::regs() + .sar_meas1_ctrl2() + .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) }); + } + + fn clear_start_sample() { + SENS::regs() + .sar_meas1_ctrl2() + .modify(|_, w| w.meas1_start_sar().clear_bit()); + } + + fn start_sample() { + SENS::regs() + .sar_meas1_ctrl2() + .modify(|_, w| w.meas1_start_sar().set_bit()); + } + + fn is_done() -> bool { + SENS::regs() + .sar_meas1_ctrl2() + .read() + .meas1_done_sar() + .bit_is_set() + } + + fn read_data() -> u16 { + SENS::regs() + .sar_meas1_ctrl2() + .read() + .meas1_data_sar() + .bits() + } + + #[cfg(any(esp32s2, esp32s3))] + fn calibration_init() { + // https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/hal/esp32s3/include/hal/adc_ll.h#L833 + // https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/hal/esp32s2/include/hal/adc_ll.h#L1145 + regi2c::ADC_SAR1_DREF.write_field(4); + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(msb); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(lsb); + } + + fn reset() { + let adc = APB_SARADC::regs(); + let sensors = SENS::regs(); + + adc.int_clr().write(|w| w.adc1_done().clear_bit_by_one()); + + sensors + .sar_meas1_ctrl2() + .modify(|_, w| w.meas1_start_sar().clear_bit()); + } +} + +#[cfg(esp32s3)] +impl super::CalibrationAccess for crate::peripherals::ADC1<'_> { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + regi2c::ADC_SAR1_DREF.write_field(enable as u8); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => regi2c::ADC_SAR1_ENCAL_GND.write_field(enable as u8), + AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_REF.write_field(enable as u8), + } + } +} + +impl RegisterAccess for crate::peripherals::ADC2<'_> { + fn set_attenuation(channel: usize, attenuation: u8) { + SENS::regs().sar_atten2().modify(|r, w| { + let new_value = (r.bits() & !(0b11 << (channel * 2))) + | (((attenuation & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar2_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + SENS::regs() + .sar_meas2_mux() + .modify(|_, w| w.sar2_rtc_force().set_bit()); + + APB_SARADC::regs() + .arb_ctrl() + .modify(|_, w| w.rtc_force().set_bit()); + } + + fn set_start_force() { + SENS::regs() + .sar_meas2_ctrl2() + .modify(|_, w| w.meas2_start_force().set_bit()); + } + + fn set_en_pad_force() { + SENS::regs() + .sar_meas2_ctrl2() + .modify(|_, w| w.sar2_en_pad_force().set_bit()); + } + + fn set_en_pad(channel: u8) { + SENS::regs() + .sar_meas2_ctrl2() + .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) }); + } + + fn clear_start_sample() { + SENS::regs() + .sar_meas2_ctrl2() + .modify(|_, w| w.meas2_start_sar().clear_bit()); + } + + fn start_sample() { + SENS::regs() + .sar_meas2_ctrl2() + .modify(|_, w| w.meas2_start_sar().set_bit()); + } + + fn is_done() -> bool { + SENS::regs() + .sar_meas2_ctrl2() + .read() + .meas2_done_sar() + .bit_is_set() + } + + fn read_data() -> u16 { + SENS::regs() + .sar_meas2_ctrl2() + .read() + .meas2_data_sar() + .bits() + } + + #[cfg(any(esp32s2, esp32s3))] + fn calibration_init() { + regi2c::ADC_SAR2_DREF.write_field(4); + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(msb); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(lsb); + } + + fn reset() { + let adc = APB_SARADC::regs(); + let sensors = SENS::regs(); + + adc.int_clr().write(|w| w.adc2_done().clear_bit_by_one()); + + sensors + .sar_meas2_ctrl2() + .modify(|_, w| w.meas2_start_sar().clear_bit()); + } +} + +#[cfg(esp32s3)] +impl super::CalibrationAccess for crate::peripherals::ADC2<'_> { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + regi2c::ADC_SAR2_DREF.write_field(enable as u8); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => regi2c::ADC_SAR2_ENCAL_GND.write_field(enable as u8), + AdcCalSource::Ref => regi2c::ADC_SAR2_ENCAL_REF.write_field(enable as u8), + } + } +} + +/// Analog-to-Digital Converter peripheral driver. +pub struct Adc<'d, ADC, Dm: crate::DriverMode> { + _adc: ADC, + active_channel: Option, + last_init_code: u16, + _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>, + _phantom: PhantomData<(Dm, &'d mut ())>, +} + +impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking> +where + ADCI: RegisterAccess + 'd, +{ + /// Configure a given ADC instance using the provided configuration, and + /// initialize the ADC for use + pub fn new(adc_instance: ADCI, config: AdcConfig) -> Self { + let guard = GenericPeripheralGuard::new(); + let sensors = SENS::regs(); + + // Set attenuation for pins + let attenuations = config.attenuations; + + for (channel, attenuation) in attenuations.iter().enumerate() { + if let Some(attenuation) = attenuation { + ADCI::set_attenuation(channel, *attenuation as u8); + } + } + + // Set controller to RTC + ADCI::clear_dig_force(); + ADCI::set_start_force(); + ADCI::set_en_pad_force(); + sensors.sar_hall_ctrl().modify(|_, w| { + w.xpd_hall_force().set_bit(); + w.hall_phase_force().set_bit() + }); + + // Set power to SW power on + #[cfg(esp32s2)] + sensors + .sar_meas1_ctrl1() + .modify(|_, w| w.rtc_saradc_clkgate_en().set_bit()); + + #[cfg(esp32s3)] + sensors + .sar_peri_clk_gate_conf() + .modify(|_, w| w.saradc_clk_en().set_bit()); + + sensors.sar_power_xpd_sar().modify(|_, w| unsafe { + w.sarclk_en().set_bit(); + w.force_xpd_sar().bits(0b11) + }); + + // disable AMP + sensors + .sar_meas1_ctrl1() + .modify(|_, w| unsafe { w.force_xpd_amp().bits(0b11) }); + sensors.sar_amp_ctrl3().modify(|_, w| unsafe { + w.amp_rst_fb_fsm().bits(0); + w.amp_short_ref_fsm().bits(0); + w.amp_short_ref_gnd_fsm().bits(0) + }); + sensors.sar_amp_ctrl1().modify(|_, w| unsafe { + w.sar_amp_wait1().bits(1); + w.sar_amp_wait2().bits(1) + }); + sensors + .sar_amp_ctrl2() + .modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) }); + + Adc { + _adc: adc_instance, + active_channel: None, + last_init_code: 0, + _guard: guard, + _phantom: PhantomData, + } + } + + /// Start and wait for a conversion on the specified pin and return the + /// result + pub fn read_blocking(&mut self, pin: &mut AdcPin) -> u16 + where + PIN: AdcChannel, + CS: AdcCalScheme, + { + self.start_sample(pin); + + // Wait for ADC to finish conversion + while !ADCI::is_done() {} + + // Get converted value + let converted_value = ADCI::read_data(); + ADCI::reset(); + + // Postprocess converted value according to calibration scheme used for pin + pin.cal_scheme.adc_val(converted_value) + } + + /// Request that the ADC begin a conversion on the specified pin + /// + /// This method takes an [AdcPin](super::AdcPin) reference, as it is + /// expected that the ADC will be able to sample whatever channel + /// underlies the pin. + pub fn read_oneshot( + &mut self, + pin: &mut super::AdcPin, + ) -> nb::Result + where + PIN: super::AdcChannel, + CS: super::AdcCalScheme, + { + if let Some(active_channel) = self.active_channel { + // There is conversion in progress: + // - if it's for a different channel try again later + // - if it's for the given channel, go ahead and check progress + if active_channel != pin.pin.adc_channel() { + return Err(nb::Error::WouldBlock); + } + } else { + // If no conversions are in progress, start a new one for given channel + self.active_channel = Some(pin.pin.adc_channel()); + + self.start_sample(pin); + } + + // Wait for ADC to finish conversion + let conversion_finished = ADCI::is_done(); + if !conversion_finished { + return Err(nb::Error::WouldBlock); + } + + // Get converted value + let converted_value = ADCI::read_data(); + ADCI::reset(); + + // Postprocess converted value according to calibration scheme used for pin + let converted_value = pin.cal_scheme.adc_val(converted_value); + + // Mark that no conversions are currently in progress + self.active_channel = None; + + Ok(converted_value) + } + + fn start_sample(&mut self, pin: &mut AdcPin) + where + PIN: AdcChannel, + CS: AdcCalScheme, + { + // Set ADC unit calibration according used scheme for pin + let init_code = pin.cal_scheme.adc_cal(); + if self.last_init_code != init_code { + ADCI::calibration_init(); + ADCI::set_init_code(init_code); + self.last_init_code = init_code; + } + + ADCI::set_en_pad(pin.pin.adc_channel()); + + ADCI::clear_start_sample(); + ADCI::start_sample(); + } +} + +#[cfg(esp32s3)] +impl super::AdcCalEfuse for crate::peripherals::ADC1<'_> { + fn init_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC1, atten) + } + + fn cal_mv(atten: Attenuation) -> u16 { + crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC1, atten) + } + + fn cal_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC1, atten) + } +} + +#[cfg(esp32s3)] +impl super::AdcCalEfuse for crate::peripherals::ADC2<'_> { + fn init_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC2, atten) + } + + fn cal_mv(atten: Attenuation) -> u16 { + crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC2, atten) + } + + fn cal_code(atten: Attenuation) -> Option { + crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC2, atten) + } +} diff --git a/esp-hal/src/analog/dac.rs b/esp-hal/src/analog/dac.rs new file mode 100644 index 00000000000..1b31c359696 --- /dev/null +++ b/esp-hal/src/analog/dac.rs @@ -0,0 +1,140 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "dac1_pin" => { + cfg(esp32) => "GPIO25", + cfg(esp32s2) => "GPIO17" + } +))] +//! # Digital to Analog Converter (DAC) +//! +//! ## Overview +//! Espressif devices usually have multiple DAC channels. Each DAC channel can +//! convert the digital value 0~255 to the analog voltage 0~Vref (The reference +//! voltage 'Vref' here is input from an input pin) +//! +//! The DAC peripheral supports outputting analog signal in the multiple ways. +//! +//! Two 8-bit DAC channels are available. +//! +//! ## Configuration +//! Developers can choose the DAC channel they want to use based on the GPIO +//! pin assignments for each channel. +//! +//! ## Examples +//! ### Write a value to a DAC channel +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::analog::dac::Dac; +//! # use esp_hal::delay::Delay; +//! # use embedded_hal::delay::DelayNs; +//! let mut dac1 = Dac::new(peripherals.DAC1, peripherals.__dac1_pin__); +//! +//! let mut delay = Delay::new(); +//! +//! let mut voltage_dac1 = 200u8; +//! +//! // Change voltage on the pins using write function: +//! loop { +//! voltage_dac1 = voltage_dac1.wrapping_add(1); +//! dac1.write(voltage_dac1); +//! +//! delay.delay_ms(50u32); +//! } +//! # } +//! ``` + +use crate::gpio::AnalogPin; + +// Only specific pins can be used with each DAC peripheral, and of course +// these pins are different depending on which chip you are using; for this +// reason, we will type alias the pins for ease of use later in this module: +cfg_if::cfg_if! { + if #[cfg(esp32)] { + type Dac1Gpio<'d> = crate::peripherals::GPIO25<'d>; + type Dac2Gpio<'d> = crate::peripherals::GPIO26<'d>; + } else if #[cfg(esp32s2)] { + type Dac1Gpio<'d> = crate::peripherals::GPIO17<'d>; + type Dac2Gpio<'d> = crate::peripherals::GPIO18<'d>; + } +} + +/// Digital-to-Analog Converter (DAC) Channel +pub struct Dac<'d, T> +where + T: Instance + 'd, + T::Pin: AnalogPin + 'd, +{ + _inner: T, + _lifetime: core::marker::PhantomData<&'d mut ()>, +} + +impl<'d, T> Dac<'d, T> +where + T: Instance + 'd, + T::Pin: AnalogPin + 'd, +{ + /// Construct a new instance of [`Dac`]. + pub fn new(dac: T, pin: T::Pin) -> Self { + // TODO: Revert on drop. + pin.set_analog(crate::private::Internal); + + #[cfg(esp32s2)] + crate::peripherals::SENS::regs() + .sar_dac_ctrl1() + .modify(|_, w| w.dac_clkgate_en().set_bit()); + + T::enable_xpd(); + + Self { + _inner: dac, + _lifetime: core::marker::PhantomData, + } + } + + /// Writes the given value. + /// + /// For each DAC channel, the output analog voltage can be calculated as + /// follows: DACn_OUT = VDD3P3_RTC * PDACn_DAC/256 + pub fn write(&mut self, value: u8) { + T::set_pad_source(); + T::write_byte(value); + } +} + +#[doc(hidden)] +pub trait Instance: crate::private::Sealed { + const INDEX: usize; + + type Pin; + + fn enable_xpd() { + crate::peripherals::RTC_IO::regs() + .pad_dac(Self::INDEX) + .modify(|_, w| w.dac_xpd_force().set_bit().xpd_dac().set_bit()); + } + + fn set_pad_source() { + crate::peripherals::SENS::regs() + .sar_dac_ctrl2() + .modify(|_, w| w.dac_cw_en(Self::INDEX as u8).clear_bit()); + } + + fn write_byte(value: u8) { + crate::peripherals::RTC_IO::regs() + .pad_dac(Self::INDEX) + .modify(|_, w| unsafe { w.dac().bits(value) }); + } +} + +#[cfg(dac_dac1)] +impl<'d> Instance for crate::peripherals::DAC1<'d> { + const INDEX: usize = 0; + + type Pin = Dac1Gpio<'d>; +} + +#[cfg(dac_dac2)] +impl<'d> Instance for crate::peripherals::DAC2<'d> { + const INDEX: usize = 1; + + type Pin = Dac2Gpio<'d>; +} diff --git a/esp-hal/src/analog/mod.rs b/esp-hal/src/analog/mod.rs new file mode 100644 index 00000000000..7613a3d95e2 --- /dev/null +++ b/esp-hal/src/analog/mod.rs @@ -0,0 +1,11 @@ +//! # Analog Peripherals +//! +//! ## Overview +//! The `analog` module provides drivers for the various analog peripherals +//! available on the device. For more information about a peripheral driver, +//! please refer to the relevant module documentation. + +#[cfg(adc_driver_supported)] +pub mod adc; +#[cfg(dac_driver_supported)] +pub mod dac; diff --git a/esp-hal/src/assist_debug.rs b/esp-hal/src/assist_debug.rs new file mode 100644 index 00000000000..633decd9cdd --- /dev/null +++ b/esp-hal/src/assist_debug.rs @@ -0,0 +1,443 @@ +//! # Debug Assistant (ASSIST_DEBUG) +//! +//! ## Overview +//! Debug Assistant is an auxiliary module that features a set of functions to +//! help locate bugs and issues during software debugging. It includes +//! capabilities such as monitoring stack pointer (SP), monitoring memory +//! regions, and handling interrupts related to debugging. +//! +//! +//! ## Configuration +//! While all the targets support program counter (PC) logging it's API is not +//! exposed here. Instead the ROM bootloader will always enable it and print the +//! last seen PC (e.g. _Saved PC:0x42002ff2_). Make sure the reset was triggered +//! by a TIMG watchdog. Not an RTC or SWD watchdog. +//! +//! ## Examples +//! Visit the [Debug Assist] example for an example of using the Debug +//! Assistant. +//! +//! [Debug Assist]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/debug_assist/src/main.rs +//! +//! ## Implementation State +//! - Bus write access logging is not available via this API +//! - This driver has only blocking API + +use crate::{ + interrupt::InterruptHandler, + pac, + peripherals::{ASSIST_DEBUG, Interrupt}, +}; + +/// The debug assist driver instance. +pub struct DebugAssist<'d> { + debug_assist: ASSIST_DEBUG<'d>, +} + +impl<'d> DebugAssist<'d> { + /// Create a new instance in [crate::Blocking] mode. + pub fn new(debug_assist: ASSIST_DEBUG<'d>) -> Self { + // NOTE: We should enable the debug assist, however, it's always enabled in ROM + // code already. + + DebugAssist { debug_assist } + } + + /// Register an interrupt handler for the Debug Assist module. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::ASSIST_DEBUG); + } + crate::interrupt::bind_handler(Interrupt::ASSIST_DEBUG, handler); + } + + fn regs(&self) -> &pac::assist_debug::RegisterBlock { + self.debug_assist.register_block() + } +} + +impl crate::private::Sealed for DebugAssist<'_> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for DebugAssist<'_> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +#[cfg(assist_debug_has_sp_monitor)] +impl DebugAssist<'_> { + /// Enable SP monitoring on main core. When the SP exceeds the + /// `lower_bound` or `upper_bound` threshold, the module will record the PC + /// pointer and generate an interrupt. + pub fn internal_sp_monitor(&mut self, cpu: usize, lower_bound: u32, upper_bound: u32) { + let regs = self.regs().cpu(cpu); + + regs.sp_min() + .write(|w| unsafe { w.sp_min().bits(lower_bound) }); + + regs.sp_max() + .write(|w| unsafe { w.sp_max().bits(upper_bound) }); + + regs.montr_ena().modify(|_, w| { + w.sp_spill_min_ena().set_bit(); + w.sp_spill_max_ena().set_bit() + }); + + regs.intr_clr().write(|w| { + w.sp_spill_max_clr().set_bit(); + w.sp_spill_min_clr().set_bit() + }); + + regs.intr_ena().modify(|_, w| { + w.sp_spill_max_intr_ena().set_bit(); + w.sp_spill_min_intr_ena().set_bit() + }); + } + + fn internal_disable_sp_monitor(&mut self, cpu: usize) { + let regs = self.regs().cpu(cpu); + + regs.intr_ena().modify(|_, w| { + w.sp_spill_max_intr_ena().clear_bit(); + w.sp_spill_min_intr_ena().clear_bit() + }); + + regs.montr_ena().modify(|_, w| { + w.sp_spill_min_ena().clear_bit(); + w.sp_spill_max_ena().clear_bit() + }); + } + + fn internal_clear_sp_monitor_interrupt(&mut self, cpu: usize) { + self.regs().cpu(cpu).intr_clr().write(|w| { + w.sp_spill_max_clr().set_bit(); + w.sp_spill_min_clr().set_bit() + }); + } + + fn internal_is_sp_monitor_interrupt_set(&self, cpu: usize) -> bool { + let regs = self.regs().cpu(cpu); + let intrs = regs.intr_raw().read(); + + intrs.sp_spill_max_raw().bit_is_set() || intrs.sp_spill_min_raw().bit_is_set() + } + + fn internal_sp_monitor_pc(&self, cpu: usize) -> u32 { + self.regs().cpu(cpu).sp_pc().read().sp_pc().bits() + } + + /// Enable SP monitoring on main core. When the SP exceeds the + /// `lower_bound` or `upper_bound` threshold, the module will record the PC + /// pointer and generate an interrupt. + pub fn enable_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) { + self.internal_sp_monitor(0, lower_bound, upper_bound); + } + + /// Disable SP monitoring on main core. + pub fn disable_sp_monitor(&mut self) { + self.internal_disable_sp_monitor(0) + } + + /// Clear SP monitoring interrupt on main core. + pub fn clear_sp_monitor_interrupt(&mut self) { + self.internal_clear_sp_monitor_interrupt(0) + } + + /// Check, if SP monitoring interrupt is set on main core. + pub fn is_sp_monitor_interrupt_set(&self) -> bool { + self.internal_is_sp_monitor_interrupt_set(0) + } + + /// Get SP monitoring PC value on main core. + pub fn sp_monitor_pc(&self) -> u32 { + self.internal_sp_monitor_pc(0) + } +} + +#[cfg(all(assist_debug_has_sp_monitor, multi_core))] +impl<'d> DebugAssist<'d> { + /// Enable SP monitoring on secondary core. When the SP exceeds the + /// `lower_bound` or `upper_bound` threshold, the module will record the PC + /// pointer and generate an interrupt. + pub fn enable_core1_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) { + self.internal_sp_monitor(1, lower_bound, upper_bound); + } + + /// Disable SP monitoring on secondary core. + pub fn disable_core1_sp_monitor(&mut self) { + self.internal_disable_sp_monitor(1) + } + + /// Clear SP monitoring interrupt on secondary core. + pub fn clear_core1_sp_monitor_interrupt(&mut self) { + self.internal_clear_sp_monitor_interrupt(1) + } + + /// Check, if SP monitoring interrupt is set on secondary core. + pub fn is_core1_sp_monitor_interrupt_set(&self) -> bool { + self.internal_is_sp_monitor_interrupt_set(1) + } + + /// Get SP monitoring PC value on secondary core. + pub fn core1_sp_monitor_pc(&self) -> u32 { + self.internal_sp_monitor_pc(1) + } +} + +#[cfg(assist_debug_has_region_monitor)] +impl DebugAssist<'_> { + fn internal_enable_region0_monitor( + &mut self, + cpu: usize, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + let regs = self.regs().cpu(cpu); + + regs.area_dram0_0_min() + .write(|w| unsafe { w.area_dram0_0_min().bits(lower_bound) }); + + regs.area_dram0_0_max() + .write(|w| unsafe { w.area_dram0_0_max().bits(upper_bound) }); + + regs.montr_ena().modify(|_, w| { + w.area_dram0_0_rd_ena().bit(reads); + w.area_dram0_0_wr_ena().bit(writes) + }); + + regs.intr_clr().write(|w| { + w.area_dram0_0_rd_clr().set_bit(); + w.area_dram0_0_wr_clr().set_bit() + }); + + regs.intr_ena().modify(|_, w| { + w.area_dram0_0_rd_intr_ena().set_bit(); + w.area_dram0_0_wr_intr_ena().set_bit() + }); + } + + fn internal_disable_region0_monitor(&mut self, cpu: usize) { + let regs = self.regs().cpu(cpu); + + regs.intr_ena().modify(|_, w| { + w.area_dram0_0_rd_intr_ena().clear_bit(); + w.area_dram0_0_wr_intr_ena().clear_bit() + }); + + regs.montr_ena().modify(|_, w| { + w.area_dram0_0_rd_ena().clear_bit(); + w.area_dram0_0_wr_ena().clear_bit() + }); + } + + fn internal_clear_region0_monitor_interrupt(&mut self, cpu: usize) { + self.regs().cpu(cpu).intr_clr().write(|w| { + w.area_dram0_0_rd_clr().set_bit(); + w.area_dram0_0_wr_clr().set_bit() + }); + } + + fn internal_is_region0_monitor_interrupt_set(&self, cpu: usize) -> bool { + let regs = self.regs().cpu(cpu); + let intrs = regs.intr_raw().read(); + + intrs.area_dram0_0_rd_raw().bit_is_set() || intrs.area_dram0_0_wr_raw().bit_is_set() + } + + fn internal_enable_region1_monitor( + &mut self, + cpu: usize, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + let regs = self.regs().cpu(cpu); + + regs.area_dram0_1_min() + .write(|w| unsafe { w.area_dram0_1_min().bits(lower_bound) }); + + regs.area_dram0_1_max() + .write(|w| unsafe { w.area_dram0_1_max().bits(upper_bound) }); + + regs.montr_ena().modify(|_, w| { + w.area_dram0_1_rd_ena().bit(reads); + w.area_dram0_1_wr_ena().bit(writes) + }); + + regs.intr_clr().write(|w| { + w.area_dram0_1_rd_clr().set_bit(); + w.area_dram0_1_wr_clr().set_bit() + }); + + regs.intr_ena().modify(|_, w| { + w.area_dram0_1_rd_intr_ena().set_bit(); + w.area_dram0_1_wr_intr_ena().set_bit() + }); + } + + fn internal_disable_region1_monitor(&mut self, cpu: usize) { + let regs = self.regs().cpu(cpu); + + regs.intr_ena().modify(|_, w| { + w.area_dram0_1_rd_intr_ena().clear_bit(); + w.area_dram0_1_wr_intr_ena().clear_bit() + }); + + regs.montr_ena().modify(|_, w| { + w.area_dram0_1_rd_ena().clear_bit(); + w.area_dram0_1_wr_ena().clear_bit() + }); + } + + fn internal_clear_region1_monitor_interrupt(&mut self, cpu: usize) { + self.regs().cpu(cpu).intr_clr().write(|w| { + w.area_dram0_1_rd_clr().set_bit(); + w.area_dram0_1_wr_clr().set_bit() + }); + } + + fn internal_is_region1_monitor_interrupt_set(&self, cpu: usize) -> bool { + let regs = self.regs().cpu(cpu); + let intrs = regs.intr_raw().read(); + + intrs.area_dram0_1_rd_raw().bit_is_set() || intrs.area_dram0_1_wr_raw().bit_is_set() + } + + fn internal_region_monitor_pc(&self, cpu: usize) -> u32 { + self.regs().cpu(cpu).area_pc().read().area_pc().bits() + } + + /// Enable region monitoring of read/write performed by the main CPU in a + /// certain memory region0. Whenever the bus reads or writes in the + /// specified memory region, an interrupt will be triggered. Two memory + /// regions (region0, region1) can be monitored at the same time. + pub fn enable_region0_monitor( + &mut self, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + self.internal_enable_region0_monitor(0, lower_bound, upper_bound, reads, writes) + } + + /// Disable region0 monitoring on main core. + pub fn disable_region0_monitor(&mut self) { + self.internal_disable_region0_monitor(0) + } + + /// Clear region0 monitoring interrupt on main core. + pub fn clear_region0_monitor_interrupt(&mut self) { + self.internal_clear_region0_monitor_interrupt(0) + } + + /// Check, if region0 monitoring interrupt is set on main core. + pub fn is_region0_monitor_interrupt_set(&self) -> bool { + self.internal_is_region0_monitor_interrupt_set(0) + } + + /// Enable region monitoring of read/write performed by the main CPU in a + /// certain memory region1. Whenever the bus reads or writes in the + /// specified memory region, an interrupt will be triggered. + pub fn enable_region1_monitor( + &mut self, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + self.internal_enable_region1_monitor(0, lower_bound, upper_bound, reads, writes) + } + + /// Disable region1 monitoring on main core. + pub fn disable_region1_monitor(&mut self) { + self.internal_disable_region1_monitor(0) + } + + /// Clear region1 monitoring interrupt on main core. + pub fn clear_region1_monitor_interrupt(&mut self) { + self.internal_clear_region1_monitor_interrupt(0) + } + + /// Check, if region1 monitoring interrupt is set on main core. + pub fn is_region1_monitor_interrupt_set(&self) -> bool { + self.internal_is_region1_monitor_interrupt_set(0) + } + + /// Get region monitoring PC value on main core. + pub fn region_monitor_pc(&self) -> u32 { + self.internal_region_monitor_pc(0) + } +} + +#[cfg(all(assist_debug_has_region_monitor, multi_core))] +impl DebugAssist<'_> { + /// Enable region monitoring of read/write performed by the secondary CPU in + /// a certain memory region0. Whenever the bus reads or writes in the + /// specified memory region, an interrupt will be triggered. + pub fn enable_core1_region0_monitor( + &mut self, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + self.internal_enable_region0_monitor(1, lower_bound, upper_bound, reads, writes) + } + + /// Disable region0 monitoring on secondary core. + pub fn disable_core1_region0_monitor(&mut self) { + self.internal_disable_region0_monitor(1) + } + + /// Clear region0 monitoring interrupt on secondary core. + pub fn clear_core1_region0_monitor_interrupt(&mut self) { + self.internal_clear_region0_monitor_interrupt(1) + } + + /// Check, if region0 monitoring interrupt is set on secondary core. + pub fn is_core1_region0_monitor_interrupt_set(&self) -> bool { + self.internal_is_region0_monitor_interrupt_set(1) + } + + /// Enable region monitoring of read/write performed by the secondary CPU in + /// a certain memory region1. Whenever the bus reads or writes in the + /// specified memory region, an interrupt will be triggered. + pub fn enable_core1_region1_monitor( + &mut self, + lower_bound: u32, + upper_bound: u32, + reads: bool, + writes: bool, + ) { + self.internal_enable_region1_monitor(1, lower_bound, upper_bound, reads, writes) + } + + /// Disable region1 monitoring on secondary core. + pub fn disable_core1_region1_monitor(&mut self) { + self.internal_disable_region1_monitor(1) + } + + /// Clear region1 monitoring interrupt on secondary core. + pub fn clear_core1_region1_monitor_interrupt(&mut self) { + self.internal_clear_region1_monitor_interrupt(1) + } + + /// Check, if region1 monitoring interrupt is set on secondary core. + pub fn is_core1_region1_monitor_interrupt_set(&self) -> bool { + self.internal_is_region1_monitor_interrupt_set(1) + } + + /// Get region monitoring PC value on secondary core. + pub fn core1_region_monitor_pc(&self) -> u32 { + self.internal_region_monitor_pc(1) + } +} diff --git a/esp-hal/src/asynch.rs b/esp-hal/src/asynch.rs new file mode 100644 index 00000000000..827ed031fbe --- /dev/null +++ b/esp-hal/src/asynch.rs @@ -0,0 +1,32 @@ +//! Asynchronous utilities. +use core::task::Waker; + +use embassy_sync::waitqueue::GenericAtomicWaker; +use esp_sync::RawMutex; + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: GenericAtomicWaker, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self { + waker: GenericAtomicWaker::new(RawMutex::new()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + #[inline] + pub fn register(&self, w: &Waker) { + self.waker.register(w); + } + + /// Wake the registered waker, if any. + #[crate::ram] + pub fn wake(&self) { + self.waker.wake(); + } +} diff --git a/esp-hal/src/clock/mod.rs b/esp-hal/src/clock/mod.rs new file mode 100644 index 00000000000..add21923dba --- /dev/null +++ b/esp-hal/src/clock/mod.rs @@ -0,0 +1,536 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # CPU Clock Control +//! +//! ## Overview +//! +//! Clocks are mainly sourced from oscillator (OSC), RC, and PLL circuits, and +//! then processed by the dividers or selectors, which allows most functional +//! modules to select their working clock according to their power consumption +//! and performance requirements. +//! +//! The clock subsystem is used to source and distribute system/module clocks +//! from a range of root clocks. The clock tree driver maintains the basic +//! functionality of the system clock and the intricate relationship among +//! module clocks. +//! +//! ## Configuration +//! +//! During HAL initialization, specify a CPU clock speed to configure the +//! desired clock frequencies. +//! +//! The `CPU clock` is responsible for defining the speed at which the central +//! processing unit (CPU) operates. This driver provides predefined options for +//! different CPU clock speeds, such as +#![cfg_attr(not(esp32h2), doc = "* 80MHz")] +#![cfg_attr(esp32h2, doc = "* 96MHz")] +#![cfg_attr(esp32c2, doc = "* 120MHz")] +#![cfg_attr(not(any(esp32c2, esp32h2)), doc = "* 160MHz")] +#![cfg_attr(any(esp32c5, xtensa), doc = "* 240MHz")] +//! ### Frozen Clock Frequencies +//! +//! Once the clock configuration is applied, the clock frequencies become +//! `frozen` and cannot be changed. +//! +//! ## Examples +//! +//! ### Initialize With Different Clock Frequencies +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::clock::CpuClock; +//! +//! // Initialize with the highest possible frequency for this chip +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! # {after_snippet} +//! ``` +#![cfg_attr(not(feature = "rt"), expect(unused))] + +#[cfg(soc_has_clock_node_lp_slow_clk)] +use clocks::LpSlowClkConfig; +#[cfg(all(not(esp32s2), soc_has_clock_node_rtc_slow_clk))] +use clocks::RtcSlowClkConfig; +#[cfg(soc_has_clock_node_timg0_function_clock)] +use clocks::Timg0FunctionClockConfig; +use esp_rom_sys::rom::ets_delay_us; + +/// Low-level clock control +/// +///
    +/// This module provides experimental low-level clock control functionality. These functions +/// can render your device temporarily unusable. Use with caution. +///
    +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +pub mod ll { + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + pub use crate::soc::clocks::*; +} + +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +pub use crate::soc::clocks::ClockConfig; +#[cfg(not(feature = "unstable"))] +pub(crate) use crate::soc::clocks::ClockConfig; +pub use crate::soc::clocks::CpuClock; +use crate::{ + ESP_HAL_LOCK, + peripherals::TIMG0, + soc::clocks::{self, ClockTree}, + time::Rate, +}; + +impl CpuClock { + #[procmacros::doc_replace] + /// Use the highest possible frequency for a particular chip. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::clock::CpuClock; + /// let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); + /// let peripherals = esp_hal::init(config); + /// # {after_snippet} + /// ``` + pub const fn max() -> Self { + cfg_if::cfg_if! { + if #[cfg(esp32c2)] { + Self::_120MHz + } else if #[cfg(any(esp32c3, esp32c6))] { + Self::_160MHz + } else if #[cfg(esp32h2)] { + Self::_96MHz + } else { + Self::_240MHz + } + } + } +} + +/// RTC Clocks. +#[instability::unstable] +pub struct RtcClock; + +cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg_calibration_clock)] { + use crate::soc::clocks::TimgCalibrationClockConfig; + } else if #[cfg(soc_has_clock_node_timg0_calibration_clock)] { + use crate::soc::clocks::Timg0CalibrationClockConfig as TimgCalibrationClockConfig; + } +} + +/// RTC Watchdog Timer driver. +impl RtcClock { + const CAL_FRACT: u32 = 19; + + /// Get the RTC_SLOW_CLK source. + #[cfg_attr(all(not(feature = "unstable"), esp32c5), expect(dead_code))] + pub fn slow_freq() -> Rate { + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_rtc_slow_clk)] { + let getter = clocks::rtc_slow_clk_frequency; + } else { + let getter = clocks::lp_slow_clk_frequency; + } + } + Rate::from_hz(ClockTree::with(getter)) + } + + /// Measure the frequency of one of the TIMG0 calibration clocks, + /// using XTAL_CLK as the reference clock. + /// + /// This function will time out and return 0 if the time for the given + /// number of cycles to be counted exceeds the expected time twice. This + /// may happen if 32k XTAL is being calibrated, but the oscillator has + /// not started up (due to incorrect loading capacitance, board design + /// issue, or lack of 32 XTAL on board). + pub(crate) fn calibrate(cal_clk: TimgCalibrationClockConfig, slowclk_cycles: u32) -> u32 { + ClockTree::with(|clocks| { + let xtal_freq = Rate::from_hz(clocks::xtal_clk_frequency(clocks)); + + let (xtal_cycles, _) = Clocks::measure_rtc_clock( + clocks, + cal_clk, + #[cfg(soc_has_clock_node_timg0_function_clock)] + Timg0FunctionClockConfig::XtalClk, + slowclk_cycles, + ); + + if xtal_cycles == 0 { + warn!("{:?} calibration failed", cal_clk); + } else { + debug!("Counted {} XTAL cycles", xtal_cycles); + + debug!( + "{:?} frequency: {}", + cal_clk, + (xtal_freq / xtal_cycles) * slowclk_cycles + ); + } + + let divider = xtal_freq.as_mhz() as u64 * slowclk_cycles as u64; + + let period_64 = + (((xtal_cycles as u64) << RtcClock::CAL_FRACT) + divider / 2u64 - 1u64) / divider; + + (period_64 & u32::MAX as u64) as u32 + }) + } +} + +/// Clock frequencies. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[doc(hidden)] +#[instability::unstable] +pub struct Clocks { + /// CPU clock frequency + #[cfg(soc_has_clock_node_cpu_clk)] + pub cpu_clock: Rate, + + /// APB clock frequency + #[cfg(soc_has_clock_node_apb_clk)] + pub apb_clock: Rate, + + /// XTAL clock frequency + #[cfg(soc_has_clock_node_xtal_clk)] + pub xtal_clock: Rate, +} + +static mut ACTIVE_CLOCKS: Option = None; + +impl Clocks { + pub(crate) fn init(cpu_clock_config: ClockConfig) { + ESP_HAL_LOCK.lock(|| { + crate::rtc_cntl::rtc::init(); + + let config = Self::configure(cpu_clock_config); + unsafe { ACTIVE_CLOCKS = Some(config) }; + }); + + Clocks::calibrate_rtc_slow_clock(); + } + + fn try_get<'a>() -> Option<&'a Clocks> { + unsafe { + // Safety: ACTIVE_CLOCKS is only set in `init` and never modified after that. + let clocks = &*core::ptr::addr_of!(ACTIVE_CLOCKS); + clocks.as_ref() + } + } + + /// Get the active clock configuration. + pub fn get<'a>() -> &'a Clocks { + unwrap!(Self::try_get()) + } +} + +impl Clocks { + /// Configure the CPU clock speed. + pub(crate) fn configure(clock_config: ClockConfig) -> Self { + use crate::soc::clocks::ClockTree; + + clock_config.configure(); + + ClockTree::with(|clocks| { + // FIXME: MCPWM clock configuration needs to know about the active clock source + // frequency. In the future, we should turn the MCPWM config structs into + // plain old data structures and remove this pre-configuration, otherwise we will not be + // able to select a different clock source. + #[cfg(soc_has_clock_node_mcpwm0_function_clock)] + clocks::configure_mcpwm0_function_clock(clocks, Default::default()); + #[cfg(soc_has_clock_node_mcpwm1_function_clock)] + clocks::configure_mcpwm1_function_clock(clocks, Default::default()); + + // Until we have every clock consumer modelled, we should manually keep clocks alive + #[cfg(soc_has_clock_node_rc_fast_clk)] + clocks::request_rc_fast_clk(clocks); + #[cfg(soc_has_clock_node_rc_slow_clk)] + clocks::request_rc_slow_clk(clocks); + #[cfg(soc_has_clock_node_pll_clk)] + clocks::request_pll_clk(clocks); + #[cfg(soc_has_clock_node_pll_f96m_clk)] + clocks::request_pll_f96m_clk(clocks); + #[cfg(soc_has_clock_node_pll_f240m)] + clocks::request_pll_f240m(clocks); + #[cfg(soc_has_clock_node_rc_fast_div_clk)] + clocks::request_rc_fast_div_clk(clocks); + + // TODO: this struct can be removed once everything uses the new internal clock tree + // code + Self { + #[cfg(soc_has_clock_node_cpu_clk)] + cpu_clock: Rate::from_hz(clocks::cpu_clk_frequency(clocks)), + #[cfg(soc_has_clock_node_apb_clk)] + apb_clock: Rate::from_hz(clocks::apb_clk_frequency(clocks)), + #[cfg(soc_has_clock_node_xtal_clk)] + xtal_clock: Rate::from_hz(clocks::xtal_clk_frequency(clocks)), + } + }) + } + + /// Uses a TIMG0 feature to count clock cycles of a high-frequency clock, for a period of time + /// that is measured by a low-frequency clock. This function can be used to calibrate two + /// clocks to each other, e.g. to determine a rough value of the XTAL clock, or to determine + /// the current frequency of a low-precision RC oscillator. + pub(crate) fn measure_rtc_clock( + clocks: &mut ClockTree, + rtc_clock: TimgCalibrationClockConfig, + // TODO: verify function clock is used, C6 TRM suggests fixed XTAL_CLK + #[cfg(soc_has_clock_node_timg0_function_clock)] function_clock: Timg0FunctionClockConfig, + slow_cycles: u32, + ) -> (u32, Rate) { + #[cfg(timergroup_rc_fast_calibration_divider)] + let calibration_divider = if rtc_clock == TimgCalibrationClockConfig::RcFastDivClk + && crate::soc::chip_revision_above(property!( + "timergroup.rc_fast_calibration_divider_min_rev" + )) { + property!("timergroup.rc_fast_calibration_divider") + } else { + 1 + }; + #[cfg(not(timergroup_rc_fast_calibration_divider))] + let calibration_divider = 1; + + // On some revisions calibration uses a divided RC_FAST tick. + let calibration_cycles = (slow_cycles / calibration_divider).max(1); + + // By default the TIMG0 bus clock is running. Do not create a peripheral guard as dropping + // it would reset the timer, and it would enable its WDT. + + // Make sure the process doesn't time out due to some spooky configuration. + #[cfg(not(esp32))] + TIMG0::regs().rtccalicfg2().reset(); + + TIMG0::regs() + .rtccalicfg() + .modify(|_, w| w.rtc_cali_start().clear_bit()); + + // Make sure we measure the crystal. + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg0_function_clock)] { + let current_function_clock = clocks::timg0_function_clock_config(clocks); + clocks::configure_timg0_function_clock(clocks, function_clock); + clocks::request_timg0_function_clock(clocks); + } + } + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg_calibration_clock)] { + let current_calib_clock = clocks::timg_calibration_clock_config(clocks); + clocks::configure_timg_calibration_clock(clocks, rtc_clock); + clocks::request_timg_calibration_clock(clocks); + + let calibration_clock_frequency = clocks::timg_calibration_clock_frequency(clocks); + } else { + let current_calib_clock = clocks::timg0_calibration_clock_config(clocks); + clocks::configure_timg0_calibration_clock(clocks, rtc_clock); + clocks::request_timg0_calibration_clock(clocks); + + let calibration_clock_frequency = clocks::timg0_calibration_clock_frequency(clocks); + } + } + + let effective_calibration_clock_frequency = + calibration_clock_frequency / calibration_divider; + + // Set up timeout based on the calibration clock frequency. This is counted in XTAL_CLK + // cycles. + #[cfg(not(esp32))] + { + let function_clk_freq = clocks::timg0_function_clock_frequency(clocks) as u64; + let expected_function_clock_cycles = (function_clk_freq * slow_cycles as u64 + / effective_calibration_clock_frequency as u64) + as u32; + + TIMG0::regs().rtccalicfg2().modify(|_, w| unsafe { + let writer = w.rtc_cali_timeout_thres(); + let mask = (1 << writer.width()) - 1; + writer.bits((expected_function_clock_cycles * 2).min(mask)); + w + }); + } + + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_max().bits(calibration_cycles as u16); + w.rtc_cali_start_cycling().clear_bit(); + w.rtc_cali_start().set_bit() + }); + + // Delay, otherwise the CPU may read back the previous state of the completion flag and skip + // waiting. + let us_time_estimate = slow_cycles * 1_000_000 / effective_calibration_clock_frequency; + ets_delay_us(us_time_estimate); + + #[cfg(esp32)] + let mut timeout_us = us_time_estimate; + + // Wait for the calibration to finish + let cali_value = loop { + if TIMG0::regs() + .rtccalicfg() + .read() + .rtc_cali_rdy() + .bit_is_set() + { + break TIMG0::regs().rtccalicfg1().read().rtc_cali_value().bits(); + } + + #[cfg(not(esp32))] + if TIMG0::regs() + .rtccalicfg2() + .read() + .rtc_cali_timeout() + .bit_is_set() + { + // Timed out waiting for calibration + break 0; + } + + #[cfg(esp32)] + if timeout_us > 0 { + timeout_us -= 1; + ets_delay_us(1); + } else { + // Timed out waiting for calibration + break 0; + } + }; + + TIMG0::regs() + .rtccalicfg() + .modify(|_, w| w.rtc_cali_start().clear_bit()); + + // TODO: this would be nicer if we had clock node objects instead of free-standing functions + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg_calibration_clock)] { + if let Some(calib_clock) = current_calib_clock + && calib_clock != rtc_clock + { + clocks::configure_timg_calibration_clock(clocks, calib_clock); + } + clocks::release_timg_calibration_clock(clocks); + } else { + if let Some(calib_clock) = current_calib_clock + && calib_clock != rtc_clock + { + clocks::configure_timg0_calibration_clock(clocks, calib_clock); + } + clocks::release_timg0_calibration_clock(clocks); + } + } + + #[cfg(soc_has_clock_node_timg0_function_clock)] + { + if let Some(func_clock) = current_function_clock + && func_clock != function_clock + { + clocks::configure_timg0_function_clock(clocks, func_clock); + } + clocks::release_timg0_function_clock(clocks); + } + + (cali_value, Rate::from_hz(calibration_clock_frequency)) + } + + pub(crate) fn calibrate_rtc_slow_clock() { + // Unfortunate device specific mapping. + // TODO: fix it by generating cfgs for each mux input? + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + // Can directly measure output of the RTC_SLOW mux + let slow_clk = TimgCalibrationClockConfig::RtcClk; + } else if #[cfg(soc_has_clock_node_rtc_slow_clk)] { + let slow_clk = match unwrap!(ClockTree::with(clocks::rtc_slow_clk_config)) { + RtcSlowClkConfig::RcFast => TimgCalibrationClockConfig::RcFastDivClk, + RtcSlowClkConfig::RcSlow => TimgCalibrationClockConfig::RcSlowClk, + #[cfg(not(esp32c2))] + RtcSlowClkConfig::Xtal32k => TimgCalibrationClockConfig::Xtal32kClk, + #[cfg(esp32c2)] + RtcSlowClkConfig::OscSlow => TimgCalibrationClockConfig::Osc32kClk, + }; + } else { + let slow_clk = match unwrap!(ClockTree::with(clocks::lp_slow_clk_config)) { + LpSlowClkConfig::OscSlow => TimgCalibrationClockConfig::Xtal32kClk, //? + LpSlowClkConfig::Xtal32k => TimgCalibrationClockConfig::Xtal32kClk, + LpSlowClkConfig::RcSlow => TimgCalibrationClockConfig::RcSlowClk, + }; + } + } + + let cal_val = RtcClock::calibrate(slow_clk, 1024); + + cfg_if::cfg_if! { + if #[cfg(soc_has_lp_aon)] { + use crate::peripherals::LP_AON; + } else { + use crate::peripherals::LPWR as LP_AON; + } + } + + LP_AON::regs() + .store1() + .write(|w| unsafe { w.bits(cal_val) }); + } +} + +/// The CPU clock frequency. +pub fn cpu_clock() -> Rate { + Clocks::get().cpu_clock +} + +/// The XTAL clock frequency. +pub fn xtal_clock() -> Rate { + Clocks::get().xtal_clock +} + +/// Read the calibrated RTC slow clock period from the STORE1 register. +/// +/// The period is in unit of microseconds, represented as a fixed-point number +/// with `RtcClock::CAL_FRACT` fractional bits. +/// +/// Written by [`Clocks::calibrate_rtc_slow_clock`] during clock +/// initialization. +fn rtc_slow_cal_period() -> u64 { + cfg_if::cfg_if! { + if #[cfg(soc_has_lp_aon)] { + use crate::peripherals::LP_AON; + } else { + use crate::peripherals::LPWR as LP_AON; + } + } + + LP_AON::regs().store1().read().bits() as u64 +} + +/// Convert RTC slow clock ticks to microseconds using the calibrated period. +#[cfg(lp_timer_driver_supported)] +pub(crate) fn rtc_ticks_to_us(ticks: u64) -> u64 { + let period = rtc_slow_cal_period(); + + // The LP timer is a 48-bit counter running from RTC_SLOW_CLK. + // `period` is a fixed point number with 19 fractional bits, it may be a 24-bit value if + // RTC_SLOW_CLK is as low as 32768 Hz. Just multiplying the two may cause an overflow on 64 + // bits. We use a calculation that prevents overflow unconditionally because the counter reaches + // 2^19 pretty quickly. We could use a larger mask (up to 40 bits), but that would make the + // calculation slower and more complex for perhaps too little benefit. + + const MASK: u64 = (1 << RtcClock::CAL_FRACT) - 1; + let upper = (ticks & !MASK) >> RtcClock::CAL_FRACT; + let lower = ticks & MASK; + + upper * period + ((lower * period) >> RtcClock::CAL_FRACT) +} + +/// Convert microseconds to RTC slow clock ticks using the calibrated period. +pub(crate) fn us_to_rtc_ticks(time_in_us: u64) -> u64 { + let period = rtc_slow_cal_period(); + + if time_in_us > (u64::MAX >> RtcClock::CAL_FRACT) { + ((time_in_us / period) << RtcClock::CAL_FRACT) + + ((time_in_us % period) << RtcClock::CAL_FRACT) / period + } else { + (time_in_us << RtcClock::CAL_FRACT) / period + } +} diff --git a/esp-hal/src/debugger.rs b/esp-hal/src/debugger.rs new file mode 100644 index 00000000000..c116efcf00b --- /dev/null +++ b/esp-hal/src/debugger.rs @@ -0,0 +1,234 @@ +//! Debugger utilities + +/// Checks if a debugger is connected. +pub fn debugger_connected() -> bool { + cfg_if::cfg_if! { + if #[cfg(xtensa)] { + xtensa_lx::is_debugger_attached() + } else if #[cfg(all(riscv, soc_has_assist_debug))] { + crate::peripherals::ASSIST_DEBUG::regs() + .cpu(0) + .debug_mode() + .read() + .debug_module_active() + .bit_is_set() + } else { + false + } + } +} + +/// Set a word-sized data breakpoint at the given address. +/// No breakpoint will be set when a debugger is currently attached if +/// the `stack_guard_monitoring_with_debugger_connected` option is false. +/// +/// Breakpoint 0 is used. +/// +/// # Safety +/// The address must be word aligned. +pub unsafe fn set_stack_watchpoint(addr: usize) { + assert!(addr.is_multiple_of(4)); + + if cfg!(stack_guard_monitoring_with_debugger_connected) + || !crate::debugger::debugger_connected() + { + cfg_if::cfg_if! { + if #[cfg(xtensa)] { + let addr = addr & !0b11; + let dbreakc = 0b1111100 | (1 << 31); // bit 31 = STORE + + unsafe { + core::arch::asm!( + " + wsr {addr}, 144 // 144 = dbreaka0 + wsr {dbreakc}, 160 // 160 = dbreakc0 + ", + addr = in(reg) addr, + dbreakc = in(reg) dbreakc, + ); + } + } else { + unsafe { set_watchpoint(0, addr, 4); } + } + } + } +} + +#[cfg(riscv)] +pub(crate) static DEBUGGER_LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new(); + +#[cfg(riscv)] +const NAPOT_MATCH: u8 = 1; + +#[cfg(riscv)] +bitfield::bitfield! { + /// Only match type (0x2) triggers are supported. + #[derive(Clone, Copy, Default)] + pub(crate) struct Tdata1(u32); + + /// Set this for configuring the selected trigger to fire right before a load operation with matching + /// data address is executed by the CPU. + pub bool, load, set_load: 0; + + /// Set this for configuring the selected trigger to fire right before a store operation with matching + /// data address is executed by the CPU. + pub bool, store, set_store: 1; + + /// Set this for configuring the selected trigger to fire right before an instruction with matching + /// virtual address is executed by the CPU. + pub bool, execute, set_execute: 2; + + /// Set this for enabling selected trigger to operate in user mode. + pub bool, u, set_u: 3; + + /// Set this for enabling selected trigger to operate in machine mode. + pub bool, m, set_m: 6; + + /// Configures the selected trigger to perform one of the available matching operations on a + /// data/instruction address. Valid options are: + /// 0x0: exact byte match, i.e. address corresponding to one of the bytes in an access must match + /// the value of maddress exactly. + /// 0x1: NAPOT match, i.e. at least one of the bytes of an access must lie in the NAPOT region + /// specified in maddress. + /// Note: Writing a larger value will clip it to the largest possible value 0x1. + pub u8, _match, set_match: 10, 7; + + /// Configures the selected trigger to perform one of the available actions when firing. Valid + /// options are: + /// 0x0: cause breakpoint exception. + /// 0x1: enter debug mode (only valid when dmode = 1) + /// Note: Writing an invalid value will set this to the default value 0x0. + pub u8, action, set_action: 15, 12; + + /// This is found to be 1 if the selected trigger had fired previously. This bit is to be cleared manually. + pub bool, hit, set_hit: 20; + + /// 0: Both Debug and M mode can write the tdata1 and tdata2 registers at the selected tselect. + /// 1: Only Debug Mode can write the tdata1 and tdata2 registers at the selected tselect. Writes from + /// other modes are ignored. + /// Note: Only writable from debug mode. + pub bool, dmode, set_dmode: 27; +} + +#[cfg(riscv)] +bitfield::bitfield! { + /// Only match type (0x2) triggers are supported. + #[derive(Clone, Copy, Default)] + pub(crate) struct Tcontrol(u32); + + /// Current M mode trigger enable bit + pub bool, mte, set_mte: 3; + + /// Previous M mode trigger enable bit + pub bool, mpte, set_mpte: 7; + +} + +#[cfg(riscv)] +pub(crate) struct WatchPoint { + tdata1: u32, + tdata2: u32, +} + +/// Clear the watchpoint +#[cfg(riscv)] +pub(crate) unsafe fn clear_watchpoint(id: u8) -> WatchPoint { + assert!(id < 4); + + // tdata1 is a WARL(write any read legal) register. We can just write 0 to it. + let mut tdata1 = 0; + let mut tdata2 = 0; + + DEBUGGER_LOCK.lock(|| unsafe { + core::arch::asm!( + " + csrw 0x7a0, {id} // tselect + csrrw {tdata1}, 0x7a1, {tdata2} // tdata1 + csrr {tdata2}, 0x7a2 // tdata2 + ", id = in(reg) id, + tdata1 = inout(reg) tdata1, + tdata2 = out(reg) tdata2, + ); + }); + + WatchPoint { tdata1, tdata2 } +} + +/// Clear the watchpoint +#[cfg(riscv)] +pub(crate) unsafe fn restore_watchpoint(id: u8, watchpoint: WatchPoint) { + DEBUGGER_LOCK.lock(|| unsafe { + core::arch::asm!( + " + csrw 0x7a0, {id} // tselect + csrw 0x7a1, {tdata1} // tdata1 + csrw 0x7a2, {tdata2} // tdata2 + ", id = in(reg) id, + tdata1 = in(reg) watchpoint.tdata1, + tdata2 = in(reg) watchpoint.tdata2, + ); + }); +} + +/// Clear the watchpoint +#[cfg(all(riscv, feature = "exception-handler"))] +pub(crate) unsafe fn watchpoint_hit(id: u8) -> bool { + assert!(id < 4); + let mut tdata = Tdata1::default(); + + DEBUGGER_LOCK.lock(|| unsafe { + core::arch::asm!( + " + csrw 0x7a0, {id} // tselect + csrr {tdata}, 0x7a1 // tdata1 + ", id = in(reg) id, + tdata = out(reg) tdata.0, + ); + }); + + tdata.hit() +} + +/// Set watchpoint and enable triggers. +#[cfg(riscv)] +pub(crate) unsafe fn set_watchpoint(id: u8, addr: usize, len: usize) { + assert!(id < 4); + assert!(len.is_power_of_two()); + assert!(addr.is_multiple_of(len)); + + let z = len.trailing_zeros(); + let mask = { + let mut mask: usize = 0; + for i in 0..z { + mask |= 1 << i; + } + mask + }; + + let napot_encoding = { mask & !(1 << (z - 1)) }; + let addr = (addr & !mask) | napot_encoding; + + let mut tdata = Tdata1::default(); + tdata.set_m(true); + tdata.set_store(true); + tdata.set_match(NAPOT_MATCH); + let tdata: u32 = tdata.0; + + let mut tcontrol = Tcontrol::default(); + tcontrol.set_mte(true); + let tcontrol: u32 = tcontrol.0; + + DEBUGGER_LOCK.lock(|| unsafe { + core::arch::asm!( + " + csrw 0x7a0, {id} // tselect + csrw 0x7a5, {tcontrol} // tcontrol + csrw 0x7a1, {tdata} // tdata1 + csrw 0x7a2, {addr} // tdata2 + ", id = in(reg) id, + addr = in(reg) addr, + tdata = in(reg) tdata, + tcontrol = in(reg) tcontrol, + ); + }); +} diff --git a/esp-hal/src/delay.rs b/esp-hal/src/delay.rs new file mode 100644 index 00000000000..e5a3d2a161c --- /dev/null +++ b/esp-hal/src/delay.rs @@ -0,0 +1,73 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Delay +//! +//! ## Overview +//! +//! The Delay driver provides blocking delay functionalities using the +//! [`Instant`] struct. +//! +//! ## Configuration +//! +//! The delays are implemented in a "best-effort" way, meaning that the CPU will +//! block for at least the amount of time specified, but accuracy can be +//! affected by many factors, including interrupt usage. +//! +//! ## Usage +//! +//! This module implements the blocking [DelayNs] trait from [embedded-hal]. +//! +//! ## Examples +//! ### Delay for 1 second +//! ```rust, no_run +//! # {before_snippet} +//! use embedded_hal::delay::DelayNs; +//! use esp_hal::delay::Delay; +//! let mut delay = Delay::new(); +//! +//! delay.delay_ms(1000 as u32); +//! # {after_snippet} +//! ``` +//! [DelayNs]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/trait.DelayNs.html +//! [embedded-hal]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/index.html + +use crate::time::{Duration, Instant}; + +/// Delay driver, using [`Instant`]. +#[derive(Clone, Copy, Default)] +#[non_exhaustive] +pub struct Delay; + +impl embedded_hal::delay::DelayNs for Delay { + fn delay_ns(&mut self, ns: u32) { + self.delay_nanos(ns); + } +} + +impl Delay { + /// Creates a new `Delay` instance. + pub const fn new() -> Self { + Self {} + } + + /// Delay for the specified time + pub fn delay(&self, delay: Duration) { + let start = Instant::now(); + + while start.elapsed() < delay {} + } + + /// Delay for the specified number of milliseconds + pub fn delay_millis(&self, ms: u32) { + self.delay(Duration::from_millis(ms as u64)); + } + + /// Delay for the specified number of microseconds + pub fn delay_micros(&self, us: u32) { + self.delay(Duration::from_micros(us as u64)); + } + + /// Delay for the specified number of nanoseconds + pub fn delay_nanos(&self, ns: u32) { + self.delay(Duration::from_micros(ns.div_ceil(1000) as u64)); + } +} diff --git a/esp-hal/src/dma/buffers.rs b/esp-hal/src/dma/buffers.rs new file mode 100644 index 00000000000..edfbfaca3b6 --- /dev/null +++ b/esp-hal/src/dma/buffers.rs @@ -0,0 +1,1950 @@ +#[cfg(psram_dma)] +use core::ops::Range; +use core::{ + ops::{Deref, DerefMut}, + ptr::{NonNull, null_mut}, +}; + +use super::*; +use crate::soc::is_slice_in_dram; +#[cfg(psram_dma)] +use crate::soc::{is_slice_in_psram, is_valid_psram_address, is_valid_ram_address}; + +/// Error returned from Dma[Rx|Tx|RxTx]Buf operations. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaBufError { + /// The buffer is smaller than the requested size. + BufferTooSmall, + + /// More descriptors are needed for the buffer size. + InsufficientDescriptors, + + /// Descriptors or buffers are not located in a supported memory region. + UnsupportedMemoryRegion, + + /// Buffer address or size is not properly aligned. + InvalidAlignment(DmaAlignmentError), + + /// Invalid chunk size: must be > 0 and <= 4095. + InvalidChunkSize, +} + +/// DMA buffer alignment errors. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaAlignmentError { + /// Buffer address is not properly aligned. + Address, + + /// Buffer size is not properly aligned. + Size, +} + +impl From for DmaBufError { + fn from(err: DmaAlignmentError) -> Self { + DmaBufError::InvalidAlignment(err) + } +} + +cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + /// Burst size used when transferring to and from external memory. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ExternalBurstConfig { + /// 16 bytes + Size16 = 16, + + /// 32 bytes + Size32 = 32, + + /// 64 bytes + Size64 = 64, + } + + impl ExternalBurstConfig { + /// The default external memory burst length. + pub const DEFAULT: Self = Self::Size16; + } + + impl Default for ExternalBurstConfig { + fn default() -> Self { + Self::DEFAULT + } + } + + /// Internal memory access burst mode. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum InternalBurstConfig { + /// Burst mode is disabled. + Disabled, + + /// Burst mode is enabled. + Enabled, + } + + impl InternalBurstConfig { + /// The default internal burst mode configuration. + pub const DEFAULT: Self = Self::Disabled; + } + + impl Default for InternalBurstConfig { + fn default() -> Self { + Self::DEFAULT + } + } + + /// Burst transfer configuration. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct BurstConfig { + /// Configures the burst size for PSRAM transfers. + /// + /// Burst mode is always enabled for PSRAM transfers. + pub external_memory: ExternalBurstConfig, + + /// Enables or disables the burst mode for internal memory transfers. + /// + /// The burst size is not configurable. + pub internal_memory: InternalBurstConfig, + } + + impl BurstConfig { + /// The default burst mode configuration. + pub const DEFAULT: Self = Self { + external_memory: ExternalBurstConfig::DEFAULT, + internal_memory: InternalBurstConfig::DEFAULT, + }; + } + + impl Default for BurstConfig { + fn default() -> Self { + Self::DEFAULT + } + } + + impl From for BurstConfig { + fn from(internal_memory: InternalBurstConfig) -> Self { + Self { + external_memory: ExternalBurstConfig::DEFAULT, + internal_memory, + } + } + } + + impl From for BurstConfig { + fn from(external_memory: ExternalBurstConfig) -> Self { + Self { + external_memory, + internal_memory: InternalBurstConfig::DEFAULT, + } + } + } + } else { + /// Burst transfer configuration. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum BurstConfig { + /// Burst mode is disabled. + Disabled, + + /// Burst mode is enabled. + Enabled, + } + + impl BurstConfig { + /// The default burst mode configuration. + pub const DEFAULT: Self = Self::Disabled; + } + + impl Default for BurstConfig { + fn default() -> Self { + Self::DEFAULT + } + } + + type InternalBurstConfig = BurstConfig; + } +} + +#[cfg(psram_dma)] +impl ExternalBurstConfig { + const fn min_psram_alignment(self, direction: TransferDirection) -> usize { + // S2 TRM: Specifically, size and buffer address pointer in receive descriptors + // should be 16-byte, 32-byte or 64-byte aligned. For data frame whose + // length is not a multiple of 16 bytes, 32 bytes, or 64 bytes, EDMA adds + // padding bytes to the end. + + // S3 TRM: Size and Address for IN transfers must be block aligned. For receive + // descriptors, if the data length received are not aligned with block size, + // GDMA will pad the data received with 0 until they are aligned to + // initiate burst transfer. You can read the length field in receive descriptors + // to obtain the length of valid data received + if matches!(direction, TransferDirection::In) { + self as usize + } else { + // S2 TRM: Size, length and buffer address pointer in transmit descriptors are + // not necessarily aligned with block size. + + // S3 TRM: Size, length, and buffer address pointer in transmit descriptors do + // not need to be aligned. + 1 + } + } +} + +impl InternalBurstConfig { + pub(super) const fn is_burst_enabled(self) -> bool { + !matches!(self, Self::Disabled) + } + + // Size and address alignment as those come in pairs on current hardware. + const fn min_dram_alignment(self, direction: TransferDirection) -> usize { + if matches!(direction, TransferDirection::In) { + if cfg!(esp32) { + // NOTE: The size must be word-aligned. + // NOTE: The buffer address must be word-aligned + 4 + } else if self.is_burst_enabled() { + // As described in "Accessing Internal Memory" paragraphs in the various TRMs. + 4 + } else { + 1 + } + } else { + // OUT transfers have no alignment requirements, except for ESP32, which is + // described below. + if cfg!(esp32) { + // SPI DMA: Burst transmission is supported. The data size for + // a single transfer must be four bytes aligned. + // I2S DMA: Burst transfer is supported. However, unlike the + // SPI DMA channels, the data size for a single transfer is + // one word, or four bytes. + 4 + } else { + 1 + } + } + } +} + +const fn max(a: usize, b: usize) -> usize { + if a > b { a } else { b } +} + +impl BurstConfig { + delegate::delegate! { + #[cfg(psram_dma)] + to self.internal_memory { + pub(super) const fn min_dram_alignment(self, direction: TransferDirection) -> usize; + pub(super) fn is_burst_enabled(self) -> bool; + } + } + + /// Calculates an alignment that is compatible with the current burst + /// configuration. + /// + /// This is an over-estimation so that Descriptors can be safely used with + /// any DMA channel in any direction. + pub const fn min_compatible_alignment(self) -> usize { + let in_alignment = self.min_dram_alignment(TransferDirection::In); + let out_alignment = self.min_dram_alignment(TransferDirection::Out); + let alignment = max(in_alignment, out_alignment); + + #[cfg(psram_dma)] + let alignment = max(alignment, self.external_memory as usize); + + alignment + } + + const fn chunk_size_for_alignment(alignment: usize) -> usize { + // DMA descriptors have a 12-bit field for the size/length of the buffer they + // point at. As there is no such thing as 0-byte alignment, this means the + // maximum size is 4095 bytes. + 4096 - alignment + } + + /// Calculates a chunk size that is compatible with the current burst + /// configuration's alignment requirements. + /// + /// This is an over-estimation so that Descriptors can be safely used with + /// any DMA channel in any direction. + pub const fn max_compatible_chunk_size(self) -> usize { + Self::chunk_size_for_alignment(self.min_compatible_alignment()) + } + + fn min_alignment(self, _buffer: &[u8], direction: TransferDirection) -> usize { + let alignment = self.min_dram_alignment(direction); + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let mut alignment = alignment; + if is_valid_psram_address(_buffer.as_ptr() as usize) { + alignment = max(alignment, self.external_memory.min_psram_alignment(direction)); + } + } + } + + alignment + } + + // Note: this function ignores address alignment as we assume the buffers are + // aligned. + fn max_chunk_size_for(self, buffer: &[u8], direction: TransferDirection) -> usize { + Self::chunk_size_for_alignment(self.min_alignment(buffer, direction)) + } + + fn ensure_buffer_aligned( + self, + buffer: &[u8], + direction: TransferDirection, + ) -> Result<(), DmaAlignmentError> { + let alignment = self.min_alignment(buffer, direction); + if !(buffer.as_ptr() as usize).is_multiple_of(alignment) { + return Err(DmaAlignmentError::Address); + } + + // NB: the TRMs suggest that buffer length don't need to be aligned, but + // for IN transfers, we configure the DMA descriptors' size field, which needs + // to be aligned. + if direction == TransferDirection::In && !buffer.len().is_multiple_of(alignment) { + return Err(DmaAlignmentError::Size); + } + + Ok(()) + } + + fn ensure_buffer_compatible( + self, + buffer: &[u8], + direction: TransferDirection, + ) -> Result<(), DmaBufError> { + // buffer can be either DRAM or PSRAM (if supported) + let is_in_dram = is_slice_in_dram(buffer); + cfg_if::cfg_if! { + if #[cfg(psram_dma)]{ + let is_in_psram = is_slice_in_psram(buffer); + } else { + let is_in_psram = false; + } + } + + if !(is_in_dram || is_in_psram) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + self.ensure_buffer_aligned(buffer, direction)?; + + Ok(()) + } +} + +/// The direction of the DMA transfer. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TransferDirection { + /// DMA transfer from peripheral or external memory to memory. + In, + /// DMA transfer from memory to peripheral or external memory. + Out, +} + +/// Holds all the information needed to configure a DMA channel for a transfer. +#[derive(PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Preparation { + /// The descriptor the DMA will start from. + pub start: *mut DmaDescriptor, + + /// The direction of the DMA transfer. + pub direction: TransferDirection, + + /// Must be `true` if any of the DMA descriptors contain data in PSRAM. + #[cfg(psram_dma)] + pub accesses_psram: bool, + + /// Configures the DMA to transfer data in bursts. + /// + /// The implementation of the buffer must ensure that buffer size + /// and alignment in each descriptor is compatible with the burst + /// transfer configuration. + /// + /// For details on alignment requirements, refer to your chip's + #[doc = crate::trm_markdown_link!()] + pub burst_transfer: BurstConfig, + + /// Configures the "check owner" feature of the DMA channel. + /// + /// Most DMA channels allow software to configure whether the hardware + /// checks that [DmaDescriptor::owner] is set to [Owner::Dma] before + /// consuming the descriptor. If this check fails, the channel stops + /// operating and fires + /// [DmaRxInterrupt::DescriptorError]/[DmaTxInterrupt::DescriptorError]. + /// + /// This field allows buffer implementation to configure this behaviour. + /// - `Some(true)`: DMA channel must check the owner bit. + /// - `Some(false)`: DMA channel must NOT check the owner bit. + /// - `None`: DMA channel should check the owner bit if it is supported. + /// + /// Some buffer implementations may require that the DMA channel performs + /// this check before consuming the descriptor to ensure correct + /// behaviour. e.g. To prevent wrap-around in a circular transfer. + /// + /// Some buffer implementations may require that the DMA channel does NOT + /// perform this check as the ownership bit will not be set before the + /// channel tries to consume the descriptor. + /// + /// Most implementations won't have any such requirements and will work + /// correctly regardless of whether the DMA channel checks or not. + /// + /// Note: If the DMA channel doesn't support the provided option, + /// preparation will fail. + pub check_owner: Option, + + /// Configures whether the DMA channel automatically clears the + /// [DmaDescriptor::owner] bit after it is done with the buffer pointed + /// to by a descriptor. + /// + /// For RX transfers, this is always true and the value specified here is + /// ignored. + /// + /// Note: SPI_DMA on the ESP32 does not support this and will panic if set + /// to true. + pub auto_write_back: bool, +} + +/// [DmaTxBuffer] is a DMA descriptor + memory combo that can be used for +/// transmitting data from a DMA channel to a peripheral's FIFO. +/// +/// # Safety +/// +/// The implementing type must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +pub unsafe trait DmaTxBuffer { + /// A type providing operations that are safe to perform on the buffer + /// whilst the DMA is actively using it. + type View; + + /// The type returned to the user when a transfer finishes. + /// + /// Some buffers don't need to be reconstructed. + type Final; + + /// Prepares the buffer for an imminent transfer and returns + /// information required to use this buffer. + /// + /// Note: This operation is idempotent. + fn prepare(&mut self) -> Preparation; + + /// This is called before the DMA starts using the buffer. + fn into_view(self) -> Self::View; + + /// This is called after the DMA is done using the buffer. + fn from_view(view: Self::View) -> Self::Final; +} + +/// [DmaRxBuffer] is a DMA descriptor + memory combo that can be used for +/// receiving data from a peripheral's FIFO to a DMA channel. +/// +/// Note: Implementations of this trait may only support having a single EOF bit +/// which resides in the last descriptor. There will be a separate trait in +/// future to support multiple EOFs. +/// +/// # Safety +/// +/// The implementing type must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +pub unsafe trait DmaRxBuffer { + /// A type providing operations that are safe to perform on the buffer + /// whilst the DMA is actively using it. + type View; + + /// The type returned to the user when a transfer finishes. + /// + /// Some buffers don't need to be reconstructed. + type Final; + + /// Prepares the buffer for an imminent transfer and returns + /// information required to use this buffer. + /// + /// Note: This operation is idempotent. + fn prepare(&mut self) -> Preparation; + + /// This is called before the DMA starts using the buffer. + fn into_view(self) -> Self::View; + + /// This is called after the DMA is done using the buffer. + fn from_view(view: Self::View) -> Self::Final; +} + +/// An in-progress view into [DmaRxBuf]/[DmaTxBuf]. +/// +/// In the future, this could support peeking into state of the +/// descriptors/buffers. +pub struct BufView(T); + +/// DMA transmit buffer +/// +/// This is a contiguous buffer linked together by DMA descriptors of length +/// 4095 at most. It can only be used for transmitting data to a peripheral's +/// FIFO. See [DmaRxBuf] for receiving data. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DmaTxBuf { + descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], + burst: BurstConfig, +} + +impl DmaTxBuf { + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Depending on alignment requirements, each descriptor can handle at most + /// 4095 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported for descriptors. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + Self::new_with_config(descriptors, buffer, BurstConfig::default()) + } + + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Depending on alignment requirements, each descriptor can handle at most + /// 4095 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported for descriptors. + pub fn new_with_config( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + config: impl Into, + ) -> Result { + let mut buf = Self { + descriptors: DescriptorSet::new(descriptors)?, + buffer, + burst: BurstConfig::default(), + }; + + let capacity = buf.capacity(); + buf.configure(config, capacity)?; + + Ok(buf) + } + + fn configure( + &mut self, + burst: impl Into, + length: usize, + ) -> Result<(), DmaBufError> { + let burst = burst.into(); + self.set_length_fallible(length, burst)?; + + self.descriptors.link_with_buffer( + self.buffer, + burst.max_chunk_size_for(self.buffer, TransferDirection::Out), + )?; + + self.burst = burst; + Ok(()) + } + + /// Configures the DMA to use burst transfers to access this buffer. + pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { + let len = self.len(); + self.configure(burst, len) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors.into_inner(), self.buffer) + } + + /// Returns the size of the underlying buffer + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Return the number of bytes that would be transmitted by this buf. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + if len > self.capacity() { + return Err(DmaBufError::BufferTooSmall); + } + burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out)?; + + self.descriptors.set_tx_length( + len, + burst.max_chunk_size_for(self.buffer, TransferDirection::Out), + )?; + + // This only needs to be done once (after every significant length change) as + // Self::prepare sets Preparation::auto_write_back to false. + for desc in self.descriptors.linked_iter_mut() { + // In non-circular mode, we only set `suc_eof` for the last descriptor to signal + // the end of the transfer. + desc.reset_for_tx(desc.next.is_null()); + } + + Ok(()) + } + + /// Reset the descriptors to only transmit `len` amount of bytes from this + /// buf. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn set_length(&mut self, len: usize) { + unwrap!(self.set_length_fallible(len, self.burst)) + } + + /// Fills the TX buffer with the bytes provided in `data` and reset the + /// descriptors to only cover the filled section. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn fill(&mut self, data: &[u8]) { + self.set_length(data.len()); + self.as_mut_slice()[..data.len()].copy_from_slice(data); + } + + /// Returns the buf as a mutable slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + /// Returns the buf as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } +} + +unsafe impl DmaTxBuffer for DmaTxBuf { + type View = BufView; + type Final = DmaTxBuf; + + fn prepare(&mut self) -> Preparation { + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize); + if is_data_in_psram { + unsafe { + crate::soc::cache_writeback_addr( + self.buffer.as_ptr() as u32, + self.buffer.len() as u32, + ) + }; + } + } + } + + Preparation { + start: self.descriptors.head(), + direction: TransferDirection::Out, + #[cfg(psram_dma)] + accesses_psram: is_data_in_psram, + burst_transfer: self.burst, + check_owner: None, + auto_write_back: false, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } +} + +/// DMA receive buffer +/// +/// This is a contiguous buffer linked together by DMA descriptors of length +/// 4092. It can only be used for receiving data from a peripheral's FIFO. +/// See [DmaTxBuf] for transmitting data. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DmaRxBuf { + descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], + burst: BurstConfig, +} + +impl DmaRxBuf { + /// Creates a new [DmaRxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + Self::new_with_config(descriptors, buffer, BurstConfig::default()) + } + + /// Creates a new [DmaRxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Depending on alignment requirements, each descriptor can handle at most + /// 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported for descriptors. + pub fn new_with_config( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + config: impl Into, + ) -> Result { + let mut buf = Self { + descriptors: DescriptorSet::new(descriptors)?, + buffer, + burst: BurstConfig::default(), + }; + + buf.configure(config, buf.capacity())?; + + Ok(buf) + } + + fn configure( + &mut self, + burst: impl Into, + length: usize, + ) -> Result<(), DmaBufError> { + let burst = burst.into(); + self.set_length_fallible(length, burst)?; + + self.descriptors.link_with_buffer( + self.buffer, + burst.max_chunk_size_for(self.buffer, TransferDirection::In), + )?; + + self.burst = burst; + Ok(()) + } + + /// Configures the DMA to use burst transfers to access this buffer. + pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { + let len = self.len(); + self.configure(burst, len) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors.into_inner(), self.buffer) + } + + /// Returns the size of the underlying buffer + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Returns the maximum number of bytes that this buf has been configured to + /// receive. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.size()) + .sum::() + } + + fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + if len > self.capacity() { + return Err(DmaBufError::BufferTooSmall); + } + burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In)?; + + self.descriptors.set_rx_length( + len, + burst.max_chunk_size_for(&self.buffer[..len], TransferDirection::In), + ) + } + + /// Reset the descriptors to only receive `len` amount of bytes into this + /// buf. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn set_length(&mut self, len: usize) { + unwrap!(self.set_length_fallible(len, self.burst)); + } + + /// Returns the entire underlying buffer as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } + + /// Returns the entire underlying buffer as a slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + /// Return the number of bytes that was received by this buf. + pub fn number_of_received_bytes(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + /// Reads the received data into the provided `buf`. + /// + /// If `buf.len()` is less than the amount of received data then only the + /// first `buf.len()` bytes of received data is written into `buf`. + /// + /// Returns the number of bytes in written to `buf`. + pub fn read_received_data(&self, mut buf: &mut [u8]) -> usize { + // Note that due to an ESP32 quirk, the last received descriptor may not get + // updated. + let capacity = buf.len(); + for chunk in self.received_data() { + if buf.is_empty() { + break; + } + let to_fill; + (to_fill, buf) = buf.split_at_mut(chunk.len()); + to_fill.copy_from_slice(chunk); + } + + capacity - buf.len() + } + + /// Returns the received data as an iterator of slices. + pub fn received_data(&self) -> impl Iterator { + self.descriptors.linked_iter().map(|desc| { + // SAFETY: We set up the descriptor to point to a subslice of the buffer, and + // here we are only recreating that slice with a perhaps shorter length. + // We are also not accessing `self.buffer` while this slice is alive, so we + // are not violating any aliasing rules. + unsafe { core::slice::from_raw_parts(desc.buffer.cast_const(), desc.len()) } + }) + } +} + +unsafe impl DmaRxBuffer for DmaRxBuf { + type View = BufView; + type Final = DmaRxBuf; + + fn prepare(&mut self) -> Preparation { + for desc in self.descriptors.linked_iter_mut() { + desc.reset_for_rx(); + } + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + // Optimization: avoid locking for PSRAM range. + let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize); + if is_data_in_psram { + unsafe { + crate::soc::cache_invalidate_addr( + self.buffer.as_ptr() as u32, + self.buffer.len() as u32, + ) + }; + } + } + } + + Preparation { + start: self.descriptors.head(), + direction: TransferDirection::In, + #[cfg(psram_dma)] + accesses_psram: is_data_in_psram, + burst_transfer: self.burst, + check_owner: None, + auto_write_back: true, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } +} + +/// DMA transmit and receive buffer. +/// +/// This is a (single) contiguous buffer linked together by two sets of DMA +/// descriptors of length 4092 each. +/// It can be used for simultaneously transmitting to and receiving from a +/// peripheral's FIFO. These are typically full-duplex transfers. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DmaRxTxBuf { + rx_descriptors: DescriptorSet<'static>, + tx_descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], + burst: BurstConfig, +} + +impl DmaRxTxBuf { + /// Creates a new [DmaRxTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported. + pub fn new( + rx_descriptors: &'static mut [DmaDescriptor], + tx_descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + let mut buf = Self { + rx_descriptors: DescriptorSet::new(rx_descriptors)?, + tx_descriptors: DescriptorSet::new(tx_descriptors)?, + buffer, + burst: BurstConfig::default(), + }; + + let capacity = buf.capacity(); + buf.configure(buf.burst, capacity)?; + + Ok(buf) + } + + fn configure( + &mut self, + burst: impl Into, + length: usize, + ) -> Result<(), DmaBufError> { + let burst = burst.into(); + self.set_length_fallible(length, burst)?; + + self.rx_descriptors.link_with_buffer( + self.buffer, + burst.max_chunk_size_for(self.buffer, TransferDirection::In), + )?; + self.tx_descriptors.link_with_buffer( + self.buffer, + burst.max_chunk_size_for(self.buffer, TransferDirection::Out), + )?; + + self.burst = burst; + + Ok(()) + } + + /// Configures the DMA to use burst transfers to access this buffer. + pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { + let len = self.len(); + self.configure(burst, len) + } + + /// Consume the buf, returning the rx descriptors, tx descriptors and + /// buffer. + pub fn split( + self, + ) -> ( + &'static mut [DmaDescriptor], + &'static mut [DmaDescriptor], + &'static mut [u8], + ) { + ( + self.rx_descriptors.into_inner(), + self.tx_descriptors.into_inner(), + self.buffer, + ) + } + + /// Return the size of the underlying buffer. + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Return the number of bytes that would be transmitted by this buf. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.tx_descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + /// Returns the entire buf as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } + + /// Returns the entire buf as a slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + if len > self.capacity() { + return Err(DmaBufError::BufferTooSmall); + } + burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In)?; + burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out)?; + + self.rx_descriptors.set_rx_length( + len, + burst.max_chunk_size_for(self.buffer, TransferDirection::In), + )?; + self.tx_descriptors.set_tx_length( + len, + burst.max_chunk_size_for(self.buffer, TransferDirection::Out), + )?; + + Ok(()) + } + + /// Reset the descriptors to only transmit/receive `len` amount of bytes + /// with this buf. + /// + /// `len` must be less than or equal to the buffer size. + pub fn set_length(&mut self, len: usize) { + unwrap!(self.set_length_fallible(len, self.burst)); + } +} + +unsafe impl DmaTxBuffer for DmaRxTxBuf { + type View = BufView; + type Final = DmaRxTxBuf; + + fn prepare(&mut self) -> Preparation { + for desc in self.tx_descriptors.linked_iter_mut() { + // In non-circular mode, we only set `suc_eof` for the last descriptor to signal + // the end of the transfer. + desc.reset_for_tx(desc.next.is_null()); + } + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + // Optimization: avoid locking for PSRAM range. + let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize); + if is_data_in_psram { + unsafe { + crate::soc::cache_writeback_addr( + self.buffer.as_ptr() as u32, + self.buffer.len() as u32, + ) + }; + } + } + } + + Preparation { + start: self.tx_descriptors.head(), + direction: TransferDirection::Out, + #[cfg(psram_dma)] + accesses_psram: is_data_in_psram, + burst_transfer: self.burst, + check_owner: None, + auto_write_back: false, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } +} + +unsafe impl DmaRxBuffer for DmaRxTxBuf { + type View = BufView; + type Final = DmaRxTxBuf; + + fn prepare(&mut self) -> Preparation { + for desc in self.rx_descriptors.linked_iter_mut() { + desc.reset_for_rx(); + } + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + // Optimization: avoid locking for PSRAM range. + let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize); + if is_data_in_psram { + unsafe { + crate::soc::cache_invalidate_addr( + self.buffer.as_ptr() as u32, + self.buffer.len() as u32, + ) + }; + } + } + } + + Preparation { + start: self.rx_descriptors.head(), + direction: TransferDirection::In, + #[cfg(psram_dma)] + accesses_psram: is_data_in_psram, + burst_transfer: self.burst, + check_owner: None, + auto_write_back: true, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } +} + +/// DMA Streaming Receive Buffer. +/// +/// This is a contiguous buffer linked together by DMA descriptors, and the +/// buffer is evenly distributed between each descriptor provided. +/// +/// It is used for continuously streaming data from a peripheral's FIFO. +/// +/// It does so by maintaining sliding window of descriptors that progresses when +/// you call [DmaRxStreamBufView::consume]. +/// +/// The list starts out like so `A (empty) -> B (empty) -> C (empty) -> D +/// (empty) -> NULL`. +/// +/// As the DMA writes to the buffers the list progresses like so: +/// - `A (empty) -> B (empty) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (empty) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (full) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` +/// +/// As you call [DmaRxStreamBufView::consume] the list (approximately) +/// progresses like so: +/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` +/// - `B (full) -> C (full) -> D (empty) -> A (empty) -> NULL` +/// - `C (full) -> D (empty) -> A (empty) -> B (empty) -> NULL` +/// - `D (empty) -> A (empty) -> B (empty) -> C (empty) -> NULL` +/// +/// If all the descriptors fill up, the [DmaRxInterrupt::DescriptorEmpty] +/// interrupt will fire and the DMA will stop writing, at which point it is up +/// to you to resume/restart the transfer. +/// +/// Note: This buffer will not tell you when this condition occurs, you should +/// check with the driver to see if the DMA has stopped. +/// +/// When constructing this buffer, it is important to tune the ratio between the +/// chunk size and buffer size appropriately. Smaller chunk sizes means you +/// receive data more frequently but this means the DMA interrupts +/// ([DmaRxInterrupt::Done]) also fire more frequently (if you use them). +/// +/// See [DmaRxStreamBufView] for APIs available whilst a transfer is in +/// progress. +pub struct DmaRxStreamBuf { + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + burst: BurstConfig, +} + +impl DmaRxStreamBuf { + /// Creates a new [DmaRxStreamBuf] evenly distributing the buffer between + /// the provided descriptors. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + if !is_slice_in_dram(descriptors) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + if descriptors.is_empty() { + return Err(DmaBufError::InsufficientDescriptors); + } + + // Evenly distribute the buffer between the descriptors. + let chunk_size = buffer.len() / descriptors.len(); + + if chunk_size > 4095 { + return Err(DmaBufError::InsufficientDescriptors); + } + + // Check that the last descriptor can hold the excess + let excess = buffer.len() % descriptors.len(); + if chunk_size + excess > 4095 { + return Err(DmaBufError::InsufficientDescriptors); + } + + let mut chunks = buffer.chunks_exact_mut(chunk_size); + for (desc, chunk) in descriptors.iter_mut().zip(chunks.by_ref()) { + desc.buffer = chunk.as_mut_ptr(); + desc.set_size(chunk.len()); + } + + let remainder = chunks.into_remainder(); + debug_assert_eq!(remainder.len(), excess); + + if !remainder.is_empty() { + // Append any excess to the last descriptor. + let last_descriptor = descriptors.last_mut().unwrap(); + last_descriptor.set_size(last_descriptor.size() + remainder.len()); + } + + Ok(Self { + descriptors, + buffer, + burst: BurstConfig::default(), + }) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors, self.buffer) + } +} + +unsafe impl DmaRxBuffer for DmaRxStreamBuf { + type View = DmaRxStreamBufView; + type Final = DmaRxStreamBuf; + + fn prepare(&mut self) -> Preparation { + // Link up all the descriptors (but not in a circle). + let mut next = null_mut(); + for desc in self.descriptors.iter_mut().rev() { + desc.next = next; + next = desc; + + desc.reset_for_rx(); + } + Preparation { + start: self.descriptors.as_mut_ptr(), + direction: TransferDirection::In, + #[cfg(psram_dma)] + accesses_psram: false, + burst_transfer: self.burst, + + // Whilst we give ownership of the descriptors the DMA, the correctness of this buffer + // implementation doesn't rely on the DMA checking for descriptor ownership. + // No descriptor is added back to the end of the stream before it's ready for the DMA + // to consume it. + check_owner: None, + auto_write_back: true, + } + } + + fn into_view(self) -> DmaRxStreamBufView { + DmaRxStreamBufView { + buf: self, + descriptor_idx: 0, + descriptor_offset: 0, + } + } + + fn from_view(view: Self::View) -> Self { + view.buf + } +} + +/// A view into a [DmaRxStreamBuf] +pub struct DmaRxStreamBufView { + buf: DmaRxStreamBuf, + descriptor_idx: usize, + descriptor_offset: usize, +} + +impl DmaRxStreamBufView { + /// Returns the number of bytes that are available to read from the buf. + pub fn available_bytes(&self) -> usize { + let (tail, head) = self.buf.descriptors.split_at(self.descriptor_idx); + let mut result = 0; + for desc in head.iter().chain(tail) { + if desc.owner() == Owner::Dma { + break; + } + result += desc.len(); + } + result - self.descriptor_offset + } + + /// Reads as much as possible into the buf from the available data. + pub fn pop(&mut self, buf: &mut [u8]) -> usize { + if buf.is_empty() { + return 0; + } + let total_bytes = buf.len(); + + let mut remaining = buf; + loop { + let available = self.peek(); + if available.len() >= remaining.len() { + remaining.copy_from_slice(&available[0..remaining.len()]); + self.consume(remaining.len()); + let consumed = remaining.len(); + remaining = &mut remaining[consumed..]; + break; + } else { + let to_consume = available.len(); + remaining[0..to_consume].copy_from_slice(available); + self.consume(to_consume); + remaining = &mut remaining[to_consume..]; + } + } + + total_bytes - remaining.len() + } + + /// Returns a slice into the buffer containing available data. + /// This will be the longest possible contiguous slice into the buffer that + /// contains data that is available to read. + /// + /// Note: This function ignores EOFs, see [Self::peek_until_eof] if you need + /// EOF support. + pub fn peek(&self) -> &[u8] { + let (slice, _) = self.peek_internal(false); + slice + } + + /// Same as [Self::peek] but will not skip over any EOFs. + /// + /// It also returns a boolean indicating whether this slice ends with an EOF + /// or not. + pub fn peek_until_eof(&self) -> (&[u8], bool) { + self.peek_internal(true) + } + + /// Consumes the first `n` bytes from the available data, returning any + /// fully consumed descriptors back to the DMA. + /// This is typically called after [Self::peek]/[Self::peek_until_eof]. + /// + /// Returns the number of bytes that were actually consumed. + pub fn consume(&mut self, n: usize) -> usize { + let mut remaining_bytes_to_consume = n; + + loop { + let desc = &mut self.buf.descriptors[self.descriptor_idx]; + + if desc.owner() == Owner::Dma { + // Descriptor is still owned by DMA so it can't be read yet. + // This should only happen when there is no more data available to read. + break; + } + + let remaining_bytes_in_descriptor = desc.len() - self.descriptor_offset; + if remaining_bytes_to_consume < remaining_bytes_in_descriptor { + self.descriptor_offset += remaining_bytes_to_consume; + remaining_bytes_to_consume = 0; + break; + } + + // Reset the descriptor for reuse. + desc.set_owner(Owner::Dma); + desc.set_suc_eof(false); + desc.set_length(0); + + // Before connecting this descriptor to the end of the list, the next descriptor + // must be disconnected from this one to prevent the DMA from + // overtaking. + desc.next = null_mut(); + + let desc_ptr: *mut _ = desc; + + let prev_descriptor_index = self + .descriptor_idx + .checked_sub(1) + .unwrap_or(self.buf.descriptors.len() - 1); + + // Connect this consumed descriptor to the end of the chain. + self.buf.descriptors[prev_descriptor_index].next = desc_ptr; + + self.descriptor_idx += 1; + if self.descriptor_idx >= self.buf.descriptors.len() { + self.descriptor_idx = 0; + } + self.descriptor_offset = 0; + + remaining_bytes_to_consume -= remaining_bytes_in_descriptor; + } + + n - remaining_bytes_to_consume + } + + fn peek_internal(&self, stop_at_eof: bool) -> (&[u8], bool) { + let descriptors = &self.buf.descriptors[self.descriptor_idx..]; + + // There must be at least one descriptor. + debug_assert!(!descriptors.is_empty()); + + if descriptors.len() == 1 { + let last_descriptor = &descriptors[0]; + if last_descriptor.owner() == Owner::Dma { + // No data available. + (&[], false) + } else { + let length = last_descriptor.len() - self.descriptor_offset; + ( + &self.buf.buffer[self.buf.buffer.len() - length..], + last_descriptor.flags.suc_eof(), + ) + } + } else { + let chunk_size = descriptors[0].size(); + let mut found_eof = false; + + let mut number_of_contiguous_bytes = 0; + for desc in descriptors { + if desc.owner() == Owner::Dma { + break; + } + number_of_contiguous_bytes += desc.len(); + + if stop_at_eof && desc.flags.suc_eof() { + found_eof = true; + break; + } + // If the length is smaller than the size, the contiguous-ness ends here. + if desc.len() < desc.size() { + break; + } + } + + ( + &self.buf.buffer[chunk_size * self.descriptor_idx..][..number_of_contiguous_bytes] + [self.descriptor_offset..], + found_eof, + ) + } + } +} + +static mut EMPTY: [DmaDescriptor; 1] = [DmaDescriptor::EMPTY]; + +/// An empty buffer that can be used when you don't need to transfer any data. +pub struct EmptyBuf; + +unsafe impl DmaTxBuffer for EmptyBuf { + type View = EmptyBuf; + type Final = EmptyBuf; + + fn prepare(&mut self) -> Preparation { + Preparation { + start: core::ptr::addr_of_mut!(EMPTY).cast(), + direction: TransferDirection::Out, + #[cfg(psram_dma)] + accesses_psram: false, + burst_transfer: BurstConfig::default(), + + // As we don't give ownership of the descriptor to the DMA, it's important that the DMA + // channel does *NOT* check for ownership, otherwise the channel will return an error. + check_owner: Some(false), + + // The DMA should not write back to the descriptor as it is shared. + auto_write_back: false, + } + } + + fn into_view(self) -> EmptyBuf { + self + } + + fn from_view(view: Self::View) -> Self { + view + } +} + +unsafe impl DmaRxBuffer for EmptyBuf { + type View = EmptyBuf; + type Final = EmptyBuf; + + fn prepare(&mut self) -> Preparation { + Preparation { + start: core::ptr::addr_of_mut!(EMPTY).cast(), + direction: TransferDirection::In, + #[cfg(psram_dma)] + accesses_psram: false, + burst_transfer: BurstConfig::default(), + + // As we don't give ownership of the descriptor to the DMA, it's important that the DMA + // channel does *NOT* check for ownership, otherwise the channel will return an error. + check_owner: Some(false), + auto_write_back: true, + } + } + + fn into_view(self) -> EmptyBuf { + self + } + + fn from_view(view: Self::View) -> Self { + view + } +} + +/// DMA Loop Buffer +/// +/// This consists of a single descriptor that points to itself and points to a +/// single buffer, resulting in the buffer being transmitted over and over +/// again, indefinitely. +/// +/// Note: A DMA descriptor is 12 bytes. If your buffer is significantly shorter +/// than this, the DMA channel will spend more time reading the descriptor than +/// it does reading the buffer, which may leave it unable to keep up with the +/// bandwidth requirements of some peripherals at high frequencies. +pub struct DmaLoopBuf { + descriptor: &'static mut DmaDescriptor, + buffer: &'static mut [u8], +} + +impl DmaLoopBuf { + /// Create a new [DmaLoopBuf]. + pub fn new( + descriptor: &'static mut DmaDescriptor, + buffer: &'static mut [u8], + ) -> Result { + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + if !is_slice_in_dram(core::slice::from_ref(descriptor)) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + if buffer.len() > BurstConfig::default().max_chunk_size_for(buffer, TransferDirection::Out) + { + return Err(DmaBufError::InsufficientDescriptors); + } + + descriptor.set_owner(Owner::Dma); // Doesn't matter + descriptor.set_suc_eof(false); + descriptor.set_length(buffer.len()); + descriptor.set_size(buffer.len()); + descriptor.buffer = buffer.as_mut_ptr(); + descriptor.next = descriptor; + + Ok(Self { descriptor, buffer }) + } + + /// Consume the buf, returning the descriptor and buffer. + pub fn split(self) -> (&'static mut DmaDescriptor, &'static mut [u8]) { + (self.descriptor, self.buffer) + } +} + +unsafe impl DmaTxBuffer for DmaLoopBuf { + type View = DmaLoopBuf; + type Final = DmaLoopBuf; + + fn prepare(&mut self) -> Preparation { + Preparation { + start: self.descriptor, + #[cfg(psram_dma)] + accesses_psram: false, + direction: TransferDirection::Out, + burst_transfer: BurstConfig::default(), + // The DMA must not check the owner bit, as it is never set. + check_owner: Some(false), + + // Doesn't matter either way but it is set to true for ESP32 SPI_DMA compatibility. + auto_write_back: false, + } + } + + fn into_view(self) -> Self::View { + self + } + + fn from_view(view: Self::View) -> Self { + view + } +} + +impl Deref for DmaLoopBuf { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.buffer + } +} + +impl DerefMut for DmaLoopBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + self.buffer + } +} + +/// A Preparation that masks itself as a DMA buffer. +/// +/// Fow low level use, where none of the pre-made buffers really fit. +/// +/// This type likely never should be visible outside of esp-hal. +pub(crate) struct NoBuffer(Preparation); +impl NoBuffer { + fn prep(&self) -> Preparation { + Preparation { + start: self.0.start, + direction: self.0.direction, + #[cfg(psram_dma)] + accesses_psram: self.0.accesses_psram, + burst_transfer: self.0.burst_transfer, + check_owner: self.0.check_owner, + auto_write_back: self.0.auto_write_back, + } + } +} +unsafe impl DmaTxBuffer for NoBuffer { + type View = (); + type Final = (); + + fn prepare(&mut self) -> Preparation { + self.prep() + } + + fn into_view(self) -> Self::View {} + fn from_view(_view: Self::View) {} +} +unsafe impl DmaRxBuffer for NoBuffer { + type View = (); + type Final = (); + + fn prepare(&mut self) -> Preparation { + self.prep() + } + + fn into_view(self) -> Self::View {} + fn from_view(_view: Self::View) {} +} + +/// Prepares data unsafely to be transmitted via DMA. +/// +/// `block_size` is the requirement imposed by the peripheral that receives the data. It +/// ensures that the DMA will not try to copy a partial block, which would cause the RX DMA (that +/// moves results back into RAM) to never complete. +/// +/// The function returns the DMA buffer, and the number of bytes that will be transferred. +/// +/// # Safety +/// +/// The caller must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +#[cfg_attr(not(aes_dma), expect(unused))] +pub(crate) unsafe fn prepare_for_tx( + descriptors: &mut [DmaDescriptor], + mut data: NonNull<[u8]>, + block_size: usize, +) -> Result<(NoBuffer, usize), DmaError> { + let alignment = + BurstConfig::DEFAULT.min_alignment(unsafe { data.as_ref() }, TransferDirection::Out); + + if !data.addr().get().is_multiple_of(alignment) { + // ESP32 has word alignment requirement on the TX descriptors, too. + return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address)); + } + + // Whichever is stricter, data location or peripheral requirements. + // + // This ensures that the RX DMA, if used, can transfer the returned number of bytes using at + // most N+2 descriptors. While the hardware doesn't require this on the TX DMA side, (the TX DMA + // can, except on the ESP32, transfer any amount of data), it makes usage MUCH simpler. + let alignment = alignment.max(block_size); + let chunk_size = 4096 - alignment; + + let data_len = data.len().min(chunk_size * descriptors.len()); + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let data_addr = data.addr().get(); + let data_in_psram = crate::psram::psram_range().contains(&data_addr); + + // Make sure input data is in PSRAM instead of cache + if data_in_psram { + unsafe { crate::soc::cache_writeback_addr(data_addr as u32, data_len as u32) }; + } + } + } + + let mut descriptors = unwrap!(DescriptorSet::new(descriptors)); + // TODO: it would be best if this function returned the amount of data that could be linked + // up. + unwrap!(descriptors.link_with_buffer(unsafe { data.as_mut() }, chunk_size)); + unwrap!(descriptors.set_tx_length(data_len, chunk_size)); + + for desc in descriptors.linked_iter_mut() { + desc.reset_for_tx(desc.next.is_null()); + } + + Ok(( + NoBuffer(Preparation { + start: descriptors.head(), + direction: TransferDirection::Out, + burst_transfer: BurstConfig::DEFAULT, + check_owner: None, + auto_write_back: true, + #[cfg(psram_dma)] + accesses_psram: data_in_psram, + }), + data_len, + )) +} + +/// Prepare buffers to receive data from DMA. +/// +/// The function returns the DMA buffer, and the number of bytes that will be transferred. +/// +/// # Safety +/// +/// The caller must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +#[cfg_attr(not(aes_dma), expect(unused))] +pub(crate) unsafe fn prepare_for_rx( + descriptors: &mut [DmaDescriptor], + #[cfg(psram_dma)] align_buffers: &mut [Option; 2], + mut data: NonNull<[u8]>, +) -> (NoBuffer, usize) { + let chunk_size = + BurstConfig::DEFAULT.max_chunk_size_for(unsafe { data.as_ref() }, TransferDirection::In); + + // The data we have to process may not be appropriate for the DMA: + // - it may be improperly aligned for PSRAM + // - it may not have a length that is a multiple of the external memory block size + + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let data_addr = data.addr().get(); + let data_in_psram = crate::psram::psram_range().contains(&data_addr); + } else { + let data_in_psram = false; + } + } + + let mut descriptors = unwrap!(DescriptorSet::new(descriptors)); + let data_len = if data_in_psram { + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + // This could use a better API, but right now we'll have to build the descriptor list by + // hand. + let consumed_bytes = build_descriptor_list_for_psram( + &mut descriptors, + align_buffers, + data, + ); + + // Invalidate data written by the DMA. As this likely affects more data than we touched, write back first. + unsafe { + crate::soc::cache_writeback_addr(data_addr as u32, consumed_bytes as u32); + crate::soc::cache_invalidate_addr(data_addr as u32, consumed_bytes as u32); + } + + consumed_bytes + } else { + unreachable!() + } + } + } else { + // Just set up descriptors as usual + let data_len = data.len(); + unwrap!(descriptors.link_with_buffer(unsafe { data.as_mut() }, chunk_size)); + unwrap!(descriptors.set_tx_length(data_len, chunk_size)); + + data_len + }; + + for desc in descriptors.linked_iter_mut() { + desc.reset_for_rx(); + } + + ( + NoBuffer(Preparation { + start: descriptors.head(), + direction: TransferDirection::In, + burst_transfer: BurstConfig::DEFAULT, + check_owner: None, + auto_write_back: true, + #[cfg(psram_dma)] + accesses_psram: data_in_psram, + }), + data_len, + ) +} + +#[cfg(psram_dma)] +fn build_descriptor_list_for_psram( + descriptors: &mut DescriptorSet<'_>, + copy_buffers: &mut [Option; 2], + data: NonNull<[u8]>, +) -> usize { + let data_len = data.len(); + let data_addr = data.addr().get(); + + let min_alignment = ExternalBurstConfig::DEFAULT.min_psram_alignment(TransferDirection::In); + let chunk_size = 4096 - min_alignment; + + let mut desciptor_iter = DescriptorChainingIter::new(descriptors.descriptors); + let mut copy_buffer_iter = copy_buffers.iter_mut(); + + // MIN_LAST_DMA_LEN could make this really annoying, so we're just allocating a bit larger + // buffer and shove edge cases into a single one. If we have >24 bytes on the S2, the 2-buffer + // alignment algo works fine as one of them can steal 16 bytes, the other will have + // MIN_LAST_DMA_LEN data to work with. + let has_aligned_data = data_len > BUF_LEN; + + // Calculate byte offset to the start of the buffer + let offset = data_addr % min_alignment; + let head_to_copy = min_alignment - offset; + let head_to_copy = if !has_aligned_data { + BUF_LEN + } else if head_to_copy > 0 && head_to_copy < MIN_LAST_DMA_LEN { + head_to_copy + min_alignment + } else { + head_to_copy + }; + let head_to_copy = head_to_copy.min(data_len); + + // Calculate last unaligned part + let tail_to_copy = (data_len - head_to_copy) % min_alignment; + let tail_to_copy = if tail_to_copy > 0 && tail_to_copy < MIN_LAST_DMA_LEN { + tail_to_copy + min_alignment + } else { + tail_to_copy + }; + + let mut consumed = 0; + + // Align beginning + if head_to_copy > 0 { + let copy_buffer = unwrap!(copy_buffer_iter.next()); + let buffer = + copy_buffer.insert(ManualWritebackBuffer::new(get_range(data, 0..head_to_copy))); + + let Some(descriptor) = desciptor_iter.next() else { + return consumed; + }; + descriptor.set_size(head_to_copy); + descriptor.buffer = buffer.buffer_ptr(); + consumed += head_to_copy; + }; + + // Chain up descriptors for the main aligned data part. + let mut aligned_data = get_range(data, head_to_copy..data.len() - tail_to_copy); + while !aligned_data.is_empty() { + let Some(descriptor) = desciptor_iter.next() else { + return consumed; + }; + let chunk = aligned_data.len().min(chunk_size); + + descriptor.set_size(chunk); + descriptor.buffer = aligned_data.cast::().as_ptr(); + consumed += chunk; + aligned_data = get_range(aligned_data, chunk..aligned_data.len()); + } + + // Align end + if tail_to_copy > 0 { + let copy_buffer = unwrap!(copy_buffer_iter.next()); + let buffer = copy_buffer.insert(ManualWritebackBuffer::new(get_range( + data, + data.len() - tail_to_copy..data.len(), + ))); + + let Some(descriptor) = desciptor_iter.next() else { + return consumed; + }; + descriptor.set_size(tail_to_copy); + descriptor.buffer = buffer.buffer_ptr(); + consumed += tail_to_copy; + } + + consumed +} + +#[cfg(psram_dma)] +fn get_range(ptr: NonNull<[u8]>, range: Range) -> NonNull<[u8]> { + let len = range.end - range.start; + NonNull::slice_from_raw_parts(unsafe { ptr.cast().byte_add(range.start) }, len) +} + +#[cfg(psram_dma)] +struct DescriptorChainingIter<'a> { + /// index of the next element to emit + index: usize, + descriptors: &'a mut [DmaDescriptor], +} +#[cfg(psram_dma)] +impl<'a> DescriptorChainingIter<'a> { + fn new(descriptors: &'a mut [DmaDescriptor]) -> Self { + Self { + descriptors, + index: 0, + } + } + + fn next(&mut self) -> Option<&'_ mut DmaDescriptor> { + if self.index == 0 { + self.index += 1; + self.descriptors.get_mut(0) + } else if self.index < self.descriptors.len() { + let index = self.index; + self.index += 1; + + // Grab a pointer to the current descriptor. + let ptr = &raw mut self.descriptors[index]; + + // Link the descriptor to the previous one. + self.descriptors[index - 1].next = ptr; + + // Reborrow the pointer so that it doesn't get invalidated by our continued use of the + // descriptor reference. + Some(unsafe { &mut *ptr }) + } else { + None + } + } +} + +#[cfg(psram_dma)] +const MIN_LAST_DMA_LEN: usize = if cfg!(esp32s2) { 5 } else { 1 }; +#[cfg(psram_dma)] +const BUF_LEN: usize = 16 + 2 * (MIN_LAST_DMA_LEN - 1); // 2x makes aligning short buffers simpler + +/// PSRAM helper. DMA can write data of any alignment into this buffer, and it can be written by +/// the CPU back to PSRAM. +#[cfg(psram_dma)] +pub(crate) struct ManualWritebackBuffer { + dst_address: NonNull, + buffer: [u8; BUF_LEN], + n_bytes: u8, +} + +#[cfg(psram_dma)] +impl ManualWritebackBuffer { + pub fn new(ptr: NonNull<[u8]>) -> Self { + assert!(ptr.len() <= BUF_LEN); + Self { + dst_address: ptr.cast(), + buffer: [0; BUF_LEN], + n_bytes: ptr.len() as u8, + } + } + + pub fn write_back(&self) { + unsafe { + self.dst_address + .as_ptr() + .copy_from(self.buffer.as_ptr(), self.n_bytes as usize); + } + } + + pub fn buffer_ptr(&self) -> *mut u8 { + self.buffer.as_ptr().cast_mut() + } +} diff --git a/esp-hal/src/dma/gdma/ahb_v1.rs b/esp-hal/src/dma/gdma/ahb_v1.rs new file mode 100644 index 00000000000..980b14ff885 --- /dev/null +++ b/esp-hal/src/dma/gdma/ahb_v1.rs @@ -0,0 +1,489 @@ +use super::*; + +impl AnyGdmaTxChannel<'_> { + #[inline(always)] + pub(super) fn ch(&self) -> &pac::dma::ch::CH { + DMA::regs().ch(self.channel as usize) + } + + #[cfg(any(esp32c2, esp32c3))] + #[inline(always)] + pub(super) fn int(&self) -> &pac::dma::int_ch::INT_CH { + DMA::regs().int_ch(self.channel as usize) + } + + #[inline(always)] + #[cfg(any(esp32c6, esp32h2))] + pub(super) fn int(&self) -> &pac::dma::out_int_ch::OUT_INT_CH { + DMA::regs().out_int_ch(self.channel as usize) + } + + #[cfg(esp32s3)] + #[inline(always)] + pub(super) fn int(&self) -> &pac::dma::ch::out_int::OUT_INT { + DMA::regs().ch(self.channel as usize).out_int() + } +} + +impl RegisterAccess for AnyGdmaTxChannel<'_> { + fn reset(&self) { + let conf0 = self.ch().out_conf0(); + conf0.modify(|_, w| w.out_rst().set_bit()); + conf0.modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.ch() + .out_conf0() + .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.ch() + .out_conf0() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + } + + fn set_priority(&self, priority: DmaPriority) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(priority as u8) }); + } + + fn set_peripheral(&self, peripheral: u8) { + self.ch() + .out_peri_sel() + .modify(|_, w| unsafe { w.peri_out_sel().bits(peripheral) }); + } + + fn set_link_addr(&self, address: u32) { + self.ch() + .out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.ch() + .out_conf1() + .modify(|_, w| w.out_check_owner().bit(check_owner.unwrap_or(true))); + } + + #[cfg(esp32s3)] + fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { + self.ch() + .out_conf1() + .modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl TxRegisterAccess for AnyGdmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32s3)] { + self.ch().outfifo_status().read().outfifo_empty_l3().bit_is_set() + } else { + self.ch().outfifo_status().read().outfifo_empty().bit_is_set() + } + } + } + + fn set_auto_write_back(&self, enable: bool) { + self.ch() + .out_conf0() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.ch() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as _ + } + + fn async_handler(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::handler_out(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::handler_out(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::handler_out(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::handler_out(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::handler_out(), + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::isr_out(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::isr_out(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::isr_out(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::isr_out(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::isr_out(), + _ => unreachable!(), + } + } +} + +impl InterruptAccess for AnyGdmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.int().ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.int().ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.int().clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.int().raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + &TX_WAKERS[self.channel as usize] + } + + fn is_async(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3))] { + TX_IS_ASYNC[self.channel as usize].load(portable_atomic::Ordering::Acquire) + } else { + true + } + } + } + + fn set_async(&self, _is_async: bool) { + cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3))] { + TX_IS_ASYNC[self.channel as usize].store(_is_async, portable_atomic::Ordering::Release); + } + } + } +} + +impl AnyGdmaRxChannel<'_> { + #[inline(always)] + fn ch(&self) -> &pac::dma::ch::CH { + DMA::regs().ch(self.channel as usize) + } + + #[cfg(any(esp32c2, esp32c3))] + #[inline(always)] + fn int(&self) -> &pac::dma::int_ch::INT_CH { + DMA::regs().int_ch(self.channel as usize) + } + + #[inline(always)] + #[cfg(any(esp32c6, esp32h2))] + fn int(&self) -> &pac::dma::in_int_ch::IN_INT_CH { + DMA::regs().in_int_ch(self.channel as usize) + } + + #[cfg(esp32s3)] + #[inline(always)] + fn int(&self) -> &pac::dma::ch::in_int::IN_INT { + DMA::regs().ch(self.channel as usize).in_int() + } +} + +impl RegisterAccess for AnyGdmaRxChannel<'_> { + fn reset(&self) { + let conf0 = self.ch().in_conf0(); + conf0.modify(|_, w| w.in_rst().set_bit()); + conf0.modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.ch() + .in_conf0() + .modify(|_, w| w.in_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.ch() + .in_conf0() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + } + + fn set_priority(&self, priority: DmaPriority) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(priority as u8) }); + } + + fn set_peripheral(&self, peripheral: u8) { + self.ch() + .in_peri_sel() + .modify(|_, w| unsafe { w.peri_in_sel().bits(peripheral) }); + } + + fn set_link_addr(&self, address: u32) { + self.ch() + .in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.ch() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.ch().in_link().modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.ch() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.ch() + .in_conf1() + .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true))); + } + + #[cfg(esp32s3)] + fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { + self.ch() + .in_conf1() + .modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl RxRegisterAccess for AnyGdmaRxChannel<'_> { + fn set_mem2mem_mode(&self, value: bool) { + self.ch() + .in_conf0() + .modify(|_, w| w.mem_trans_en().bit(value)); + } + + fn async_handler(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::handler_in(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::handler_in(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::handler_in(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::handler_in(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::handler_in(), + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::isr_in(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::isr_in(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::isr_in(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::isr_in(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::isr_in(), + _ => unreachable!(), + } + } +} + +impl InterruptAccess for AnyGdmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.int().ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.int().ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.int().clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.int().raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + &RX_WAKERS[self.channel as usize] + } + + fn is_async(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3))] { + RX_IS_ASYNC[self.channel as usize].load(portable_atomic::Ordering::Acquire) + } else { + true + } + } + } + + fn set_async(&self, _is_async: bool) { + cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3))] { + RX_IS_ASYNC[self.channel as usize].store(_is_async, portable_atomic::Ordering::Release); + } + } + } +} + +pub(crate) fn setup() {} diff --git a/esp-hal/src/dma/gdma/ahb_v2.rs b/esp-hal/src/dma/gdma/ahb_v2.rs new file mode 100644 index 00000000000..31a72cb6070 --- /dev/null +++ b/esp-hal/src/dma/gdma/ahb_v2.rs @@ -0,0 +1,438 @@ +use super::*; + +impl AnyGdmaTxChannel<'_> { + #[inline(always)] + pub(super) fn ch(&self) -> &pac::dma::ch::CH { + DMA::regs().ch(self.channel as usize) + } + + #[inline(always)] + pub(super) fn int(&self) -> &pac::dma::out_int_ch::OUT_INT_CH { + DMA::regs().out_int_ch(self.channel as usize) + } +} + +impl RegisterAccess for AnyGdmaTxChannel<'_> { + fn reset(&self) { + let conf0 = self.ch().out_conf0(); + conf0.modify(|_, w| w.out_rst().set_bit()); + conf0.modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.ch() + .out_conf0() + .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.ch() + .out_conf0() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + } + + fn set_priority(&self, priority: DmaPriority) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(priority as u8) }); + } + + fn set_peripheral(&self, peripheral: u8) { + self.ch() + .out_peri_sel() + .write(|w| unsafe { w.peri_out_sel().bits(peripheral) }); + } + + fn set_link_addr(&self, address: u32) { + trace!("Setting out-link address to 0x{:08X}", address); + DMA::regs() + .out_link_addr_ch(self.channel as usize) + .write(|w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.ch() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.ch() + .out_conf1() + .modify(|_, w| w.out_check_owner().bit(check_owner.unwrap_or(true))); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl TxRegisterAccess for AnyGdmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + self.ch() + .outfifo_status() + .read() + .outfifo_empty() + .bit_is_set() + } + + fn set_auto_write_back(&self, enable: bool) { + self.ch() + .out_conf0() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.ch() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as _ + } + + fn async_handler(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::handler_out(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::handler_out(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::handler_out(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::handler_out(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::handler_out(), + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::isr_out(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::isr_out(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::isr_out(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::isr_out(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::isr_out(), + _ => unreachable!(), + } + } +} + +impl InterruptAccess for AnyGdmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.int().ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.int().ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.int().clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.int().raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + &TX_WAKERS[self.channel as usize] + } + + fn is_async(&self) -> bool { + true + } + + fn set_async(&self, _is_async: bool) {} +} + +impl AnyGdmaRxChannel<'_> { + #[inline(always)] + fn ch(&self) -> &pac::dma::ch::CH { + DMA::regs().ch(self.channel as usize) + } + + #[inline(always)] + fn int(&self) -> &pac::dma::in_int_ch::IN_INT_CH { + DMA::regs().in_int_ch(self.channel as usize) + } +} + +impl RegisterAccess for AnyGdmaRxChannel<'_> { + fn reset(&self) { + let conf0 = self.ch().in_conf0(); + conf0.modify(|_, w| w.in_rst().set_bit()); + conf0.modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.ch() + .in_conf0() + .modify(|_, w| w.in_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.ch() + .in_conf0() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + } + + fn set_priority(&self, priority: DmaPriority) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(priority as u8) }); + } + + fn set_peripheral(&self, peripheral: u8) { + self.ch() + .in_peri_sel() + .write(|w| unsafe { w.peri_in_sel().bits(peripheral) }); + } + + fn set_link_addr(&self, address: u32) { + trace!("Setting in-link address to 0x{:08X}", address); + DMA::regs() + .in_link_addr_ch(self.channel as usize) + .write(|w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.ch() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.ch().in_link().modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.ch() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.ch() + .in_conf1() + .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true))); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl RxRegisterAccess for AnyGdmaRxChannel<'_> { + fn set_mem2mem_mode(&self, value: bool) { + self.ch() + .in_conf0() + .modify(|_, w| w.mem_trans_en().bit(value)); + } + + fn async_handler(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::handler_in(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::handler_in(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::handler_in(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::handler_in(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::handler_in(), + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Option { + match self.channel { + #[cfg(soc_has_dma_ch0)] + 0 => DMA_CH0::isr_in(), + #[cfg(soc_has_dma_ch1)] + 1 => DMA_CH1::isr_in(), + #[cfg(soc_has_dma_ch2)] + 2 => DMA_CH2::isr_in(), + #[cfg(soc_has_dma_ch3)] + 3 => DMA_CH3::isr_in(), + #[cfg(soc_has_dma_ch4)] + 4 => DMA_CH4::isr_in(), + _ => unreachable!(), + } + } +} + +impl InterruptAccess for AnyGdmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.int().ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.int().ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.int().clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.int().raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + &RX_WAKERS[self.channel as usize] + } + + fn is_async(&self) -> bool { + true + } + + fn set_async(&self, _is_async: bool) {} +} + +pub(crate) fn setup() { + // TODO: configure accessible memory range with non-hardcoded data + // let range = 0x4080_0000..0x4400_0000; + // trace!( + // "Configuring accessible memory range: 0x{:x}..0x{:x}", + // range.start, range.end + // ); + // DMA::regs() + // .intr_mem_start_addr() + // .write(|w| unsafe { w.bits(range.start) }); + // DMA::regs() + // .intr_mem_end_addr() + // .write(|w| unsafe { w.bits(range.end) }); +} diff --git a/esp-hal/src/dma/gdma/mod.rs b/esp-hal/src/dma/gdma/mod.rs new file mode 100644 index 00000000000..1da09035905 --- /dev/null +++ b/esp-hal/src/dma/gdma/mod.rs @@ -0,0 +1,281 @@ +//! # General Direct Memory Access (GDMA) +//! +//! ## Overview +//! GDMA is a feature that allows peripheral-to-memory, memory-to-peripheral, +//! and memory-to-memory data transfer at high speed. The CPU is not involved in +//! the GDMA transfer and therefore is more efficient with less workload. +//! +//! The `GDMA` module provides multiple DMA channels, each capable of managing +//! data transfer for various peripherals. + +use core::marker::PhantomData; + +use crate::{ + dma::*, + handler, + interrupt::Priority, + peripherals::{DMA, Interrupt, pac}, +}; + +#[cfg_attr(dma_gdma_version = "1", path = "ahb_v1.rs")] +#[cfg_attr(dma_gdma_version = "2", path = "ahb_v2.rs")] +mod implementation; + +/// An arbitrary GDMA channel +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyGdmaChannel<'d> { + channel: u8, + _lifetime: PhantomData<&'d mut ()>, +} + +impl AnyGdmaChannel<'_> { + #[cfg_attr(esp32c2, expect(unused))] + pub(crate) unsafe fn clone_unchecked(&self) -> Self { + Self { + channel: self.channel, + _lifetime: PhantomData, + } + } +} + +impl crate::private::Sealed for AnyGdmaChannel<'_> {} +impl<'d> DmaChannel for AnyGdmaChannel<'d> { + type Rx = AnyGdmaRxChannel<'d>; + type Tx = AnyGdmaTxChannel<'d>; + + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + AnyGdmaRxChannel { + channel: self.channel, + _lifetime: PhantomData, + }, + AnyGdmaTxChannel { + channel: self.channel, + _lifetime: PhantomData, + }, + ) + } +} + +/// An arbitrary GDMA RX channel +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyGdmaRxChannel<'d> { + channel: u8, + _lifetime: PhantomData<&'d mut ()>, +} + +impl<'d> DmaChannelConvert> for AnyGdmaRxChannel<'d> { + fn degrade(self) -> AnyGdmaRxChannel<'d> { + self + } +} + +/// An arbitrary GDMA TX channel +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyGdmaTxChannel<'d> { + channel: u8, + _lifetime: PhantomData<&'d mut ()>, +} + +impl<'d> DmaChannelConvert> for AnyGdmaTxChannel<'d> { + fn degrade(self) -> AnyGdmaTxChannel<'d> { + self + } +} + +use crate::asynch::AtomicWaker; + +static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; +static RX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; + +cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3))] { + use portable_atomic::AtomicBool; + static TX_IS_ASYNC: [AtomicBool; CHANNEL_COUNT] = [const { AtomicBool::new(false) }; CHANNEL_COUNT]; + static RX_IS_ASYNC: [AtomicBool; CHANNEL_COUNT] = [const { AtomicBool::new(false) }; CHANNEL_COUNT]; + } +} + +impl crate::private::Sealed for AnyGdmaTxChannel<'_> {} +impl DmaTxChannel for AnyGdmaTxChannel<'_> {} + +impl crate::private::Sealed for AnyGdmaRxChannel<'_> {} +impl DmaRxChannel for AnyGdmaRxChannel<'_> {} + +impl Channel { + /// Asserts that the channel is compatible with the given peripheral. + pub fn runtime_ensure_compatible(&self, _peripheral: &P) { + // No runtime checks; GDMA channels are compatible with any peripheral + } +} + +macro_rules! impl_channel { + ($num:literal, $interrupt_in:ident $(, $interrupt_out:ident)? ) => { + paste::paste! { + use $crate::peripherals::[]; + impl []<'_> { + fn handler_in() -> Option { + $crate::if_set! { + $({ + // $interrupt_out is present, meaning we have split handlers + #[handler(priority = Priority::max())] + fn interrupt_handler_in() { + $crate::ignore!($interrupt_out); + super::asynch::handle_in_interrupt::<[< DMA_CH $num >]<'static>>(); + } + Some(interrupt_handler_in) + })?, + { + #[handler(priority = Priority::max())] + fn interrupt_handler() { + super::asynch::handle_in_interrupt::<[< DMA_CH $num >]<'static>>(); + super::asynch::handle_out_interrupt::<[< DMA_CH $num >]<'static>>(); + } + Some(interrupt_handler) + } + } + } + + fn isr_in() -> Option { + Some(Interrupt::$interrupt_in) + } + + fn handler_out() -> Option { + $crate::if_set! { + $({ + #[handler(priority = Priority::max())] + fn interrupt_handler_out() { + $crate::ignore!($interrupt_out); + super::asynch::handle_out_interrupt::<[< DMA_CH $num >]<'static>>(); + } + Some(interrupt_handler_out) + })?, + None + } + } + + fn isr_out() -> Option { + $crate::if_set! { $(Some(Interrupt::$interrupt_out))?, None } + } + } + + impl<'d> DmaChannel for []<'d> { + type Rx = AnyGdmaRxChannel<'d>; + type Tx = AnyGdmaTxChannel<'d>; + + unsafe fn split_internal(self, _: $crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + AnyGdmaRxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + }, + AnyGdmaTxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + }, + ) + } + } + + impl<'d> DmaChannelConvert> for []<'d> { + fn degrade(self) -> AnyGdmaChannel<'d> { + AnyGdmaChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + } + } + } + + impl<'d> DmaChannelConvert> for []<'d> { + fn degrade(self) -> AnyGdmaRxChannel<'d> { + AnyGdmaRxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + } + } + } + + impl<'d> DmaChannelConvert> for []<'d> { + fn degrade(self) -> AnyGdmaTxChannel<'d> { + AnyGdmaTxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + } + } + } + + impl DmaChannelExt for []<'_> { + fn rx_interrupts() -> impl InterruptAccess { + AnyGdmaRxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + } + } + + fn tx_interrupts() -> impl InterruptAccess { + AnyGdmaTxChannel { + channel: $num, + _lifetime: core::marker::PhantomData, + } + } + } + } + }; +} + +const CHANNEL_COUNT: usize = cfg!(soc_has_dma_ch0) as usize + + cfg!(soc_has_dma_ch1) as usize + + cfg!(soc_has_dma_ch2) as usize + + cfg!(soc_has_dma_ch3) as usize + + cfg!(soc_has_dma_ch4) as usize; + +cfg_if::cfg_if! { + if #[cfg(dma_separate_in_out_interrupts)] { + #[cfg(soc_has_dma_ch0)] + impl_channel!(0, DMA_IN_CH0, DMA_OUT_CH0); + #[cfg(soc_has_dma_ch1)] + impl_channel!(1, DMA_IN_CH1, DMA_OUT_CH1); + #[cfg(soc_has_dma_ch2)] + impl_channel!(2, DMA_IN_CH2, DMA_OUT_CH2); + #[cfg(soc_has_dma_ch3)] + impl_channel!(3, DMA_IN_CH3, DMA_OUT_CH3); + #[cfg(soc_has_dma_ch4)] + impl_channel!(4, DMA_IN_CH4, DMA_OUT_CH4); + } else { + #[cfg(soc_has_dma_ch0)] + impl_channel!(0, DMA_CH0); + #[cfg(soc_has_dma_ch1)] + impl_channel!(1, DMA_CH1); + #[cfg(soc_has_dma_ch2)] + impl_channel!(2, DMA_CH2); + #[cfg(soc_has_dma_ch3)] + impl_channel!(3, DMA_CH3); + #[cfg(soc_has_dma_ch4)] + impl_channel!(4, DMA_CH4); + } +} + +for_each_peripheral! { + (dma_eligible $(( $peri:ident, $name:ident, $id:literal )),*) => { + crate::dma::impl_dma_eligible! { + AnyGdmaChannel { + $($peri => $name,)* + } + } + }; +} + +pub(super) fn init_dma_racey() { + DMA::regs() + .misc_conf() + .modify(|_, w| w.ahbm_rst_inter().set_bit()); + DMA::regs() + .misc_conf() + .modify(|_, w| w.ahbm_rst_inter().clear_bit()); + DMA::regs().misc_conf().modify(|_, w| w.clk_en().set_bit()); + + implementation::setup(); +} diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs new file mode 100644 index 00000000000..b0a0cd3e87f --- /dev/null +++ b/esp-hal/src/dma/m2m.rs @@ -0,0 +1,543 @@ +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +#[cfg(not(esp32s2))] +use crate::dma::{AnyGdmaChannel, AnyGdmaRxChannel, AnyGdmaTxChannel, DmaEligible}; +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + BurstConfig, + Channel, + ChannelRx, + ChannelTx, + DmaChannelConvert, + DmaDescriptor, + DmaError, + DmaPeripheral, + DmaRxBuf, + DmaRxBuffer, + DmaRxInterrupt, + DmaTxBuf, + DmaTxBuffer, + DmaTxInterrupt, + }, +}; +#[cfg(esp32s2)] +use crate::{ + dma::{CopyDmaRxChannel, CopyDmaTxChannel}, + peripherals::DMA_COPY, +}; + +cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + type Mem2MemChannel<'d> = DMA_COPY<'d>; + type Mem2MemRxChannel<'d> = CopyDmaRxChannel<'d>; + type Mem2MemTxChannel<'d> = CopyDmaTxChannel<'d>; + } else { + type Mem2MemChannel<'d> = AnyGdmaChannel<'d>; + type Mem2MemRxChannel<'d> = AnyGdmaRxChannel<'d>; + type Mem2MemTxChannel<'d> = AnyGdmaTxChannel<'d>; + } +} + +/// DMA Memory to Memory pseudo-Peripheral +/// +/// This is a pseudo-peripheral that allows for memory to memory transfers. +/// It is not a real peripheral, but a way to use the DMA engine for memory +/// to memory transfers. +pub struct Mem2Mem<'d, Dm> +where + Dm: DriverMode, +{ + /// RX Half + pub rx: Mem2MemRx<'d, Dm>, + /// TX Half + pub tx: Mem2MemTx<'d, Dm>, +} + +impl<'d> Mem2Mem<'d, Blocking> { + /// Create a new Mem2Mem instance. + pub fn new( + channel: impl DmaChannelConvert>, + #[cfg(dma_kind = "gdma")] peripheral: impl DmaEligible, + ) -> Self { + unsafe { + Self::new_unsafe( + channel, + #[cfg(dma_kind = "gdma")] + peripheral.dma_peripheral(), + ) + } + } + + /// Create a new Mem2Mem instance. + /// + /// # Safety + /// + /// You must ensure that you're not using DMA for the same peripheral and + /// that you're the only one using the DmaPeripheral. + pub unsafe fn new_unsafe( + channel: impl DmaChannelConvert>, + #[cfg(dma_kind = "gdma")] peripheral: DmaPeripheral, + ) -> Self { + let channel = Channel::new(channel.degrade()); + + cfg_if::cfg_if! { + if #[cfg(dma_kind = "gdma")] { + let mut channel = channel; + channel.rx.set_mem2mem_mode(true); + } else { + // The S2's COPY DMA channel doesn't care about this. Once support for other + // channels are added, this will need updating. + let peripheral = DmaPeripheral::Spi2; + } + } + + Mem2Mem { + rx: Mem2MemRx { + channel: channel.rx, + peripheral, + }, + tx: Mem2MemTx { + channel: channel.tx, + peripheral, + }, + } + } + + /// Shortcut to create a [SimpleMem2Mem] + pub fn with_descriptors( + self, + rx_descriptors: &'static mut [DmaDescriptor], + tx_descriptors: &'static mut [DmaDescriptor], + config: BurstConfig, + ) -> Result, DmaError> { + SimpleMem2Mem::new(self, rx_descriptors, tx_descriptors, config) + } + + /// Convert Mem2Mem to an async Mem2Mem. + pub fn into_async(self) -> Mem2Mem<'d, Async> { + Mem2Mem { + rx: self.rx.into_async(), + tx: self.tx.into_async(), + } + } +} + +/// The RX half of [Mem2Mem]. +pub struct Mem2MemRx<'d, Dm: DriverMode> { + channel: ChannelRx>, + peripheral: DmaPeripheral, +} + +impl<'d> Mem2MemRx<'d, Blocking> { + /// Convert Mem2MemRx to an async Mem2MemRx. + pub fn into_async(self) -> Mem2MemRx<'d, Async> { + Mem2MemRx { + channel: self.channel.into_async(), + peripheral: self.peripheral, + } + } +} + +impl<'d, Dm> Mem2MemRx<'d, Dm> +where + Dm: DriverMode, +{ + /// Start the RX half of a memory to memory transfer. + pub fn receive( + mut self, + mut buf: BUF, + ) -> Result, (DmaError, Self, BUF)> + where + BUF: DmaRxBuffer, + { + let result = unsafe { + self.channel + .prepare_transfer(self.peripheral, &mut buf) + .and_then(|_| self.channel.start_transfer()) + }; + + if let Err(e) = result { + return Err((e, self, buf)); + } + + Ok(Mem2MemRxTransfer { + m2m: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buf.into_view()), + }) + } +} + +/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory RX +/// transfer. +pub struct Mem2MemRxTransfer<'d, M: DriverMode, BUF: DmaRxBuffer> { + m2m: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, M: DriverMode, BUF: DmaRxBuffer> Mem2MemRxTransfer<'d, M, BUF> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + let done_interrupts = DmaRxInterrupt::DescriptorError | DmaRxInterrupt::DescriptorEmpty; + !self + .m2m + .channel + .pending_in_interrupts() + .is_disjoint(done_interrupts) + } + + /// Waits for the transfer to stop and returns the peripheral and buffer. + pub fn wait(self) -> (Result<(), DmaError>, Mem2MemRx<'d, M>, BUF::Final) { + while !self.is_done() {} + + let (m2m, view) = self.release(); + + let result = if m2m.channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, m2m, BUF::from_view(view)) + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn stop(self) -> (Mem2MemRx<'d, M>, BUF::Final) { + let (mut m2m, view) = self.release(); + + m2m.channel.stop_transfer(); + + (m2m, BUF::from_view(view)) + } + + fn release(mut self) -> (Mem2MemRx<'d, M>, BUF::View) { + // SAFETY: Since forget is called on self, we know that self.m2m and + // self.buf_view won't be touched again. + let result = unsafe { + let m2m = ManuallyDrop::take(&mut self.m2m); + let view = ManuallyDrop::take(&mut self.buf_view); + (m2m, view) + }; + core::mem::forget(self); + result + } +} + +impl Deref for Mem2MemRxTransfer<'_, M, BUF> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for Mem2MemRxTransfer<'_, M, BUF> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for Mem2MemRxTransfer<'_, M, BUF> { + fn drop(&mut self) { + self.m2m.channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.m2m and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.m2m); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +/// The TX half of [Mem2Mem]. +pub struct Mem2MemTx<'d, Dm: DriverMode> { + channel: ChannelTx>, + peripheral: DmaPeripheral, +} + +impl<'d> Mem2MemTx<'d, Blocking> { + /// Convert Mem2MemTx to an async Mem2MemTx. + pub fn into_async(self) -> Mem2MemTx<'d, Async> { + Mem2MemTx { + channel: self.channel.into_async(), + peripheral: self.peripheral, + } + } +} + +impl<'d, Dm: DriverMode> Mem2MemTx<'d, Dm> { + /// Start the TX half of a memory to memory transfer. + pub fn send( + mut self, + mut buf: BUF, + ) -> Result, (DmaError, Self, BUF)> + where + BUF: DmaTxBuffer, + { + let result = unsafe { + self.channel + .prepare_transfer(self.peripheral, &mut buf) + .and_then(|_| self.channel.start_transfer()) + }; + + if let Err(e) = result { + return Err((e, self, buf)); + } + + Ok(Mem2MemTxTransfer { + m2m: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buf.into_view()), + }) + } +} + +/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory TX +/// transfer. +pub struct Mem2MemTxTransfer<'d, Dm: DriverMode, BUF: DmaTxBuffer> { + m2m: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, Dm: DriverMode, BUF: DmaTxBuffer> Mem2MemTxTransfer<'d, Dm, BUF> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + let done_interrupts = DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof; + !self + .m2m + .channel + .pending_out_interrupts() + .is_disjoint(done_interrupts) + } + + /// Waits for the transfer to stop and returns the peripheral and buffer. + pub fn wait(self) -> (Result<(), DmaError>, Mem2MemTx<'d, Dm>, BUF::Final) { + while !self.is_done() {} + + let (m2m, view) = self.release(); + + let result = if m2m.channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, m2m, BUF::from_view(view)) + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn stop(self) -> (Mem2MemTx<'d, Dm>, BUF::Final) { + let (mut m2m, view) = self.release(); + + m2m.channel.stop_transfer(); + + (m2m, BUF::from_view(view)) + } + + fn release(mut self) -> (Mem2MemTx<'d, Dm>, BUF::View) { + // SAFETY: Since forget is called on self, we know that self.m2m and + // self.buf_view won't be touched again. + let result = unsafe { + let m2m = ManuallyDrop::take(&mut self.m2m); + let view = ManuallyDrop::take(&mut self.buf_view); + (m2m, view) + }; + core::mem::forget(self); + result + } +} + +impl Deref for Mem2MemTxTransfer<'_, Dm, BUF> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for Mem2MemTxTransfer<'_, Dm, BUF> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for Mem2MemTxTransfer<'_, Dm, BUF> { + fn drop(&mut self) { + self.m2m.channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.m2m and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.m2m); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +/// A simple and easy to use wrapper around [SimpleMem2Mem]. +/// More complex memory to memory transfers should use [Mem2Mem] directly. +pub struct SimpleMem2Mem<'d, Dm: DriverMode> { + state: State<'d, Dm>, + config: BurstConfig, +} + +enum State<'d, Dm: DriverMode> { + Idle( + Mem2Mem<'d, Dm>, + &'d mut [DmaDescriptor], + &'d mut [DmaDescriptor], + ), + Active( + Mem2MemRxTransfer<'d, Dm, DmaRxBuf>, + Mem2MemTxTransfer<'d, Dm, DmaTxBuf>, + ), + InUse, +} + +impl<'d, Dm: DriverMode> SimpleMem2Mem<'d, Dm> { + /// Creates a new [SimpleMem2Mem]. + pub fn new( + mem2mem: Mem2Mem<'d, Dm>, + rx_descriptors: &'d mut [DmaDescriptor], + tx_descriptors: &'d mut [DmaDescriptor], + config: BurstConfig, + ) -> Result { + if rx_descriptors.is_empty() || tx_descriptors.is_empty() { + return Err(DmaError::OutOfDescriptors); + } + Ok(Self { + state: State::Idle(mem2mem, rx_descriptors, tx_descriptors), + config, + }) + } +} + +impl<'d, Dm: DriverMode> SimpleMem2Mem<'d, Dm> { + /// Starts a memory to memory transfer. + pub fn start_transfer( + &mut self, + rx_buffer: &mut [u8], + tx_buffer: &[u8], + ) -> Result, DmaError> { + let State::Idle(mem2mem, rx_descriptors, tx_descriptors) = + core::mem::replace(&mut self.state, State::InUse) + else { + panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar"); + }; + + // Raise these buffers to 'static. This is not safe, bad things will happen if + // the user calls core::mem::forget on SimpleMem2MemTransfer. This is + // just the unfortunate consequence of doing DMA without enforcing + // 'static. + let rx_buffer = + unsafe { core::slice::from_raw_parts_mut(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; + let tx_buffer = + unsafe { core::slice::from_raw_parts_mut(tx_buffer.as_ptr() as _, tx_buffer.len()) }; + let rx_descriptors = unsafe { + core::slice::from_raw_parts_mut(rx_descriptors.as_mut_ptr(), rx_descriptors.len()) + }; + let tx_descriptors = unsafe { + core::slice::from_raw_parts_mut(tx_descriptors.as_mut_ptr(), tx_descriptors.len()) + }; + + // Note: The ESP32-S2 insists that RX is started before TX. Contrary to the TRM + // and every other chip. + + let dma_rx_buf = unwrap!( + DmaRxBuf::new_with_config(rx_descriptors, rx_buffer, self.config), + "There's no way to get the descriptors back yet" + ); + + let rx = match mem2mem.rx.receive(dma_rx_buf) { + Ok(rx) => rx, + Err((err, rx, buf)) => { + let (rx_descriptors, _rx_buffer) = buf.split(); + self.state = State::Idle( + Mem2Mem { rx, tx: mem2mem.tx }, + rx_descriptors, + tx_descriptors, + ); + return Err(err); + } + }; + + let dma_tx_buf = unwrap!( + DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config), + "There's no way to get the descriptors back yet" + ); + + let tx = match mem2mem.tx.send(dma_tx_buf) { + Ok(tx) => tx, + Err((err, tx, buf)) => { + let (tx_descriptors, _tx_buffer) = buf.split(); + let (rx, buf) = rx.stop(); + let (rx_descriptors, _rx_buffer) = buf.split(); + self.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors); + return Err(err); + } + }; + + self.state = State::Active(rx, tx); + + Ok(SimpleMem2MemTransfer(self)) + } +} + +impl Drop for SimpleMem2Mem<'_, Dm> { + fn drop(&mut self) { + if !matches!(&mut self.state, State::Idle(_, _, _)) { + panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar"); + } + } +} + +/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory +/// transfer. +pub struct SimpleMem2MemTransfer<'a, 'd, Dm: DriverMode>(&'a mut SimpleMem2Mem<'d, Dm>); + +impl SimpleMem2MemTransfer<'_, '_, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + let State::Active(rx, tx) = &self.0.state else { + unreachable!() + }; + + // Wait for transmission to finish, and wait for the RX channel to receive the + // one and only EOF that DmaTxBuf will send. + tx.is_done() + && rx + .m2m + .channel + .pending_in_interrupts() + .contains(DmaRxInterrupt::SuccessfulEof) + } + + /// Wait for the transfer to finish. + pub fn wait(self) -> Result<(), DmaError> { + while !self.is_done() {} + Ok(()) + } +} + +impl Drop for SimpleMem2MemTransfer<'_, '_, Dm> { + fn drop(&mut self) { + let State::Active(rx, tx) = core::mem::replace(&mut self.0.state, State::InUse) else { + unreachable!() + }; + + let (tx, dma_tx_buf) = tx.stop(); + let (rx, dma_rx_buf) = rx.stop(); + + let (tx_descriptors, _tx_buffer) = dma_tx_buf.split(); + let (rx_descriptors, _rx_buffer) = dma_rx_buf.split(); + + self.0.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors); + } +} diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs new file mode 100644 index 00000000000..f34777a6129 --- /dev/null +++ b/esp-hal/src/dma/mod.rs @@ -0,0 +1,3114 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "dma_channel" => { + cfg(dma_kind = "pdma") => "DMA_SPI2", + cfg(dma_kind = "gdma") => "DMA_CH0" + } +))] +//! # Direct Memory Access (DMA) +//! +//! ## Overview +//! +//! The DMA driver provides an interface to efficiently transfer data between +//! different memory regions and peripherals within the ESP microcontroller +//! without involving the CPU. The DMA controller is responsible for managing +//! these data transfers. +//! +//! Notice, that this module is a common version of the DMA driver, `ESP32` and +//! `ESP32-S2` are using older `PDMA` controller, whenever other chips are using +//! newer `GDMA` controller. +//! +//! ## Examples +//! +//! ### Initialize and utilize DMA controller in `SPI` +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::dma_buffers; +//! # use esp_hal::spi::{master::{Config, Spi}, Mode}; +//! let sclk = peripherals.GPIO0; +//! let miso = peripherals.GPIO2; +//! let mosi = peripherals.GPIO4; +//! let cs = peripherals.GPIO5; +//! +//! let mut spi = Spi::new( +//! peripherals.SPI2, +//! Config::default() +//! .with_frequency(Rate::from_khz(100)) +//! .with_mode(Mode::_0), +//! )? +//! .with_sck(sclk) +//! .with_mosi(mosi) +//! .with_miso(miso) +//! .with_cs(cs) +//! .with_dma(peripherals.__dma_channel__); +//! # {after_snippet} +//! ``` +//! +//! ⚠️ Note: Descriptors should be sized as `(max_transfer_size + CHUNK_SIZE - 1) / CHUNK_SIZE`. +//! I.e., to transfer buffers of size `1..=CHUNK_SIZE`, you need 1 descriptor. +//! +//! ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM +//! have extra alignment requirements. The address and size of the buffer pointed to by +//! each descriptor must be a multiple of the cache line (block) size. This is 32 bytes +//! on ESP32-S3. +//! +//! For convenience you can use the [crate::dma_buffers] macro. + +use core::{cmp::min, fmt::Debug, marker::PhantomData, sync::atomic::compiler_fence}; + +use enumset::{EnumSet, EnumSetType}; + +pub use self::buffers::*; +#[cfg(dma_kind = "gdma")] +pub use self::gdma::*; +#[cfg(any(dma_kind = "gdma", esp32s2))] // TODO +pub use self::m2m::*; +#[cfg(dma_kind = "pdma")] +pub use self::pdma::*; +#[cfg(dma_kind = "pdma")] +use crate::system::Peripheral; +use crate::{ + Async, + Blocking, + DriverMode, + interrupt::InterruptHandler, + peripherals::Interrupt, + soc::{is_slice_in_dram, is_valid_memory_address, is_valid_ram_address}, + system::{self, Cpu}, +}; + +trait Word: crate::private::Sealed {} + +macro_rules! impl_word { + ($w:ty) => { + impl $crate::private::Sealed for $w {} + impl Word for $w {} + }; +} + +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); +impl_word!(i8); +impl_word!(i16); +impl_word!(i32); + +impl crate::private::Sealed for [W; S] where W: Word {} + +impl crate::private::Sealed for &[W; S] where W: Word {} + +impl crate::private::Sealed for &[W] where W: Word {} + +impl crate::private::Sealed for &mut [W] where W: Word {} + +/// Trait for buffers that can be given to DMA for reading. +/// +/// # Safety +/// +/// Once the `read_buffer` method has been called, it is unsafe to call any +/// `&mut self` methods on this object as long as the returned value is in use +/// (by DMA). +pub unsafe trait ReadBuffer { + /// Provide a buffer usable for DMA reads. + /// + /// The return value is: + /// + /// - pointer to the start of the buffer + /// - buffer size in bytes + /// + /// # Safety + /// + /// Once this method has been called, it is unsafe to call any `&mut self` + /// methods on this object as long as the returned value is in use (by DMA). + unsafe fn read_buffer(&self) -> (*const u8, usize); +} + +unsafe impl ReadBuffer for [W; S] +where + W: Word, +{ + unsafe fn read_buffer(&self) -> (*const u8, usize) { + (self.as_ptr() as *const u8, core::mem::size_of_val(self)) + } +} + +unsafe impl ReadBuffer for &[W; S] +where + W: Word, +{ + unsafe fn read_buffer(&self) -> (*const u8, usize) { + (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) + } +} + +unsafe impl ReadBuffer for &mut [W; S] +where + W: Word, +{ + unsafe fn read_buffer(&self) -> (*const u8, usize) { + (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) + } +} + +unsafe impl ReadBuffer for &[W] +where + W: Word, +{ + unsafe fn read_buffer(&self) -> (*const u8, usize) { + (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) + } +} + +unsafe impl ReadBuffer for &mut [W] +where + W: Word, +{ + unsafe fn read_buffer(&self) -> (*const u8, usize) { + (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) + } +} + +/// Trait for buffers that can be given to DMA for writing. +/// +/// # Safety +/// +/// Once the `write_buffer` method has been called, it is unsafe to call any +/// `&mut self` methods, except for `write_buffer`, on this object as long as +/// the returned value is in use (by DMA). +pub unsafe trait WriteBuffer { + /// Provide a buffer usable for DMA writes. + /// + /// The return value is: + /// + /// - pointer to the start of the buffer + /// - buffer size in bytes + /// + /// # Safety + /// + /// Once this method has been called, it is unsafe to call any `&mut self` + /// methods, except for `write_buffer`, on this object as long as the + /// returned value is in use (by DMA). + unsafe fn write_buffer(&mut self) -> (*mut u8, usize); +} + +unsafe impl WriteBuffer for [W; S] +where + W: Word, +{ + unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(self)) + } +} + +unsafe impl WriteBuffer for &mut [W; S] +where + W: Word, +{ + unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) + } +} + +unsafe impl WriteBuffer for &mut [W] +where + W: Word, +{ + unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) + } +} + +bitfield::bitfield! { + /// DMA descriptor flags. + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct DmaDescriptorFlags(u32); + + u16; + + /// Specifies the size of the buffer that this descriptor points to. + pub size, set_size: 11, 0; + + /// Specifies the number of valid bytes in the buffer that this descriptor points to. + /// + /// This field in a transmit descriptor is written by software and indicates how many bytes can + /// be read from the buffer. + /// + /// This field in a receive descriptor is written by hardware automatically and indicates how + /// many valid bytes have been stored into the buffer. + pub length, set_length: 23, 12; + + /// For receive descriptors, software needs to clear this bit to 0, and hardware will set it to 1 after receiving + /// data containing the EOF flag. + /// For transmit descriptors, software needs to set this bit to 1 as needed. + /// If software configures this bit to 1 in a descriptor, the DMA will include the EOF flag in the data sent to + /// the corresponding peripheral, indicating to the peripheral that this data segment marks the end of one + /// transfer phase. + pub suc_eof, set_suc_eof: 30; + + /// Specifies who is allowed to access the buffer that this descriptor points to. + /// - 0: CPU can access the buffer; + /// - 1: The GDMA controller can access the buffer. + pub owner, set_owner: 31; +} + +impl Debug for DmaDescriptorFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DmaDescriptorFlags") + .field("size", &self.size()) + .field("length", &self.length()) + .field("suc_eof", &self.suc_eof()) + .field("owner", &(if self.owner() { "DMA" } else { "CPU" })) + .finish() + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for DmaDescriptorFlags { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!( + fmt, + "DmaDescriptorFlags {{ size: {}, length: {}, suc_eof: {}, owner: {} }}", + self.size(), + self.length(), + self.suc_eof(), + if self.owner() { "DMA" } else { "CPU" } + ); + } +} + +/// A DMA transfer descriptor. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct DmaDescriptor { + /// Descriptor flags. + pub flags: DmaDescriptorFlags, + + /// Address of the buffer. + pub buffer: *mut u8, + + /// Address of the next descriptor. + /// If the current descriptor is the last one, this value is 0. + /// This field can only point to internal RAM. + pub next: *mut DmaDescriptor, +} + +impl DmaDescriptor { + /// An empty DMA descriptor used to initialize the descriptor list. + pub const EMPTY: Self = Self { + flags: DmaDescriptorFlags(0), + buffer: core::ptr::null_mut(), + next: core::ptr::null_mut(), + }; + + /// Resets the descriptor for a new receive transfer. + pub fn reset_for_rx(&mut self) { + // Give ownership to the DMA + self.set_owner(Owner::Dma); + + // Clear this to allow hardware to set it when the peripheral returns an EOF + // bit. + self.set_suc_eof(false); + + // Clear this to allow hardware to set it when it's + // done receiving data for this descriptor. + self.set_length(0); + } + + /// Resets the descriptor for a new transmit transfer. See + /// [DmaDescriptorFlags::suc_eof] for more details on the `set_eof` + /// parameter. + pub fn reset_for_tx(&mut self, set_eof: bool) { + // Give ownership to the DMA + self.set_owner(Owner::Dma); + + // The `suc_eof` bit doesn't affect the transfer itself, but signals when the + // hardware should trigger an interrupt request. + self.set_suc_eof(set_eof); + } + + /// Set the size of the buffer. See [DmaDescriptorFlags::size]. + pub fn set_size(&mut self, len: usize) { + self.flags.set_size(len as u16) + } + + /// Set the length of the descriptor. See [DmaDescriptorFlags::length]. + pub fn set_length(&mut self, len: usize) { + self.flags.set_length(len as u16) + } + + /// Returns the size of the buffer. See [DmaDescriptorFlags::size]. + pub fn size(&self) -> usize { + self.flags.size() as usize + } + + /// Returns the length of the descriptor. See [DmaDescriptorFlags::length]. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.flags.length() as usize + } + + /// Set the suc_eof bit. See [DmaDescriptorFlags::suc_eof]. + pub fn set_suc_eof(&mut self, suc_eof: bool) { + self.flags.set_suc_eof(suc_eof) + } + + /// Set the owner. See [DmaDescriptorFlags::owner]. + pub fn set_owner(&mut self, owner: Owner) { + let owner = match owner { + Owner::Cpu => false, + Owner::Dma => true, + }; + self.flags.set_owner(owner) + } + + /// Returns the owner. See [DmaDescriptorFlags::owner]. + pub fn owner(&self) -> Owner { + match self.flags.owner() { + false => Owner::Cpu, + true => Owner::Dma, + } + } +} + +// The pointers in the descriptor can be Sent. +// Marking this Send also allows DmaBuffer implementations to automatically be +// Send (where the compiler sees fit). +unsafe impl Send for DmaDescriptor {} + +mod buffers; +cfg_if::cfg_if! { + if #[cfg(dma_kind = "gdma")] { + mod gdma; + } else if #[cfg(dma_kind = "pdma")] { + mod pdma; + } else { + compile_error!("Unsupported DMA kind"); + } +} +#[cfg(dma_supports_mem2mem)] +mod m2m; + +/// Kinds of interrupt to listen to. +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaInterrupt { + /// RX is done + RxDone, + /// TX is done + TxDone, +} + +/// Types of interrupts emitted by the TX channel. +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaTxInterrupt { + /// Triggered when all data corresponding to a linked list (including + /// multiple descriptors) have been sent via transmit channel. + TotalEof, + + /// Triggered when an error is detected in a transmit descriptor on transmit + /// channel. + DescriptorError, + + /// Triggered when EOF in a transmit descriptor is true and data + /// corresponding to this descriptor have been sent via transmit + /// channel. + Eof, + + /// Triggered when all data corresponding to a transmit descriptor have been + /// sent via transmit channel. + Done, +} + +/// Types of interrupts emitted by the RX channel. +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaRxInterrupt { + /// Triggered when the size of the buffer pointed by receive descriptors + /// is smaller than the length of data to be received via receive channel. + DescriptorEmpty, + + /// Triggered when an error is detected in a receive descriptor on receive + /// channel. + DescriptorError, + + /// Triggered when an error is detected in the data segment corresponding to + /// a descriptor received via receive channel n. + /// This interrupt is used only for UHCI0 peripheral (UART0 or UART1). + ErrorEof, + + /// Triggered when the suc_eof bit in a receive descriptor is 1 and the data + /// corresponding to this receive descriptor has been received via receive + /// channel. + SuccessfulEof, + + /// Triggered when all data corresponding to a receive descriptor have been + /// received via receive channel. + Done, +} + +/// The default chunk size used for DMA transfers. +pub const CHUNK_SIZE: usize = 4092; + +#[procmacros::doc_replace] +/// Convenience macro to create DMA buffers and descriptors. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_buffers; +/// +/// // RX and TX buffers are 32000 bytes - passing only one parameter makes RX +/// // and TX the same size. +/// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000, 32000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_buffers { + ($rx_size:expr, $tx_size:expr) => { + $crate::dma_buffers_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) + }; + ($size:expr) => { + $crate::dma_buffers_chunk_size!($size, $crate::dma::CHUNK_SIZE) + }; +} + +#[procmacros::doc_replace] +/// Convenience macro to create circular DMA buffers and descriptors. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_circular_buffers; +/// +/// // RX and TX buffers are 32000 bytes - passing only one parameter makes RX +/// // and TX the same size. +/// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = +/// dma_circular_buffers!(32000, 32000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_circular_buffers { + ($rx_size:expr, $tx_size:expr) => { + $crate::dma_circular_buffers_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) + }; + + ($size:expr) => { + $crate::dma_circular_buffers_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + }; +} + +#[procmacros::doc_replace] +/// Convenience macro to create DMA descriptors. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_descriptors; +/// +/// // Create RX and TX descriptors for transactions up to 32000 bytes - passing +/// // only one parameter assumes RX and TX are the same size. +/// let (rx_descriptors, tx_descriptors) = dma_descriptors!(32000, 32000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_descriptors { + ($rx_size:expr, $tx_size:expr) => { + $crate::dma_descriptors_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) + }; + + ($size:expr) => { + $crate::dma_descriptors_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + }; +} + +#[procmacros::doc_replace] +/// Convenience macro to create circular DMA descriptors. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_circular_descriptors; +/// +/// // Create RX and TX descriptors for transactions up to 32000 +/// // bytes - passing only one parameter assumes RX and TX are the same size. +/// let (rx_descriptors, tx_descriptors) = dma_circular_descriptors!(32000, 32000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_circular_descriptors { + ($rx_size:expr, $tx_size:expr) => { + $crate::dma_circular_descriptors_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) + }; + + ($size:expr) => { + $crate::dma_circular_descriptors_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + }; +} + +/// Declares a DMA buffer with a specific size, aligned to 4 bytes +#[doc(hidden)] +#[macro_export] +macro_rules! declare_aligned_dma_buffer { + ($name:ident, $size:expr) => { + // ESP32 requires word alignment for DMA buffers. + // ESP32-S2 technically supports byte-aligned DMA buffers, but the + // transfer ends up writing out of bounds. + // if the buffer's length is 2 or 3 (mod 4). + static mut $name: [u32; ($size as usize).div_ceil(4)] = [0; ($size as usize).div_ceil(4)]; + }; +} + +/// Turns the potentially oversized static `u32`` array reference into a +/// correctly sized `u8` one +#[doc(hidden)] +#[macro_export] +macro_rules! as_mut_byte_array { + ($name:expr, $size:expr) => { + unsafe { &mut *($name.as_mut_ptr() as *mut [u8; $size]) } + }; +} +pub use as_mut_byte_array; // TODO: can be removed as soon as DMA is stabilized + +#[procmacros::doc_replace] +/// Convenience macro to create DMA buffers and descriptors with specific chunk +/// size. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_buffers_chunk_size; +/// +/// // TX and RX buffers are 32000 bytes - passing only one parameter makes TX +/// // and RX the same size. +/// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = +/// dma_buffers_chunk_size!(32000, 32000, 4032); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_buffers_chunk_size { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = false) }}; + + ($size:expr, $chunk_size:expr) => { + $crate::dma_buffers_chunk_size!($size, $size, $chunk_size) + }; +} + +#[procmacros::doc_replace] +/// Convenience macro to create circular DMA buffers and descriptors with +/// specific chunk size. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_circular_buffers_chunk_size; +/// +/// // RX and TX buffers are 32000 bytes - passing only one parameter makes RX +/// // and TX the same size. +/// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = +/// dma_circular_buffers_chunk_size!(32000, 32000, 4032); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_circular_buffers_chunk_size { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = true) }}; + + ($size:expr, $chunk_size:expr) => {{ $crate::dma_circular_buffers_chunk_size!($size, $size, $chunk_size) }}; +} + +#[procmacros::doc_replace] +/// Convenience macro to create DMA descriptors with specific chunk size +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_descriptors_chunk_size; +/// +/// // Create RX and TX descriptors for transactions up to 32000 bytes - passing +/// // only one parameter assumes RX and TX are the same size. +/// let (rx_descriptors, tx_descriptors) = dma_descriptors_chunk_size!(32000, 32000, 4032); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_descriptors_chunk_size { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = false) }}; + + ($size:expr, $chunk_size:expr) => { + $crate::dma_descriptors_chunk_size!($size, $size, $chunk_size) + }; +} + +#[procmacros::doc_replace] +/// Convenience macro to create circular DMA descriptors with specific chunk +/// size +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_circular_descriptors_chunk_size; +/// +/// // Create RX and TX descriptors for transactions up to 32000 bytes - passing +/// // only one parameter assumes RX and TX are the same size. +/// let (rx_descriptors, tx_descriptors) = dma_circular_descriptors_chunk_size!(32000, 32000, 4032); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_circular_descriptors_chunk_size { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = true) }}; + + ($size:expr, $chunk_size:expr) => { + $crate::dma_circular_descriptors_chunk_size!($size, $size, $chunk_size) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! dma_buffers_impl { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr, is_circular = $circular:tt) => {{ + let rx = $crate::dma_buffers_impl!($rx_size, $chunk_size, is_circular = $circular); + let tx = $crate::dma_buffers_impl!($tx_size, $chunk_size, is_circular = $circular); + (rx.0, rx.1, tx.0, tx.1) + }}; + + ($size:expr, $chunk_size:expr, is_circular = $circular:tt) => {{ + $crate::declare_aligned_dma_buffer!(BUFFER, $size); + + unsafe { + ( + $crate::dma::as_mut_byte_array!(BUFFER, $size), + $crate::dma_descriptors_impl!($size, $chunk_size, is_circular = $circular), + ) + } + }}; + + ($size:expr, is_circular = $circular:tt) => { + $crate::dma_buffers_impl!( + $size, + $crate::dma::BurstConfig::DEFAULT.max_compatible_chunk_size(), + is_circular = $circular + ); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! dma_descriptors_impl { + ($rx_size:expr, $tx_size:expr, $chunk_size:expr, is_circular = $circular:tt) => {{ + let rx = $crate::dma_descriptors_impl!($rx_size, $chunk_size, is_circular = $circular); + let tx = $crate::dma_descriptors_impl!($tx_size, $chunk_size, is_circular = $circular); + (rx, tx) + }}; + + ($size:expr, $chunk_size:expr, is_circular = $circular:tt) => {{ + const COUNT: usize = + $crate::dma_descriptor_count!($size, $chunk_size, is_circular = $circular); + + static mut DESCRIPTORS: [$crate::dma::DmaDescriptor; COUNT] = + [$crate::dma::DmaDescriptor::EMPTY; COUNT]; + + unsafe { &mut DESCRIPTORS } + }}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! dma_descriptor_count { + ($size:expr, $chunk_size:expr, is_circular = $is_circular:tt) => {{ + const { + ::core::assert!($chunk_size <= 4095, "chunk size must be <= 4095"); + ::core::assert!($chunk_size > 0, "chunk size must be > 0"); + } + + // We allow 0 in the macros as a "not needed" case. + if $size == 0 { + 0 + } else { + $crate::dma::descriptor_count($size, $chunk_size, $is_circular) + } + }}; +} + +#[procmacros::doc_replace] +/// Convenience macro to create a DmaTxBuf from buffer size. The buffer and +/// descriptors are statically allocated and used to create the `DmaTxBuf`. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_tx_buffer; +/// +/// let tx_buf = dma_tx_buffer!(32000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_tx_buffer { + ($tx_size:expr) => {{ + let (tx_buffer, tx_descriptors) = $crate::dma_buffers_impl!($tx_size, is_circular = false); + + $crate::dma::DmaTxBuf::new(tx_descriptors, tx_buffer) + }}; +} + +#[procmacros::doc_replace] +/// Convenience macro to create a [DmaRxStreamBuf] from buffer size and +/// optional chunk size (uses max if unspecified). +/// The buffer and descriptors are statically allocated and +/// used to create the [DmaRxStreamBuf]. +/// +/// Smaller chunk sizes are recommended for lower latency. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_rx_stream_buffer; +/// +/// let buf = dma_rx_stream_buffer!(32000); +/// let buf = dma_rx_stream_buffer!(32000, 1000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_rx_stream_buffer { + ($rx_size:expr) => { + $crate::dma_rx_stream_buffer!($rx_size, 4095) + }; + ($rx_size:expr, $chunk_size:expr) => {{ + let (buffer, descriptors) = + $crate::dma_buffers_impl!($rx_size, $chunk_size, is_circular = false); + + $crate::dma::DmaRxStreamBuf::new(descriptors, buffer).unwrap() + }}; +} + +#[procmacros::doc_replace] +/// Convenience macro to create a [DmaLoopBuf] from a buffer size. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_loop_buffer; +/// +/// let buf = dma_loop_buffer!(2000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_loop_buffer { + ($size:expr) => {{ + const { + ::core::assert!($size <= 4095, "size must be <= 4095"); + ::core::assert!($size > 0, "size must be > 0"); + } + + let (buffer, descriptors) = $crate::dma_buffers_impl!($size, $size, is_circular = false); + + $crate::dma::DmaLoopBuf::new(&mut descriptors[0], buffer).unwrap() + }}; +} + +/// DMA Errors +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaError { + /// The alignment of data is invalid + InvalidAlignment(DmaAlignmentError), + /// More descriptors are needed for the buffer size + OutOfDescriptors, + /// DescriptorError the DMA rejected the descriptor configuration. This + /// could be because the source address of the data is not in RAM. Ensure + /// your source data is in a valid address space. + DescriptorError, + /// The available free buffer is less than the amount of data to push + Overflow, + /// The given buffer is too small + BufferTooSmall, + /// Descriptors or buffers are not located in a supported memory region + UnsupportedMemoryRegion, + /// Invalid DMA chunk size + InvalidChunkSize, + /// Indicates writing to or reading from a circular DMA transaction is done + /// too late and the DMA buffers already overrun / underrun. + Late, +} + +impl From for DmaError { + fn from(error: DmaBufError) -> Self { + // FIXME: use nested errors + match error { + DmaBufError::InsufficientDescriptors => DmaError::OutOfDescriptors, + DmaBufError::UnsupportedMemoryRegion => DmaError::UnsupportedMemoryRegion, + DmaBufError::InvalidAlignment(err) => DmaError::InvalidAlignment(err), + DmaBufError::InvalidChunkSize => DmaError::InvalidChunkSize, + DmaBufError::BufferTooSmall => DmaError::BufferTooSmall, + } + } +} + +/// DMA Priorities +#[cfg(dma_max_priority_is_set)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaPriority { + /// The lowest priority level (Priority 0). + Priority0 = 0, + /// Priority level 1. + Priority1 = 1, + /// Priority level 2. + Priority2 = 2, + /// Priority level 3. + Priority3 = 3, + /// Priority level 4. + Priority4 = 4, + /// Priority level 5. + Priority5 = 5, + /// Priority level 6. + #[cfg(dma_max_priority = "9")] + Priority6 = 6, + /// Priority level 7. + #[cfg(dma_max_priority = "9")] + Priority7 = 7, + /// Priority level 8. + #[cfg(dma_max_priority = "9")] + Priority8 = 8, + /// Priority level 9. + #[cfg(dma_max_priority = "9")] + Priority9 = 9, +} + +for_each_peripheral! { + (dma_eligible $(( $peri:ident, $name:ident, $id:literal )),*) => { + /// DMA capable peripherals + /// The values need to match the TRM + #[derive(Debug, Clone, Copy, PartialEq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum DmaPeripheral { + $( + #[doc = concat!("DMA accesses ", stringify!($name))] + $name = $id, + )* + } + }; +} + +/// The owner bit of a DMA descriptor. +#[derive(PartialEq, PartialOrd)] +pub enum Owner { + /// Owned by CPU + Cpu = 0, + /// Owned by DMA + Dma = 1, +} + +impl From for Owner { + fn from(value: u32) -> Self { + match value { + 0 => Owner::Cpu, + _ => Owner::Dma, + } + } +} + +#[doc(hidden)] +#[instability::unstable] +pub trait DmaEligible { + /// The most specific DMA channel type usable by this peripheral. + type Dma: DmaChannel; + + fn dma_peripheral(&self) -> DmaPeripheral; +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct DescriptorChain { + pub(crate) descriptors: &'static mut [DmaDescriptor], + chunk_size: usize, +} + +impl DescriptorChain { + pub fn new(descriptors: &'static mut [DmaDescriptor]) -> Self { + Self::new_with_chunk_size(descriptors, CHUNK_SIZE) + } + + pub fn new_with_chunk_size( + descriptors: &'static mut [DmaDescriptor], + chunk_size: usize, + ) -> Self { + Self { + descriptors, + chunk_size, + } + } + + pub fn first_mut(&mut self) -> *mut DmaDescriptor { + self.descriptors.as_mut_ptr() + } + + pub fn first(&self) -> *const DmaDescriptor { + self.descriptors.as_ptr() + } + + pub fn last_mut(&mut self) -> *mut DmaDescriptor { + self.descriptors.last_mut().unwrap() + } + + pub fn last(&self) -> *const DmaDescriptor { + self.descriptors.last().unwrap() + } + + pub fn fill_for_rx( + &mut self, + circular: bool, + data: *mut u8, + len: usize, + ) -> Result<(), DmaError> { + self.fill(circular, data, len, |desc, _| { + desc.reset_for_rx(); + // Descriptor::size has been set up by `fill` + }) + } + + pub fn fill_for_tx( + &mut self, + is_circular: bool, + data: *const u8, + len: usize, + ) -> Result<(), DmaError> { + self.fill(is_circular, data.cast_mut(), len, |desc, chunk_size| { + // In circular mode, we set the `suc_eof` bit for every buffer we send. We use + // this for I2S to track progress of a transfer by checking OUTLINK_DSCR_ADDR. + // In non-circular mode, we only set `suc_eof` for the last descriptor to signal + // the end of the transfer. + desc.reset_for_tx(desc.next.is_null() || is_circular); + desc.set_length(chunk_size); // align to 32 bits? + }) + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn fill( + &mut self, + circular: bool, + data: *mut u8, + len: usize, + prepare_descriptor: impl Fn(&mut DmaDescriptor, usize), + ) -> Result<(), DmaError> { + if !is_valid_ram_address(self.first() as usize) + || !is_valid_ram_address(self.last() as usize) + || !is_valid_memory_address(data as usize) + || !is_valid_memory_address(unsafe { data.add(len) } as usize) + { + return Err(DmaError::UnsupportedMemoryRegion); + } + + let max_chunk_size = if circular && len <= self.chunk_size * 2 { + if len <= 3 { + return Err(DmaError::BufferTooSmall); + } + len / 3 + len % 3 + } else { + self.chunk_size + }; + + DescriptorSet::set_up_buffer_ptrs( + unsafe { core::slice::from_raw_parts_mut(data, len) }, + self.descriptors, + max_chunk_size, + circular, + )?; + DescriptorSet::set_up_descriptors( + self.descriptors, + len, + max_chunk_size, + circular, + prepare_descriptor, + )?; + + Ok(()) + } +} + +/// Computes the number of descriptors required for a given buffer size with +/// a given chunk size. +pub const fn descriptor_count(buffer_size: usize, chunk_size: usize, is_circular: bool) -> usize { + if is_circular && buffer_size <= chunk_size * 2 { + return 3; + } + + if buffer_size < chunk_size { + // At least one descriptor is always required. + return 1; + } + + buffer_size.div_ceil(chunk_size) +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DescriptorSet<'a> { + descriptors: &'a mut [DmaDescriptor], +} + +impl<'a> DescriptorSet<'a> { + /// Creates a new `DescriptorSet` from a slice of descriptors and associates + /// them with the given buffer. + fn new(descriptors: &'a mut [DmaDescriptor]) -> Result { + if !is_slice_in_dram(descriptors) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + descriptors.fill(DmaDescriptor::EMPTY); + + Ok(unsafe { Self::new_unchecked(descriptors) }) + } + + /// Creates a new `DescriptorSet` from a slice of descriptors and associates + /// them with the given buffer. + /// + /// # Safety + /// + /// The caller must ensure that the descriptors are located in a supported + /// memory region. + unsafe fn new_unchecked(descriptors: &'a mut [DmaDescriptor]) -> Self { + Self { descriptors } + } + + /// Consumes the `DescriptorSet` and returns the inner slice of descriptors. + fn into_inner(self) -> &'a mut [DmaDescriptor] { + self.descriptors + } + + /// Returns a pointer to the first descriptor in the chain. + fn head(&mut self) -> *mut DmaDescriptor { + self.descriptors.as_mut_ptr() + } + + /// Returns an iterator over the linked descriptors. + fn linked_iter(&self) -> impl Iterator { + let mut was_last = false; + self.descriptors.iter().take_while(move |d| { + if was_last { + false + } else { + was_last = d.next.is_null(); + true + } + }) + } + + /// Returns an iterator over the linked descriptors. + fn linked_iter_mut(&mut self) -> impl Iterator + use<'_> { + let mut was_last = false; + self.descriptors.iter_mut().take_while(move |d| { + if was_last { + false + } else { + was_last = d.next.is_null(); + true + } + }) + } + + /// Associate each descriptor with a chunk of the buffer. + /// + /// This function checks the alignment and location of the buffer. + /// + /// See [`Self::set_up_buffer_ptrs`] for more details. + fn link_with_buffer( + &mut self, + buffer: &mut [u8], + chunk_size: usize, + ) -> Result<(), DmaBufError> { + Self::set_up_buffer_ptrs(buffer, self.descriptors, chunk_size, false) + } + + /// Prepares descriptors for transferring `len` bytes of data. + /// + /// See [`Self::set_up_descriptors`] for more details. + fn set_length( + &mut self, + len: usize, + chunk_size: usize, + prepare: fn(&mut DmaDescriptor, usize), + ) -> Result<(), DmaBufError> { + Self::set_up_descriptors(self.descriptors, len, chunk_size, false, prepare) + } + + /// Prepares descriptors for reading `len` bytes of data. + /// + /// See [`Self::set_up_descriptors`] for more details. + fn set_rx_length(&mut self, len: usize, chunk_size: usize) -> Result<(), DmaBufError> { + self.set_length(len, chunk_size, |desc, chunk_size| { + desc.set_size(chunk_size); + }) + } + + /// Prepares descriptors for writing `len` bytes of data. + /// + /// See [`Self::set_up_descriptors`] for more details. + fn set_tx_length(&mut self, len: usize, chunk_size: usize) -> Result<(), DmaBufError> { + self.set_length(len, chunk_size, |desc, chunk_size| { + desc.set_length(chunk_size); + }) + } + + /// Returns a slice of descriptors that can cover a buffer of length `len`. + fn descriptors_for_buffer_len( + descriptors: &mut [DmaDescriptor], + len: usize, + chunk_size: usize, + is_circular: bool, + ) -> Result<&mut [DmaDescriptor], DmaBufError> { + // First, pick enough descriptors to cover the buffer. + let required_descriptors = descriptor_count(len, chunk_size, is_circular); + if descriptors.len() < required_descriptors { + return Err(DmaBufError::InsufficientDescriptors); + } + Ok(&mut descriptors[..required_descriptors]) + } + + /// Prepares descriptors for transferring `len` bytes of data. + /// + /// `Prepare` means setting up the descriptor lengths and flags, as well as + /// linking the descriptors into a linked list. + /// + /// The actual descriptor setup is done in a callback, because different + /// transfer directions require different descriptor setup. + fn set_up_descriptors( + descriptors: &mut [DmaDescriptor], + len: usize, + chunk_size: usize, + is_circular: bool, + prepare: impl Fn(&mut DmaDescriptor, usize), + ) -> Result<(), DmaBufError> { + let descriptors = + Self::descriptors_for_buffer_len(descriptors, len, chunk_size, is_circular)?; + + // Link up the descriptors. + let mut next = if is_circular { + descriptors.as_mut_ptr() + } else { + core::ptr::null_mut() + }; + for desc in descriptors.iter_mut().rev() { + desc.next = next; + next = desc; + } + + // Prepare each descriptor. + let mut remaining_length = len; + for desc in descriptors.iter_mut() { + let chunk_size = min(chunk_size, remaining_length); + prepare(desc, chunk_size); + remaining_length -= chunk_size; + } + debug_assert_eq!(remaining_length, 0); + + Ok(()) + } + + /// Associate each descriptor with a chunk of the buffer. + /// + /// This function does not check the alignment and location of the buffer, + /// because some callers may not have enough information currently. + /// + /// This function does not set up descriptor lengths or states. + /// + /// This function also does not link descriptors into a linked list. This is + /// intentional, because it is done in `set_up_descriptors` to support + /// changing length without requiring buffer pointers to be set + /// repeatedly. + fn set_up_buffer_ptrs( + buffer: &mut [u8], + descriptors: &mut [DmaDescriptor], + chunk_size: usize, + is_circular: bool, + ) -> Result<(), DmaBufError> { + let descriptors = + Self::descriptors_for_buffer_len(descriptors, buffer.len(), chunk_size, is_circular)?; + + let chunks = buffer.chunks_mut(chunk_size); + for (desc, chunk) in descriptors.iter_mut().zip(chunks) { + desc.set_size(chunk.len()); + desc.buffer = chunk.as_mut_ptr(); + } + + Ok(()) + } +} + +/// Block size for transfers to/from PSRAM +#[cfg(psram_dma)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum DmaExtMemBKSize { + /// External memory block size of 16 bytes. + Size16 = 0, + /// External memory block size of 32 bytes. + Size32 = 1, + /// External memory block size of 64 bytes. + Size64 = 2, +} + +#[cfg(psram_dma)] +impl From for DmaExtMemBKSize { + fn from(size: ExternalBurstConfig) -> Self { + match size { + ExternalBurstConfig::Size16 => DmaExtMemBKSize::Size16, + ExternalBurstConfig::Size32 => DmaExtMemBKSize::Size32, + ExternalBurstConfig::Size64 => DmaExtMemBKSize::Size64, + } + } +} + +pub(crate) struct TxCircularState { + write_offset: usize, + write_descr_ptr: *mut DmaDescriptor, + pub(crate) available: usize, + last_seen_handled_descriptor_ptr: *mut DmaDescriptor, + buffer_start: *const u8, + buffer_len: usize, + + first_desc_ptr: *mut DmaDescriptor, +} + +impl TxCircularState { + pub(crate) fn new(chain: &mut DescriptorChain) -> Self { + Self { + write_offset: 0, + write_descr_ptr: chain.first_mut(), + available: 0, + last_seen_handled_descriptor_ptr: chain.first_mut(), + buffer_start: chain.descriptors[0].buffer as _, + buffer_len: chain.descriptors.iter().map(|d| d.len()).sum(), + + first_desc_ptr: chain.first_mut(), + } + } + + pub(crate) fn update(&mut self, channel: &ChannelTx) -> Result<(), DmaError> + where + Dm: DriverMode, + CH: DmaTxChannel, + { + if channel + .pending_out_interrupts() + .contains(DmaTxInterrupt::Eof) + { + channel.clear_out(DmaTxInterrupt::Eof); + + // check if all descriptors are owned by CPU - this indicates we failed to push + // data fast enough in future we can enable `check_owner` and check + // the interrupt instead + let mut current = self.last_seen_handled_descriptor_ptr; + loop { + let descr = unsafe { current.read_volatile() }; + if descr.owner() == Owner::Cpu { + current = descr.next; + } else { + break; + } + + if core::ptr::eq(current, self.last_seen_handled_descriptor_ptr) { + return Err(DmaError::Late); + } + } + + let descr_address = channel.last_out_dscr_address() as *mut DmaDescriptor; + + let mut ptr = self.last_seen_handled_descriptor_ptr; + if descr_address >= self.last_seen_handled_descriptor_ptr { + unsafe { + while ptr < descr_address { + let dw0 = ptr.read_volatile(); + self.available += dw0.len(); + ptr = ptr.offset(1); + } + } + } else { + unsafe { + while !((*ptr).next.is_null() + || core::ptr::eq((*ptr).next, self.first_desc_ptr)) + { + let dw0 = ptr.read_volatile(); + self.available += dw0.len(); + ptr = ptr.offset(1); + } + + // add bytes pointed to by the last descriptor + let dw0 = ptr.read_volatile(); + self.available += dw0.len(); + + // in circular mode we need to honor the now available bytes at start + if core::ptr::eq((*ptr).next, self.first_desc_ptr) { + ptr = self.first_desc_ptr; + while ptr < descr_address { + let dw0 = ptr.read_volatile(); + self.available += dw0.len(); + ptr = ptr.offset(1); + } + } + } + } + + if self.available >= self.buffer_len { + unsafe { + let dw0 = self.write_descr_ptr.read_volatile(); + let segment_len = dw0.len(); + let next_descriptor = dw0.next; + self.available -= segment_len; + self.write_offset = (self.write_offset + segment_len) % self.buffer_len; + + self.write_descr_ptr = if next_descriptor.is_null() { + self.first_desc_ptr + } else { + next_descriptor + } + } + } + + self.last_seen_handled_descriptor_ptr = descr_address; + } + + Ok(()) + } + + pub(crate) fn push(&mut self, data: &[u8]) -> Result { + let avail = self.available; + + if avail < data.len() { + return Err(DmaError::Overflow); + } + + let mut remaining = data.len(); + let mut offset = 0; + while self.available >= remaining && remaining > 0 { + let written = self.push_with(|buffer| { + let len = usize::min(buffer.len(), data.len() - offset); + buffer[..len].copy_from_slice(&data[offset..][..len]); + len + })?; + offset += written; + remaining -= written; + } + + Ok(data.len()) + } + + pub(crate) fn push_with( + &mut self, + f: impl FnOnce(&mut [u8]) -> usize, + ) -> Result { + // this might write less than available in case of a wrap around + // caller needs to check and write the remaining part + let written = unsafe { + let dst = self.buffer_start.add(self.write_offset).cast_mut(); + let block_size = usize::min(self.available, self.buffer_len - self.write_offset); + let buffer = core::slice::from_raw_parts_mut(dst, block_size); + f(buffer) + }; + + let mut forward = written; + loop { + unsafe { + let mut descr = self.write_descr_ptr.read_volatile(); + descr.set_owner(Owner::Dma); + self.write_descr_ptr.write_volatile(descr); + + let segment_len = descr.len(); + self.write_descr_ptr = if descr.next.is_null() { + self.first_desc_ptr + } else { + descr.next + }; + + if forward <= segment_len { + break; + } + + forward -= segment_len; + } + } + + self.write_offset = (self.write_offset + written) % self.buffer_len; + self.available -= written; + + Ok(written) + } +} + +pub(crate) struct RxCircularState { + read_descr_ptr: *mut DmaDescriptor, + pub(crate) available: usize, + last_seen_handled_descriptor_ptr: *mut DmaDescriptor, + last_descr_ptr: *mut DmaDescriptor, +} + +impl RxCircularState { + pub(crate) fn new(chain: &mut DescriptorChain) -> Self { + Self { + read_descr_ptr: chain.first_mut(), + available: 0, + last_seen_handled_descriptor_ptr: core::ptr::null_mut(), + last_descr_ptr: chain.last_mut(), + } + } + + pub(crate) fn update(&mut self) -> Result<(), DmaError> { + if self.last_seen_handled_descriptor_ptr.is_null() { + // initially start at last descriptor (so that next will be the first + // descriptor) + self.last_seen_handled_descriptor_ptr = self.last_descr_ptr; + } + + let mut current_in_descr_ptr = + unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next; + let mut current_in_descr = unsafe { current_in_descr_ptr.read_volatile() }; + + let last_seen_ptr = self.last_seen_handled_descriptor_ptr; + while current_in_descr.owner() == Owner::Cpu { + self.available += current_in_descr.len(); + self.last_seen_handled_descriptor_ptr = current_in_descr_ptr; + + current_in_descr_ptr = + unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next; + current_in_descr = unsafe { current_in_descr_ptr.read_volatile() }; + + if core::ptr::eq(current_in_descr_ptr, last_seen_ptr) { + return Err(DmaError::Late); + } + } + + Ok(()) + } + + pub(crate) fn pop(&mut self, data: &mut [u8]) -> Result { + let len = data.len(); + let mut avail = self.available; + + if avail > len { + return Err(DmaError::BufferTooSmall); + } + + let mut remaining_buffer = data; + let mut descr_ptr = self.read_descr_ptr; + + if descr_ptr.is_null() { + return Ok(0); + } + + let mut descr = unsafe { descr_ptr.read_volatile() }; + + while avail > 0 && !remaining_buffer.is_empty() && remaining_buffer.len() >= descr.len() { + unsafe { + let dst = remaining_buffer.as_mut_ptr(); + let src = descr.buffer; + let count = descr.len(); + core::ptr::copy_nonoverlapping(src, dst, count); + + descr.set_owner(Owner::Dma); + descr.set_suc_eof(false); + descr.set_length(0); + descr_ptr.write_volatile(descr); + + remaining_buffer = &mut remaining_buffer[count..]; + avail -= count; + descr_ptr = descr.next; + } + + if descr_ptr.is_null() { + break; + } + + descr = unsafe { descr_ptr.read_volatile() }; + } + + self.read_descr_ptr = descr_ptr; + self.available = avail; + Ok(len - remaining_buffer.len()) + } +} + +#[doc(hidden)] +macro_rules! impl_dma_eligible { + ([$dma_ch:ident] $name:ident => $dma:ident) => { + impl<'d> $crate::dma::DmaEligible for $crate::peripherals::$name<'d> { + type Dma = $dma_ch<'d>; + + fn dma_peripheral(&self) -> $crate::dma::DmaPeripheral { + $crate::dma::DmaPeripheral::$dma + } + } + }; + + ( + $dma_ch:ident { + $($(#[$cfg:meta])? $name:ident => $dma:ident,)* + } + ) => { + $( + $(#[$cfg])? + $crate::dma::impl_dma_eligible!([$dma_ch] $name => $dma); + )* + }; +} + +pub(crate) use impl_dma_eligible; // TODO: can be removed as soon as DMA is stabilized + +/// Helper type to get the DMA (Rx and Tx) channel for a peripheral. +pub type PeripheralDmaChannel = ::Dma; +/// Helper type to get the DMA Rx channel for a peripheral. +pub type PeripheralRxChannel = as DmaChannel>::Rx; +/// Helper type to get the DMA Tx channel for a peripheral. +pub type PeripheralTxChannel = as DmaChannel>::Tx; + +#[instability::unstable] +pub trait DmaRxChannel: RxRegisterAccess + InterruptAccess {} + +#[instability::unstable] +pub trait DmaTxChannel: TxRegisterAccess + InterruptAccess {} + +/// A description of a DMA Channel. +pub trait DmaChannel: Sized { + /// A description of the RX half of a DMA Channel. + type Rx: DmaRxChannel; + + /// A description of the TX half of a DMA Channel. + type Tx: DmaTxChannel; + + /// Splits the DMA channel into its RX and TX halves. + #[cfg(any(esp32c5, esp32c6, esp32h2, esp32s3))] // TODO relax this to allow splitting on all chips + fn split(self) -> (Self::Rx, Self::Tx) { + // This function is exposed safely on chips that have separate IN and OUT + // interrupt handlers. + // TODO: this includes the P4 as well. + unsafe { self.split_internal(crate::private::Internal) } + } + + /// Splits the DMA channel into its RX and TX halves. + /// + /// # Safety + /// + /// This function must only be used if the separate halves are used by the + /// same peripheral. + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx); +} + +#[doc(hidden)] +pub trait DmaChannelExt: DmaChannel { + fn rx_interrupts() -> impl InterruptAccess; + fn tx_interrupts() -> impl InterruptAccess; +} + +#[diagnostic::on_unimplemented( + message = "The DMA channel isn't suitable for this peripheral", + label = "This DMA channel", + note = "Not all channels are useable with all peripherals" +)] +#[doc(hidden)] +pub trait DmaChannelConvert { + fn degrade(self) -> DEG; +} + +impl DmaChannelConvert for DEG { + fn degrade(self) -> DEG { + self + } +} + +#[procmacros::doc_replace( + "dma_channel" => { + cfg(dma_kind = "pdma") => "DMA_SPI2", + cfg(dma_kind = "gdma") => "DMA_CH0" + }, + "note" => { + cfg(dma_kind = "pdma") => "\n\nNote that using mismatching channels (e.g. trying to use `DMA_SPI2` with SPI3) may compile, but will panic in runtime.\n\n", + _ => "" + } +)] +/// Trait implemented for DMA channels that are compatible with a particular +/// peripheral. +/// +/// You can use this in places where a peripheral driver would expect a +/// `DmaChannel` implementation. +/// # {note} +/// ## Example +/// +/// The following example demonstrates how this trait can be used to only accept +/// types compatible with a specific peripheral. +/// +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::{ +/// Blocking, +/// dma::DmaChannelFor, +/// spi::master::{AnySpi, Config, Instance as SpiInstance, Spi, SpiDma}, +/// }; +/// +/// fn configures_spi_dma<'d>( +/// spi: Spi<'d, Blocking>, +/// channel: impl DmaChannelFor>, +/// ) -> SpiDma<'d, Blocking> { +/// spi.with_dma(channel) +/// } +/// +/// let spi = Spi::new(peripherals.SPI2, Config::default())?; +/// +/// let spi_dma = configures_spi_dma(spi, peripherals.__dma_channel__); +/// # {after_snippet} +/// ``` +pub trait DmaChannelFor: + DmaChannel + DmaChannelConvert> +{ +} +impl DmaChannelFor

    for CH +where + P: DmaEligible, + CH: DmaChannel + DmaChannelConvert>, +{ +} + +/// Trait implemented for the RX half of split DMA channels that are compatible +/// with a particular peripheral. Accepts complete DMA channels or split halves. +/// +/// This trait is similar in use to [`DmaChannelFor`]. +/// +/// You can use this in places where a peripheral driver would expect a +/// `DmaRxChannel` implementation. +pub trait RxChannelFor: DmaChannelConvert> {} +impl RxChannelFor

    for RX +where + P: DmaEligible, + RX: DmaChannelConvert>, +{ +} + +/// Trait implemented for the TX half of split DMA channels that are compatible +/// with a particular peripheral. Accepts complete DMA channels or split halves. +/// +/// This trait is similar in use to [`DmaChannelFor`]. +/// +/// You can use this in places where a peripheral driver would expect a +/// `DmaTxChannel` implementation. +pub trait TxChannelFor: DmaChannelConvert> {} +impl TxChannelFor

    for TX +where + P: DmaEligible, + TX: DmaChannelConvert>, +{ +} + +// NOTE(p4): because the P4 has two different GDMAs, we won't be able to use +// `GenericPeripheralGuard`. +cfg_if::cfg_if! { + if #[cfg(dma_kind = "pdma")] { + type PeripheralGuard = Option; + } else { + type PeripheralGuard = system::GenericPeripheralGuard<{ system::Peripheral::Dma as u8}>; + } +} + +fn create_guard(_ch: &impl RegisterAccess) -> PeripheralGuard { + cfg_if::cfg_if! { + if #[cfg(dma_kind = "pdma")] { + _ch.peripheral_clock().map(|peri_clock| system::PeripheralGuard::new_with(peri_clock, init_dma_racey)) + } else { + // NOTE(p4): this function will read the channel's DMA peripheral from `_ch` + system::GenericPeripheralGuard::new_with(init_dma_racey) + } + } +} + +// DMA receive channel +#[non_exhaustive] +#[doc(hidden)] +pub struct ChannelRx +where + Dm: DriverMode, + CH: DmaRxChannel, +{ + pub(crate) rx_impl: CH, + pub(crate) _phantom: PhantomData, + pub(crate) _guard: PeripheralGuard, +} + +impl ChannelRx +where + CH: DmaRxChannel, +{ + /// Creates a new RX channel half. + pub fn new(rx_impl: CH) -> Self { + let _guard = create_guard(&rx_impl); + + #[cfg(dma_kind = "gdma")] + // clear the mem2mem mode to avoid failed DMA if this + // channel was previously used for a mem2mem transfer. + rx_impl.set_mem2mem_mode(false); + + if let Some(interrupt) = rx_impl.peripheral_interrupt() { + for cpu in Cpu::all() { + crate::interrupt::disable(cpu, interrupt); + } + } + rx_impl.set_async(false); + + Self { + rx_impl, + _phantom: PhantomData, + _guard, + } + } + + /// Converts a blocking channel to an async channel. + pub(crate) fn into_async(mut self) -> ChannelRx { + if let Some(handler) = self.rx_impl.async_handler() { + self.set_interrupt_handler(handler); + } + self.rx_impl.set_async(true); + ChannelRx { + rx_impl: self.rx_impl, + _phantom: PhantomData, + _guard: self._guard, + } + } + + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.unlisten_in(EnumSet::all()); + self.clear_in(EnumSet::all()); + + if let Some(interrupt) = self.rx_impl.peripheral_interrupt() { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + crate::interrupt::bind_handler(interrupt, handler); + } + } +} + +impl ChannelRx +where + CH: DmaRxChannel, +{ + /// Converts an async channel into a blocking channel. + pub(crate) fn into_blocking(self) -> ChannelRx { + if let Some(interrupt) = self.rx_impl.peripheral_interrupt() { + crate::interrupt::disable(Cpu::current(), interrupt); + } + self.rx_impl.set_async(false); + ChannelRx { + rx_impl: self.rx_impl, + _phantom: PhantomData, + _guard: self._guard, + } + } +} + +impl ChannelRx +where + Dm: DriverMode, + CH: DmaRxChannel, +{ + /// Configure the channel. + #[cfg(dma_kind = "gdma")] + pub fn set_priority(&mut self, priority: DmaPriority) { + self.rx_impl.set_priority(priority); + } + + fn do_prepare( + &mut self, + preparation: Preparation, + peri: DmaPeripheral, + ) -> Result<(), DmaError> { + debug_assert_eq!(preparation.direction, TransferDirection::In); + + debug!("Preparing RX transfer {:?}", preparation); + trace!("First descriptor {:?}", unsafe { &*preparation.start }); + + #[cfg(psram_dma)] + if preparation.accesses_psram && !self.rx_impl.can_access_psram() { + return Err(DmaError::UnsupportedMemoryRegion); + } + + #[cfg(psram_dma)] + self.rx_impl + .set_ext_mem_block_size(preparation.burst_transfer.external_memory.into()); + self.rx_impl.set_burst_mode(preparation.burst_transfer); + self.rx_impl.set_descr_burst_mode(true); + self.rx_impl.set_check_owner(preparation.check_owner); + + compiler_fence(core::sync::atomic::Ordering::SeqCst); + + self.rx_impl.clear_all(); + self.rx_impl.reset(); + self.rx_impl.set_link_addr(preparation.start as u32); + self.rx_impl.set_peripheral(peri as u8); + + Ok(()) + } +} + +impl crate::private::Sealed for ChannelRx +where + Dm: DriverMode, + CH: DmaRxChannel, +{ +} + +#[allow(unused)] +impl ChannelRx +where + Dm: DriverMode, + CH: DmaRxChannel, +{ + // TODO: used by I2S, which should be rewritten to use the Preparation-based + // API. + pub(crate) unsafe fn prepare_transfer_without_start( + &mut self, + peri: DmaPeripheral, + chain: &DescriptorChain, + ) -> Result<(), DmaError> { + // We check each descriptor buffer that points to PSRAM for + // alignment and invalidate the cache for that buffer. + // NOTE: for RX the `buffer` and `size` need to be aligned but the `len` does + // not. TRM section 3.4.9 + // Note that DmaBuffer implementations are required to do this for us. + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let mut uses_psram = false; + let psram_range = crate::psram::psram_range(); + for des in chain.descriptors.iter() { + // we are forcing the DMA alignment to the cache line size + // required when we are using dcache + let alignment = unsafe { crate::soc::cache_get_dcache_line_size() } as usize; + if crate::soc::addr_in_range(des.buffer as usize, psram_range.clone()) { + uses_psram = true; + // both the size and address of the buffer must be aligned + if !(des.buffer as usize).is_multiple_of(alignment) { + return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address)); + } + if !des.size().is_multiple_of(alignment) { + return Err(DmaError::InvalidAlignment(DmaAlignmentError::Size)); + } + unsafe {crate::soc::cache_invalidate_addr(des.buffer as u32, des.size() as u32); } + } + } + } + } + + let preparation = Preparation { + start: chain.first().cast_mut(), + direction: TransferDirection::In, + #[cfg(psram_dma)] + accesses_psram: uses_psram, + burst_transfer: BurstConfig::default(), + check_owner: Some(false), + auto_write_back: true, + }; + self.do_prepare(preparation, peri) + } + + pub(crate) unsafe fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + buffer: &mut BUF, + ) -> Result<(), DmaError> { + let preparation = buffer.prepare(); + + self.do_prepare(preparation, peri) + } + + pub(crate) fn start_transfer(&mut self) -> Result<(), DmaError> { + self.rx_impl.start(); + + if self + .pending_in_interrupts() + .contains(DmaRxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } + + pub(crate) fn stop_transfer(&mut self) { + self.rx_impl.stop() + } + + #[cfg(dma_kind = "gdma")] + pub(crate) fn set_mem2mem_mode(&mut self, value: bool) { + self.rx_impl.set_mem2mem_mode(value); + } + + pub(crate) fn listen_in(&self, interrupts: impl Into>) { + self.rx_impl.listen(interrupts); + } + + pub(crate) fn unlisten_in(&self, interrupts: impl Into>) { + self.rx_impl.unlisten(interrupts); + } + + pub(crate) fn is_listening_in(&self) -> EnumSet { + self.rx_impl.is_listening() + } + + pub(crate) fn clear_in(&self, interrupts: impl Into>) { + self.rx_impl.clear(interrupts); + } + + pub(crate) fn pending_in_interrupts(&self) -> EnumSet { + self.rx_impl.pending_interrupts() + } + + pub(crate) fn is_done(&self) -> bool { + self.pending_in_interrupts() + .contains(DmaRxInterrupt::SuccessfulEof) + } + + pub(crate) fn clear_interrupts(&self) { + self.rx_impl.clear_all(); + } + + pub(crate) fn waker(&self) -> &'static crate::asynch::AtomicWaker { + self.rx_impl.waker() + } + + pub(crate) fn has_error(&self) -> bool { + self.pending_in_interrupts() + .contains(DmaRxInterrupt::DescriptorError) + } + + pub(crate) fn has_dscr_empty_error(&self) -> bool { + self.pending_in_interrupts() + .contains(DmaRxInterrupt::DescriptorEmpty) + } + + pub(crate) fn has_eof_error(&self) -> bool { + self.pending_in_interrupts() + .contains(DmaRxInterrupt::ErrorEof) + } +} + +/// DMA transmit channel +#[doc(hidden)] +pub struct ChannelTx +where + Dm: DriverMode, + CH: DmaTxChannel, +{ + pub(crate) tx_impl: CH, + pub(crate) _phantom: PhantomData, + pub(crate) _guard: PeripheralGuard, +} + +impl ChannelTx +where + CH: DmaTxChannel, +{ + /// Creates a new TX channel half. + pub fn new(tx_impl: CH) -> Self { + let _guard = create_guard(&tx_impl); + + if let Some(interrupt) = tx_impl.peripheral_interrupt() { + for cpu in Cpu::all() { + crate::interrupt::disable(cpu, interrupt); + } + } + tx_impl.set_async(false); + Self { + tx_impl, + _phantom: PhantomData, + _guard, + } + } + + /// Converts a blocking channel to an async channel. + pub(crate) fn into_async(mut self) -> ChannelTx { + if let Some(handler) = self.tx_impl.async_handler() { + self.set_interrupt_handler(handler); + } + self.tx_impl.set_async(true); + ChannelTx { + tx_impl: self.tx_impl, + _phantom: PhantomData, + _guard: self._guard, + } + } + + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.unlisten_out(EnumSet::all()); + self.clear_out(EnumSet::all()); + + if let Some(interrupt) = self.tx_impl.peripheral_interrupt() { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + crate::interrupt::bind_handler(interrupt, handler); + } + } +} + +impl ChannelTx +where + CH: DmaTxChannel, +{ + /// Converts an async channel into a blocking channel. + pub(crate) fn into_blocking(self) -> ChannelTx { + if let Some(interrupt) = self.tx_impl.peripheral_interrupt() { + crate::interrupt::disable(Cpu::current(), interrupt); + } + self.tx_impl.set_async(false); + ChannelTx { + tx_impl: self.tx_impl, + _phantom: PhantomData, + _guard: self._guard, + } + } +} + +impl ChannelTx +where + Dm: DriverMode, + CH: DmaTxChannel, +{ + /// Configure the channel priority. + #[cfg(dma_kind = "gdma")] + pub fn set_priority(&mut self, priority: DmaPriority) { + self.tx_impl.set_priority(priority); + } + + fn do_prepare( + &mut self, + preparation: Preparation, + peri: DmaPeripheral, + ) -> Result<(), DmaError> { + debug_assert_eq!(preparation.direction, TransferDirection::Out); + + debug!("Preparing TX transfer {:?}", preparation); + trace!("First descriptor {:?}", unsafe { &*preparation.start }); + + #[cfg(psram_dma)] + if preparation.accesses_psram && !self.tx_impl.can_access_psram() { + return Err(DmaError::UnsupportedMemoryRegion); + } + + #[cfg(psram_dma)] + self.tx_impl + .set_ext_mem_block_size(preparation.burst_transfer.external_memory.into()); + self.tx_impl.set_burst_mode(preparation.burst_transfer); + self.tx_impl.set_descr_burst_mode(true); + self.tx_impl.set_check_owner(preparation.check_owner); + self.tx_impl + .set_auto_write_back(preparation.auto_write_back); + + compiler_fence(core::sync::atomic::Ordering::SeqCst); + + self.tx_impl.clear_all(); + self.tx_impl.reset(); + self.tx_impl.set_link_addr(preparation.start as u32); + self.tx_impl.set_peripheral(peri as u8); + + Ok(()) + } +} + +impl crate::private::Sealed for ChannelTx +where + Dm: DriverMode, + CH: DmaTxChannel, +{ +} + +#[allow(unused)] +impl ChannelTx +where + Dm: DriverMode, + CH: DmaTxChannel, +{ + // TODO: used by I2S, which should be rewritten to use the Preparation-based + // API. + pub(crate) unsafe fn prepare_transfer_without_start( + &mut self, + peri: DmaPeripheral, + chain: &DescriptorChain, + ) -> Result<(), DmaError> { + // Based on the ESP32-S3 TRM the alignment check is not needed for TX + + // We check each descriptor buffer that points to PSRAM for + // alignment and writeback the cache for that buffer. + // Note that DmaBuffer implementations are required to do this for us. + #[cfg(psram_dma)] + cfg_if::cfg_if! { + if #[cfg(psram_dma)] { + let mut uses_psram = false; + let psram_range = crate::psram::psram_range(); + for des in chain.descriptors.iter() { + // we are forcing the DMA alignment to the cache line size + // required when we are using dcache + let alignment = unsafe { crate::soc::cache_get_dcache_line_size()} as usize; + if crate::soc::addr_in_range(des.buffer as usize, psram_range.clone()) { + uses_psram = true; + // both the size and address of the buffer must be aligned + if !(des.buffer as usize).is_multiple_of(alignment) { + return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address)); + } + if !des.size().is_multiple_of(alignment) { + return Err(DmaError::InvalidAlignment(DmaAlignmentError::Size)); + } + unsafe { crate::soc::cache_writeback_addr(des.buffer as u32, des.size() as u32); } + } + } + } + } + + let preparation = Preparation { + start: chain.first().cast_mut(), + direction: TransferDirection::Out, + #[cfg(psram_dma)] + accesses_psram: uses_psram, + burst_transfer: BurstConfig::default(), + check_owner: Some(false), + // enable descriptor write back in circular mode + auto_write_back: !(unsafe { *chain.last() }).next.is_null(), + }; + self.do_prepare(preparation, peri)?; + + Ok(()) + } + + pub(crate) unsafe fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + buffer: &mut BUF, + ) -> Result<(), DmaError> { + let preparation = buffer.prepare(); + + self.do_prepare(preparation, peri) + } + + pub(crate) fn start_transfer(&mut self) -> Result<(), DmaError> { + self.tx_impl.start(); + while self.tx_impl.is_fifo_empty() && self.pending_out_interrupts().is_empty() {} + + if self + .pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } + + pub(crate) fn stop_transfer(&mut self) { + self.tx_impl.stop() + } + + pub(crate) fn listen_out(&self, interrupts: impl Into>) { + self.tx_impl.listen(interrupts); + } + + pub(crate) fn unlisten_out(&self, interrupts: impl Into>) { + self.tx_impl.unlisten(interrupts); + } + + pub(crate) fn is_listening_out(&self) -> EnumSet { + self.tx_impl.is_listening() + } + + pub(crate) fn clear_out(&self, interrupts: impl Into>) { + self.tx_impl.clear(interrupts); + } + + pub(crate) fn pending_out_interrupts(&self) -> EnumSet { + self.tx_impl.pending_interrupts() + } + + pub(crate) fn waker(&self) -> &'static crate::asynch::AtomicWaker { + self.tx_impl.waker() + } + + pub(crate) fn clear_interrupts(&self) { + self.tx_impl.clear_all(); + } + + pub(crate) fn last_out_dscr_address(&self) -> usize { + self.tx_impl.last_dscr_address() + } + + pub(crate) fn is_done(&self) -> bool { + self.pending_out_interrupts() + .contains(DmaTxInterrupt::TotalEof) + } + + pub(crate) fn has_error(&self) -> bool { + self.pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + } +} + +#[doc(hidden)] +pub trait RegisterAccess: crate::private::Sealed { + #[cfg(dma_kind = "pdma")] + fn peripheral_clock(&self) -> Option; + + /// Reset the state machine of the channel and FIFO pointer. + fn reset(&self); + + /// Enable/Disable INCR burst transfer for channel reading + /// accessing data in internal RAM. + fn set_burst_mode(&self, burst_mode: BurstConfig); + + /// Enable/Disable burst transfer for channel reading + /// descriptors in internal RAM. + fn set_descr_burst_mode(&self, burst_mode: bool); + + /// The priority of the channel. The larger the value, the higher the + /// priority. + #[cfg(dma_kind = "gdma")] + fn set_priority(&self, priority: DmaPriority); + + /// Select a peripheral for the channel. + fn set_peripheral(&self, peripheral: u8); + + /// Set the address of the first descriptor. + fn set_link_addr(&self, address: u32); + + /// Enable the channel for data transfer. + fn start(&self); + + /// Stop the channel from transferring data. + fn stop(&self); + + /// Mount a new descriptor. + fn restart(&self); + + /// Configure the bit to enable checking the owner attribute of the + /// descriptor. + fn set_check_owner(&self, check_owner: Option); + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize); + + #[cfg(dma_kind = "pdma")] + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool; + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool; +} + +#[doc(hidden)] +pub trait RxRegisterAccess: RegisterAccess { + #[cfg(dma_kind = "gdma")] + fn set_mem2mem_mode(&self, value: bool); + + fn peripheral_interrupt(&self) -> Option; + fn async_handler(&self) -> Option; +} + +#[doc(hidden)] +pub trait TxRegisterAccess: RegisterAccess { + /// Returns whether the DMA's FIFO is empty. + fn is_fifo_empty(&self) -> bool; + + /// Enable/disable outlink-writeback + fn set_auto_write_back(&self, enable: bool); + + /// Outlink descriptor address when EOF occurs of Tx channel. + fn last_dscr_address(&self) -> usize; + + fn peripheral_interrupt(&self) -> Option; + fn async_handler(&self) -> Option; +} + +#[doc(hidden)] +pub trait InterruptAccess: crate::private::Sealed { + fn listen(&self, interrupts: impl Into>) { + self.enable_listen(interrupts.into(), true) + } + fn unlisten(&self, interrupts: impl Into>) { + self.enable_listen(interrupts.into(), false) + } + + fn clear_all(&self) { + self.clear(EnumSet::all()); + } + + fn enable_listen(&self, interrupts: EnumSet, enable: bool); + fn is_listening(&self) -> EnumSet; + fn clear(&self, interrupts: impl Into>); + fn pending_interrupts(&self) -> EnumSet; + fn waker(&self) -> &'static crate::asynch::AtomicWaker; + + fn is_async(&self) -> bool; + fn set_async(&self, is_async: bool); +} + +/// DMA Channel +#[non_exhaustive] +pub struct Channel +where + Dm: DriverMode, + CH: DmaChannel, +{ + /// RX half of the channel + pub rx: ChannelRx, + /// TX half of the channel + pub tx: ChannelTx, +} + +impl Channel +where + CH: DmaChannel, +{ + /// Creates a new DMA channel driver. + #[instability::unstable] + pub fn new(channel: CH) -> Self { + let (rx, tx) = unsafe { channel.split_internal(crate::private::Internal) }; + Self { + rx: ChannelRx::new(rx), + tx: ChannelTx::new(tx), + } + } + + /// Sets the interrupt handler for RX and TX interrupts. + /// + /// Interrupts are not enabled at the peripheral level here. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.rx.set_interrupt_handler(handler); + self.tx.set_interrupt_handler(handler); + } + + /// Listen for the given interrupts + pub fn listen(&mut self, interrupts: impl Into>) { + for interrupt in interrupts.into() { + match interrupt { + DmaInterrupt::RxDone => self.rx.listen_in(DmaRxInterrupt::Done), + DmaInterrupt::TxDone => self.tx.listen_out(DmaTxInterrupt::Done), + } + } + } + + /// Unlisten the given interrupts + pub fn unlisten(&mut self, interrupts: impl Into>) { + for interrupt in interrupts.into() { + match interrupt { + DmaInterrupt::RxDone => self.rx.unlisten_in(DmaRxInterrupt::Done), + DmaInterrupt::TxDone => self.tx.unlisten_out(DmaTxInterrupt::Done), + } + } + } + + /// Gets asserted interrupts + pub fn interrupts(&mut self) -> EnumSet { + let mut res = EnumSet::new(); + if self.rx.is_done() { + res.insert(DmaInterrupt::RxDone); + } + if self.tx.is_done() { + res.insert(DmaInterrupt::TxDone); + } + res + } + + /// Resets asserted interrupts + pub fn clear_interrupts(&mut self, interrupts: impl Into>) { + for interrupt in interrupts.into() { + match interrupt { + DmaInterrupt::RxDone => self.rx.clear_in(DmaRxInterrupt::Done), + DmaInterrupt::TxDone => self.tx.clear_out(DmaTxInterrupt::Done), + } + } + } + + /// Configure the channel priorities. + #[cfg(dma_kind = "gdma")] + pub fn set_priority(&mut self, priority: DmaPriority) { + self.tx.set_priority(priority); + self.rx.set_priority(priority); + } + + /// Converts a blocking channel to an async channel. + pub fn into_async(self) -> Channel { + Channel { + rx: self.rx.into_async(), + tx: self.tx.into_async(), + } + } +} + +impl Channel +where + CH: DmaChannel, +{ + /// Converts an async channel to a blocking channel. + pub fn into_blocking(self) -> Channel { + Channel { + rx: self.rx.into_blocking(), + tx: self.tx.into_blocking(), + } + } +} + +impl From> for Channel { + fn from(channel: Channel) -> Self { + channel.into_async() + } +} + +impl From> for Channel { + fn from(channel: Channel) -> Self { + channel.into_blocking() + } +} + +pub(crate) mod dma_private { + use super::*; + + pub trait DmaSupport { + type DriverMode: DriverMode; + + /// Wait until the transfer is done. + /// + /// Depending on the peripheral this might include checking the DMA + /// channel and/or the peripheral. + /// + /// After this all data should be processed by the peripheral - i.e. the + /// peripheral should have processed it's FIFO(s) + /// + /// Please note: This is called in the transfer's `wait` function _and_ + /// by it's [Drop] implementation. + fn peripheral_wait_dma(&mut self, is_rx: bool, is_tx: bool); + + /// Only used by circular DMA transfers in both, the `stop` function + /// _and_ it's [Drop] implementation + fn peripheral_dma_stop(&mut self); + } + + #[instability::unstable] + pub trait DmaSupportTx: DmaSupport { + type Channel: DmaTxChannel; + + fn tx(&mut self) -> &mut ChannelTx; + + fn chain(&mut self) -> &mut DescriptorChain; + } + + #[instability::unstable] + pub trait DmaSupportRx: DmaSupport { + type Channel: DmaRxChannel; + + fn rx(&mut self) -> &mut ChannelRx; + + fn chain(&mut self) -> &mut DescriptorChain; + } +} + +/// DMA transaction for TX only transfers +/// +/// # Safety +/// +/// Never use [core::mem::forget] on an in-progress transfer +#[non_exhaustive] +#[must_use] +#[cfg(i2s_driver_supported)] +pub struct DmaTransferTx<'a, I> +where + I: dma_private::DmaSupportTx, +{ + instance: &'a mut I, +} + +#[cfg(i2s_driver_supported)] +impl<'a, I> DmaTransferTx<'a, I> +where + I: dma_private::DmaSupportTx, +{ + pub(crate) fn new(instance: &'a mut I) -> Self { + Self { instance } + } + + /// Wait for the transfer to finish. + pub fn wait(self) -> Result<(), DmaError> { + self.instance.peripheral_wait_dma(false, true); + + if self + .instance + .tx() + .pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } + + /// Check if the transfer is finished. + pub fn is_done(&mut self) -> bool { + self.instance.tx().is_done() + } +} + +#[cfg(i2s_driver_supported)] +impl Drop for DmaTransferTx<'_, I> +where + I: dma_private::DmaSupportTx, +{ + fn drop(&mut self) { + self.instance.peripheral_wait_dma(true, false); + } +} + +/// DMA transaction for RX only transfers +/// +/// # Safety +/// +/// Never use [core::mem::forget] on an in-progress transfer +#[non_exhaustive] +#[must_use] +#[cfg(i2s_driver_supported)] +pub struct DmaTransferRx<'a, I> +where + I: dma_private::DmaSupportRx, +{ + instance: &'a mut I, +} + +#[cfg(i2s_driver_supported)] +impl<'a, I> DmaTransferRx<'a, I> +where + I: dma_private::DmaSupportRx, +{ + pub(crate) fn new(instance: &'a mut I) -> Self { + Self { instance } + } + + /// Wait for the transfer to finish. + pub fn wait(self) -> Result<(), DmaError> { + self.instance.peripheral_wait_dma(true, false); + + if self + .instance + .rx() + .pending_in_interrupts() + .contains(DmaRxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } + + /// Check if the transfer is finished. + pub fn is_done(&mut self) -> bool { + self.instance.rx().is_done() + } +} + +#[cfg(i2s_driver_supported)] +impl Drop for DmaTransferRx<'_, I> +where + I: dma_private::DmaSupportRx, +{ + fn drop(&mut self) { + self.instance.peripheral_wait_dma(true, false); + } +} + +/// DMA transaction for TX+RX transfers +/// +/// # Safety +/// +/// Never use [core::mem::forget] on an in-progress transfer +#[non_exhaustive] +#[must_use] +pub struct DmaTransferRxTx<'a, I> +where + I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, +{ + instance: &'a mut I, +} + +impl<'a, I> DmaTransferRxTx<'a, I> +where + I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, +{ + #[allow(dead_code)] + pub(crate) fn new(instance: &'a mut I) -> Self { + Self { instance } + } + + /// Wait for the transfer to finish. + pub fn wait(self) -> Result<(), DmaError> { + self.instance.peripheral_wait_dma(true, true); + + if self + .instance + .tx() + .pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + || self + .instance + .rx() + .pending_in_interrupts() + .contains(DmaRxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } + + /// Check if the transfer is finished. + pub fn is_done(&mut self) -> bool { + self.instance.tx().is_done() && self.instance.rx().is_done() + } +} + +impl Drop for DmaTransferRxTx<'_, I> +where + I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, +{ + fn drop(&mut self) { + self.instance.peripheral_wait_dma(true, true); + } +} + +/// DMA transaction for TX only circular transfers +/// +/// # Safety +/// +/// Never use [core::mem::forget] on an in-progress transfer +#[non_exhaustive] +#[must_use] +pub struct DmaTransferTxCircular<'a, I> +where + I: dma_private::DmaSupportTx, +{ + instance: &'a mut I, + state: TxCircularState, +} + +impl<'a, I> DmaTransferTxCircular<'a, I> +where + I: dma_private::DmaSupportTx, +{ + #[allow(unused)] // currently used by peripherals not available on all chips + pub(crate) fn new(instance: &'a mut I) -> Self { + let state = TxCircularState::new(instance.chain()); + Self { instance, state } + } + + /// Amount of bytes which can be pushed. + pub fn available(&mut self) -> Result { + self.state.update(self.instance.tx())?; + Ok(self.state.available) + } + + /// Push bytes into the DMA buffer. + pub fn push(&mut self, data: &[u8]) -> Result { + self.state.update(self.instance.tx())?; + self.state.push(data) + } + + /// Push bytes into the DMA buffer via the given closure. + /// The closure *must* return the actual number of bytes written. + /// The closure *might* get called with a slice which is smaller than the + /// total available buffer. + pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result { + self.state.update(self.instance.tx())?; + self.state.push_with(f) + } + + /// Stop the DMA transfer + #[allow(clippy::type_complexity)] + pub fn stop(self) -> Result<(), DmaError> { + self.instance.peripheral_dma_stop(); + + if self + .instance + .tx() + .pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + { + Err(DmaError::DescriptorError) + } else { + Ok(()) + } + } +} + +impl Drop for DmaTransferTxCircular<'_, I> +where + I: dma_private::DmaSupportTx, +{ + fn drop(&mut self) { + self.instance.peripheral_dma_stop(); + } +} + +/// DMA transaction for RX only circular transfers +/// +/// # Safety +/// +/// Never use [core::mem::forget] on an in-progress transfer +#[non_exhaustive] +#[must_use] +pub struct DmaTransferRxCircular<'a, I> +where + I: dma_private::DmaSupportRx, +{ + instance: &'a mut I, + state: RxCircularState, +} + +impl<'a, I> DmaTransferRxCircular<'a, I> +where + I: dma_private::DmaSupportRx, +{ + #[allow(unused)] // currently used by peripherals not available on all chips + pub(crate) fn new(instance: &'a mut I) -> Self { + let state = RxCircularState::new(instance.chain()); + Self { instance, state } + } + + /// Amount of bytes which can be popped. + /// + /// It's expected to call this before trying to [DmaTransferRxCircular::pop] + /// data. + pub fn available(&mut self) -> Result { + self.state.update()?; + Ok(self.state.available) + } + + /// Get available data. + /// + /// It's expected that the amount of available data is checked before by + /// calling [DmaTransferRxCircular::available] and that the buffer can hold + /// all available data. + /// + /// Fails with [DmaError::BufferTooSmall] if the given buffer is too small + /// to hold all available data + pub fn pop(&mut self, data: &mut [u8]) -> Result { + self.state.update()?; + self.state.pop(data) + } +} + +impl Drop for DmaTransferRxCircular<'_, I> +where + I: dma_private::DmaSupportRx, +{ + fn drop(&mut self) { + self.instance.peripheral_dma_stop(); + } +} + +pub(crate) mod asynch { + use core::task::Poll; + + use enumset::enum_set; + + use super::*; + + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct DmaTxFuture<'a, CH> + where + CH: DmaTxChannel, + { + pub(crate) tx: &'a mut ChannelTx, + } + + impl<'a, CH> DmaTxFuture<'a, CH> + where + CH: DmaTxChannel, + { + const SUCCESS_INTERRUPTS: EnumSet = enum_set!(DmaTxInterrupt::TotalEof); + const FAILURE_INTERRUPTS: EnumSet = + enum_set!(DmaTxInterrupt::DescriptorError); + + #[cfg_attr(esp32c2, expect(dead_code))] + pub fn new(tx: &'a mut ChannelTx) -> Self { + Self { tx } + } + } + + impl core::future::Future for DmaTxFuture<'_, CH> + where + CH: DmaTxChannel, + { + type Output = Result<(), DmaError>; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + let interrupts = self.tx.pending_out_interrupts(); + let result = if !interrupts.is_disjoint(Self::SUCCESS_INTERRUPTS) { + Ok(()) + } else if !interrupts.is_disjoint(Self::FAILURE_INTERRUPTS) { + Err(DmaError::DescriptorError) + } else { + // The interrupt may become pending before we register the waker and start + // listening, but that should just trigger the interrupt handler. The only + // constraint we have is that the waker must be registered before we start + // listening. + self.tx.waker().register(cx.waker()); + self.tx + .listen_out(Self::SUCCESS_INTERRUPTS | Self::FAILURE_INTERRUPTS); + + return Poll::Pending; + }; + + self.tx.clear_interrupts(); + + Poll::Ready(result) + } + } + + impl Drop for DmaTxFuture<'_, CH> + where + CH: DmaTxChannel, + { + fn drop(&mut self) { + self.tx + .unlisten_out(Self::SUCCESS_INTERRUPTS | Self::FAILURE_INTERRUPTS); + } + } + + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct DmaRxFuture<'a, CH> + where + CH: DmaRxChannel, + { + pub(crate) rx: &'a mut ChannelRx, + } + + impl<'a, CH> DmaRxFuture<'a, CH> + where + CH: DmaRxChannel, + { + const SUCCESS_INTERRUPTS: EnumSet = + enum_set!(DmaRxInterrupt::SuccessfulEof); + const FAILURE_INTERRUPTS: EnumSet = enum_set!( + DmaRxInterrupt::DescriptorError + | DmaRxInterrupt::DescriptorEmpty + | DmaRxInterrupt::ErrorEof + ); + + pub fn new(rx: &'a mut ChannelRx) -> Self { + Self { rx } + } + } + + impl core::future::Future for DmaRxFuture<'_, CH> + where + CH: DmaRxChannel, + { + type Output = Result<(), DmaError>; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + let interrupts = self.rx.pending_in_interrupts(); + let result = if !interrupts.is_disjoint(Self::SUCCESS_INTERRUPTS) { + Ok(()) + } else if !interrupts.is_disjoint(Self::FAILURE_INTERRUPTS) { + Err(DmaError::DescriptorError) + } else { + // The interrupt may become pending before we register the waker and start + // listening, but that should just trigger the interrupt handler. The only + // constraint we have is that the waker must be registered before we start + // listening. + self.rx.waker().register(cx.waker()); + self.rx + .listen_in(Self::SUCCESS_INTERRUPTS | Self::FAILURE_INTERRUPTS); + + return Poll::Pending; + }; + + self.rx.clear_interrupts(); + + Poll::Ready(result) + } + } + + impl Drop for DmaRxFuture<'_, CH> + where + CH: DmaRxChannel, + { + fn drop(&mut self) { + self.rx + .unlisten_in(Self::SUCCESS_INTERRUPTS | Self::FAILURE_INTERRUPTS); + } + } + + // Legacy API still used by I2S + #[cfg(i2s_driver_supported)] + pub struct DmaTxDoneChFuture<'a, CH> + where + CH: DmaTxChannel, + { + pub(crate) tx: &'a mut ChannelTx, + _a: (), + } + + #[cfg(i2s_driver_supported)] + impl<'a, CH> DmaTxDoneChFuture<'a, CH> + where + CH: DmaTxChannel, + { + pub fn new(tx: &'a mut ChannelTx) -> Self { + Self { tx, _a: () } + } + } + + #[cfg(i2s_driver_supported)] + impl core::future::Future for DmaTxDoneChFuture<'_, CH> + where + CH: DmaTxChannel, + { + type Output = Result<(), DmaError>; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + if self + .tx + .pending_out_interrupts() + .contains(DmaTxInterrupt::Done) + { + self.tx.clear_out(DmaTxInterrupt::Done); + Poll::Ready(Ok(())) + } else if self + .tx + .pending_out_interrupts() + .contains(DmaTxInterrupt::DescriptorError) + { + self.tx.clear_interrupts(); + Poll::Ready(Err(DmaError::DescriptorError)) + } else { + self.tx.waker().register(cx.waker()); + self.tx + .listen_out(DmaTxInterrupt::Done | DmaTxInterrupt::DescriptorError); + Poll::Pending + } + } + } + + #[cfg(i2s_driver_supported)] + impl Drop for DmaTxDoneChFuture<'_, CH> + where + CH: DmaTxChannel, + { + fn drop(&mut self) { + self.tx + .unlisten_out(DmaTxInterrupt::Done | DmaTxInterrupt::DescriptorError); + } + } + + #[cfg(i2s_driver_supported)] + pub struct DmaRxDoneChFuture<'a, CH> + where + CH: DmaRxChannel, + { + pub(crate) rx: &'a mut ChannelRx, + _a: (), + } + + #[cfg(i2s_driver_supported)] + impl<'a, CH> DmaRxDoneChFuture<'a, CH> + where + CH: DmaRxChannel, + { + pub fn new(rx: &'a mut ChannelRx) -> Self { + Self { rx, _a: () } + } + } + + #[cfg(i2s_driver_supported)] + impl core::future::Future for DmaRxDoneChFuture<'_, CH> + where + CH: DmaRxChannel, + { + type Output = Result<(), DmaError>; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + if self + .rx + .pending_in_interrupts() + .contains(DmaRxInterrupt::Done) + { + self.rx.clear_in(DmaRxInterrupt::Done); + Poll::Ready(Ok(())) + } else if !self.rx.pending_in_interrupts().is_disjoint( + DmaRxInterrupt::DescriptorError + | DmaRxInterrupt::DescriptorEmpty + | DmaRxInterrupt::ErrorEof, + ) { + self.rx.clear_interrupts(); + Poll::Ready(Err(DmaError::DescriptorError)) + } else { + self.rx.waker().register(cx.waker()); + self.rx.listen_in( + DmaRxInterrupt::Done + | DmaRxInterrupt::DescriptorError + | DmaRxInterrupt::DescriptorEmpty + | DmaRxInterrupt::ErrorEof, + ); + Poll::Pending + } + } + } + + #[cfg(i2s_driver_supported)] + impl Drop for DmaRxDoneChFuture<'_, CH> + where + CH: DmaRxChannel, + { + fn drop(&mut self) { + self.rx.unlisten_in( + DmaRxInterrupt::Done + | DmaRxInterrupt::DescriptorError + | DmaRxInterrupt::DescriptorEmpty + | DmaRxInterrupt::ErrorEof, + ); + } + } + + pub(super) fn handle_in_interrupt() { + let rx = CH::rx_interrupts(); + + if !rx.is_async() { + return; + } + + let pending = rx.pending_interrupts(); + let enabled = rx.is_listening(); + + if !pending.is_disjoint(enabled) { + rx.unlisten(EnumSet::all()); + rx.waker().wake() + } + } + + pub(super) fn handle_out_interrupt() { + let tx = CH::tx_interrupts(); + + if !tx.is_async() { + return; + } + + let pending = tx.pending_interrupts(); + let enabled = tx.is_listening(); + + if !pending.is_disjoint(enabled) { + tx.unlisten(EnumSet::all()); + + tx.waker().wake() + } + } +} diff --git a/esp-hal/src/dma/pdma/copy.rs b/esp-hal/src/dma/pdma/copy.rs new file mode 100644 index 00000000000..ca352843320 --- /dev/null +++ b/esp-hal/src/dma/pdma/copy.rs @@ -0,0 +1,454 @@ +use enumset::EnumSet; +use portable_atomic::{AtomicBool, Ordering}; + +use crate::{ + asynch::AtomicWaker, + dma::{ + BurstConfig, + DmaChannel, + DmaChannelConvert, + DmaChannelExt, + DmaExtMemBKSize, + DmaPeripheral, + DmaRxChannel, + DmaRxInterrupt, + DmaTxChannel, + DmaTxInterrupt, + InterruptAccess, + PdmaChannel, + RegisterAccess, + RxRegisterAccess, + TxRegisterAccess, + asynch, + }, + interrupt::{InterruptHandler, Priority}, + peripherals::{DMA_COPY, Interrupt}, + system::Peripheral, +}; + +pub(super) type CopyRegisterBlock = crate::pac::copy_dma::RegisterBlock; + +/// The RX half of a Copy DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CopyDmaRxChannel<'d>(pub(crate) DMA_COPY<'d>); + +impl CopyDmaRxChannel<'_> { + fn regs(&self) -> &CopyRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CopyDmaRxChannel<'_> {} +impl DmaRxChannel for CopyDmaRxChannel<'_> {} + +/// The TX half of a Copy DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CopyDmaTxChannel<'d>(pub(crate) DMA_COPY<'d>); + +impl CopyDmaTxChannel<'_> { + fn regs(&self) -> &CopyRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CopyDmaTxChannel<'_> {} +impl DmaTxChannel for CopyDmaTxChannel<'_> {} + +impl RegisterAccess for CopyDmaTxChannel<'_> { + fn peripheral_clock(&self) -> Option { + Some(Peripheral::CopyDma) + } + + fn reset(&self) { + self.regs().conf().modify(|_, w| w.out_rst().set_bit()); + self.regs().conf().modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, _burst_mode: bool) {} + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Copy DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { + // not supported + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + false + } +} + +impl TxRegisterAccess for CopyDmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + self.regs().in_st().read().fifo_empty().bit() + } + + fn set_auto_write_back(&self, enable: bool) { + self.regs() + .conf() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.regs() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as usize + } + + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CopyDmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.tx_waker() + } + + fn is_async(&self) -> bool { + self.0.tx_async_flag().load(Ordering::Acquire) + } + + fn set_async(&self, is_async: bool) { + self.0.tx_async_flag().store(is_async, Ordering::Release); + } +} + +impl RegisterAccess for CopyDmaRxChannel<'_> { + fn peripheral_clock(&self) -> Option { + Some(Peripheral::CopyDma) + } + + fn reset(&self) { + self.regs().conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().conf().modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, _burst_mode: bool) {} + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Copy DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { + // not supported + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + false + } +} + +impl RxRegisterAccess for CopyDmaRxChannel<'_> { + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CopyDmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => unimplemented!(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => continue, + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.rx_waker() + } + + fn is_async(&self) -> bool { + self.0.rx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.rx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +impl<'d> DmaChannel for DMA_COPY<'d> { + type Rx = CopyDmaRxChannel<'d>; + type Tx = CopyDmaTxChannel<'d>; + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + CopyDmaRxChannel(unsafe { Self::steal() }), + CopyDmaTxChannel(unsafe { Self::steal() }), + ) + } +} +impl DmaChannelExt for DMA_COPY<'_> { + fn rx_interrupts() -> impl InterruptAccess { + CopyDmaRxChannel(unsafe { Self::steal() }) + } + fn tx_interrupts() -> impl InterruptAccess { + CopyDmaTxChannel(unsafe { Self::steal() }) + } +} +impl PdmaChannel for DMA_COPY<'_> { + type RegisterBlock = CopyRegisterBlock; + fn register_block(&self) -> &Self::RegisterBlock { + DMA_COPY::regs() + } + fn tx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn rx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + let compatible_peripherals = [DmaPeripheral::Aes, DmaPeripheral::Sha]; + compatible_peripherals.contains(&peripheral) + } + fn peripheral_interrupt(&self) -> Interrupt { + Interrupt::DMA_COPY + } + fn async_handler(&self) -> InterruptHandler { + pub(crate) extern "C" fn __esp_hal_internal_interrupt_handler() { + asynch::handle_in_interrupt::>(); + asynch::handle_out_interrupt::>(); + } + pub(crate) static INTERRUPT_HANDLER: InterruptHandler = + InterruptHandler::new(__esp_hal_internal_interrupt_handler, Priority::max()); + INTERRUPT_HANDLER + } + fn rx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } + fn tx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } +} +impl<'d> DmaChannelConvert> for DMA_COPY<'d> { + fn degrade(self) -> CopyDmaRxChannel<'d> { + CopyDmaRxChannel(self) + } +} +impl<'d> DmaChannelConvert> for DMA_COPY<'d> { + fn degrade(self) -> CopyDmaTxChannel<'d> { + CopyDmaTxChannel(self) + } +} diff --git a/esp-hal/src/dma/pdma/crypto.rs b/esp-hal/src/dma/pdma/crypto.rs new file mode 100644 index 00000000000..a5f5eadfa81 --- /dev/null +++ b/esp-hal/src/dma/pdma/crypto.rs @@ -0,0 +1,510 @@ +use enumset::EnumSet; +use portable_atomic::{AtomicBool, Ordering}; + +use crate::{ + asynch::AtomicWaker, + dma::{ + BurstConfig, + DmaChannel, + DmaChannelConvert, + DmaChannelExt, + DmaExtMemBKSize, + DmaPeripheral, + DmaRxChannel, + DmaRxInterrupt, + DmaTxChannel, + DmaTxInterrupt, + InterruptAccess, + PdmaChannel, + RegisterAccess, + RxRegisterAccess, + TxRegisterAccess, + asynch, + }, + interrupt::{InterruptHandler, Priority}, + peripherals::{DMA_CRYPTO, Interrupt}, + system::Peripheral, +}; + +pub(super) type CryptoRegisterBlock = crate::pac::crypto_dma::RegisterBlock; + +/// The RX half of a Crypto DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CryptoDmaRxChannel<'d>(pub(crate) DMA_CRYPTO<'d>); + +impl CryptoDmaRxChannel<'_> { + fn regs(&self) -> &CryptoRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CryptoDmaRxChannel<'_> {} +impl DmaRxChannel for CryptoDmaRxChannel<'_> {} + +/// The TX half of a Crypto DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CryptoDmaTxChannel<'d>(pub(crate) DMA_CRYPTO<'d>); + +impl CryptoDmaTxChannel<'_> { + fn regs(&self) -> &CryptoRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CryptoDmaTxChannel<'_> {} +impl DmaTxChannel for CryptoDmaTxChannel<'_> {} + +impl RegisterAccess for CryptoDmaTxChannel<'_> { + fn peripheral_clock(&self) -> Option { + Some(Peripheral::CryptoDma) + } + + fn reset(&self) { + self.regs().conf().modify(|_, w| { + w.out_rst().set_bit(); + w.ahbm_rst().set_bit(); + w.ahbm_fifo_rst().set_bit() + }); + self.regs().conf().modify(|_, w| { + w.out_rst().clear_bit(); + w.ahbm_rst().clear_bit(); + w.ahbm_fifo_rst().clear_bit() + }); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.regs() + .conf() + .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .conf() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + } + + fn set_peripheral(&self, peripheral: u8) { + use esp32s2::crypto_dma::aes_sha_select::SELECT; + let peripheral = match peripheral { + p if p == DmaPeripheral::Aes as u8 => SELECT::Aes, + p if p == DmaPeripheral::Sha as u8 => SELECT::Sha, + _ => unreachable!(), + }; + self.regs() + .aes_sha_select() + .modify(|_, w| w.select().variant(peripheral)); + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Crypto DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { + self.regs() + .conf1() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl TxRegisterAccess for CryptoDmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + self.regs().state1().read().outfifo_cnt_debug().bits() == 0 + } + + fn set_auto_write_back(&self, enable: bool) { + self.regs() + .conf() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.regs() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as usize + } + + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CryptoDmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.tx_waker() + } + + fn is_async(&self) -> bool { + self.0.tx_async_flag().load(Ordering::Acquire) + } + + fn set_async(&self, is_async: bool) { + self.0.tx_async_flag().store(is_async, Ordering::Release); + } +} + +impl RegisterAccess for CryptoDmaRxChannel<'_> { + fn peripheral_clock(&self) -> Option { + Some(Peripheral::CryptoDma) + } + + fn reset(&self) { + self.regs().conf().modify(|_, w| { + w.in_rst().set_bit(); + w.ahbm_rst().set_bit(); + w.ahbm_fifo_rst().set_bit() + }); + self.regs().conf().modify(|_, w| { + w.in_rst().clear_bit(); + w.ahbm_rst().clear_bit(); + w.ahbm_fifo_rst().clear_bit() + }); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .conf() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + } + + fn set_peripheral(&self, peripheral: u8) { + use esp32s2::crypto_dma::aes_sha_select::SELECT; + let peripheral = match peripheral { + p if p == DmaPeripheral::Aes as u8 => SELECT::Aes, + p if p == DmaPeripheral::Sha as u8 => SELECT::Sha, + _ => unreachable!(), + }; + self.regs() + .aes_sha_select() + .modify(|_, w| w.select().variant(peripheral)); + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Crypto DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { + self.regs() + .conf1() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + true + } +} + +impl RxRegisterAccess for CryptoDmaRxChannel<'_> { + fn peripheral_interrupt(&self) -> Option { + // We don't know if the channel is used by AES or SHA, so interrupt handler + // setup is the responsibility of the peripheral driver. + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CryptoDmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.rx_waker() + } + + fn is_async(&self) -> bool { + self.0.rx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.rx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +impl<'d> DmaChannel for DMA_CRYPTO<'d> { + type Rx = CryptoDmaRxChannel<'d>; + type Tx = CryptoDmaTxChannel<'d>; + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + CryptoDmaRxChannel(unsafe { Self::steal() }), + CryptoDmaTxChannel(unsafe { Self::steal() }), + ) + } +} +impl DmaChannelExt for DMA_CRYPTO<'_> { + fn rx_interrupts() -> impl InterruptAccess { + CryptoDmaRxChannel(unsafe { Self::steal() }) + } + fn tx_interrupts() -> impl InterruptAccess { + CryptoDmaTxChannel(unsafe { Self::steal() }) + } +} +impl PdmaChannel for DMA_CRYPTO<'_> { + type RegisterBlock = CryptoRegisterBlock; + fn register_block(&self) -> &Self::RegisterBlock { + DMA_CRYPTO::regs() + } + fn tx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn rx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + let compatible_peripherals = [DmaPeripheral::Aes, DmaPeripheral::Sha]; + compatible_peripherals.contains(&peripheral) + } + fn peripheral_interrupt(&self) -> Interrupt { + Interrupt::CRYPTO_DMA + } + fn async_handler(&self) -> InterruptHandler { + pub(crate) extern "C" fn __esp_hal_internal_interrupt_handler() { + asynch::handle_in_interrupt::>(); + asynch::handle_out_interrupt::>(); + } + pub(crate) static INTERRUPT_HANDLER: InterruptHandler = + InterruptHandler::new(__esp_hal_internal_interrupt_handler, Priority::max()); + INTERRUPT_HANDLER + } + fn rx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } + fn tx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } +} +impl<'d> DmaChannelConvert> for DMA_CRYPTO<'d> { + fn degrade(self) -> CryptoDmaRxChannel<'d> { + CryptoDmaRxChannel(self) + } +} +impl<'d> DmaChannelConvert> for DMA_CRYPTO<'d> { + fn degrade(self) -> CryptoDmaTxChannel<'d> { + CryptoDmaTxChannel(self) + } +} diff --git a/esp-hal/src/dma/pdma/i2s.rs b/esp-hal/src/dma/pdma/i2s.rs new file mode 100644 index 00000000000..38a10770473 --- /dev/null +++ b/esp-hal/src/dma/pdma/i2s.rs @@ -0,0 +1,455 @@ +use enumset::EnumSet; +use portable_atomic::{AtomicBool, Ordering}; + +use crate::{ + asynch::AtomicWaker, + dma::{ + BurstConfig, + DmaChannel, + DmaPeripheral, + DmaRxChannel, + DmaRxInterrupt, + DmaTxChannel, + DmaTxInterrupt, + InterruptAccess, + PdmaChannel, + RegisterAccess, + RxRegisterAccess, + TxRegisterAccess, + }, + interrupt::InterruptHandler, + peripherals::Interrupt, + system::Peripheral, +}; + +pub(super) type I2sRegisterBlock = crate::pac::i2s0::RegisterBlock; + +/// The RX half of an arbitrary I2S DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyI2sDmaRxChannel<'d>(pub(crate) AnyI2sDmaChannel<'d>); + +impl AnyI2sDmaRxChannel<'_> { + fn regs(&self) -> &I2sRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for AnyI2sDmaRxChannel<'_> {} +impl DmaRxChannel for AnyI2sDmaRxChannel<'_> {} + +/// The TX half of an arbitrary I2S DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyI2sDmaTxChannel<'d>(pub(crate) AnyI2sDmaChannel<'d>); + +impl AnyI2sDmaTxChannel<'_> { + fn regs(&self) -> &I2sRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for AnyI2sDmaTxChannel<'_> {} +impl DmaTxChannel for AnyI2sDmaTxChannel<'_> {} + +impl RegisterAccess for AnyI2sDmaTxChannel<'_> { + fn peripheral_clock(&self) -> Option { + None + } + + fn reset(&self) { + self.regs().lc_conf().modify(|_, w| w.out_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.regs() + .lc_conf() + .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .lc_conf() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn start(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.regs() + .lc_conf() + .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true))); + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { + self.regs() + .lc_conf() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + matches!(self.0, AnyI2sDmaChannel(any::Inner::I2s0(_))) + } +} + +impl TxRegisterAccess for AnyI2sDmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().lc_state0().read().bits() & 0x80000000 != 0 + } else { + self.regs().lc_state0().read().out_empty().bit_is_set() + } + } + } + + fn set_auto_write_back(&self, enable: bool) { + self.regs() + .lc_conf() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.regs() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as usize + } + + fn peripheral_interrupt(&self) -> Option { + Some(self.0.peripheral_interrupt()) + } + + fn async_handler(&self) -> Option { + Some(self.0.async_handler()) + } +} + +impl InterruptAccess for AnyI2sDmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.tx_waker() + } + + fn is_async(&self) -> bool { + self.0.tx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.tx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +impl RegisterAccess for AnyI2sDmaRxChannel<'_> { + fn peripheral_clock(&self) -> Option { + None + } + + fn reset(&self) { + self.regs().lc_conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .lc_conf() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn start(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + self.regs() + .lc_conf() + .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true))); + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { + self.regs() + .lc_conf() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + matches!(self.0, AnyI2sDmaChannel(any::Inner::I2s0(_))) + } +} + +impl RxRegisterAccess for AnyI2sDmaRxChannel<'_> { + fn peripheral_interrupt(&self) -> Option { + Some(self.0.peripheral_interrupt()) + } + + fn async_handler(&self) -> Option { + Some(self.0.async_handler()) + } +} + +impl InterruptAccess for AnyI2sDmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.rx_waker() + } + + fn is_async(&self) -> bool { + self.0.rx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.rx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +crate::any_peripheral! { + /// An I2S-compatible type-erased DMA channel. + pub peripheral AnyI2sDmaChannel<'d> { + #[cfg(soc_has_i2s0)] + I2s0(crate::peripherals::DMA_I2S0<'d>), + #[cfg(soc_has_i2s1)] + I2s1(crate::peripherals::DMA_I2S1<'d>), + } +} + +impl<'d> DmaChannel for AnyI2sDmaChannel<'d> { + type Rx = AnyI2sDmaRxChannel<'d>; + type Tx = AnyI2sDmaTxChannel<'d>; + + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + AnyI2sDmaRxChannel(unsafe { self.clone_unchecked() }), + AnyI2sDmaTxChannel(unsafe { self.clone_unchecked() }), + ) + } +} + +impl PdmaChannel for AnyI2sDmaChannel<'_> { + type RegisterBlock = I2sRegisterBlock; + + delegate::delegate! { + to match &self.0 { + #[cfg(soc_has_i2s0)] + any::Inner::I2s0(channel) => channel, + #[cfg(soc_has_i2s1)] + any::Inner::I2s1(channel) => channel, + } { + fn register_block(&self) -> &I2sRegisterBlock; + fn tx_waker(&self) -> &'static AtomicWaker; + fn rx_waker(&self) -> &'static AtomicWaker; + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool; + fn peripheral_interrupt(&self) -> Interrupt; + fn async_handler(&self) -> InterruptHandler; + fn rx_async_flag(&self) -> &'static AtomicBool; + fn tx_async_flag(&self) -> &'static AtomicBool; + } + } +} diff --git a/esp-hal/src/dma/pdma/mod.rs b/esp-hal/src/dma/pdma/mod.rs new file mode 100644 index 00000000000..59355671f51 --- /dev/null +++ b/esp-hal/src/dma/pdma/mod.rs @@ -0,0 +1,215 @@ +//! # Direct Memory Access +//! +//! ## Overview +//! The `pdma` module is part of the DMA driver of `ESP32` and `ESP32-S2`. +//! +//! This module provides efficient direct data transfer capabilities between +//! peripherals and memory without involving the CPU. It enables bidirectional +//! data transfers through DMA channels, making it particularly useful for +//! high-speed data transfers, such as [SPI] and [I2S] communication. +//! +//! [SPI]: ../spi/index.html +//! [I2S]: ../i2s/index.html + +use portable_atomic::AtomicBool; + +use crate::{ + DriverMode, + asynch::AtomicWaker, + dma::{ + Channel, + DmaChannel, + DmaChannelConvert, + DmaChannelExt, + DmaEligible, + DmaPeripheral, + DmaRxInterrupt, + DmaTxInterrupt, + InterruptAccess, + InterruptHandler, + RegisterAccess, + }, + handler, + interrupt::Priority, + peripherals::Interrupt, +}; + +#[cfg(soc_has_dma_copy)] +mod copy; +#[cfg(soc_has_dma_crypto)] +mod crypto; +mod i2s; +mod spi; + +#[cfg(soc_has_dma_copy)] +pub use copy::{CopyDmaRxChannel, CopyDmaTxChannel}; +#[cfg(soc_has_dma_crypto)] +pub use crypto::{CryptoDmaRxChannel, CryptoDmaTxChannel}; +use i2s::I2sRegisterBlock; +pub use i2s::{AnyI2sDmaChannel, AnyI2sDmaRxChannel, AnyI2sDmaTxChannel}; +use spi::SpiRegisterBlock; +pub use spi::{AnySpiDmaChannel, AnySpiDmaRxChannel, AnySpiDmaTxChannel}; + +#[doc(hidden)] +pub trait PdmaChannel: crate::private::Sealed { + type RegisterBlock; + + fn register_block(&self) -> &Self::RegisterBlock; + fn tx_waker(&self) -> &'static AtomicWaker; + fn rx_waker(&self) -> &'static AtomicWaker; + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool; + + fn peripheral_interrupt(&self) -> Interrupt; + fn async_handler(&self) -> InterruptHandler; + fn rx_async_flag(&self) -> &'static AtomicBool; + fn tx_async_flag(&self) -> &'static AtomicBool; +} + +macro_rules! impl_pdma_channel { + ($peri:ident, $register_block:ident, $instance:ident, $int:ident, [$($compatible:ident),*]) => { + paste::paste! { + use $crate::peripherals::[< $instance >]; + impl<'d> DmaChannel for $instance<'d> { + type Rx = [<$peri DmaRxChannel>]<'d>; + type Tx = [<$peri DmaTxChannel>]<'d>; + + unsafe fn split_internal(self, _: $crate::private::Internal) -> (Self::Rx, Self::Tx) { unsafe { + ( + [<$peri DmaRxChannel>](Self::steal().into()), + [<$peri DmaTxChannel>](Self::steal().into()), + ) + }} + } + + impl DmaChannelExt for $instance<'_> { + fn rx_interrupts() -> impl InterruptAccess { + [<$peri DmaRxChannel>](unsafe { Self::steal() }.into()) + } + fn tx_interrupts() -> impl InterruptAccess { + [<$peri DmaTxChannel>](unsafe { Self::steal() }.into()) + } + } + + impl PdmaChannel for $instance<'_> { + type RegisterBlock = $register_block; + + fn register_block(&self) -> &Self::RegisterBlock { + $crate::peripherals::[< $instance >]::regs() + } + fn tx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn rx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + let compatible_peripherals = [$(DmaPeripheral::$compatible),*]; + compatible_peripherals.contains(&peripheral) + } + + fn peripheral_interrupt(&self) -> Interrupt { + Interrupt::$int + } + + fn async_handler(&self) -> InterruptHandler { + #[handler(priority = Priority::max())] + pub(crate) fn interrupt_handler() { + super::asynch::handle_in_interrupt::<$instance<'static>>(); + super::asynch::handle_out_interrupt::<$instance<'static>>(); + } + + interrupt_handler + } + fn rx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } + fn tx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } + } + + impl<'d> DmaChannelConvert<[<$peri DmaChannel>]<'d>> for $instance<'d> { + fn degrade(self) -> [<$peri DmaChannel>]<'d> { + self.into() + } + } + + impl<'d> DmaChannelConvert<[<$peri DmaRxChannel>]<'d>> for $instance<'d> { + fn degrade(self) -> [<$peri DmaRxChannel>]<'d> { + [<$peri DmaRxChannel>](self.into()) + } + } + + impl<'d> DmaChannelConvert<[<$peri DmaTxChannel>]<'d>> for $instance<'d> { + fn degrade(self) -> [<$peri DmaTxChannel>]<'d> { + [<$peri DmaTxChannel>](self.into()) + } + } + } + }; +} + +impl_pdma_channel!(AnySpi, SpiRegisterBlock, DMA_SPI2, SPI2_DMA, [Spi2]); +impl_pdma_channel!(AnySpi, SpiRegisterBlock, DMA_SPI3, SPI3_DMA, [Spi3]); + +#[cfg(soc_has_i2s0)] +impl_pdma_channel!(AnyI2s, I2sRegisterBlock, DMA_I2S0, I2S0, [I2s0]); +#[cfg(soc_has_i2s1)] +impl_pdma_channel!(AnyI2s, I2sRegisterBlock, DMA_I2S1, I2S1, [I2s1]); + +// Specific peripherals use specific channels. Note that this may be overly +// restrictive (ESP32 allows configuring 2 SPI DMA channels between 3 different +// peripherals), but for the current set of restrictions this is sufficient. +#[cfg(soc_has_spi2)] +crate::dma::impl_dma_eligible!([DMA_SPI2] SPI2 => Spi2); +#[cfg(soc_has_spi3)] +crate::dma::impl_dma_eligible!([DMA_SPI3] SPI3 => Spi3); +#[cfg(soc_has_i2s0)] +crate::dma::impl_dma_eligible!([DMA_I2S0] I2S0 => I2s0); +#[cfg(soc_has_i2s1)] +crate::dma::impl_dma_eligible!([DMA_I2S1] I2S1 => I2s1); +#[cfg(esp32s2)] +use crate::peripherals::DMA_CRYPTO; +#[cfg(esp32s2)] +crate::dma::impl_dma_eligible!([DMA_CRYPTO] AES => Aes); +#[cfg(esp32s2)] +crate::dma::impl_dma_eligible!([DMA_CRYPTO] SHA => Sha); + +pub(super) fn init_dma_racey() { + #[cfg(esp32)] + { + // (only) on ESP32 we need to configure DPORT for the SPI DMA channels + // This assigns the DMA channels to the SPI peripherals, which is more + // restrictive than necessary but we currently support the same + // number of SPI peripherals as SPI DMA channels so it's not a big + // deal. + use crate::peripherals::DPORT; + + DPORT::regs().spi_dma_chan_sel().modify(|_, w| unsafe { + w.spi2_dma_chan_sel().bits(1); + w.spi3_dma_chan_sel().bits(2) + }); + } +} + +impl Channel +where + CH: DmaChannel, + Dm: DriverMode, +{ + /// Asserts that the channel is compatible with the given peripheral. + #[instability::unstable] + pub fn runtime_ensure_compatible(&self, peripheral: &impl DmaEligible) { + assert!( + self.tx + .tx_impl + .is_compatible_with(peripheral.dma_peripheral()), + "This DMA channel is not compatible with {:?}", + peripheral.dma_peripheral() + ); + } +} diff --git a/esp-hal/src/dma/pdma/spi.rs b/esp-hal/src/dma/pdma/spi.rs new file mode 100644 index 00000000000..bf152d7ad04 --- /dev/null +++ b/esp-hal/src/dma/pdma/spi.rs @@ -0,0 +1,470 @@ +use enumset::EnumSet; +use portable_atomic::{AtomicBool, Ordering}; + +use crate::{ + asynch::AtomicWaker, + dma::{ + BurstConfig, + DmaChannel, + DmaPeripheral, + DmaRxChannel, + DmaRxInterrupt, + DmaTxChannel, + DmaTxInterrupt, + InterruptAccess, + PdmaChannel, + RegisterAccess, + RxRegisterAccess, + TxRegisterAccess, + }, + interrupt::InterruptHandler, + peripherals::Interrupt, + system::Peripheral, +}; + +pub(super) type SpiRegisterBlock = crate::pac::spi2::RegisterBlock; + +/// The RX half of an arbitrary SPI DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnySpiDmaRxChannel<'d>(pub(crate) AnySpiDmaChannel<'d>); + +impl AnySpiDmaRxChannel<'_> { + fn regs(&self) -> &SpiRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for AnySpiDmaRxChannel<'_> {} +impl DmaRxChannel for AnySpiDmaRxChannel<'_> {} + +/// The TX half of an arbitrary SPI DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnySpiDmaTxChannel<'d>(pub(crate) AnySpiDmaChannel<'d>); + +impl AnySpiDmaTxChannel<'_> { + fn regs(&self) -> &SpiRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for AnySpiDmaTxChannel<'_> {} +impl DmaTxChannel for AnySpiDmaTxChannel<'_> {} + +impl RegisterAccess for AnySpiDmaTxChannel<'_> { + fn peripheral_clock(&self) -> Option { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + Some(Peripheral::SpiDma) + } else { + match self.0 { + AnySpiDmaChannel(any::Inner::Spi2(_)) => Some(Peripheral::Spi2Dma), + AnySpiDmaChannel(any::Inner::Spi3(_)) => Some(Peripheral::Spi3Dma), + } + } + } + } + + fn reset(&self) { + self.regs().dma_conf().modify(|_, w| w.out_rst().set_bit()); + self.regs() + .dma_conf() + .modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, burst_mode: BurstConfig) { + self.regs() + .dma_conf() + .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + } + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .dma_conf() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + } + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .dma_out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .dma_out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .dma_out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .dma_out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("SPI DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { + self.regs() + .dma_conf() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + matches!(self.0, AnySpiDmaChannel(any::Inner::Spi2(_))) + } +} + +impl TxRegisterAccess for AnySpiDmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().dma_rstatus().read().dma_out_status().bits() & 0x80000000 != 0 + } else { + self.regs().dma_outstatus().read().dma_outfifo_empty().bit_is_set() + } + } + } + + fn set_auto_write_back(&self, enable: bool) { + // there is no `auto_wrback` for SPI + assert!(!enable); + } + + fn last_dscr_address(&self) -> usize { + self.regs() + .out_eof_des_addr() + .read() + .dma_out_eof_des_addr() + .bits() as usize + } + + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for AnySpiDmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().dma_int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().dma_int_ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.outlink_dscr_error().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().dma_int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().dma_int_raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.outlink_dscr_error().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.tx_waker() + } + + fn is_async(&self) -> bool { + self.0.tx_async_flag().load(Ordering::Acquire) + } + + fn set_async(&self, is_async: bool) { + self.0.tx_async_flag().store(is_async, Ordering::Release); + } +} + +impl RegisterAccess for AnySpiDmaRxChannel<'_> { + fn peripheral_clock(&self) -> Option { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + Some(Peripheral::SpiDma) + } else { + match self.0 { + AnySpiDmaChannel(any::Inner::Spi2(_)) => Some(Peripheral::Spi2Dma), + AnySpiDmaChannel(any::Inner::Spi3(_)) => Some(Peripheral::Spi3Dma), + } + } + } + } + + fn reset(&self) { + self.regs().dma_conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().dma_conf().modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, burst_mode: bool) { + self.regs() + .dma_conf() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + } + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .dma_in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .dma_in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .dma_in_link() + .modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .dma_in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("SPI DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { + self.regs() + .dma_conf() + .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + matches!(self.0, AnySpiDmaChannel(any::Inner::Spi2(_))) + } +} + +impl RxRegisterAccess for AnySpiDmaRxChannel<'_> { + fn peripheral_interrupt(&self) -> Option { + Some(self.0.peripheral_interrupt()) + } + + fn async_handler(&self) -> Option { + Some(self.0.async_handler()) + } +} + +impl InterruptAccess for AnySpiDmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().dma_int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable), + DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().dma_int_ena().read(); + if int_ena.inlink_dscr_error().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.inlink_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().dma_int_clr().modify(|_, w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(), + DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().dma_int_raw().read(); + if int_raw.inlink_dscr_error().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.inlink_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_err_eof().bit_is_set() { + result |= DmaRxInterrupt::ErrorEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.rx_waker() + } + + fn is_async(&self) -> bool { + self.0.rx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.rx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +crate::any_peripheral! { + /// An SPI-compatible type-erased DMA channel. + pub peripheral AnySpiDmaChannel<'d> { + Spi2(crate::peripherals::DMA_SPI2<'d>), + Spi3(crate::peripherals::DMA_SPI3<'d>), + } +} + +impl<'d> DmaChannel for AnySpiDmaChannel<'d> { + type Rx = AnySpiDmaRxChannel<'d>; + type Tx = AnySpiDmaTxChannel<'d>; + + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + AnySpiDmaRxChannel(unsafe { self.clone_unchecked() }), + AnySpiDmaTxChannel(unsafe { self.clone_unchecked() }), + ) + } +} + +impl PdmaChannel for AnySpiDmaChannel<'_> { + type RegisterBlock = SpiRegisterBlock; + + delegate::delegate! { + to match &self.0 { + any::Inner::Spi2(channel) => channel, + any::Inner::Spi3(channel) => channel, + } { + fn register_block(&self) -> &SpiRegisterBlock; + fn tx_waker(&self) -> &'static AtomicWaker; + fn rx_waker(&self) -> &'static AtomicWaker; + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool; + fn peripheral_interrupt(&self) -> Interrupt; + fn async_handler(&self) -> InterruptHandler; + fn rx_async_flag(&self) -> &'static AtomicBool; + fn tx_async_flag(&self) -> &'static AtomicBool; + } + } +} diff --git a/esp-hal/src/ecc.rs b/esp-hal/src/ecc.rs new file mode 100644 index 00000000000..7e3d43386af --- /dev/null +++ b/esp-hal/src/ecc.rs @@ -0,0 +1,1318 @@ +//! # Elliptic Curve Cryptography (ECC) Accelerator +//! +//! ## Overview +//! +//! Elliptic Curve Cryptography (ECC) is an approach to public-key cryptography +//! based on the algebraic structure of elliptic curves. ECC allows smaller +//! keys compared to RSA cryptography while providing equivalent security. +//! +//! ECC Accelerator can complete various calculation based on different +//! elliptic curves, thus accelerating ECC algorithm and ECC-derived +//! algorithms (such as ECDSA). + +use core::{marker::PhantomData, ptr::NonNull}; + +use procmacros::BuilderLite; + +use crate::{ + Blocking, + DriverMode, + interrupt::InterruptHandler, + pac::{self, ecc::mult_conf::KEY_LENGTH}, + peripherals::{ECC, Interrupt}, + private::Sealed, + system::{self, GenericPeripheralGuard}, + work_queue::{Handle, Poll, Status, VTable, WorkQueue, WorkQueueDriver, WorkQueueFrontend}, +}; + +/// This macro defines 4 other macros: +/// - `doc_summary` that takes the first line of the documentation and returns it as a string +/// - `result_type` that generates the return types for each operation +/// - `operation` that generates the operation function +/// - `backend_operation` that generates the backend operation function +/// +/// These generated macros can then be fed to `for_each_ecc_working_mode!` to generate operations +/// the device supports. +macro_rules! define_operations { + ($($op:tt { + // The first line is used for summary, and it is prepended with `# ` on the driver method. + docs: [$first_line:literal $(, $lines:literal)*], + // The driver method name + function: $function:ident, + // Whether the operation is modular, i.e. whether it needs a modulus argument. + $(modular_arithmetic_method: $is_modular:literal,)? + // Whether the operation does point verification first. + $(verifies_point: $verifies_point:literal,)? + // Input parameters. This determines the name and order of the function arguments, + // as well as which memory block they will be written to. Depending on the value of + // cfg(ecc_separate_jacobian_point_memory), qx, qy and qz may be mapped to px, py and k. + inputs: [$($input:ident),*], + // What data does the output contain? + // - Scalar (and which memory block contains the scalar) + // - AffinePoint + // - JacobianPoint + returns: [ + $( + // What data is computed may be device specific. + $(#[$returns_meta:meta])* + $returns:ident $({ const $c:ident: $t:tt = $v:expr })? + ),* + ] + }),*) => { + macro_rules! doc_summary { + $( + ($op) => { $first_line }; + )* + } + macro_rules! result_type { + $( + ($op) => { + #[doc = concat!("A marker type representing ", doc_summary!($op))] + #[non_exhaustive] + pub struct $op; + + impl crate::private::Sealed for $op {} + + impl EccOperation for $op { + const WORK_MODE: WorkMode = WorkMode::$op; + const VERIFIES_POINT: bool = $crate::if_set!($($verifies_point)?, false); + } + + paste::paste! { + $( + $(#[$returns_meta])* + impl [] for $op { + $( + const $c: $t = $v; + )? + } + )* + + $( + const _: bool = $verifies_point; // I just need this ignored. + impl OperationVerifiesPoint for $op {} + )? + } + }; + )* + } + macro_rules! driver_method { + $( + ($op) => { + #[doc = concat!("# ", $first_line)] + $(#[doc = $lines])* + #[doc = r" + +## Errors + +This function will return an error if the bitlength of the parameters is different +from the bitlength of the prime fields of the curve."] + #[inline] + pub fn $function<'op>( + &'op mut self, + curve: EllipticCurve, + $(#[cfg($is_modular)] modulus: EccModBase,)? + $($input: &[u8],)* + ) -> Result, KeyLengthMismatch> { + curve.size_check([$($input),*])?; + + paste::paste! { + $( + self.info().write_mem(self.info().[<$input _mem>](), $input); + )* + }; + + #[cfg(ecc_has_modular_arithmetic)] + let mod_base = $crate::if_set! { + $( + { + $crate::ignore!($is_modular); + modulus + } + )?, + // else + EccModBase::OrderOfCurve + }; + + Ok(self.run_operation::<$op>( + curve, + #[cfg(ecc_has_modular_arithmetic)] mod_base, + )) + } + }; + )* + } + + macro_rules! backend_operation { + $( + ($op) => { + #[doc = concat!("Configures a new ", $first_line, " operation with the given inputs, to be executed on [`EccBackend`].")] + /// + /// Outputs need to be assigned separately before executing the operation. + pub fn $function<'op>( + self, + $(#[cfg($is_modular)] modulus: EccModBase,)? + $($input: &'op [u8],)* + ) -> Result, KeyLengthMismatch> { + self.size_check([&$($input,)*])?; + + #[cfg(ecc_has_modular_arithmetic)] + let mod_base = $crate::if_set! { + $( + { + $crate::ignore!($is_modular); + modulus + } + )?, + // else + EccModBase::OrderOfCurve + }; + + let work_item = EccWorkItem { + curve: self, + operation: WorkMode::$op, + cancelled: false, + #[cfg(ecc_has_modular_arithmetic)] + mod_base, + inputs: { + let mut inputs = MemoryPointers::default(); + $( + paste::paste! { + inputs.[](NonNull::from($input)); + }; + )* + inputs + }, + point_verification_result: false, + outputs: MemoryPointers::default(), + }; + + Ok(EccBackendOperation::new(work_item)) + } + }; + )* + } + } +} + +define_operations! { + AffinePointMultiplication { + docs: [ + "Base Point Multiplication", + "", + "This operation performs `(Qx, Qy) = k * (Px, Py)`." + ], + function: affine_point_multiplication, + inputs: [k, px, py], + returns: [AffinePoint] + }, + + AffinePointVerification { + docs: [ + "Base Point Verification", + "", + "This operation verifies whether Point (Px, Py) is on the selected elliptic curve." + ], + function: affine_point_verification, + verifies_point: true, + inputs: [px, py], + returns: [] + }, + + AffinePointVerificationAndMultiplication { + docs: [ + "Base Point Verification and Multiplication", + "", + "This operation verifies whether Point (Px, Py) is on the selected elliptic curve and performs `(Qx, Qy) = k * (Px, Py)`." + ], + function: affine_point_verification_multiplication, + verifies_point: true, + inputs: [k, px, py], + returns: [ + AffinePoint, + #[cfg(ecc_separate_jacobian_point_memory)] + JacobianPoint + ] + }, + + AffinePointAddition { + docs: [ + "Point Addition", + "", + "This operation performs `(Rx, Ry) = (Jx, Jy, Jz) = (Px, Py, 1) + (Qx, Qy, Qz)`." + ], + function: affine_point_addition, + inputs: [px, py, qx, qy, qz], + returns: [ + AffinePoint, + #[cfg(ecc_separate_jacobian_point_memory)] + JacobianPoint + ] + }, + + JacobianPointMultiplication { + docs: [ + "Jacobian Point Multiplication", + "", + "This operation performs `(Qx, Qy, Qz) = k * (Px, Py, 1)`." + ], + function: jacobian_point_multiplication, + inputs: [k, px, py], + returns: [ + JacobianPoint + ] + }, + + JacobianPointVerification { + docs: [ + "Jacobian Point Verification", + "", + "This operation verifies whether Point (Qx, Qy, Qz) is on the selected elliptic curve." + ], + function: jacobian_point_verification, + verifies_point: true, + inputs: [qx, qy, qz], + returns: [ + JacobianPoint + ] + }, + + AffinePointVerificationAndJacobianPointMultiplication { + docs: [ + "Base Point Verification + Jacobian Point Multiplication", + "", + "This operation first verifies whether Point (Px, Py) is on the selected elliptic curve. If yes, it performs `(Qx, Qy, Qz) = k * (Px, Py, 1)`." + ], + function: affine_point_verification_jacobian_multiplication, + verifies_point: true, + inputs: [k, px, py], + returns: [ + JacobianPoint + ] + }, + + FiniteFieldDivision { + docs: [ + "Finite Field Division", + "", + "This operation performs `R = Py * k^{−1} mod p`." + ], + function: finite_field_division, + inputs: [k, py], + returns: [ + Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py } + ] + }, + + ModularAddition { + docs: [ + "Modular Addition", + "", + "This operation performs `R = Px + Py mod p`." + ], + function: modular_addition, + modular_arithmetic_method: true, + inputs: [px, py], + returns: [ + Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Px } + ] + }, + + ModularSubtraction { + docs: [ + "Modular Subtraction", + "", + "This operation performs `R = Px - Py mod p`." + ], + function: modular_subtraction, + modular_arithmetic_method: true, + inputs: [px, py], + returns: [ + Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Px } + ] + }, + + ModularMultiplication { + docs: [ + "Modular Multiplication", + "", + "This operation performs `R = Px * Py mod p`." + ], + function: modular_multiplication, + modular_arithmetic_method: true, + inputs: [px, py], + returns: [ + Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py } + ] + }, + + ModularDivision { + docs: [ + "Modular Division", + "", + "This operation performs `R = Px * Py^{−1} mod p`." + ], + function: modular_division, + modular_arithmetic_method: true, + inputs: [px, py], + returns: [ + Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py } + ] + } +} + +const MEM_BLOCK_SIZE: usize = property!("ecc.mem_block_size"); + +/// The ECC Accelerator driver. +/// +/// Note that as opposed to commonly used standards, this driver operates on +/// **little-endian** data. +pub struct Ecc<'d, Dm: DriverMode> { + _ecc: ECC<'d>, + phantom: PhantomData, + _memory_guard: EccMemoryPowerGuard, + _guard: GenericPeripheralGuard<{ system::Peripheral::Ecc as u8 }>, +} + +struct EccMemoryPowerGuard; + +impl EccMemoryPowerGuard { + fn new() -> Self { + #[cfg(soc_has_pcr)] + crate::peripherals::PCR::regs() + .ecc_pd_ctrl() + .modify(|_, w| { + w.ecc_mem_force_pd().clear_bit(); + w.ecc_mem_force_pu().set_bit(); + w.ecc_mem_pd().clear_bit() + }); + Self + } +} + +impl Drop for EccMemoryPowerGuard { + fn drop(&mut self) { + #[cfg(soc_has_pcr)] + crate::peripherals::PCR::regs() + .ecc_pd_ctrl() + .modify(|_, w| { + w.ecc_mem_force_pd().clear_bit(); + w.ecc_mem_force_pu().clear_bit(); + w.ecc_mem_pd().set_bit() + }); + } +} + +/// ECC peripheral configuration. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Force enable register clock. + force_enable_reg_clock: bool, + + /// Force enable memory clock. + #[cfg(ecc_has_memory_clock_gate)] + force_enable_mem_clock: bool, + + /// Enable constant time operation and minimized power consumption variation for + /// point-multiplication operations. + #[cfg_attr( + esp32h2, + doc = r" + +Only available on chip revision 1.2 and above." + )] + #[cfg(ecc_supports_enhanced_security)] + enhanced_security: bool, +} + +/// The length of the arguments do not match the length required by the curve. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct KeyLengthMismatch; + +/// ECC operation error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OperationError { + /// The length of the arguments do not match the length required by the curve. + ParameterLengthMismatch, + + /// Point verification failed. + PointNotOnCurve, +} + +/// Modulus base. +#[cfg(ecc_has_modular_arithmetic)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EccModBase { + /// The order of the curve. + OrderOfCurve = 0, + + /// Prime modulus. + PrimeModulus = 1, +} + +impl From for OperationError { + fn from(_: KeyLengthMismatch) -> Self { + OperationError::ParameterLengthMismatch + } +} + +for_each_ecc_curve! { + (all $(( $id:literal, $name:ident, $bits:literal )),*) => { + /// Represents supported elliptic curves for cryptographic operations. + /// + /// The methods that represent operations require the `EccBackend` to be started before use. + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum EllipticCurve { + $( + #[doc = concat!("The ", stringify!($name), " elliptic curve, a ", $bits, "-bit curve.")] + $name, + )* + } + impl EllipticCurve { + /// Returns the size of the elliptic curve in bytes. + pub const fn size(self) -> usize { + match self { + $( + EllipticCurve::$name => $bits / 8, + )* + } + } + } + }; +} + +for_each_ecc_working_mode! { + (all $(($wm_id:literal, $op:tt)),*) => { + impl EllipticCurve { + fn size_check(&self, params: [&[u8]; N]) -> Result<(), KeyLengthMismatch> { + let bytes = self.size(); + + if params.iter().any(|p| p.len() != bytes) { + return Err(KeyLengthMismatch); + } + + Ok(()) + } + + $( + // Macro defined by `define_operations` + backend_operation!($op); + )* + } + }; +} + +impl<'d> Ecc<'d, Blocking> { + /// Create a new instance in [Blocking] mode. + pub fn new(ecc: ECC<'d>, config: Config) -> Self { + let this = Self { + _ecc: ecc, + phantom: PhantomData, + _memory_guard: EccMemoryPowerGuard::new(), + _guard: GenericPeripheralGuard::new(), + }; + + this.info().apply_config(&config); + + this + } +} + +impl crate::private::Sealed for Ecc<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Ecc<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +struct Info { + regs: &'static pac::ecc::RegisterBlock, +} + +impl Info { + fn reset(&self) { + self.regs.mult_conf().reset() + } + + fn apply_config(&self, config: &Config) { + self.regs.mult_conf().modify(|_, w| { + w.clk_en().bit(config.force_enable_reg_clock); + + #[cfg(ecc_has_memory_clock_gate)] + w.mem_clock_gate_force_on() + .bit(config.force_enable_mem_clock); + + #[cfg(ecc_supports_enhanced_security)] + if !cfg!(esp32h2) || crate::soc::chip_revision_above(102) { + w.security_mode().bit(config.enhanced_security); + } + + w + }); + } + + fn check_point_verification_result(&self) -> Result<(), OperationError> { + if self + .regs + .mult_conf() + .read() + .verification_result() + .bit_is_set() + { + Ok(()) + } else { + Err(OperationError::PointNotOnCurve) + } + } + + #[inline] + fn write_mem(&self, mut word_ptr: *mut u32, data: &[u8]) { + // Note that at least the C2 requires writing this memory in words. + + debug_assert!(data.len() <= MEM_BLOCK_SIZE); + + #[cfg(ecc_zero_extend_writes)] + let end = word_ptr.wrapping_byte_add(MEM_BLOCK_SIZE); + + let (chunks, remainder) = data.as_chunks::<4>(); + debug_assert!(remainder.is_empty()); + + for word_bytes in chunks { + unsafe { word_ptr.write_volatile(u32::from_le_bytes(*word_bytes)) }; + word_ptr = word_ptr.wrapping_add(1); + } + + #[cfg(ecc_zero_extend_writes)] + while word_ptr < end { + unsafe { word_ptr.write_volatile(0) }; + word_ptr = word_ptr.wrapping_add(1); + } + } + + #[inline] + fn read_mem(&self, mut word_ptr: *const u32, out: &mut [u8]) { + for word_bytes in out.chunks_exact_mut(4) { + let word = unsafe { word_ptr.read_volatile() }; + word_ptr = word_ptr.wrapping_add(1); + word_bytes.copy_from_slice(&word.to_le_bytes()); + } + } + + fn k_mem(&self) -> *mut u32 { + self.regs.k_mem(0).as_ptr() + } + + fn px_mem(&self) -> *mut u32 { + self.regs.px_mem(0).as_ptr() + } + + fn py_mem(&self) -> *mut u32 { + self.regs.py_mem(0).as_ptr() + } + + fn qx_mem(&self) -> *mut u32 { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.regs.qx_mem(0).as_ptr() + } else { + self.regs.px_mem(0).as_ptr() + } + } + } + + fn qy_mem(&self) -> *mut u32 { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.regs.qy_mem(0).as_ptr() + } else { + self.regs.py_mem(0).as_ptr() + } + } + } + + fn qz_mem(&self) -> *mut u32 { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.regs.qz_mem(0).as_ptr() + } else { + self.regs.k_mem(0).as_ptr() + } + } + } + + fn read_point_result(&self, x: &mut [u8], y: &mut [u8]) { + self.read_mem(self.px_mem(), x); + self.read_mem(self.py_mem(), y); + } + + fn read_jacobian_result(&self, qx: &mut [u8], qy: &mut [u8], qz: &mut [u8]) { + self.read_mem(self.qx_mem(), qx); + self.read_mem(self.qy_mem(), qy); + self.read_mem(self.qz_mem(), qz); + } + + fn is_busy(&self) -> bool { + self.regs.mult_conf().read().start().bit_is_set() + } + + fn start_operation( + &self, + mode: WorkMode, + curve: EllipticCurve, + #[cfg(ecc_has_modular_arithmetic)] mod_base: EccModBase, + ) { + let curve_variant; + for_each_ecc_curve! { + (all $(($_id:tt, $name:ident, $_bits:tt)),*) => { + curve_variant = match curve { + $(EllipticCurve::$name => KEY_LENGTH::$name,)* + } + }; + }; + self.regs.mult_conf().modify(|_, w| unsafe { + w.work_mode().bits(mode as u8); + w.key_length().variant(curve_variant); + + #[cfg(ecc_has_modular_arithmetic)] + w.mod_base().bit(mod_base as u8 == 1); + + w.start().set_bit() + }); + } +} + +// Broken into separate macro invocations per item, to make the "Expand macro" LSP output more +// readable + +for_each_ecc_working_mode! { + (all $(( $id:literal, $mode:tt )),*) => { + #[derive(Clone, Copy)] + #[doc(hidden)] + /// Represents the operational modes for elliptic curve or modular arithmetic + /// computations. + pub enum WorkMode { + $( + $mode = $id, + )* + } + }; +} + +// Result type for each operation +for_each_ecc_working_mode! { + (all $(( $id:literal, $mode:tt )),*) => { + $( + result_type!($mode); + )* + }; +} + +// The main driver implementation +for_each_ecc_working_mode! { + (all $(( $id:literal, $mode:tt )),*) => { + impl<'d, Dm: DriverMode> Ecc<'d, Dm> { + fn info(&self) -> Info { + Info { regs: ECC::regs() } + } + + fn run_operation<'op, O: EccOperation>( + &'op mut self, + curve: EllipticCurve, + #[cfg(ecc_has_modular_arithmetic)] mod_base: EccModBase, + ) -> EccResultHandle<'op, O> { + self.info().start_operation( + O::WORK_MODE, + curve, + #[cfg(ecc_has_modular_arithmetic)] mod_base, + ); + + // wait for interrupt + while self.info().is_busy() {} + + EccResultHandle::new(curve, self) + } + + /// Applies the given configuration to the ECC peripheral. + pub fn apply_config(&mut self, config: &Config) { + self.info().apply_config(config); + } + + /// Resets the ECC peripheral. + pub fn reset(&mut self) { + self.info().reset() + } + + /// Register an interrupt handler for the ECC peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::ECC); + } + crate::interrupt::bind_handler(Interrupt::ECC, handler); + } + + $( + driver_method!($mode); + )* + } + }; +} + +/// Marks an ECC operation. +pub trait EccOperation: Sealed { + /// Whether the operation verifies that the input point is on the curve. + const VERIFIES_POINT: bool; + + /// Work mode + #[doc(hidden)] + const WORK_MODE: WorkMode; +} + +/// Scalar result location. +#[doc(hidden)] +pub enum ScalarResultLocation { + /// The scalar value is stored in the `Px` memory location. + Px, + /// The scalar value is stored in the `Py` memory location. + Py, + /// The scalar value is stored in the `k` memory location. + K, +} + +/// Marks operations that return a scalar value. +pub trait OperationReturnsScalar: EccOperation { + /// Where the scalar value is stored. + #[doc(hidden)] + const LOCATION: ScalarResultLocation; +} + +/// Marks operations that return a point in affine format. +pub trait OperationReturnsAffinePoint: EccOperation {} + +/// Marks operations that return a point in Jacobian format. +pub trait OperationReturnsJacobianPoint: EccOperation {} + +/// Marks operations that verify that the input point is on the curve. +pub trait OperationVerifiesPoint: EccOperation {} + +/// The result of an ECC operation. +/// +/// This struct can be used to read the result of an ECC operation. The methods which can be used +/// depend on the operation. An operation can compute multiple values, such as an affine point and +/// a Jacobian point at the same time. +#[must_use] +pub struct EccResultHandle<'op, O> +where + O: EccOperation, +{ + curve: EllipticCurve, + info: Info, + _marker: PhantomData<(&'op mut (), O)>, +} + +impl<'op, O> EccResultHandle<'op, O> +where + O: EccOperation, +{ + fn new<'d, Dm: DriverMode>(curve: EllipticCurve, driver: &'op mut Ecc<'d, Dm>) -> Self { + Self { + curve, + info: driver.info(), + _marker: PhantomData, + } + } + + fn run_checks(&self, params: [&[u8]; N]) -> Result<(), OperationError> { + self.curve.size_check(params)?; + if O::VERIFIES_POINT { + self.info.check_point_verification_result()?; + } + Ok(()) + } + + /// Returns whether the operation was successful. + /// + /// For operations that only perform point verification, this method returns whether the point + /// is on the curve. For operations that do not perform point verification, this method always + /// returns true. + pub fn success(&self) -> bool { + if O::VERIFIES_POINT { + self.info.check_point_verification_result().is_ok() + } else { + true + } + } + + /// Retrieve the scalar result of the operation. + /// + /// ## Errors + /// + /// Returns an error if point verification failed, or if `out` is not the correct size. + pub fn read_scalar_result(&self, out: &mut [u8]) -> Result<(), OperationError> + where + O: OperationReturnsScalar, + { + self.run_checks([out])?; + + match O::LOCATION { + ScalarResultLocation::Px => self.info.read_mem(self.info.px_mem(), out), + ScalarResultLocation::Py => self.info.read_mem(self.info.py_mem(), out), + ScalarResultLocation::K => self.info.read_mem(self.info.k_mem(), out), + } + + Ok(()) + } + + /// Retrieve the affine point result of the operation. + /// + /// ## Errors + /// + /// Returns an error if point verification failed, or if `x` or `y` are not the correct size. + pub fn read_affine_point_result(&self, x: &mut [u8], y: &mut [u8]) -> Result<(), OperationError> + where + O: OperationReturnsAffinePoint, + { + self.run_checks([x, y])?; + self.info.read_point_result(x, y); + Ok(()) + } + + /// Retrieve the Jacobian point result of the operation. + /// + /// ## Errors + /// + /// Returns an error if point verification failed, or if `x`, `y`, or `z` are not the correct + /// size. + pub fn read_jacobian_point_result( + &self, + x: &mut [u8], + y: &mut [u8], + z: &mut [u8], + ) -> Result<(), OperationError> + where + O: OperationReturnsJacobianPoint, + { + self.run_checks([x, y, z])?; + self.info.read_jacobian_result(x, y, z); + Ok(()) + } +} + +struct EccWorkItem { + curve: EllipticCurve, + operation: WorkMode, + cancelled: bool, + #[cfg(ecc_has_modular_arithmetic)] + mod_base: EccModBase, + inputs: MemoryPointers, + point_verification_result: bool, + outputs: MemoryPointers, +} + +#[derive(Default)] +struct MemoryPointers { + // All of these pointers point to slices with curve-appropriate lengths. + k: Option>, + px: Option>, + py: Option>, + #[cfg(ecc_separate_jacobian_point_memory)] + qx: Option>, + #[cfg(ecc_separate_jacobian_point_memory)] + qy: Option>, + #[cfg(ecc_separate_jacobian_point_memory)] + qz: Option>, +} + +impl MemoryPointers { + fn set_scalar(&mut self, location: ScalarResultLocation, ptr: NonNull<[u8]>) { + match location { + ScalarResultLocation::Px => self.set_px(ptr), + ScalarResultLocation::Py => self.set_py(ptr), + ScalarResultLocation::K => self.set_k(ptr), + } + } + + fn set_k(&mut self, ptr: NonNull<[u8]>) { + self.k = Some(ptr.cast()); + } + + fn set_px(&mut self, ptr: NonNull<[u8]>) { + self.px = Some(ptr.cast()); + } + + fn set_py(&mut self, ptr: NonNull<[u8]>) { + self.py = Some(ptr.cast()); + } + + fn set_qx(&mut self, ptr: NonNull<[u8]>) { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.qx = Some(ptr.cast()); + } else { + self.px = Some(ptr.cast()); + } + } + } + + fn set_qy(&mut self, ptr: NonNull<[u8]>) { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.qy = Some(ptr.cast()); + } else { + self.py = Some(ptr.cast()); + } + } + } + + fn set_qz(&mut self, ptr: NonNull<[u8]>) { + cfg_if::cfg_if! { + if #[cfg(ecc_separate_jacobian_point_memory)] { + self.qz = Some(ptr.cast()); + } else { + self.k = Some(ptr.cast()); + } + } + } +} + +// Safety: MemoryPointers is safe to share between threads, in the context of a WorkQueue. The +// WorkQueue ensures that only a single location can access the data. All the internals, except +// for the pointers, are Sync. The pointers are safe to share because they point at data that the +// ECC driver ensures can be accessed safely and soundly. +unsafe impl Sync for MemoryPointers {} +// Safety: we will not hold on to the pointers when the work item leaves the queue. +unsafe impl Send for MemoryPointers {} + +static ECC_WORK_QUEUE: WorkQueue = WorkQueue::new(); + +const ECC_VTABLE: VTable = VTable { + post: |driver, item| { + let driver = unsafe { EccBackend::from_raw(driver) }; + + // Ensure driver is initialized + if let DriverState::Uninitialized(ecc) = &driver.driver { + let mut ecc = Ecc::new(unsafe { ecc.clone_unchecked() }, driver.config); + ecc.set_interrupt_handler(ecc_work_queue_handler); + driver.driver = DriverState::Initialized(ecc); + }; + + Some(driver.process(item)) + }, + poll: |driver, item| { + let driver = unsafe { EccBackend::from_raw(driver) }; + driver.poll(item) + }, + cancel: |driver, item| { + let driver = unsafe { EccBackend::from_raw(driver) }; + driver.cancel(item); + }, + stop: |driver| { + let driver = unsafe { EccBackend::from_raw(driver) }; + driver.deinitialize() + }, +}; + +enum DriverState<'d> { + Uninitialized(ECC<'d>), + Initialized(Ecc<'d, Blocking>), +} + +/// ECC processing backend. +/// +/// This struct enables shared access to the device's ECC hardware using a work queue. +pub struct EccBackend<'d> { + driver: DriverState<'d>, + config: Config, +} + +impl<'d> EccBackend<'d> { + /// Creates a new ECC backend. + /// + /// The backend needs to be [`start`][Self::start]ed before it can execute ECC operations. + pub fn new(ecc: ECC<'d>, config: Config) -> Self { + Self { + driver: DriverState::Uninitialized(ecc), + config, + } + } + + /// Registers the ECC driver to process ECC operations. + /// + /// The driver stops operating when the returned object is dropped. + pub fn start(&mut self) -> EccWorkQueueDriver<'_, 'd> { + EccWorkQueueDriver { + inner: WorkQueueDriver::new(self, ECC_VTABLE, &ECC_WORK_QUEUE), + } + } + + // WorkQueue callbacks. They may run in any context. + + unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self { + unsafe { ptr.cast::>().as_mut() } + } + + fn process(&mut self, item: &mut EccWorkItem) -> Poll { + let DriverState::Initialized(driver) = &mut self.driver else { + unreachable!() + }; + + let bytes = item.curve.size(); + + macro_rules! set_input { + ($input:ident, $input_mem:ident) => { + if let Some($input) = item.inputs.$input { + driver.info().write_mem(driver.info().$input_mem(), unsafe { + core::slice::from_raw_parts($input.as_ptr(), bytes) + }); + } + }; + } + + set_input!(k, k_mem); + set_input!(px, px_mem); + set_input!(py, py_mem); + + #[cfg(ecc_separate_jacobian_point_memory)] + { + set_input!(qx, qx_mem); + set_input!(qy, qy_mem); + set_input!(qz, qz_mem); + } + + driver.info().start_operation( + item.operation, + item.curve, + #[cfg(ecc_has_modular_arithmetic)] + item.mod_base, + ); + Poll::Pending(false) + } + + fn poll(&mut self, item: &mut EccWorkItem) -> Poll { + let DriverState::Initialized(driver) = &mut self.driver else { + unreachable!() + }; + + if driver.info().is_busy() { + return Poll::Pending(false); + } + if item.cancelled { + return Poll::Ready(Status::Cancelled); + } + + let bytes = item.curve.size(); + + macro_rules! read_output { + ($output:ident, $output_mem:ident) => { + if let Some($output) = item.outputs.$output { + driver.info().read_mem(driver.info().$output_mem(), unsafe { + core::slice::from_raw_parts_mut($output.as_ptr(), bytes) + }); + } + }; + } + + read_output!(k, k_mem); + read_output!(px, px_mem); + read_output!(py, py_mem); + + #[cfg(ecc_separate_jacobian_point_memory)] + { + read_output!(qx, qx_mem); + read_output!(qy, qy_mem); + read_output!(qz, qz_mem); + } + + item.point_verification_result = driver.info().check_point_verification_result().is_ok(); + + Poll::Ready(Status::Completed) + } + + fn cancel(&mut self, item: &mut EccWorkItem) { + let DriverState::Initialized(driver) = &mut self.driver else { + unreachable!() + }; + driver.reset(); + item.cancelled = true; + } + + fn deinitialize(&mut self) { + if let DriverState::Initialized(ref ecc) = self.driver { + self.driver = DriverState::Uninitialized(unsafe { ecc._ecc.clone_unchecked() }); + } + } +} + +/// An active work queue driver. +/// +/// This object must be kept around, otherwise ECC operations will never complete. +pub struct EccWorkQueueDriver<'t, 'd> { + inner: WorkQueueDriver<'t, EccBackend<'d>, EccWorkItem>, +} + +impl<'t, 'd> EccWorkQueueDriver<'t, 'd> { + /// Finishes processing the current work queue item, then stops the driver. + pub fn stop(self) -> impl Future { + self.inner.stop() + } +} + +#[crate::ram] +#[crate::handler] +fn ecc_work_queue_handler() { + if !ECC_WORK_QUEUE.process() { + // The queue may indicate that it needs to be polled again. In this case, we do not clear + // the interrupt bit, which causes the interrupt to be re-handled. + cfg_if::cfg_if! { + if #[cfg(esp32c5)] { + let reg = ECC::regs().int_clr(); + } else { + let reg = ECC::regs().mult_int_clr(); + } + } + reg.write(|w| w.calc_done().clear_bit_by_one()); + } +} + +/// An ECC operation that can be enqueued on the work queue. +pub struct EccBackendOperation<'op, O: EccOperation> { + frontend: WorkQueueFrontend, + _marker: PhantomData<(&'op mut (), O)>, +} + +impl<'op, O: EccOperation> EccBackendOperation<'op, O> { + fn new(work_item: EccWorkItem) -> Self { + Self { + frontend: WorkQueueFrontend::new(work_item), + _marker: PhantomData, + } + } + + /// Designate a buffer for the scalar result of the operation. + /// + /// Once the operation is processed, the result can be retrieved from the designated buffer. + /// + /// ## Errors + /// + /// Returns an error if `out` is not the correct size. + pub fn with_scalar_result(mut self, out: &'op mut [u8]) -> Result + where + O: OperationReturnsScalar, + { + self.frontend.data().curve.size_check([out])?; + + self.frontend + .data_mut() + .outputs + .set_scalar(O::LOCATION, NonNull::from(out)); + + Ok(self) + } + + /// Designate buffers for the affine point result of the operation. + /// + /// Once the operation is processed, the result can be retrieved from the designated buffers. + /// + /// ## Errors + /// + /// Returns an error if `x` or `y` are not the correct size. + pub fn with_affine_point_result( + mut self, + px: &'op mut [u8], + py: &'op mut [u8], + ) -> Result + where + O: OperationReturnsAffinePoint, + { + self.frontend.data().curve.size_check([px, py])?; + + self.frontend.data_mut().outputs.set_px(NonNull::from(px)); + self.frontend.data_mut().outputs.set_py(NonNull::from(py)); + + Ok(self) + } + + /// Designate buffers for the Jacobian point result of the operation. + /// + /// Once the operation is processed, the result can be retrieved from the designated buffers. + /// + /// ## Errors + /// + /// Returns an error if `x`, `y`, or `z` are not the correct size. + pub fn with_jacobian_point_result( + mut self, + qx: &'op mut [u8], + qy: &'op mut [u8], + qz: &'op mut [u8], + ) -> Result + where + O: OperationReturnsJacobianPoint, + { + self.frontend.data().curve.size_check([qx, qy, qz])?; + + self.frontend.data_mut().outputs.set_qx(NonNull::from(qx)); + self.frontend.data_mut().outputs.set_qy(NonNull::from(qy)); + self.frontend.data_mut().outputs.set_qz(NonNull::from(qz)); + + Ok(self) + } + + /// Returns `true` if the input point is on the curve. + /// + /// The operation must be processed before this method returns a meaningful value. + pub fn point_on_curve(&self) -> bool + where + O: OperationVerifiesPoint, + { + self.frontend.data().point_verification_result + } + + /// Starts processing the operation. + /// + /// The returned [`EccHandle`] must be polled to completion before the operation is considered + /// complete. + pub fn process(&mut self) -> EccHandle<'_> { + EccHandle(self.frontend.post(&ECC_WORK_QUEUE)) + } +} + +/// A handle for an in-progress operation. +#[must_use] +pub struct EccHandle<'t>(Handle<'t, EccWorkItem>); + +impl EccHandle<'_> { + /// Polls the status of the work item. + /// + /// This function returns `true` if the item has been processed. + #[inline] + pub fn poll(&mut self) -> bool { + self.0.poll() + } + + /// Polls the work item to completion, by busy-looping. + /// + /// This function returns immediately if `poll` returns `true`. + #[inline] + pub fn wait_blocking(self) -> Status { + self.0.wait_blocking() + } + + /// Waits until the work item is completed. + #[inline] + pub fn wait(&mut self) -> impl Future { + self.0.wait() + } + + /// Cancels the work item and asynchronously waits until it is removed from the work queue. + #[inline] + pub fn cancel(&mut self) -> impl Future { + self.0.cancel() + } +} diff --git a/esp-hal/src/efuse/esp32/fields.rs b/esp-hal/src/efuse/esp32/fields.rs new file mode 100644 index 00000000000..73cb4d69e01 --- /dev/null +++ b/esp-hal/src/efuse/esp32/fields.rs @@ -0,0 +1,142 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-05-30 12:24 +//! Version: 369d2d860d34e777c0f7d545a7dfc3c4 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Efuse write disable mask +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 16); +/// Disable reading from BlOCK1-3 +pub const RD_DIS: EfuseField = EfuseField::new(0, 0, 16, 4); +/// Flash encryption is enabled if this field has an odd number of bits set +pub const FLASH_CRYPT_CNT: EfuseField = EfuseField::new(0, 0, 20, 7); +/// Disable UART download mode. Valid for ESP32 V3 and newer; only +pub const UART_DOWNLOAD_DIS: EfuseField = EfuseField::new(0, 0, 27, 1); +/// reserved +pub const RESERVED_0_28: EfuseField = EfuseField::new(0, 0, 28, 4); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(0, 1, 32, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(0, 2, 64, 16); +/// CRC8 for MAC address +pub const MAC_CRC: EfuseField = EfuseField::new(0, 2, 80, 8); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_88: EfuseField = EfuseField::new(0, 2, 88, 8); +/// Disables APP CPU +pub const DISABLE_APP_CPU: EfuseField = EfuseField::new(0, 3, 96, 1); +/// Disables Bluetooth +pub const DISABLE_BT: EfuseField = EfuseField::new(0, 3, 97, 1); +/// Chip package identifier #4bit +pub const CHIP_PACKAGE_4BIT: EfuseField = EfuseField::new(0, 3, 98, 1); +/// Disables cache +pub const DIS_CACHE: EfuseField = EfuseField::new(0, 3, 99, 1); +/// read for SPI_pad_config_hd +pub const SPI_PAD_CONFIG_HD: EfuseField = EfuseField::new(0, 3, 100, 5); +/// Chip package identifier +pub const CHIP_PACKAGE: EfuseField = EfuseField::new(0, 3, 105, 3); +/// If set alongside EFUSE_RD_CHIP_CPU_FREQ_RATED; the ESP32's max CPU frequency +/// is rated for 160MHz. 240MHz otherwise +pub const CHIP_CPU_FREQ_LOW: EfuseField = EfuseField::new(0, 3, 108, 1); +/// If set; the ESP32's maximum CPU frequency has been rated +pub const CHIP_CPU_FREQ_RATED: EfuseField = EfuseField::new(0, 3, 109, 1); +/// BLOCK3 partially served for ADC calibration data +pub const BLK3_PART_RESERVE: EfuseField = EfuseField::new(0, 3, 110, 1); +/// bit is set to 1 for rev1 silicon +pub const CHIP_VER_REV1: EfuseField = EfuseField::new(0, 3, 111, 1); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_112: EfuseField = EfuseField::new(0, 3, 112, 16); +/// 8MHz clock freq override +pub const CLK8M_FREQ: EfuseField = EfuseField::new(0, 4, 128, 8); +/// True ADC reference voltage +pub const ADC_VREF: EfuseField = EfuseField::new(0, 4, 136, 5); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_141: EfuseField = EfuseField::new(0, 4, 141, 1); +/// read for XPD_SDIO_REG +pub const XPD_SDIO_REG: EfuseField = EfuseField::new(0, 4, 142, 1); +/// If XPD_SDIO_FORCE & XPD_SDIO_REG +pub const XPD_SDIO_TIEH: EfuseField = EfuseField::new(0, 4, 143, 1); +/// Ignore MTDI pin (GPIO12) for VDD_SDIO on reset +pub const XPD_SDIO_FORCE: EfuseField = EfuseField::new(0, 4, 144, 1); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_145: EfuseField = EfuseField::new(0, 4, 145, 15); +/// Override SD_CLK pad (GPIO6/SPICLK) +pub const SPI_PAD_CONFIG_CLK: EfuseField = EfuseField::new(0, 5, 160, 5); +/// Override SD_DATA_0 pad (GPIO7/SPIQ) +pub const SPI_PAD_CONFIG_Q: EfuseField = EfuseField::new(0, 5, 165, 5); +/// Override SD_DATA_1 pad (GPIO8/SPID) +pub const SPI_PAD_CONFIG_D: EfuseField = EfuseField::new(0, 5, 170, 5); +/// Override SD_CMD pad (GPIO11/SPICS0) +pub const SPI_PAD_CONFIG_CS0: EfuseField = EfuseField::new(0, 5, 175, 5); +/// +pub const CHIP_VER_REV2: EfuseField = EfuseField::new(0, 5, 180, 1); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_181: EfuseField = EfuseField::new(0, 5, 181, 1); +/// This field stores the voltage level for CPU to run at 240 MHz; or for +/// flash/PSRAM to run at 80 MHz.0x0: level 7; 0x1: level 6; 0x2: level 5; 0x3: +/// level 4. (RO) +pub const VOL_LEVEL_HP_INV: EfuseField = EfuseField::new(0, 5, 182, 2); +/// +pub const WAFER_VERSION_MINOR: EfuseField = EfuseField::new(0, 5, 184, 2); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_186: EfuseField = EfuseField::new(0, 5, 186, 2); +/// Flash encryption config (key tweak bits) +pub const FLASH_CRYPT_CONFIG: EfuseField = EfuseField::new(0, 5, 188, 4); +/// Efuse variable block length scheme +pub const CODING_SCHEME: EfuseField = EfuseField::new(0, 6, 192, 2); +/// Disable ROM BASIC interpreter fallback +pub const CONSOLE_DEBUG_DISABLE: EfuseField = EfuseField::new(0, 6, 194, 1); +/// +pub const DISABLE_SDIO_HOST: EfuseField = EfuseField::new(0, 6, 195, 1); +/// Secure boot V1 is enabled for bootloader image +pub const ABS_DONE_0: EfuseField = EfuseField::new(0, 6, 196, 1); +/// Secure boot V2 is enabled for bootloader image +pub const ABS_DONE_1: EfuseField = EfuseField::new(0, 6, 197, 1); +/// Disable JTAG +pub const JTAG_DISABLE: EfuseField = EfuseField::new(0, 6, 198, 1); +/// Disable flash encryption in UART bootloader +pub const DISABLE_DL_ENCRYPT: EfuseField = EfuseField::new(0, 6, 199, 1); +/// Disable flash decryption in UART bootloader +pub const DISABLE_DL_DECRYPT: EfuseField = EfuseField::new(0, 6, 200, 1); +/// Disable flash cache in UART bootloader +pub const DISABLE_DL_CACHE: EfuseField = EfuseField::new(0, 6, 201, 1); +/// Usage of efuse block 3 (reserved) +pub const KEY_STATUS: EfuseField = EfuseField::new(0, 6, 202, 1); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_203: EfuseField = EfuseField::new(0, 6, 203, 21); +/// Flash encryption key +pub const BLOCK1: EfuseField = EfuseField::new(1, 0, 0, 256); +/// Security boot key +pub const BLOCK2: EfuseField = EfuseField::new(2, 0, 0, 256); +/// CRC8 for custom MAC address +pub const CUSTOM_MAC_CRC: EfuseField = EfuseField::new(3, 0, 0, 8); +/// Custom MAC address +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 0, 8, 48); +/// reserved +pub const RESERVED_3_56: EfuseField = EfuseField::new(3, 1, 56, 8); +/// read for BLOCK3 +pub const BLK3_RESERVED_2: EfuseField = EfuseField::new(3, 2, 64, 32); +/// ADC1 Two Point calibration low point. Only valid if +/// EFUSE_RD_BLK3_PART_RESERVE +pub const ADC1_TP_LOW: EfuseField = EfuseField::new(3, 3, 96, 7); +/// ADC1 Two Point calibration high point. Only valid if +/// EFUSE_RD_BLK3_PART_RESERVE +pub const ADC1_TP_HIGH: EfuseField = EfuseField::new(3, 3, 103, 9); +/// ADC2 Two Point calibration low point. Only valid if +/// EFUSE_RD_BLK3_PART_RESERVE +pub const ADC2_TP_LOW: EfuseField = EfuseField::new(3, 3, 112, 7); +/// ADC2 Two Point calibration high point. Only valid if +/// EFUSE_RD_BLK3_PART_RESERVE +pub const ADC2_TP_HIGH: EfuseField = EfuseField::new(3, 3, 119, 9); +/// Secure version for anti-rollback +pub const SECURE_VERSION: EfuseField = EfuseField::new(3, 4, 128, 32); +/// reserved +pub const RESERVED_3_160: EfuseField = EfuseField::new(3, 5, 160, 24); +/// Version of the MAC field +pub const MAC_VERSION: EfuseField = EfuseField::new(3, 5, 184, 8); +/// read for BLOCK3 +pub const BLK3_RESERVED_6: EfuseField = EfuseField::new(3, 6, 192, 32); +/// read for BLOCK3 +pub const BLK3_RESERVED_7: EfuseField = EfuseField::new(3, 7, 224, 32); diff --git a/esp-hal/src/efuse/esp32/mod.rs b/esp-hal/src/efuse/esp32/mod.rs new file mode 100644 index 00000000000..1ae56c11b27 --- /dev/null +++ b/esp-hal/src/efuse/esp32/mod.rs @@ -0,0 +1,128 @@ +use crate::{peripherals::EFUSE, time::Rate}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Representing different types of ESP32 chips. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[instability::unstable] +pub enum ChipType { + /// Represents the ESP32 D0WDQ6 chip variant. + Esp32D0wdq6, + /// Represents the ESP32 D0WDQ5 chip variant. + Esp32D0wdq5, + /// Represents the ESP32 D2WDQ5 chip variant. + Esp32D2wdq5, + /// Represents the ESP32 Pico D2 chip variant. + Esp32Picod2, + /// Represents the ESP32 Pico D4 chip variant. + Esp32Picod4, + /// Represents the ESP32 Pico v3.02 chip variant. + Esp32Picov302, + /// Represents an unknown or unsupported chip variant. + Unknown, +} + +/// Returns the number of CPUs available on the chip. +/// +/// While ESP32 chips usually come with two mostly equivalent CPUs (protocol +/// CPU and application CPU), the application CPU is unavailable on +/// some. +#[instability::unstable] +pub fn core_count() -> u32 { + if super::read_bit(DISABLE_APP_CPU) { + 1 + } else { + 2 + } +} + +/// Returns the maximum rated clock of the CPU in MHz. +/// +/// Note that the actual clock may be lower, depending on the current power +/// configuration of the chip, clock source, and other settings. +#[instability::unstable] +pub fn max_cpu_frequency() -> Rate { + let has_rating = super::read_bit(CHIP_CPU_FREQ_RATED); + let has_low_rating = super::read_bit(CHIP_CPU_FREQ_LOW); + + if has_rating && has_low_rating { + Rate::from_mhz(160) + } else { + Rate::from_mhz(240) + } +} + +/// Returns the CHIP_VER_DIS_BT eFuse value. +#[instability::unstable] +pub fn is_bluetooth_enabled() -> bool { + !super::read_bit(DISABLE_BT) +} + +/// Returns the CHIP_VER_PKG eFuse value. +#[instability::unstable] +pub fn chip_type() -> ChipType { + let chip_ver = super::read_field_le::(CHIP_PACKAGE) + | (super::read_field_le::(CHIP_PACKAGE_4BIT) << 4); + + match chip_ver { + 0 => ChipType::Esp32D0wdq6, + 1 => ChipType::Esp32D0wdq5, + 2 => ChipType::Esp32D2wdq5, + 4 => ChipType::Esp32Picod2, + 5 => ChipType::Esp32Picod4, + 6 => ChipType::Esp32Picov302, + _ => ChipType::Unknown, + } +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(FLASH_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + let eco_bit0 = super::read_field_le::(CHIP_VER_REV1); + let eco_bit1 = super::read_field_le::(CHIP_VER_REV2); + let eco_bit2 = (crate::peripherals::APB_CTRL::regs().date().read().bits() & 0x80000000) >> 31; + + match (eco_bit2 << 2) | (eco_bit1 << 1) | eco_bit0 { + 1 => 1, + 3 => 2, + 7 => 3, + _ => 0, + } +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MINOR) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.blk0_rdata0().as_ptr(), + Self::Block1 => efuse.blk1_rdata0().as_ptr(), + Self::Block2 => efuse.blk2_rdata0().as_ptr(), + Self::Block3 => efuse.blk3_rdata0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32c2/fields.rs b/esp-hal/src/efuse/esp32c2/fields.rs new file mode 100644 index 00000000000..cd92a284493 --- /dev/null +++ b/esp-hal/src/efuse/esp32c2/fields.rs @@ -0,0 +1,116 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: 897499b0349a608b895d467abbcf006b + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 8); +/// +pub const RESERVED_0_8: EfuseField = EfuseField::new(0, 0, 8, 24); +/// Disable reading from BlOCK3 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 2); +/// RTC watchdog timeout threshold; in unit of slow clock cycle +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 1, 34, 2); +/// Set this bit to disable pad jtag +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 36, 1); +/// The bit be set to disable icache in download mode +pub const DIS_DOWNLOAD_ICACHE: EfuseField = EfuseField::new(0, 1, 37, 1); +/// The bit be set to disable manual encryption +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 38, 1); +/// Enables flash encryption when 1 or 3 bits are set and disables otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 1, 39, 3); +/// Flash encryption key length +pub const XTS_KEY_LENGTH_256: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Set the default UARTboot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 1, 43, 2); +/// Set this bit to force ROM code to send a resume command during SPI boot +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Set this bit to disable download mode (boot_mode\[3:0\] = 0; 1; 2; 4; 5; 6; +/// 7) +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 1, 46, 1); +/// This bit set means disable direct_boot mode +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Set this bit to enable secure UART download mode +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 48, 1); +/// Configures flash waiting time after power-up; in unit of ms. If the value is +/// less than 15; the waiting time is the configurable value. Otherwise; the +/// waiting time is twice the configurable value +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 1, 49, 4); +/// The bit be set to enable secure boot +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 1, 53, 1); +/// Secure version for anti-rollback +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 1, 54, 4); +/// True if MAC_CUSTOM is burned +pub const CUSTOM_MAC_USED: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(0, 1, 59, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(0, 1, 60, 1); +/// reserved +pub const RESERVED_0_61: EfuseField = EfuseField::new(0, 1, 61, 3); +/// Custom MAC address +pub const CUSTOM_MAC: EfuseField = EfuseField::new(1, 0, 0, 48); +/// reserved +pub const RESERVED_1_48: EfuseField = EfuseField::new(1, 1, 48, 16); +/// Stores the bits \[64:87\] of system data +pub const SYSTEM_DATA2: EfuseField = EfuseField::new(1, 2, 64, 24); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(2, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(2, 1, 32, 16); +/// WAFER_VERSION_MINOR +pub const WAFER_VERSION_MINOR: EfuseField = EfuseField::new(2, 1, 48, 4); +/// WAFER_VERSION_MAJOR +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(2, 1, 52, 2); +/// EFUSE_PKG_VERSION +pub const PKG_VERSION: EfuseField = EfuseField::new(2, 1, 54, 3); +/// Minor version of BLOCK2 +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(2, 1, 57, 3); +/// Major version of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(2, 1, 60, 2); +/// OCode +pub const OCODE: EfuseField = EfuseField::new(2, 1, 62, 7); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 2, 69, 9); +/// ADC1 init code at atten0 +pub const ADC1_INIT_CODE_ATTEN0: EfuseField = EfuseField::new(2, 2, 78, 8); +/// ADC1 init code at atten3 +pub const ADC1_INIT_CODE_ATTEN3: EfuseField = EfuseField::new(2, 2, 86, 5); +/// ADC1 calibration voltage at atten0 +pub const ADC1_CAL_VOL_ATTEN0: EfuseField = EfuseField::new(2, 2, 91, 8); +/// ADC1 calibration voltage at atten3 +pub const ADC1_CAL_VOL_ATTEN3: EfuseField = EfuseField::new(2, 3, 99, 6); +/// BLOCK2 digital dbias when hvt +pub const DIG_DBIAS_HVT: EfuseField = EfuseField::new(2, 3, 105, 5); +/// BLOCK2 DIG_LDO_DBG0_DBIAS2 +pub const DIG_LDO_SLP_DBIAS2: EfuseField = EfuseField::new(2, 3, 110, 7); +/// BLOCK2 DIG_LDO_DBG0_DBIAS26 +pub const DIG_LDO_SLP_DBIAS26: EfuseField = EfuseField::new(2, 3, 117, 8); +/// BLOCK2 DIG_LDO_ACT_DBIAS26 +pub const DIG_LDO_ACT_DBIAS26: EfuseField = EfuseField::new(2, 3, 125, 6); +/// BLOCK2 DIG_LDO_ACT_STEPD10 +pub const DIG_LDO_ACT_STEPD10: EfuseField = EfuseField::new(2, 4, 131, 4); +/// BLOCK2 DIG_LDO_SLP_DBIAS13 +pub const RTC_LDO_SLP_DBIAS13: EfuseField = EfuseField::new(2, 4, 135, 7); +/// BLOCK2 DIG_LDO_SLP_DBIAS29 +pub const RTC_LDO_SLP_DBIAS29: EfuseField = EfuseField::new(2, 4, 142, 9); +/// BLOCK2 DIG_LDO_SLP_DBIAS31 +pub const RTC_LDO_SLP_DBIAS31: EfuseField = EfuseField::new(2, 4, 151, 6); +/// BLOCK2 DIG_LDO_ACT_DBIAS31 +pub const RTC_LDO_ACT_DBIAS31: EfuseField = EfuseField::new(2, 4, 157, 6); +/// BLOCK2 DIG_LDO_ACT_DBIAS13 +pub const RTC_LDO_ACT_DBIAS13: EfuseField = EfuseField::new(2, 5, 163, 8); +/// reserved +pub const RESERVED_2_171: EfuseField = EfuseField::new(2, 5, 171, 21); +/// Store the bit \[86:96\] of ADC calibration data +pub const ADC_CALIBRATION_3: EfuseField = EfuseField::new(2, 6, 192, 11); +/// Store the bit \[0:20\] of block2 reserved data +pub const BLK2_RESERVED_DATA_0: EfuseField = EfuseField::new(2, 6, 203, 21); +/// Store the bit \[21:52\] of block2 reserved data +pub const BLK2_RESERVED_DATA_1: EfuseField = EfuseField::new(2, 7, 224, 32); +/// BLOCK_KEY0 - 256-bits. 256-bit key of Flash Encryption +pub const BLOCK_KEY0: EfuseField = EfuseField::new(3, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32c2/mod.rs b/esp-hal/src/efuse/esp32c2/mod.rs new file mode 100644 index 00000000000..3878b21876c --- /dev/null +++ b/esp-hal/src/efuse/esp32c2/mod.rs @@ -0,0 +1,157 @@ +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Selects which ADC we are interested in the efuse calibration data for +#[instability::unstable] +pub enum AdcCalibUnit { + /// Select efuse calibration data for ADC1 + ADC1, +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// see +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + // see + // + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// see +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (major, _minor) = block_version(); + if major == 0 { 1 } else { 0 } +} + +/// Get ADC initial code for specified attenuation from efuse +/// +/// see +#[instability::unstable] +pub fn rtc_calib_init_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // see + let diff_code0: u16 = super::read_field_le(ADC1_INIT_CODE_ATTEN0); + let code0 = if diff_code0 & (1 << 7) != 0 { + 2160 - (diff_code0 & 0x7f) + } else { + 2160 + diff_code0 + }; + + if matches!(atten, Attenuation::_0dB) { + return Some(code0); + } + + // see + let diff_code11: u16 = super::read_field_le(ADC1_INIT_CODE_ATTEN3); + let code11 = code0 + diff_code11; + + Some(code11) +} + +/// Get ADC reference point voltage for specified attenuation in millivolts +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_mv(_unit: AdcCalibUnit, atten: Attenuation) -> u16 { + match atten { + Attenuation::_0dB => 400, + Attenuation::_11dB => 1370, + _ => panic!("Unsupported attenuation"), + } +} + +/// Get ADC reference point digital code for specified attenuation +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // see + let diff_code0: u16 = super::read_field_le(ADC1_CAL_VOL_ATTEN0); + let code0 = if diff_code0 & (1 << 7) != 0 { + 1540 - (diff_code0 & 0x7f) + } else { + 1540 + diff_code0 + }; + + if matches!(atten, Attenuation::_0dB) { + return Some(code0); + } + + // see + let diff_code11: u16 = super::read_field_le(ADC1_CAL_VOL_ATTEN3); + let code11 = if diff_code0 & (1 << 5) != 0 { + code0 - (diff_code11 & 0x1f) + } else { + code0 + diff_code11 + } - 123; + + Some(code11) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MINOR) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_blk1_data0().as_ptr(), + Self::Block2 => efuse.rd_blk2_data0().as_ptr(), + Self::Block3 => efuse.rd_blk3_data0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32c3/fields.rs b/esp-hal/src/efuse/esp32c3/fields.rs new file mode 100644 index 00000000000..46c807e3567 --- /dev/null +++ b/esp-hal/src/efuse/esp32c3/fields.rs @@ -0,0 +1,240 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: 4622cf9245401eca0eb1df8122449a6d + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Set this bit to disable boot from RTC RAM +pub const DIS_RTC_RAM_BOOT: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Set this bit to disable Icache +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Set this bit to disable function of usb switch to jtag in module of usb +/// device +pub const DIS_USB_JTAG: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Set this bit to disable Icache in download mode (boot_mode\[3:0\] is 0; 1; +/// 2; 3; 6; 7) +pub const DIS_DOWNLOAD_ICACHE: EfuseField = EfuseField::new(0, 1, 42, 1); +/// USB-Serial-JTAG +pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Set this bit to disable the function that forces chip into download mode +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED6: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Set this bit to disable CAN function +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Set this bit to enable selection between usb_to_jtag and pad_to_jtag through +/// strapping gpio10 when both reg_dis_usb_jtag and reg_dis_pad_jtag are equal +/// to 0 +pub const JTAG_SEL_ENABLE: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Set these bits to disable JTAG in the soft way (odd number 1 means disable +/// ). JTAG can be enabled in HMAC module +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 48, 3); +/// Set this bit to disable JTAG in the hard way. JTAG is disabled permanently +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Set this bit to disable flash encryption when in download boot modes +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 52, 1); +/// Controls single-end input threshold vrefh; 1.76 V to 2 V with step of 80 mV; +/// stored in eFuse +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 53, 2); +/// Controls single-end input threshold vrefl; 0.8 V to 1.04 V with step of 80 +/// mV; stored in eFuse +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 55, 2); +/// Set this bit to exchange USB D+ and D- pins +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 57, 1); +/// Set this bit to vdd spi pin function as gpio +pub const VDD_SPI_AS_GPIO: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Enable btlc gpio +pub const BTLC_GPIO_ENABLE: EfuseField = EfuseField::new(0, 1, 59, 2); +/// Set this bit to enable power glitch function +pub const POWERGLITCH_EN: EfuseField = EfuseField::new(0, 1, 61, 1); +/// Sample delay configuration of power glitch +pub const POWER_GLITCH_DSENSE: EfuseField = EfuseField::new(0, 1, 62, 2); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED2: EfuseField = EfuseField::new(0, 2, 64, 16); +/// RTC watchdog timeout threshold; in unit of slow clock cycle +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 2, 80, 2); +/// Enables flash encryption when 1 or 3 bits are set and disables otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 82, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 86, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 87, 1); +/// Purpose of Key0 +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 88, 4); +/// Purpose of Key1 +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 92, 4); +/// Purpose of Key2 +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 4); +/// Purpose of Key3 +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 100, 4); +/// Purpose of Key4 +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 104, 4); +/// Purpose of Key5 +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 108, 4); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED3: EfuseField = EfuseField::new(0, 3, 112, 4); +/// Set this bit to enable secure boot +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 116, 1); +/// Set this bit to enable revoking aggressive secure boot +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 117, 1); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED0: EfuseField = EfuseField::new(0, 3, 118, 6); +/// Configures flash waiting time after power-up; in unit of ms. If the value is +/// less than 15; the waiting time is the configurable value; Otherwise; the +/// waiting time is twice the configurable value +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Set this bit to disable download mode (boot_mode\[3:0\] = 0; 1; 2; 3; 6; 7) +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Disable direct boot mode +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// USB printing +pub const DIS_USB_SERIAL_JTAG_ROM_PRINT: EfuseField = EfuseField::new(0, 4, 130, 1); +/// ECC mode in ROM +pub const FLASH_ECC_MODE: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Disable UART download mode through USB-Serial-JTAG +pub const DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Set this bit to enable secure UART download mode +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UARTboot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// GPIO33-GPIO37 power supply selection in ROM code +pub const PIN_POWER_SELECTION: EfuseField = EfuseField::new(0, 4, 136, 1); +/// Maximum lines of SPI flash +pub const FLASH_TYPE: EfuseField = EfuseField::new(0, 4, 137, 1); +/// Set Flash page size +pub const FLASH_PAGE_SIZE: EfuseField = EfuseField::new(0, 4, 138, 2); +/// Set 1 to enable ECC for flash boot +pub const FLASH_ECC_EN: EfuseField = EfuseField::new(0, 4, 140, 1); +/// Set this bit to force ROM code to send a resume command during SPI boot +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 141, 1); +/// Secure version (used by ESP-IDF anti-rollback feature) +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 142, 16); +/// reserved +pub const RESERVED_0_158: EfuseField = EfuseField::new(0, 4, 158, 1); +/// Use BLOCK0 to check error record registers +pub const ERR_RST_ENABLE: EfuseField = EfuseField::new(0, 4, 159, 1); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 160, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 161, 1); +/// reserved +pub const RESERVED_0_162: EfuseField = EfuseField::new(0, 5, 162, 22); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// SPI PAD CLK +pub const SPI_PAD_CONFIG_CLK: EfuseField = EfuseField::new(1, 1, 48, 6); +/// SPI PAD Q(D1) +pub const SPI_PAD_CONFIG_Q: EfuseField = EfuseField::new(1, 1, 54, 6); +/// SPI PAD D(D0) +pub const SPI_PAD_CONFIG_D: EfuseField = EfuseField::new(1, 1, 60, 6); +/// SPI PAD CS +pub const SPI_PAD_CONFIG_CS: EfuseField = EfuseField::new(1, 2, 66, 6); +/// SPI PAD HD(D3) +pub const SPI_PAD_CONFIG_HD: EfuseField = EfuseField::new(1, 2, 72, 6); +/// SPI PAD WP(D2) +pub const SPI_PAD_CONFIG_WP: EfuseField = EfuseField::new(1, 2, 78, 6); +/// SPI PAD DQS +pub const SPI_PAD_CONFIG_DQS: EfuseField = EfuseField::new(1, 2, 84, 6); +/// SPI PAD D4 +pub const SPI_PAD_CONFIG_D4: EfuseField = EfuseField::new(1, 2, 90, 6); +/// SPI PAD D5 +pub const SPI_PAD_CONFIG_D5: EfuseField = EfuseField::new(1, 3, 96, 6); +/// SPI PAD D6 +pub const SPI_PAD_CONFIG_D6: EfuseField = EfuseField::new(1, 3, 102, 6); +/// SPI PAD D7 +pub const SPI_PAD_CONFIG_D7: EfuseField = EfuseField::new(1, 3, 108, 6); +/// WAFER_VERSION_MINOR least significant bits +pub const WAFER_VERSION_MINOR_LO: EfuseField = EfuseField::new(1, 3, 114, 3); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 3, 117, 3); +/// BLK_VERSION_MINOR +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(1, 3, 120, 3); +/// Flash capacity +pub const FLASH_CAP: EfuseField = EfuseField::new(1, 3, 123, 3); +/// Flash temperature +pub const FLASH_TEMP: EfuseField = EfuseField::new(1, 3, 126, 2); +/// Flash vendor +pub const FLASH_VENDOR: EfuseField = EfuseField::new(1, 4, 128, 3); +/// reserved +pub const RESERVED_1_131: EfuseField = EfuseField::new(1, 4, 131, 4); +/// BLOCK1 K_RTC_LDO +pub const K_RTC_LDO: EfuseField = EfuseField::new(1, 4, 135, 7); +/// BLOCK1 K_DIG_LDO +pub const K_DIG_LDO: EfuseField = EfuseField::new(1, 4, 142, 7); +/// BLOCK1 voltage of rtc dbias20 +pub const V_RTC_DBIAS20: EfuseField = EfuseField::new(1, 4, 149, 8); +/// BLOCK1 voltage of digital dbias20 +pub const V_DIG_DBIAS20: EfuseField = EfuseField::new(1, 4, 157, 8); +/// BLOCK1 digital dbias when hvt +pub const DIG_DBIAS_HVT: EfuseField = EfuseField::new(1, 5, 165, 5); +/// BLOCK1 pvt threshold when hvt +pub const THRES_HVT: EfuseField = EfuseField::new(1, 5, 170, 10); +/// reserved +pub const RESERVED_1_180: EfuseField = EfuseField::new(1, 5, 180, 3); +/// WAFER_VERSION_MINOR most significant bit +pub const WAFER_VERSION_MINOR_HI: EfuseField = EfuseField::new(1, 5, 183, 1); +/// WAFER_VERSION_MAJOR +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 5, 184, 2); +/// reserved +pub const RESERVED_1_186: EfuseField = EfuseField::new(1, 5, 186, 6); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// BLK_VERSION_MAJOR of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(2, 4, 128, 2); +/// reserved +pub const RESERVED_2_130: EfuseField = EfuseField::new(2, 4, 130, 1); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 4, 131, 9); +/// ADC OCode +pub const OCODE: EfuseField = EfuseField::new(2, 4, 140, 8); +/// ADC1 init code at atten0 +pub const ADC1_INIT_CODE_ATTEN0: EfuseField = EfuseField::new(2, 4, 148, 10); +/// ADC1 init code at atten1 +pub const ADC1_INIT_CODE_ATTEN1: EfuseField = EfuseField::new(2, 4, 158, 10); +/// ADC1 init code at atten2 +pub const ADC1_INIT_CODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 168, 10); +/// ADC1 init code at atten3 +pub const ADC1_INIT_CODE_ATTEN3: EfuseField = EfuseField::new(2, 5, 178, 10); +/// ADC1 calibration voltage at atten0 +pub const ADC1_CAL_VOL_ATTEN0: EfuseField = EfuseField::new(2, 5, 188, 10); +/// ADC1 calibration voltage at atten1 +pub const ADC1_CAL_VOL_ATTEN1: EfuseField = EfuseField::new(2, 6, 198, 10); +/// ADC1 calibration voltage at atten2 +pub const ADC1_CAL_VOL_ATTEN2: EfuseField = EfuseField::new(2, 6, 208, 10); +/// ADC1 calibration voltage at atten3 +pub const ADC1_CAL_VOL_ATTEN3: EfuseField = EfuseField::new(2, 6, 218, 10); +/// reserved +pub const RESERVED_2_228: EfuseField = EfuseField::new(2, 7, 228, 28); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC address +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32c3/mod.rs b/esp-hal/src/efuse/esp32c3/mod.rs new file mode 100644 index 00000000000..4cb567a4660 --- /dev/null +++ b/esp-hal/src/efuse/esp32c3/mod.rs @@ -0,0 +1,162 @@ +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Selects which ADC we are interested in the efuse calibration data for +#[instability::unstable] +pub enum AdcCalibUnit { + /// Select efuse calibration data for ADC1 + ADC1, + /// Select efuse calibration data for ADC2 + ADC2, +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// see +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + // see + // + // + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// see +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (major, _minor) = block_version(); + if major == 1 { 1 } else { 0 } +} + +/// Get ADC initial code for specified attenuation from efuse +/// +/// see +#[instability::unstable] +pub fn rtc_calib_init_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // See + let init_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_INIT_CODE_ATTEN0, + Attenuation::_2p5dB => ADC1_INIT_CODE_ATTEN1, + Attenuation::_6dB => ADC1_INIT_CODE_ATTEN2, + Attenuation::_11dB => ADC1_INIT_CODE_ATTEN3, + }); + + Some(init_code + 1000) // version 1 logic +} + +/// Get ADC reference point voltage for specified attenuation in millivolts +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_mv(_unit: AdcCalibUnit, atten: Attenuation) -> u16 { + match atten { + Attenuation::_0dB => 400, + Attenuation::_2p5dB => 550, + Attenuation::_6dB => 750, + Attenuation::_11dB => 1370, + } +} + +/// Get ADC reference point digital code for specified attenuation +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // See + let cal_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_CAL_VOL_ATTEN0, + Attenuation::_2p5dB => ADC1_CAL_VOL_ATTEN1, + Attenuation::_6dB => ADC1_CAL_VOL_ATTEN2, + Attenuation::_11dB => ADC1_CAL_VOL_ATTEN3, + }); + + let cal_code = if cal_code & (1 << 9) != 0 { + 2000 - (cal_code & !(1 << 9)) + } else { + 2000 + cal_code + }; + + Some(cal_code) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le::(WAFER_VERSION_MINOR_HI) << 3 + | super::read_field_le::(WAFER_VERSION_MINOR_LO) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_mac_spi_sys_0().as_ptr(), + Self::Block2 => efuse.rd_sys_part1_data0().as_ptr(), + Self::Block3 => efuse.rd_usr_data0().as_ptr(), + Self::Block4 => efuse.rd_key0_data0().as_ptr(), + Self::Block5 => efuse.rd_key1_data0().as_ptr(), + Self::Block6 => efuse.rd_key2_data0().as_ptr(), + Self::Block7 => efuse.rd_key3_data0().as_ptr(), + Self::Block8 => efuse.rd_key4_data0().as_ptr(), + Self::Block9 => efuse.rd_key5_data0().as_ptr(), + Self::Block10 => efuse.rd_sys_part2_data0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32c5/fields.rs b/esp-hal/src/efuse/esp32c5/fields.rs new file mode 100644 index 00000000000..0eda80c06d9 --- /dev/null +++ b/esp-hal/src/efuse/esp32c5/fields.rs @@ -0,0 +1,293 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2026-01-21 00:00 +//! Version: 31c7fe3f5f4e0a55b178a57126c0aca7 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Represents the anti-rollback secure version of the 2nd stage bootloader used +/// by the ROM bootloader (the high part of the field) +pub const BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION_HI: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Represents whether cache is disabled +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Represents whether the USB-to-JTAG function in USB Serial/JTAG is disabled. +/// Note that EFUSE_DIS_USB_JTAG is available only when +/// EFUSE_DIS_USB_SERIAL_JTAG is configured to 0 +pub const DIS_USB_JTAG: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Represents whether the ani-rollback check for the 2nd stage bootloader is +/// enabled +pub const BOOTLOADER_ANTI_ROLLBACK_EN: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Represents whether USB Serial/JTAG is disabled +pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Represents whether the function that forces chip into Download mode is +/// disabled +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Represents whether SPI0 controller during boot_mode_download is disabled +pub const SPI_DOWNLOAD_MSPI_DIS: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Represents whether TWAI function is disabled +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Represents whether the selection of a JTAG signal source through the +/// strapping pin value is enabled when all of EFUSE_DIS_PAD_JTAG; +/// EFUSE_DIS_USB_JTAG and EFUSE_DIS_USB_SERIAL_JTAG are configured to 0 +pub const JTAG_SEL_ENABLE: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Represents whether PAD JTAG is disabled in the soft way. It can be restarted +/// via HMAC. Odd count of bits with a value of 1: Disabled; Even count of bits +/// with a value of 1: Enabled +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 48, 3); +/// Represents whether PAD JTAG is disabled in the hard way (permanently) +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Represents whether flash encryption is disabled (except in SPI boot mode) +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 52, 1); +/// Represents the single-end input threshold vrefh; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 53, 2); +/// Represents the single-end input threshold vrefl; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 55, 2); +/// Represents whether the D+ and D- pins is exchanged +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 57, 1); +/// Represents whether VDD SPI pin is functioned as GPIO +pub const VDD_SPI_AS_GPIO: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Represents RTC watchdog timeout threshold. The originally configured STG0 +/// threshold * (2 ^ (EFUSE_WDT_DELAY_SEL + 1)) +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 1, 59, 2); +/// Represents the anti-rollback secure version of the 2nd stage bootloader used +/// by the ROM bootloader (the low part of the field) +pub const BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION_LO: EfuseField = EfuseField::new(0, 1, 61, 3); +/// Represents whether the new key deployment of key manager is disabled. Bit0: +/// ECDSA key deployment (0: Enabled, 1: Disabled). Bit1: XTS-AES (flash and +/// PSRAM) key deployment (0: Enabled, 1: Disabled). Bit2: HMAC key deployment +/// (0: Enabled, 1: Disabled). Bit3: DS key deployment (0: Enabled, 1: Disabled) +pub const KM_DISABLE_DEPLOY_MODE: EfuseField = EfuseField::new(0, 2, 64, 4); +/// Represents the cycle at which the Key Manager switches random numbers +pub const KM_RND_SWITCH_CYCLE: EfuseField = EfuseField::new(0, 2, 68, 2); +/// Represents whether the corresponding key can be deployed only once. Bit0: +/// ECDSA key (0: Multiple times, 1: Only once). Bit1: XTS-AES (flash and PSRAM) +/// key (0: Multiple times, 1: Only once). Bit2: HMAC key (0: Multiple times, 1: +/// Only once). Bit3: DS key (0: Multiple times, 1: Only once) +pub const KM_DEPLOY_ONLY_ONCE: EfuseField = EfuseField::new(0, 2, 70, 4); +/// Represents whether the corresponding key must come from Key Manager. Bit0: +/// ECDSA key (0: Not required, 1: Required). Bit1: XTS-AES (flash and PSRAM) +/// key (0: Not required, 1: Required). Bit2: HMAC key (0: Not required, 1: +/// Required). Bit3: DS key (0: Not required, 1: Required) +pub const FORCE_USE_KEY_MANAGER_KEY: EfuseField = EfuseField::new(0, 2, 74, 4); +/// Represents whether to disable the use of the initialization key written by +/// software and instead force use efuse_init_key +pub const FORCE_DISABLE_SW_INIT_KEY: EfuseField = EfuseField::new(0, 2, 78, 1); +/// Represents whether the ani-rollback SECURE_VERSION will be updated from the +/// ROM bootloader +pub const BOOTLOADER_ANTI_ROLLBACK_UPDATE_IN_ROM: EfuseField = EfuseField::new(0, 2, 79, 1); +/// Enables flash encryption when 1 or 3 bits are set and disables otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 80, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 83, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 84, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Represents the purpose of Key0. +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 86, 5); +/// Represents the purpose of Key1. +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 91, 5); +/// Represents the purpose of Key2. +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 5); +/// Represents the purpose of Key3. +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 101, 5); +/// Represents the purpose of Key4. +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 106, 5); +/// Represents the purpose of Key5. +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 111, 5); +/// Represents the security level of anti-DPA attack. The level is adjusted by +/// configuring the clock random frequency division mode +pub const SEC_DPA_LEVEL: EfuseField = EfuseField::new(0, 3, 116, 2); +/// Represents the starting flash sector (flash sector size is 0x1000) of the +/// recovery bootloader used by the ROM bootloader If the primary bootloader +/// fails. 0 and 0xFFF - this feature is disabled. (The high part of the field) +pub const RECOVERY_BOOTLOADER_FLASH_SECTOR_HI: EfuseField = EfuseField::new(0, 3, 118, 3); +/// Represents whether Secure Boot is enabled +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 121, 1); +/// Represents whether aggressive revocation of Secure Boot is enabled +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 122, 1); +/// Represents which key flash encryption uses +pub const KM_XTS_KEY_LENGTH_256: EfuseField = EfuseField::new(0, 3, 123, 1); +/// Represents the flash waiting time after power-up. Measurement unit: ms. When +/// the value is less than 15; the waiting time is the programmed value. +/// Otherwise; the waiting time is a fixed value; i.e. 30 ms +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Represents whether Download mode is disable or enable +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Represents whether direct boot mode is disabled or enabled +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// Represents whether print from USB-Serial-JTAG is disabled or enabled +pub const DIS_USB_SERIAL_JTAG_ROM_PRINT: EfuseField = EfuseField::new(0, 4, 130, 1); +/// Represents whether the keys in the Key Manager are locked after deployment +pub const LOCK_KM_KEY: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Represents whether the USB-Serial-JTAG download function is disabled or +/// enabled +pub const DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Represents whether security download is enabled. Only downloading into flash +/// is supported. Reading/writing RAM or registers is not supported (i.e. stub +/// download is not supported) +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UART boot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// Represents whether ROM code is forced to send a resume command during SPI +/// boot +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 136, 1); +/// Represents the app secure version used by ESP-IDF anti-rollback feature +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 137, 9); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_146: EfuseField = EfuseField::new(0, 4, 146, 7); +/// Represents whether FAST VERIFY ON WAKE is disabled when Secure Boot is +/// enabled +pub const SECURE_BOOT_DISABLE_FAST_WAKE: EfuseField = EfuseField::new(0, 4, 153, 1); +/// Represents whether the hysteresis function of PAD0 - PAD27 is enabled +pub const HYS_EN_PAD: EfuseField = EfuseField::new(0, 4, 154, 1); +/// Represents the pseudo round level of XTS-AES anti-DPA attack +pub const XTS_DPA_PSEUDO_LEVEL: EfuseField = EfuseField::new(0, 4, 155, 2); +/// Represents whether XTS-AES anti-DPA attack clock is enabled +pub const XTS_DPA_CLK_ENABLE: EfuseField = EfuseField::new(0, 4, 157, 1); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_158: EfuseField = EfuseField::new(0, 4, 158, 1); +/// Represents if the chip supports Secure Boot using SHA-384 +pub const SECURE_BOOT_SHA384_EN: EfuseField = EfuseField::new(0, 4, 159, 1); +/// Represents whether the HUK generate mode is valid. Odd count of bits with a +/// value of 1: Invalid; Even count of bits with a value of 1: Valid +pub const HUK_GEN_STATE: EfuseField = EfuseField::new(0, 5, 160, 9); +/// Represents whether XTAL frequency is 48MHz or not. If not; 40MHz XTAL will +/// be used. If this field contains Odd number bit 1: Enable 48MHz XTAL; Even +/// number bit 1: Enable 40MHz XTAL +pub const XTAL_48M_SEL: EfuseField = EfuseField::new(0, 5, 169, 3); +/// Represents what determines the XTAL frequency in Joint Download Boot mode +pub const XTAL_48M_SEL_MODE: EfuseField = EfuseField::new(0, 5, 172, 1); +/// Represents whether to force ECC to use constant-time mode for point +/// multiplication calculation +pub const ECC_FORCE_CONST_TIME: EfuseField = EfuseField::new(0, 5, 173, 1); +/// Represents the starting flash sector (flash sector size is 0x1000) of the +/// recovery bootloader used by the ROM bootloader If the primary bootloader +/// fails. 0 and 0xFFF - this feature is disabled. (The low part of the field) +pub const RECOVERY_BOOTLOADER_FLASH_SECTOR_LO: EfuseField = EfuseField::new(0, 5, 174, 9); +/// Reserved; it was created by set_missed_fields_in_regs func +pub const RESERVE_0_183: EfuseField = EfuseField::new(0, 5, 183, 9); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// Represents the extended bits of MAC address +pub const MAC_EXT: EfuseField = EfuseField::new(1, 1, 48, 16); +/// Minor chip version +pub const WAFER_VERSION_MINOR: EfuseField = EfuseField::new(1, 2, 64, 4); +/// Minor chip version +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 2, 68, 2); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 2, 70, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(1, 2, 71, 1); +/// BLK_VERSION_MINOR of BLOCK2 +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(1, 2, 72, 3); +/// BLK_VERSION_MAJOR of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(1, 2, 75, 2); +/// Flash capacity +pub const FLASH_CAP: EfuseField = EfuseField::new(1, 2, 77, 3); +/// Flash vendor +pub const FLASH_VENDOR: EfuseField = EfuseField::new(1, 2, 80, 3); +/// Psram capacity +pub const PSRAM_CAP: EfuseField = EfuseField::new(1, 2, 83, 3); +/// Psram vendor +pub const PSRAM_VENDOR: EfuseField = EfuseField::new(1, 2, 86, 2); +/// Temp (die embedded inside) +pub const TEMP: EfuseField = EfuseField::new(1, 2, 88, 2); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 2, 90, 3); +/// PADC CAL PA trim version +pub const PA_TRIM_VERSION: EfuseField = EfuseField::new(1, 2, 93, 3); +/// PADC CAL N bias +pub const TRIM_N_BIAS: EfuseField = EfuseField::new(1, 3, 96, 5); +/// PADC CAL P bias +pub const TRIM_P_BIAS: EfuseField = EfuseField::new(1, 3, 101, 5); +/// Active HP DBIAS of fixed voltage +pub const ACTIVE_HP_DBIAS: EfuseField = EfuseField::new(1, 3, 106, 4); +/// Active LP DBIAS of fixed voltage +pub const ACTIVE_LP_DBIAS: EfuseField = EfuseField::new(1, 3, 110, 4); +/// LSLP HP DBG of fixed voltage +pub const LSLP_HP_DBG: EfuseField = EfuseField::new(1, 3, 114, 2); +/// LSLP HP DBIAS of fixed voltage +pub const LSLP_HP_DBIAS: EfuseField = EfuseField::new(1, 3, 116, 4); +/// DSLP LP DBG of fixed voltage +pub const DSLP_LP_DBG: EfuseField = EfuseField::new(1, 3, 120, 4); +/// DSLP LP DBIAS of fixed voltage +pub const DSLP_LP_DBIAS: EfuseField = EfuseField::new(1, 3, 124, 5); +/// DBIAS gap between LP and HP +pub const LP_HP_DBIAS_VOL_GAP: EfuseField = EfuseField::new(1, 4, 129, 5); +/// REF PADC Calibration Curr +pub const REF_CURR_CODE: EfuseField = EfuseField::new(1, 4, 134, 4); +/// RES PADC Calibration Tune +pub const RES_TUNE_CODE: EfuseField = EfuseField::new(1, 4, 138, 5); +/// reserved +pub const RESERVED_1_143: EfuseField = EfuseField::new(1, 4, 143, 17); +/// Represents the third 32-bit of zeroth part of system data +pub const SYS_DATA_PART0_2: EfuseField = EfuseField::new(1, 5, 160, 32); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// Temperature calibration data +pub const TEMPERATURE_SENSOR: EfuseField = EfuseField::new(2, 4, 128, 9); +/// ADC OCode +pub const OCODE: EfuseField = EfuseField::new(2, 4, 137, 8); +/// Average initcode of ADC1 atten0 +pub const ADC1_AVE_INITCODE_ATTEN0: EfuseField = EfuseField::new(2, 4, 145, 10); +/// Average initcode of ADC1 atten0 +pub const ADC1_AVE_INITCODE_ATTEN1: EfuseField = EfuseField::new(2, 4, 155, 10); +/// Average initcode of ADC1 atten0 +pub const ADC1_AVE_INITCODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 165, 10); +/// Average initcode of ADC1 atten0 +pub const ADC1_AVE_INITCODE_ATTEN3: EfuseField = EfuseField::new(2, 5, 175, 10); +/// HI DOUT of ADC1 atten0 +pub const ADC1_HI_DOUT_ATTEN0: EfuseField = EfuseField::new(2, 5, 185, 10); +/// HI DOUT of ADC1 atten1 +pub const ADC1_HI_DOUT_ATTEN1: EfuseField = EfuseField::new(2, 6, 195, 10); +/// HI DOUT of ADC1 atten2 +pub const ADC1_HI_DOUT_ATTEN2: EfuseField = EfuseField::new(2, 6, 205, 10); +/// HI DOUT of ADC1 atten3 +pub const ADC1_HI_DOUT_ATTEN3: EfuseField = EfuseField::new(2, 6, 215, 10); +/// Gap between ADC1 CH0 and average initcode +pub const ADC1_CH0_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 225, 4); +/// Gap between ADC1 CH1 and average initcode +pub const ADC1_CH1_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 229, 4); +/// Gap between ADC1 CH2 and average initcode +pub const ADC1_CH2_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 233, 4); +/// Gap between ADC1 CH3 and average initcode +pub const ADC1_CH3_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 237, 4); +/// Gap between ADC1 CH4 and average initcode +pub const ADC1_CH4_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 241, 4); +/// Gap between ADC1 CH5 and average initcode +pub const ADC1_CH5_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 245, 4); +/// reserved +pub const RESERVED_2_249: EfuseField = EfuseField::new(2, 7, 249, 7); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32c5/mod.rs b/esp-hal/src/efuse/esp32c5/mod.rs new file mode 100644 index 00000000000..d66e8a9ba9e --- /dev/null +++ b/esp-hal/src/efuse/esp32c5/mod.rs @@ -0,0 +1,99 @@ +use crate::peripherals::EFUSE; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// see +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + // see + // + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// see +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (_major, minor) = block_version(); + if minor >= 1 { 1 } else { 0 } +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MINOR) +} + +/// Returns the frequency of the crystal oscillator in MHz +#[instability::unstable] +pub fn xtal_frequency_mhz() -> u32 { + let sel = super::read_field_le::(XTAL_48M_SEL); + if sel.count_ones().is_multiple_of(2) { + 40 + } else { + 48 + } +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis0().as_ptr(), + Self::Block1 => efuse.rd_mac_sys0().as_ptr(), + Self::Block2 => efuse.rd_sys_part1_data(0).as_ptr(), + Self::Block3 => efuse.rd_usr_data(0).as_ptr(), + Self::Block4 => efuse.rd_key0_data(0).as_ptr(), + Self::Block5 => efuse.rd_key1_data(0).as_ptr(), + Self::Block6 => efuse.rd_key2_data(0).as_ptr(), + Self::Block7 => efuse.rd_key3_data(0).as_ptr(), + Self::Block8 => efuse.rd_key4_data(0).as_ptr(), + Self::Block9 => efuse.rd_key5_data(0).as_ptr(), + Self::Block10 => efuse.rd_sys_part2_data(0).as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32c6/fields.rs b/esp-hal/src/efuse/esp32c6/fields.rs new file mode 100644 index 00000000000..703da90396b --- /dev/null +++ b/esp-hal/src/efuse/esp32c6/fields.rs @@ -0,0 +1,259 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: df46b69f0ed3913114ba53d3a0b2b843 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Represents whether pad of uart and sdio is swapped or not. 1: swapped. 0: +/// not swapped +pub const SWAP_UART_SDIO_EN: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Represents whether icache is disabled or enabled. 1: disabled. 0: enabled +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Represents whether the function of usb switch to jtag is disabled or +/// enabled. 1: disabled. 0: enabled +pub const DIS_USB_JTAG: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Represents whether icache is disabled or enabled in Download mode. 1: +/// disabled. 0: enabled +pub const DIS_DOWNLOAD_ICACHE: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Represents whether USB-Serial-JTAG is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Represents whether the function that forces chip into download mode is +/// disabled or enabled. 1: disabled. 0: enabled +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Represents whether SPI0 controller during boot_mode_download is disabled or +/// enabled. 1: disabled. 0: enabled +pub const SPI_DOWNLOAD_MSPI_DIS: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Represents whether TWAI function is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Represents whether the selection between usb_to_jtag and pad_to_jtag through +/// strapping gpio15 when both EFUSE_DIS_PAD_JTAG and EFUSE_DIS_USB_JTAG are +/// equal to 0 is enabled or disabled. 1: enabled. 0: disabled +pub const JTAG_SEL_ENABLE: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Represents whether JTAG is disabled in soft way. Odd number: disabled. Even +/// number: enabled +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 48, 3); +/// Represents whether JTAG is disabled in the hard way(permanently). 1: +/// disabled. 0: enabled +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Represents whether flash encrypt function is disabled or enabled(except in +/// SPI boot mode). 1: disabled. 0: enabled +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 52, 1); +/// Represents the single-end input threshold vrefh; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 53, 2); +/// Represents the single-end input threshold vrefl; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 55, 2); +/// Represents whether the D+ and D- pins is exchanged. 1: exchanged. 0: not +/// exchanged +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 57, 1); +/// Represents whether vdd spi pin is functioned as gpio. 1: functioned. 0: not +/// functioned +pub const VDD_SPI_AS_GPIO: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Reserved +pub const RPT4_RESERVED0_2: EfuseField = EfuseField::new(0, 1, 59, 2); +/// Reserved +pub const RPT4_RESERVED0_1: EfuseField = EfuseField::new(0, 1, 61, 1); +/// Reserved +pub const RPT4_RESERVED0_0: EfuseField = EfuseField::new(0, 1, 62, 2); +/// Reserved +pub const RPT4_RESERVED1_0: EfuseField = EfuseField::new(0, 2, 64, 16); +/// Represents whether RTC watchdog timeout threshold is selected at startup. 1: +/// selected. 0: not selected +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 2, 80, 2); +/// Enables flash encryption when 1 or 3 bits are set and disables otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 82, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 86, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 87, 1); +/// Represents the purpose of Key0 +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 88, 4); +/// Represents the purpose of Key1 +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 92, 4); +/// Represents the purpose of Key2 +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 4); +/// Represents the purpose of Key3 +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 100, 4); +/// Represents the purpose of Key4 +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 104, 4); +/// Represents the purpose of Key5 +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 108, 4); +/// Represents the spa secure level by configuring the clock random divide mode +pub const SEC_DPA_LEVEL: EfuseField = EfuseField::new(0, 3, 112, 2); +/// Represents whether anti-dpa attack is enabled. 1:enabled. 0: disabled +pub const CRYPT_DPA_ENABLE: EfuseField = EfuseField::new(0, 3, 114, 1); +/// Reserved +pub const RPT4_RESERVED2_1: EfuseField = EfuseField::new(0, 3, 115, 1); +/// Represents whether secure boot is enabled or disabled. 1: enabled. 0: +/// disabled +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 116, 1); +/// Represents whether revoking aggressive secure boot is enabled or disabled. +/// 1: enabled. 0: disabled +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 117, 1); +/// Reserved +pub const RPT4_RESERVED2_0: EfuseField = EfuseField::new(0, 3, 118, 6); +/// Represents the flash waiting time after power-up; in unit of ms. When the +/// value less than 15; the waiting time is the programmed value. Otherwise; the +/// waiting time is 2 times the programmed value +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Represents whether Download mode is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Represents whether direct boot mode is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// Represents whether print from USB-Serial-JTAG is disabled or enabled. 1: +/// disabled. 0: enabled +pub const DIS_USB_SERIAL_JTAG_ROM_PRINT: EfuseField = EfuseField::new(0, 4, 130, 1); +/// Reserved +pub const RPT4_RESERVED3_5: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Represents whether the USB-Serial-JTAG download function is disabled or +/// enabled. 1: disabled. 0: enabled +pub const DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Represents whether security download is enabled or disabled. 1: enabled. 0: +/// disabled +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UARTboot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// Reserved +pub const RPT4_RESERVED3_4: EfuseField = EfuseField::new(0, 4, 136, 1); +/// Reserved +pub const RPT4_RESERVED3_3: EfuseField = EfuseField::new(0, 4, 137, 1); +/// Reserved +pub const RPT4_RESERVED3_2: EfuseField = EfuseField::new(0, 4, 138, 2); +/// Reserved +pub const RPT4_RESERVED3_1: EfuseField = EfuseField::new(0, 4, 140, 1); +/// Represents whether ROM code is forced to send a resume command during SPI +/// boot. 1: forced. 0:not forced +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 141, 1); +/// Represents the version used by ESP-IDF anti-rollback feature +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 142, 16); +/// Represents whether FAST VERIFY ON WAKE is disabled or enabled when Secure +/// Boot is enabled. 1: disabled. 0: enabled +pub const SECURE_BOOT_DISABLE_FAST_WAKE: EfuseField = EfuseField::new(0, 4, 158, 1); +/// Reserved +pub const RPT4_RESERVED3_0: EfuseField = EfuseField::new(0, 4, 159, 1); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 160, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 161, 1); +/// reserved +pub const RESERVED_0_162: EfuseField = EfuseField::new(0, 5, 162, 22); +/// Reserved +pub const RPT4_RESERVED4_0: EfuseField = EfuseField::new(0, 5, 184, 8); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// Stores the extended bits of MAC address +pub const MAC_EXT: EfuseField = EfuseField::new(1, 1, 48, 16); +/// Stores the active hp dbias +pub const ACTIVE_HP_DBIAS: EfuseField = EfuseField::new(1, 2, 64, 5); +/// Stores the active lp dbias +pub const ACTIVE_LP_DBIAS: EfuseField = EfuseField::new(1, 2, 69, 5); +/// Stores the lslp hp dbg +pub const LSLP_HP_DBG: EfuseField = EfuseField::new(1, 2, 74, 2); +/// Stores the lslp hp dbias +pub const LSLP_HP_DBIAS: EfuseField = EfuseField::new(1, 2, 76, 4); +/// Stores the dslp lp dbg +pub const DSLP_LP_DBG: EfuseField = EfuseField::new(1, 2, 80, 3); +/// Stores the dslp lp dbias +pub const DSLP_LP_DBIAS: EfuseField = EfuseField::new(1, 2, 83, 4); +/// Stores the hp and lp dbias vol gap +pub const DBIAS_VOL_GAP: EfuseField = EfuseField::new(1, 2, 87, 5); +/// Stores the first part of SPI_PAD_CONF +pub const SPI_PAD_CONF_1: EfuseField = EfuseField::new(1, 2, 92, 4); +/// Stores the second part of SPI_PAD_CONF +pub const SPI_PAD_CONF_2: EfuseField = EfuseField::new(1, 3, 96, 18); +/// +pub const WAFER_VERSION_MINOR: EfuseField = EfuseField::new(1, 3, 114, 4); +/// +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 118, 2); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 3, 120, 3); +/// BLK_VERSION_MINOR of BLOCK2 +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(1, 3, 123, 3); +/// BLK_VERSION_MAJOR of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 126, 2); +/// +pub const FLASH_CAP: EfuseField = EfuseField::new(1, 4, 128, 3); +/// +pub const FLASH_TEMP: EfuseField = EfuseField::new(1, 4, 131, 2); +/// +pub const FLASH_VENDOR: EfuseField = EfuseField::new(1, 4, 133, 3); +/// reserved +pub const RESERVED_1_136: EfuseField = EfuseField::new(1, 4, 136, 24); +/// Stores the second 32 bits of the zeroth part of system data +pub const SYS_DATA_PART0_2: EfuseField = EfuseField::new(1, 5, 160, 32); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 4, 128, 9); +/// ADC OCode +pub const OCODE: EfuseField = EfuseField::new(2, 4, 137, 8); +/// ADC1 init code at atten0 +pub const ADC1_INIT_CODE_ATTEN0: EfuseField = EfuseField::new(2, 4, 145, 10); +/// ADC1 init code at atten1 +pub const ADC1_INIT_CODE_ATTEN1: EfuseField = EfuseField::new(2, 4, 155, 10); +/// ADC1 init code at atten2 +pub const ADC1_INIT_CODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 165, 10); +/// ADC1 init code at atten3 +pub const ADC1_INIT_CODE_ATTEN3: EfuseField = EfuseField::new(2, 5, 175, 10); +/// ADC1 calibration voltage at atten0 +pub const ADC1_CAL_VOL_ATTEN0: EfuseField = EfuseField::new(2, 5, 185, 10); +/// ADC1 calibration voltage at atten1 +pub const ADC1_CAL_VOL_ATTEN1: EfuseField = EfuseField::new(2, 6, 195, 10); +/// ADC1 calibration voltage at atten2 +pub const ADC1_CAL_VOL_ATTEN2: EfuseField = EfuseField::new(2, 6, 205, 10); +/// ADC1 calibration voltage at atten3 +pub const ADC1_CAL_VOL_ATTEN3: EfuseField = EfuseField::new(2, 6, 215, 10); +/// ADC1 init code at atten0 ch0 +pub const ADC1_INIT_CODE_ATTEN0_CH0: EfuseField = EfuseField::new(2, 7, 225, 4); +/// ADC1 init code at atten0 ch1 +pub const ADC1_INIT_CODE_ATTEN0_CH1: EfuseField = EfuseField::new(2, 7, 229, 4); +/// ADC1 init code at atten0 ch2 +pub const ADC1_INIT_CODE_ATTEN0_CH2: EfuseField = EfuseField::new(2, 7, 233, 4); +/// ADC1 init code at atten0 ch3 +pub const ADC1_INIT_CODE_ATTEN0_CH3: EfuseField = EfuseField::new(2, 7, 237, 4); +/// ADC1 init code at atten0 ch4 +pub const ADC1_INIT_CODE_ATTEN0_CH4: EfuseField = EfuseField::new(2, 7, 241, 4); +/// ADC1 init code at atten0 ch5 +pub const ADC1_INIT_CODE_ATTEN0_CH5: EfuseField = EfuseField::new(2, 7, 245, 4); +/// ADC1 init code at atten0 ch6 +pub const ADC1_INIT_CODE_ATTEN0_CH6: EfuseField = EfuseField::new(2, 7, 249, 4); +/// reserved +pub const RESERVED_2_253: EfuseField = EfuseField::new(2, 7, 253, 3); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32c6/mod.rs b/esp-hal/src/efuse/esp32c6/mod.rs new file mode 100644 index 00000000000..c8e4ba03526 --- /dev/null +++ b/esp-hal/src/efuse/esp32c6/mod.rs @@ -0,0 +1,160 @@ +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Selects which ADC we are interested in the efuse calibration data for +#[instability::unstable] +pub enum AdcCalibUnit { + /// Select efuse calibration data for ADC1 + ADC1, + /// Select efuse calibration data for ADC2 + ADC2, +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// see +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + // see + // + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// see +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (_major, minor) = block_version(); + if minor >= 1 { 1 } else { 0 } +} + +/// Get ADC initial code for specified attenuation from efuse +/// +/// see +#[instability::unstable] +pub fn rtc_calib_init_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // See + let init_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_INIT_CODE_ATTEN0, + Attenuation::_2p5dB => ADC1_INIT_CODE_ATTEN1, + Attenuation::_6dB => ADC1_INIT_CODE_ATTEN2, + Attenuation::_11dB => ADC1_INIT_CODE_ATTEN3, + }); + + Some(init_code + 1600) // version 1 logic +} + +/// Get ADC reference point voltage for specified attenuation in millivolts +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_mv(_unit: AdcCalibUnit, atten: Attenuation) -> u16 { + match atten { + Attenuation::_0dB => 400, + Attenuation::_2p5dB => 550, + Attenuation::_6dB => 750, + Attenuation::_11dB => 1370, + } +} + +/// Get ADC reference point digital code for specified attenuation +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + // See + let cal_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_CAL_VOL_ATTEN0, + Attenuation::_2p5dB => ADC1_CAL_VOL_ATTEN1, + Attenuation::_6dB => ADC1_CAL_VOL_ATTEN2, + Attenuation::_11dB => ADC1_CAL_VOL_ATTEN3, + }); + + let cal_code = if cal_code & (1 << 9) != 0 { + 1500 - (cal_code & !(1 << 9)) + } else { + 1500 + cal_code + }; + + Some(cal_code) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MINOR) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_mac_spi_sys_0().as_ptr(), + Self::Block2 => efuse.rd_sys_part1_data0().as_ptr(), + Self::Block3 => efuse.rd_usr_data0().as_ptr(), + Self::Block4 => efuse.rd_key0_data0().as_ptr(), + Self::Block5 => efuse.rd_key1_data0().as_ptr(), + Self::Block6 => efuse.rd_key2_data0().as_ptr(), + Self::Block7 => efuse.rd_key3_data0().as_ptr(), + Self::Block8 => efuse.rd_key4_data0().as_ptr(), + Self::Block9 => efuse.rd_key5_data0().as_ptr(), + Self::Block10 => efuse.rd_sys_part2_data0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32h2/fields.rs b/esp-hal/src/efuse/esp32h2/fields.rs new file mode 100644 index 00000000000..fe4bed5c052 --- /dev/null +++ b/esp-hal/src/efuse/esp32h2/fields.rs @@ -0,0 +1,249 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: 44563d2af4ebdba4db6c0a34a50c94f9 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Reserved +pub const RPT4_RESERVED0_4: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Represents whether icache is disabled or enabled. 1: disabled. 0: enabled +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Represents whether the function of usb switch to jtag is disabled or +/// enabled. 1: disabled. 0: enabled +pub const DIS_USB_JTAG: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Represents whether power glitch function is enabled. 1: enabled. 0: disabled +pub const POWERGLITCH_EN: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Represents whether USB-Serial-JTAG is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Represents whether the function that forces chip into download mode is +/// disabled or enabled. 1: disabled. 0: enabled +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Represents whether SPI0 controller during boot_mode_download is disabled or +/// enabled. 1: disabled. 0: enabled +pub const SPI_DOWNLOAD_MSPI_DIS: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Represents whether TWAI function is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Set this bit to enable selection between usb_to_jtag and pad_to_jtag through +/// strapping gpio25 when both EFUSE_DIS_PAD_JTAG and EFUSE_DIS_USB_JTAG are +/// equal to 0 +pub const JTAG_SEL_ENABLE: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Represents whether JTAG is disabled in soft way. Odd number: disabled. Even +/// number: enabled +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 48, 3); +/// Represents whether JTAG is disabled in the hard way(permanently). 1: +/// disabled. 0: enabled +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Represents whether flash encrypt function is disabled or enabled(except in +/// SPI boot mode). 1: disabled. 0: enabled +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 52, 1); +/// Represents the single-end input threshold vrefh; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 53, 2); +/// Represents the single-end input threshold vrefl; 1.76 V to 2 V with step of +/// 80 mV +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 55, 2); +/// Represents whether the D+ and D- pins is exchanged. 1: exchanged. 0: not +/// exchanged +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 57, 1); +/// Represents whether vdd spi pin is functioned as gpio. 1: functioned. 0: not +/// functioned +pub const VDD_SPI_AS_GPIO: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Configures the curve of ECDSA calculation: 0: only enable P256. 1: only +/// enable P192. 2: both enable P256 and P192. 3: only enable P256 +pub const ECDSA_CURVE_MODE: EfuseField = EfuseField::new(0, 1, 59, 2); +/// Set this bit to permanently turn on ECC const-time mode +pub const ECC_FORCE_CONST_TIME: EfuseField = EfuseField::new(0, 1, 61, 1); +/// Set this bit to control the xts pseudo-round anti-dpa attack function: 0: +/// controlled by register. 1-3: the higher the value is; the more pseudo-rounds +/// are inserted to the xts-aes calculation +pub const XTS_DPA_PSEUDO_LEVEL: EfuseField = EfuseField::new(0, 1, 62, 2); +/// Reserved +pub const RPT4_RESERVED1_1: EfuseField = EfuseField::new(0, 2, 64, 16); +/// Represents whether RTC watchdog timeout threshold is selected at startup. 1: +/// selected. 0: not selected +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 2, 80, 2); +/// Enables flash encryption when 1 or 3 bits are set and disables otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 82, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 86, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 87, 1); +/// Represents the purpose of Key0 +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 88, 4); +/// Represents the purpose of Key1 +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 92, 4); +/// Represents the purpose of Key2 +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 4); +/// Represents the purpose of Key3 +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 100, 4); +/// Represents the purpose of Key4 +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 104, 4); +/// Represents the purpose of Key5 +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 108, 4); +/// Represents the spa secure level by configuring the clock random divide mode +pub const SEC_DPA_LEVEL: EfuseField = EfuseField::new(0, 3, 112, 2); +/// Reserved +pub const RESERVE_0_114: EfuseField = EfuseField::new(0, 3, 114, 1); +/// Represents whether anti-dpa attack is enabled. 1:enabled. 0: disabled +pub const CRYPT_DPA_ENABLE: EfuseField = EfuseField::new(0, 3, 115, 1); +/// Represents whether secure boot is enabled or disabled. 1: enabled. 0: +/// disabled +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 116, 1); +/// Represents whether revoking aggressive secure boot is enabled or disabled. +/// 1: enabled. 0: disabled +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 117, 1); +/// Set these bits to enable power glitch function when chip power on +pub const POWERGLITCH_EN1: EfuseField = EfuseField::new(0, 3, 118, 5); +/// reserved +pub const RESERVED_0_123: EfuseField = EfuseField::new(0, 3, 123, 1); +/// Represents the flash waiting time after power-up; in unit of ms. When the +/// value less than 15; the waiting time is the programmed value. Otherwise; the +/// waiting time is 2 times the programmed value +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Represents whether Download mode is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Represents whether direct boot mode is disabled or enabled. 1: disabled. 0: +/// enabled +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// Set this bit to disable USB-Serial-JTAG print during rom boot +pub const DIS_USB_SERIAL_JTAG_ROM_PRINT: EfuseField = EfuseField::new(0, 4, 130, 1); +/// Reserved +pub const RPT4_RESERVED3_5: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Represents whether the USB-Serial-JTAG download function is disabled or +/// enabled. 1: disabled. 0: enabled +pub const DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Represents whether security download is enabled or disabled. 1: enabled. 0: +/// disabled +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UARTboot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// Represents whether ROM code is forced to send a resume command during SPI +/// boot. 1: forced. 0:not forced +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 136, 1); +/// Represents the version used by ESP-IDF anti-rollback feature +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 137, 16); +/// Represents whether FAST VERIFY ON WAKE is disabled or enabled when Secure +/// Boot is enabled. 1: disabled. 0: enabled +pub const SECURE_BOOT_DISABLE_FAST_WAKE: EfuseField = EfuseField::new(0, 4, 153, 1); +/// Set bits to enable hysteresis function of PAD0~5 +pub const HYS_EN_PAD0: EfuseField = EfuseField::new(0, 4, 154, 6); +/// Set bits to enable hysteresis function of PAD6~27 +pub const HYS_EN_PAD1: EfuseField = EfuseField::new(0, 5, 160, 22); +/// Reserved +pub const RPT4_RESERVED4_1: EfuseField = EfuseField::new(0, 5, 182, 2); +/// Reserved +pub const RPT4_RESERVED4_0: EfuseField = EfuseField::new(0, 5, 184, 8); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// Stores the extended bits of MAC address +pub const MAC_EXT: EfuseField = EfuseField::new(1, 1, 48, 16); +/// Stores RF Calibration data. RXIQ version +pub const RXIQ_VERSION: EfuseField = EfuseField::new(1, 2, 64, 3); +/// Stores RF Calibration data. RXIQ data 0 +pub const RXIQ_0: EfuseField = EfuseField::new(1, 2, 67, 7); +/// Stores RF Calibration data. RXIQ data 1 +pub const RXIQ_1: EfuseField = EfuseField::new(1, 2, 74, 7); +/// Stores the PMU active hp dbias +pub const ACTIVE_HP_DBIAS: EfuseField = EfuseField::new(1, 2, 81, 5); +/// Stores the PMU active lp dbias +pub const ACTIVE_LP_DBIAS: EfuseField = EfuseField::new(1, 2, 86, 5); +/// Stores the PMU sleep dbias +pub const DSLP_DBIAS: EfuseField = EfuseField::new(1, 2, 91, 4); +/// Stores the low 1 bit of dbias_vol_gap +pub const DBIAS_VOL_GAP: EfuseField = EfuseField::new(1, 2, 95, 5); +/// Reserved +pub const MAC_RESERVED_2: EfuseField = EfuseField::new(1, 3, 100, 14); +/// Stores the wafer version minor +pub const WAFER_VERSION_MINOR: EfuseField = EfuseField::new(1, 3, 114, 3); +/// Stores the wafer version major +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 117, 2); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 119, 1); +/// Stores the flash cap +pub const FLASH_CAP: EfuseField = EfuseField::new(1, 3, 120, 3); +/// Stores the flash temp +pub const FLASH_TEMP: EfuseField = EfuseField::new(1, 3, 123, 2); +/// Stores the flash vendor +pub const FLASH_VENDOR: EfuseField = EfuseField::new(1, 3, 125, 3); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 4, 128, 3); +/// reserved +pub const RESERVED_1_131: EfuseField = EfuseField::new(1, 4, 131, 29); +/// Stores the second 32 bits of the zeroth part of system data +pub const SYS_DATA_PART0_2: EfuseField = EfuseField::new(1, 5, 160, 32); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// reserved +pub const RESERVED_2_128: EfuseField = EfuseField::new(2, 4, 128, 2); +/// BLK_VERSION_MINOR of BLOCK2. 1: RF Calibration data in BLOCK1 +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(2, 4, 130, 3); +/// BLK_VERSION_MAJOR of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(2, 4, 133, 2); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(2, 4, 135, 1); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 4, 136, 9); +/// ADC1 calibration data +pub const ADC1_AVE_INITCODE_ATTEN0: EfuseField = EfuseField::new(2, 4, 145, 10); +/// ADC1 calibration data +pub const ADC1_AVE_INITCODE_ATTEN1: EfuseField = EfuseField::new(2, 4, 155, 10); +/// ADC1 calibration data +pub const ADC1_AVE_INITCODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 165, 10); +/// ADC1 calibration data +pub const ADC1_AVE_INITCODE_ATTEN3: EfuseField = EfuseField::new(2, 5, 175, 10); +/// ADC1 calibration data +pub const ADC1_HI_DOUT_ATTEN0: EfuseField = EfuseField::new(2, 5, 185, 10); +/// ADC1 calibration data +pub const ADC1_HI_DOUT_ATTEN1: EfuseField = EfuseField::new(2, 6, 195, 10); +/// ADC1 calibration data +pub const ADC1_HI_DOUT_ATTEN2: EfuseField = EfuseField::new(2, 6, 205, 10); +/// ADC1 calibration data +pub const ADC1_HI_DOUT_ATTEN3: EfuseField = EfuseField::new(2, 6, 215, 10); +/// ADC1 calibration data +pub const ADC1_CH0_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 225, 4); +/// ADC1 calibration data +pub const ADC1_CH1_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 229, 4); +/// ADC1 calibration data +pub const ADC1_CH2_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 233, 4); +/// ADC1 calibration data +pub const ADC1_CH3_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 237, 4); +/// ADC1 calibration data +pub const ADC1_CH4_ATTEN0_INITCODE_DIFF: EfuseField = EfuseField::new(2, 7, 241, 4); +/// reserved +pub const RESERVED_2_245: EfuseField = EfuseField::new(2, 7, 245, 11); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32h2/mod.rs b/esp-hal/src/efuse/esp32h2/mod.rs new file mode 100644 index 00000000000..7baddf6987b --- /dev/null +++ b/esp-hal/src/efuse/esp32h2/mod.rs @@ -0,0 +1,160 @@ +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Selects which ADC we are interested in the efuse calibration data for +#[instability::unstable] +pub enum AdcCalibUnit { + /// Select efuse calibration data for ADC1 + ADC1, +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// See: +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// See: +/// //esp_efuse_rtc_calib_get_ver +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (_major, minor) = block_version(); + if minor >= 2 { 1 } else { 0 } +} + +/// Get ADC initial code for specified attenuation from efuse +/// +/// See: +#[instability::unstable] +pub fn rtc_calib_init_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version > 4 { + return None; + } + + // See: + let init_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_AVE_INITCODE_ATTEN0, + Attenuation::_2p5dB => ADC1_AVE_INITCODE_ATTEN1, + Attenuation::_6dB => ADC1_AVE_INITCODE_ATTEN2, + Attenuation::_11dB => ADC1_AVE_INITCODE_ATTEN3, + }); + + Some(init_code + 1600) // version 1 logic +} + +/// Get ADC reference point voltage for specified attenuation in millivolts +/// +/// See: +#[instability::unstable] +pub fn rtc_calib_cal_mv(_unit: AdcCalibUnit, atten: Attenuation) -> u16 { + const INPUT_VOUT_MV: [[u16; 4]; 1] = [ + [750, 1000, 1500, 2800], // Calibration V1 coefficients + ]; + + let version = rtc_calib_version(); + + // https://github.com/espressif/esp-idf/blob/master/components/efuse/esp32h2/include/esp_efuse_rtc_calib.h#L15C9-L17 + // ESP_EFUSE_ADC_CALIB_VER1 1 + // ESP_EFUSE_ADC_CALIB_VER_MIN ESP_EFUSE_ADC_CALIB_VER1 + // ESP_EFUSE_ADC_CALIB_VER_MAX ESP_EFUSE_ADC_CALIB_VER1 + if version != 1 { + // The required efuse bits for this chip are not burnt. + // 1100 is the middle of the reference voltage range. + // See: + return 1100; + } + + INPUT_VOUT_MV[version as usize - 1][atten as usize] +} + +/// Returns the call code +/// +/// See: +#[instability::unstable] +pub fn rtc_calib_cal_code(_unit: AdcCalibUnit, atten: Attenuation) -> Option { + let cal_code: u16 = super::read_field_le(match atten { + Attenuation::_0dB => ADC1_HI_DOUT_ATTEN0, + Attenuation::_2p5dB => ADC1_HI_DOUT_ATTEN1, + Attenuation::_6dB => ADC1_HI_DOUT_ATTEN2, + Attenuation::_11dB => ADC1_HI_DOUT_ATTEN3, + }); + let cal_code: u16 = if atten == Attenuation::_6dB { + 2970 + cal_code + } else { + 2900 + cal_code + }; + Some(cal_code) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MINOR) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_mac_sys_0().as_ptr(), + Self::Block2 => efuse.rd_sys_part1_data0().as_ptr(), + Self::Block3 => efuse.rd_usr_data0().as_ptr(), + Self::Block4 => efuse.rd_key0_data0().as_ptr(), + Self::Block5 => efuse.rd_key1_data0().as_ptr(), + Self::Block6 => efuse.rd_key2_data0().as_ptr(), + Self::Block7 => efuse.rd_key3_data0().as_ptr(), + Self::Block8 => efuse.rd_key4_data0().as_ptr(), + Self::Block9 => efuse.rd_key5_data0().as_ptr(), + Self::Block10 => efuse.rd_sys_part2_data0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32s2/fields.rs b/esp-hal/src/efuse/esp32s2/fields.rs new file mode 100644 index 00000000000..57fed9943d1 --- /dev/null +++ b/esp-hal/src/efuse/esp32s2/fields.rs @@ -0,0 +1,253 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: 888a61f6f500d9c7ee0aa32016b0bee7 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Reserved +pub const DIS_RTC_RAM_BOOT: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Set this bit to disable Icache +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Set this bit to disable Dcache +pub const DIS_DCACHE: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Disables Icache when SoC is in Download mode +pub const DIS_DOWNLOAD_ICACHE: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Disables Dcache when SoC is in Download mode +pub const DIS_DOWNLOAD_DCACHE: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Set this bit to disable the function that forces chip into download mode +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Set this bit to disable USB OTG function +pub const DIS_USB: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Set this bit to disable the TWAI Controller function +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Disables capability to Remap RAM to ROM address space +pub const DIS_BOOT_REMAP: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED5: EfuseField = EfuseField::new(0, 1, 48, 1); +/// Software disables JTAG. When software disabled; JTAG can be activated +/// temporarily by HMAC peripheral +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 49, 1); +/// Hardware disables JTAG permanently +pub const HARD_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 50, 1); +/// Disables flash encryption when in download boot modes +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Controls single-end input threshold vrefh; 1.76 V to 2 V with step of 80 mV; +/// stored in eFuse +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 52, 2); +/// Controls single-end input threshold vrefl; 0.8 V to 1.04 V with step of 80 +/// mV; stored in eFuse +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 54, 2); +/// Set this bit to exchange USB D+ and D- pins +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 56, 1); +/// Set this bit to enable external USB PHY +pub const USB_EXT_PHY_ENABLE: EfuseField = EfuseField::new(0, 1, 57, 1); +/// If set; forces USB BVALID to 1 +pub const USB_FORCE_NOPERSIST: EfuseField = EfuseField::new(0, 1, 58, 1); +/// BLOCK0 efuse version +pub const BLOCK0_VERSION: EfuseField = EfuseField::new(0, 1, 59, 2); +/// SPI regulator switches current limit mode +pub const VDD_SPI_MODECURLIM: EfuseField = EfuseField::new(0, 1, 61, 1); +/// SPI regulator high voltage reference +pub const VDD_SPI_DREFH: EfuseField = EfuseField::new(0, 1, 62, 2); +/// SPI regulator medium voltage reference +pub const VDD_SPI_DREFM: EfuseField = EfuseField::new(0, 2, 64, 2); +/// SPI regulator low voltage reference +pub const VDD_SPI_DREFL: EfuseField = EfuseField::new(0, 2, 66, 2); +/// If VDD_SPI_FORCE is 1; this value determines if the VDD_SPI regulator is +/// powered on +pub const VDD_SPI_XPD: EfuseField = EfuseField::new(0, 2, 68, 1); +/// If VDD_SPI_FORCE is 1; determines VDD_SPI voltage +pub const VDD_SPI_TIEH: EfuseField = EfuseField::new(0, 2, 69, 1); +/// Set this bit to use XPD_VDD_PSI_REG and VDD_SPI_TIEH to configure VDD_SPI +/// LDO +pub const VDD_SPI_FORCE: EfuseField = EfuseField::new(0, 2, 70, 1); +/// Set SPI regulator to 0 to configure init\[1:0\]=0 +pub const VDD_SPI_EN_INIT: EfuseField = EfuseField::new(0, 2, 71, 1); +/// Set SPI regulator to 1 to enable output current limit +pub const VDD_SPI_ENCURLIM: EfuseField = EfuseField::new(0, 2, 72, 1); +/// Tunes the current limit threshold of SPI regulator when tieh=0; about 800 +/// mA/(8+d) +pub const VDD_SPI_DCURLIM: EfuseField = EfuseField::new(0, 2, 73, 3); +/// Adds resistor from LDO output to ground +pub const VDD_SPI_INIT: EfuseField = EfuseField::new(0, 2, 76, 2); +/// Prevents SPI regulator from overshoot +pub const VDD_SPI_DCAP: EfuseField = EfuseField::new(0, 2, 78, 2); +/// RTC watchdog timeout threshold; in unit of slow clock cycle +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 2, 80, 2); +/// Enables flash encryption when 1 or 3 bits are set and disabled otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 82, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 86, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 87, 1); +/// Purpose of KEY0 +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 88, 4); +/// Purpose of KEY1 +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 92, 4); +/// Purpose of KEY2 +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 4); +/// Purpose of KEY3 +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 100, 4); +/// Purpose of KEY4 +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 104, 4); +/// Purpose of KEY5 +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 108, 4); +/// Purpose of KEY6 +pub const KEY_PURPOSE_6: EfuseField = EfuseField::new(0, 3, 112, 4); +/// Set this bit to enable secure boot +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 116, 1); +/// Set this bit to enable aggressive secure boot key revocation mode +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 117, 1); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED1: EfuseField = EfuseField::new(0, 3, 118, 6); +/// Configures flash startup delay after SoC power-up; in unit of (ms/2). When +/// the value is 15; delay is 7.5 ms +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Set this bit to disable all download boot modes +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Set this bit to disable Legacy SPI boot mode +pub const DIS_LEGACY_SPI_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// Selects the default UART for printing boot messages +pub const UART_PRINT_CHANNEL: EfuseField = EfuseField::new(0, 4, 130, 1); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED3: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Set this bit to disable use of USB OTG in UART download boot mode +pub const DIS_USB_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Set this bit to enable secure UART download mode (read/write flash only) +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UART boot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// Set default power supply for GPIO33-GPIO37; set when SPI flash is +/// initialized +pub const PIN_POWER_SELECTION: EfuseField = EfuseField::new(0, 4, 136, 1); +/// SPI flash type +pub const FLASH_TYPE: EfuseField = EfuseField::new(0, 4, 137, 1); +/// If set; forces ROM code to send an SPI flash resume command during SPI boot +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 138, 1); +/// Secure version (used by ESP-IDF anti-rollback feature) +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 139, 16); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED2: EfuseField = EfuseField::new(0, 4, 155, 5); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 160, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 161, 1); +/// reserved +pub const RESERVED_0_162: EfuseField = EfuseField::new(0, 5, 162, 30); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// SPI_PAD_configure CLK +pub const SPI_PAD_CONFIG_CLK: EfuseField = EfuseField::new(1, 1, 48, 6); +/// SPI_PAD_configure Q(D1) +pub const SPI_PAD_CONFIG_Q: EfuseField = EfuseField::new(1, 1, 54, 6); +/// SPI_PAD_configure D(D0) +pub const SPI_PAD_CONFIG_D: EfuseField = EfuseField::new(1, 1, 60, 6); +/// SPI_PAD_configure CS +pub const SPI_PAD_CONFIG_CS: EfuseField = EfuseField::new(1, 2, 66, 6); +/// SPI_PAD_configure HD(D3) +pub const SPI_PAD_CONFIG_HD: EfuseField = EfuseField::new(1, 2, 72, 6); +/// SPI_PAD_configure WP(D2) +pub const SPI_PAD_CONFIG_WP: EfuseField = EfuseField::new(1, 2, 78, 6); +/// SPI_PAD_configure DQS +pub const SPI_PAD_CONFIG_DQS: EfuseField = EfuseField::new(1, 2, 84, 6); +/// SPI_PAD_configure D4 +pub const SPI_PAD_CONFIG_D4: EfuseField = EfuseField::new(1, 2, 90, 6); +/// SPI_PAD_configure D5 +pub const SPI_PAD_CONFIG_D5: EfuseField = EfuseField::new(1, 3, 96, 6); +/// SPI_PAD_configure D6 +pub const SPI_PAD_CONFIG_D6: EfuseField = EfuseField::new(1, 3, 102, 6); +/// SPI_PAD_configure D7 +pub const SPI_PAD_CONFIG_D7: EfuseField = EfuseField::new(1, 3, 108, 6); +/// WAFER_VERSION_MAJOR +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 114, 2); +/// WAFER_VERSION_MINOR most significant bit +pub const WAFER_VERSION_MINOR_HI: EfuseField = EfuseField::new(1, 3, 116, 1); +/// Flash version +pub const FLASH_VERSION: EfuseField = EfuseField::new(1, 3, 117, 4); +/// BLK_VERSION_MAJOR +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(1, 3, 121, 2); +/// reserved +pub const RESERVED_1_123: EfuseField = EfuseField::new(1, 3, 123, 1); +/// PSRAM version +pub const PSRAM_VERSION: EfuseField = EfuseField::new(1, 3, 124, 4); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 4, 128, 4); +/// WAFER_VERSION_MINOR least significant bits +pub const WAFER_VERSION_MINOR_LO: EfuseField = EfuseField::new(1, 4, 132, 3); +/// reserved +pub const RESERVED_1_135: EfuseField = EfuseField::new(1, 4, 135, 25); +/// Stores the second part of the zeroth part of system data +pub const SYS_DATA_PART0_2: EfuseField = EfuseField::new(1, 5, 160, 32); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// 4 bit of ADC calibration +pub const ADC_CALIB: EfuseField = EfuseField::new(2, 4, 128, 4); +/// BLK_VERSION_MINOR of BLOCK2 +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(2, 4, 132, 3); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 4, 135, 9); +/// +pub const RTCCALIB_V1IDX_A10H: EfuseField = EfuseField::new(2, 4, 144, 8); +/// +pub const RTCCALIB_V1IDX_A11H: EfuseField = EfuseField::new(2, 4, 152, 8); +/// +pub const RTCCALIB_V1IDX_A12H: EfuseField = EfuseField::new(2, 5, 160, 8); +/// +pub const RTCCALIB_V1IDX_A13H: EfuseField = EfuseField::new(2, 5, 168, 8); +/// +pub const RTCCALIB_V1IDX_A20H: EfuseField = EfuseField::new(2, 5, 176, 8); +/// +pub const RTCCALIB_V1IDX_A21H: EfuseField = EfuseField::new(2, 5, 184, 8); +/// +pub const RTCCALIB_V1IDX_A22H: EfuseField = EfuseField::new(2, 6, 192, 8); +/// +pub const RTCCALIB_V1IDX_A23H: EfuseField = EfuseField::new(2, 6, 200, 8); +/// +pub const RTCCALIB_V1IDX_A10L: EfuseField = EfuseField::new(2, 6, 208, 6); +/// +pub const RTCCALIB_V1IDX_A11L: EfuseField = EfuseField::new(2, 6, 214, 6); +/// +pub const RTCCALIB_V1IDX_A12L: EfuseField = EfuseField::new(2, 6, 220, 6); +/// +pub const RTCCALIB_V1IDX_A13L: EfuseField = EfuseField::new(2, 7, 226, 6); +/// +pub const RTCCALIB_V1IDX_A20L: EfuseField = EfuseField::new(2, 7, 232, 6); +/// +pub const RTCCALIB_V1IDX_A21L: EfuseField = EfuseField::new(2, 7, 238, 6); +/// +pub const RTCCALIB_V1IDX_A22L: EfuseField = EfuseField::new(2, 7, 244, 6); +/// +pub const RTCCALIB_V1IDX_A23L: EfuseField = EfuseField::new(2, 7, 250, 6); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32s2/mod.rs b/esp-hal/src/efuse/esp32s2/mod.rs new file mode 100644 index 00000000000..3422972349e --- /dev/null +++ b/esp-hal/src/efuse/esp32s2/mod.rs @@ -0,0 +1,67 @@ +use crate::peripherals::EFUSE; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le::(WAFER_VERSION_MINOR_HI) << 3 + | super::read_field_le::(WAFER_VERSION_MINOR_LO) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_mac_spi_sys_0().as_ptr(), + Self::Block2 => efuse.rd_sys_data_part1_(0).as_ptr(), + Self::Block3 => efuse.rd_usr_data(0).as_ptr(), + Self::Block4 => efuse.rd_key0_data(0).as_ptr(), + Self::Block5 => efuse.rd_key1_data(0).as_ptr(), + Self::Block6 => efuse.rd_key2_data(0).as_ptr(), + Self::Block7 => efuse.rd_key3_data(0).as_ptr(), + Self::Block8 => efuse.rd_key4_data(0).as_ptr(), + Self::Block9 => efuse.rd_key5_data(0).as_ptr(), + Self::Block10 => efuse.rd_sys_data_part2_(0).as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/esp32s3/fields.rs b/esp-hal/src/efuse/esp32s3/fields.rs new file mode 100644 index 00000000000..13e79a7ef2d --- /dev/null +++ b/esp-hal/src/efuse/esp32s3/fields.rs @@ -0,0 +1,293 @@ +//! This file was automatically generated, please do not edit it manually! +//! +//! Generated: 2025-04-22 11:33 +//! Version: 7127dd097e72bb90d0b790d460993126 + +#![allow(clippy::empty_docs)] + +use crate::efuse::EfuseField; + +/// Disable programming of individual eFuses +pub const WR_DIS: EfuseField = EfuseField::new(0, 0, 0, 32); +/// Disable reading from BlOCK4-10 +pub const RD_DIS: EfuseField = EfuseField::new(0, 1, 32, 7); +/// Set this bit to disable boot from RTC RAM +pub const DIS_RTC_RAM_BOOT: EfuseField = EfuseField::new(0, 1, 39, 1); +/// Set this bit to disable Icache +pub const DIS_ICACHE: EfuseField = EfuseField::new(0, 1, 40, 1); +/// Set this bit to disable Dcache +pub const DIS_DCACHE: EfuseField = EfuseField::new(0, 1, 41, 1); +/// Set this bit to disable Icache in download mode (boot_mode\[3:0\] is 0; 1; +/// 2; 3; 6; 7) +pub const DIS_DOWNLOAD_ICACHE: EfuseField = EfuseField::new(0, 1, 42, 1); +/// Set this bit to disable Dcache in download mode ( boot_mode\[3:0\] is 0; 1; +/// 2; 3; 6; 7) +pub const DIS_DOWNLOAD_DCACHE: EfuseField = EfuseField::new(0, 1, 43, 1); +/// Set this bit to disable the function that forces chip into download mode +pub const DIS_FORCE_DOWNLOAD: EfuseField = EfuseField::new(0, 1, 44, 1); +/// Set this bit to disable USB function +pub const DIS_USB_OTG: EfuseField = EfuseField::new(0, 1, 45, 1); +/// Set this bit to disable CAN function +pub const DIS_TWAI: EfuseField = EfuseField::new(0, 1, 46, 1); +/// Disable app cpu +pub const DIS_APP_CPU: EfuseField = EfuseField::new(0, 1, 47, 1); +/// Set these bits to disable JTAG in the soft way (odd number 1 means disable +/// ). JTAG can be enabled in HMAC module +pub const SOFT_DIS_JTAG: EfuseField = EfuseField::new(0, 1, 48, 3); +/// Set this bit to disable JTAG in the hard way. JTAG is disabled permanently +pub const DIS_PAD_JTAG: EfuseField = EfuseField::new(0, 1, 51, 1); +/// Set this bit to disable flash encryption when in download boot modes +pub const DIS_DOWNLOAD_MANUAL_ENCRYPT: EfuseField = EfuseField::new(0, 1, 52, 1); +/// Controls single-end input threshold vrefh; 1.76 V to 2 V with step of 80 mV; +/// stored in eFuse +pub const USB_DREFH: EfuseField = EfuseField::new(0, 1, 53, 2); +/// Controls single-end input threshold vrefl; 0.8 V to 1.04 V with step of 80 +/// mV; stored in eFuse +pub const USB_DREFL: EfuseField = EfuseField::new(0, 1, 55, 2); +/// Set this bit to exchange USB D+ and D- pins +pub const USB_EXCHG_PINS: EfuseField = EfuseField::new(0, 1, 57, 1); +/// Set this bit to enable external PHY +pub const USB_EXT_PHY_ENABLE: EfuseField = EfuseField::new(0, 1, 58, 1); +/// Bluetooth GPIO signal output security level control +pub const BTLC_GPIO_ENABLE: EfuseField = EfuseField::new(0, 1, 59, 2); +/// SPI regulator switches current limit mode +pub const VDD_SPI_MODECURLIM: EfuseField = EfuseField::new(0, 1, 61, 1); +/// SPI regulator high voltage reference +pub const VDD_SPI_DREFH: EfuseField = EfuseField::new(0, 1, 62, 2); +/// SPI regulator medium voltage reference +pub const VDD_SPI_DREFM: EfuseField = EfuseField::new(0, 2, 64, 2); +/// SPI regulator low voltage reference +pub const VDD_SPI_DREFL: EfuseField = EfuseField::new(0, 2, 66, 2); +/// SPI regulator power up signal +pub const VDD_SPI_XPD: EfuseField = EfuseField::new(0, 2, 68, 1); +/// If VDD_SPI_FORCE is 1; determines VDD_SPI voltage +pub const VDD_SPI_TIEH: EfuseField = EfuseField::new(0, 2, 69, 1); +/// Set this bit and force to use the configuration of eFuse to configure +/// VDD_SPI +pub const VDD_SPI_FORCE: EfuseField = EfuseField::new(0, 2, 70, 1); +/// Set SPI regulator to 0 to configure init\[1:0\]=0 +pub const VDD_SPI_EN_INIT: EfuseField = EfuseField::new(0, 2, 71, 1); +/// Set SPI regulator to 1 to enable output current limit +pub const VDD_SPI_ENCURLIM: EfuseField = EfuseField::new(0, 2, 72, 1); +/// Tunes the current limit threshold of SPI regulator when tieh=0; about 800 +/// mA/(8+d) +pub const VDD_SPI_DCURLIM: EfuseField = EfuseField::new(0, 2, 73, 3); +/// Adds resistor from LDO output to ground +pub const VDD_SPI_INIT: EfuseField = EfuseField::new(0, 2, 76, 2); +/// Prevents SPI regulator from overshoot +pub const VDD_SPI_DCAP: EfuseField = EfuseField::new(0, 2, 78, 2); +/// RTC watchdog timeout threshold; in unit of slow clock cycle +pub const WDT_DELAY_SEL: EfuseField = EfuseField::new(0, 2, 80, 2); +/// Enables flash encryption when 1 or 3 bits are set and disabled otherwise +pub const SPI_BOOT_CRYPT_CNT: EfuseField = EfuseField::new(0, 2, 82, 3); +/// Revoke 1st secure boot key +pub const SECURE_BOOT_KEY_REVOKE0: EfuseField = EfuseField::new(0, 2, 85, 1); +/// Revoke 2nd secure boot key +pub const SECURE_BOOT_KEY_REVOKE1: EfuseField = EfuseField::new(0, 2, 86, 1); +/// Revoke 3rd secure boot key +pub const SECURE_BOOT_KEY_REVOKE2: EfuseField = EfuseField::new(0, 2, 87, 1); +/// Purpose of Key0 +pub const KEY_PURPOSE_0: EfuseField = EfuseField::new(0, 2, 88, 4); +/// Purpose of Key1 +pub const KEY_PURPOSE_1: EfuseField = EfuseField::new(0, 2, 92, 4); +/// Purpose of Key2 +pub const KEY_PURPOSE_2: EfuseField = EfuseField::new(0, 3, 96, 4); +/// Purpose of Key3 +pub const KEY_PURPOSE_3: EfuseField = EfuseField::new(0, 3, 100, 4); +/// Purpose of Key4 +pub const KEY_PURPOSE_4: EfuseField = EfuseField::new(0, 3, 104, 4); +/// Purpose of Key5 +pub const KEY_PURPOSE_5: EfuseField = EfuseField::new(0, 3, 108, 4); +/// Reserved (used for four backups method) +pub const RPT4_RESERVED0: EfuseField = EfuseField::new(0, 3, 112, 4); +/// Set this bit to enable secure boot +pub const SECURE_BOOT_EN: EfuseField = EfuseField::new(0, 3, 116, 1); +/// Set this bit to enable revoking aggressive secure boot +pub const SECURE_BOOT_AGGRESSIVE_REVOKE: EfuseField = EfuseField::new(0, 3, 117, 1); +/// Set this bit to disable function of usb switch to jtag in module of usb +/// device +pub const DIS_USB_JTAG: EfuseField = EfuseField::new(0, 3, 118, 1); +/// Set this bit to disable usb device +pub const DIS_USB_SERIAL_JTAG: EfuseField = EfuseField::new(0, 3, 119, 1); +/// Set this bit to enable selection between usb_to_jtag and pad_to_jtag through +/// strapping gpio3 when both reg_dis_usb_jtag and reg_dis_pad_jtag are equal to +/// 0 +pub const STRAP_JTAG_SEL: EfuseField = EfuseField::new(0, 3, 120, 1); +/// This bit is used to switch internal PHY and external PHY for USB OTG and USB +/// Device +pub const USB_PHY_SEL: EfuseField = EfuseField::new(0, 3, 121, 1); +/// Sample delay configuration of power glitch +pub const POWER_GLITCH_DSENSE: EfuseField = EfuseField::new(0, 3, 122, 2); +/// Configures flash waiting time after power-up; in unit of ms. If the value is +/// less than 15; the waiting time is the configurable value. Otherwise; the +/// waiting time is twice the configurable value +pub const FLASH_TPUW: EfuseField = EfuseField::new(0, 3, 124, 4); +/// Set this bit to disable download mode (boot_mode\[3:0\] = 0; 1; 2; 3; 6; 7) +pub const DIS_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 128, 1); +/// Disable direct boot mode +pub const DIS_DIRECT_BOOT: EfuseField = EfuseField::new(0, 4, 129, 1); +/// USB printing +pub const DIS_USB_SERIAL_JTAG_ROM_PRINT: EfuseField = EfuseField::new(0, 4, 130, 1); +/// Flash ECC mode in ROM +pub const FLASH_ECC_MODE: EfuseField = EfuseField::new(0, 4, 131, 1); +/// Set this bit to disable UART download mode through USB +pub const DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 132, 1); +/// Set this bit to enable secure UART download mode +pub const ENABLE_SECURITY_DOWNLOAD: EfuseField = EfuseField::new(0, 4, 133, 1); +/// Set the default UART boot message output mode +pub const UART_PRINT_CONTROL: EfuseField = EfuseField::new(0, 4, 134, 2); +/// Set default power supply for GPIO33-GPIO37; set when SPI flash is +/// initialized +pub const PIN_POWER_SELECTION: EfuseField = EfuseField::new(0, 4, 136, 1); +/// SPI flash type +pub const FLASH_TYPE: EfuseField = EfuseField::new(0, 4, 137, 1); +/// Set Flash page size +pub const FLASH_PAGE_SIZE: EfuseField = EfuseField::new(0, 4, 138, 2); +/// Set 1 to enable ECC for flash boot +pub const FLASH_ECC_EN: EfuseField = EfuseField::new(0, 4, 140, 1); +/// Set this bit to force ROM code to send a resume command during SPI boot +pub const FORCE_SEND_RESUME: EfuseField = EfuseField::new(0, 4, 141, 1); +/// Secure version (used by ESP-IDF anti-rollback feature) +pub const SECURE_VERSION: EfuseField = EfuseField::new(0, 4, 142, 16); +/// Set this bit to enable power glitch function +pub const POWERGLITCH_EN: EfuseField = EfuseField::new(0, 4, 158, 1); +/// Set this bit to disable download through USB-OTG +pub const DIS_USB_OTG_DOWNLOAD_MODE: EfuseField = EfuseField::new(0, 4, 159, 1); +/// Disables check of wafer version major +pub const DISABLE_WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 160, 1); +/// Disables check of blk version major +pub const DISABLE_BLK_VERSION_MAJOR: EfuseField = EfuseField::new(0, 5, 161, 1); +/// reserved +pub const RESERVED_0_162: EfuseField = EfuseField::new(0, 5, 162, 22); +/// MAC address +pub const MAC0: EfuseField = EfuseField::new(1, 0, 0, 32); +/// MAC address +pub const MAC1: EfuseField = EfuseField::new(1, 1, 32, 16); +/// SPI_PAD_configure CLK +pub const SPI_PAD_CONFIG_CLK: EfuseField = EfuseField::new(1, 1, 48, 6); +/// SPI_PAD_configure Q(D1) +pub const SPI_PAD_CONFIG_Q: EfuseField = EfuseField::new(1, 1, 54, 6); +/// SPI_PAD_configure D(D0) +pub const SPI_PAD_CONFIG_D: EfuseField = EfuseField::new(1, 1, 60, 6); +/// SPI_PAD_configure CS +pub const SPI_PAD_CONFIG_CS: EfuseField = EfuseField::new(1, 2, 66, 6); +/// SPI_PAD_configure HD(D3) +pub const SPI_PAD_CONFIG_HD: EfuseField = EfuseField::new(1, 2, 72, 6); +/// SPI_PAD_configure WP(D2) +pub const SPI_PAD_CONFIG_WP: EfuseField = EfuseField::new(1, 2, 78, 6); +/// SPI_PAD_configure DQS +pub const SPI_PAD_CONFIG_DQS: EfuseField = EfuseField::new(1, 2, 84, 6); +/// SPI_PAD_configure D4 +pub const SPI_PAD_CONFIG_D4: EfuseField = EfuseField::new(1, 2, 90, 6); +/// SPI_PAD_configure D5 +pub const SPI_PAD_CONFIG_D5: EfuseField = EfuseField::new(1, 3, 96, 6); +/// SPI_PAD_configure D6 +pub const SPI_PAD_CONFIG_D6: EfuseField = EfuseField::new(1, 3, 102, 6); +/// SPI_PAD_configure D7 +pub const SPI_PAD_CONFIG_D7: EfuseField = EfuseField::new(1, 3, 108, 6); +/// WAFER_VERSION_MINOR least significant bits +pub const WAFER_VERSION_MINOR_LO: EfuseField = EfuseField::new(1, 3, 114, 3); +/// Package version +pub const PKG_VERSION: EfuseField = EfuseField::new(1, 3, 117, 3); +/// BLK_VERSION_MINOR +pub const BLK_VERSION_MINOR: EfuseField = EfuseField::new(1, 3, 120, 3); +/// Flash capacity +pub const FLASH_CAP: EfuseField = EfuseField::new(1, 3, 123, 3); +/// Flash temperature +pub const FLASH_TEMP: EfuseField = EfuseField::new(1, 3, 126, 2); +/// Flash vendor +pub const FLASH_VENDOR: EfuseField = EfuseField::new(1, 4, 128, 3); +/// PSRAM capacity +pub const PSRAM_CAP: EfuseField = EfuseField::new(1, 4, 131, 2); +/// PSRAM temperature +pub const PSRAM_TEMP: EfuseField = EfuseField::new(1, 4, 133, 2); +/// PSRAM vendor +pub const PSRAM_VENDOR: EfuseField = EfuseField::new(1, 4, 135, 2); +/// reserved +pub const RESERVED_1_137: EfuseField = EfuseField::new(1, 4, 137, 4); +/// BLOCK1 K_RTC_LDO +pub const K_RTC_LDO: EfuseField = EfuseField::new(1, 4, 141, 7); +/// BLOCK1 K_DIG_LDO +pub const K_DIG_LDO: EfuseField = EfuseField::new(1, 4, 148, 7); +/// BLOCK1 voltage of rtc dbias20 +pub const V_RTC_DBIAS20: EfuseField = EfuseField::new(1, 4, 155, 8); +/// BLOCK1 voltage of digital dbias20 +pub const V_DIG_DBIAS20: EfuseField = EfuseField::new(1, 5, 163, 8); +/// BLOCK1 digital dbias when hvt +pub const DIG_DBIAS_HVT: EfuseField = EfuseField::new(1, 5, 171, 5); +/// reserved +pub const RESERVED_1_176: EfuseField = EfuseField::new(1, 5, 176, 3); +/// PSRAM capacity bit 3 +pub const PSRAM_CAP_3: EfuseField = EfuseField::new(1, 5, 179, 1); +/// reserved +pub const RESERVED_1_180: EfuseField = EfuseField::new(1, 5, 180, 3); +/// WAFER_VERSION_MINOR most significant bit +pub const WAFER_VERSION_MINOR_HI: EfuseField = EfuseField::new(1, 5, 183, 1); +/// WAFER_VERSION_MAJOR +pub const WAFER_VERSION_MAJOR: EfuseField = EfuseField::new(1, 5, 184, 2); +/// ADC2 calibration voltage at atten3 +pub const ADC2_CAL_VOL_ATTEN3: EfuseField = EfuseField::new(1, 5, 186, 6); +/// Optional unique 128-bit ID +pub const OPTIONAL_UNIQUE_ID: EfuseField = EfuseField::new(2, 0, 0, 128); +/// BLK_VERSION_MAJOR of BLOCK2 +pub const BLK_VERSION_MAJOR: EfuseField = EfuseField::new(2, 4, 128, 2); +/// reserved +pub const RESERVED_2_130: EfuseField = EfuseField::new(2, 4, 130, 2); +/// Temperature calibration data +pub const TEMP_CALIB: EfuseField = EfuseField::new(2, 4, 132, 9); +/// ADC OCode +pub const OCODE: EfuseField = EfuseField::new(2, 4, 141, 8); +/// ADC1 init code at atten0 +pub const ADC1_INIT_CODE_ATTEN0: EfuseField = EfuseField::new(2, 4, 149, 8); +/// ADC1 init code at atten1 +pub const ADC1_INIT_CODE_ATTEN1: EfuseField = EfuseField::new(2, 4, 157, 6); +/// ADC1 init code at atten2 +pub const ADC1_INIT_CODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 163, 6); +/// ADC1 init code at atten3 +pub const ADC1_INIT_CODE_ATTEN3: EfuseField = EfuseField::new(2, 5, 169, 6); +/// ADC2 init code at atten0 +pub const ADC2_INIT_CODE_ATTEN0: EfuseField = EfuseField::new(2, 5, 175, 8); +/// ADC2 init code at atten1 +pub const ADC2_INIT_CODE_ATTEN1: EfuseField = EfuseField::new(2, 5, 183, 6); +/// ADC2 init code at atten2 +pub const ADC2_INIT_CODE_ATTEN2: EfuseField = EfuseField::new(2, 5, 189, 6); +/// ADC2 init code at atten3 +pub const ADC2_INIT_CODE_ATTEN3: EfuseField = EfuseField::new(2, 6, 195, 6); +/// ADC1 calibration voltage at atten0 +pub const ADC1_CAL_VOL_ATTEN0: EfuseField = EfuseField::new(2, 6, 201, 8); +/// ADC1 calibration voltage at atten1 +pub const ADC1_CAL_VOL_ATTEN1: EfuseField = EfuseField::new(2, 6, 209, 8); +/// ADC1 calibration voltage at atten2 +pub const ADC1_CAL_VOL_ATTEN2: EfuseField = EfuseField::new(2, 6, 217, 8); +/// ADC1 calibration voltage at atten3 +pub const ADC1_CAL_VOL_ATTEN3: EfuseField = EfuseField::new(2, 7, 225, 8); +/// ADC2 calibration voltage at atten0 +pub const ADC2_CAL_VOL_ATTEN0: EfuseField = EfuseField::new(2, 7, 233, 8); +/// ADC2 calibration voltage at atten1 +pub const ADC2_CAL_VOL_ATTEN1: EfuseField = EfuseField::new(2, 7, 241, 7); +/// ADC2 calibration voltage at atten2 +pub const ADC2_CAL_VOL_ATTEN2: EfuseField = EfuseField::new(2, 7, 248, 7); +/// reserved +pub const RESERVED_2_255: EfuseField = EfuseField::new(2, 7, 255, 1); +/// User data +pub const BLOCK_USR_DATA: EfuseField = EfuseField::new(3, 0, 0, 192); +/// reserved +pub const RESERVED_3_192: EfuseField = EfuseField::new(3, 6, 192, 8); +/// Custom MAC +pub const CUSTOM_MAC: EfuseField = EfuseField::new(3, 6, 200, 48); +/// reserved +pub const RESERVED_3_248: EfuseField = EfuseField::new(3, 7, 248, 8); +/// Key0 or user data +pub const BLOCK_KEY0: EfuseField = EfuseField::new(4, 0, 0, 256); +/// Key1 or user data +pub const BLOCK_KEY1: EfuseField = EfuseField::new(5, 0, 0, 256); +/// Key2 or user data +pub const BLOCK_KEY2: EfuseField = EfuseField::new(6, 0, 0, 256); +/// Key3 or user data +pub const BLOCK_KEY3: EfuseField = EfuseField::new(7, 0, 0, 256); +/// Key4 or user data +pub const BLOCK_KEY4: EfuseField = EfuseField::new(8, 0, 0, 256); +/// Key5 or user data +pub const BLOCK_KEY5: EfuseField = EfuseField::new(9, 0, 0, 256); +/// System data part 2 (reserved) +pub const BLOCK_SYS_DATA2: EfuseField = EfuseField::new(10, 0, 0, 256); diff --git a/esp-hal/src/efuse/esp32s3/mod.rs b/esp-hal/src/efuse/esp32s3/mod.rs new file mode 100644 index 00000000000..b76c4288e3e --- /dev/null +++ b/esp-hal/src/efuse/esp32s3/mod.rs @@ -0,0 +1,207 @@ +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; + +mod fields; +#[instability::unstable] +pub use fields::*; + +/// Selects which ADC we are interested in the efuse calibration data for +#[instability::unstable] +pub enum AdcCalibUnit { + /// Select efuse calibration data for ADC1 + ADC1, + /// Select efuse calibration data for ADC2 + ADC2, +} + +/// Get status of SPI boot encryption. +#[instability::unstable] +pub fn flash_encryption() -> bool { + !super::read_field_le::(SPI_BOOT_CRYPT_CNT) + .count_ones() + .is_multiple_of(2) +} + +/// Get the multiplier for the timeout value of the RWDT STAGE 0 register. +#[instability::unstable] +pub fn rwdt_multiplier() -> u8 { + super::read_field_le::(WDT_DELAY_SEL) +} + +/// Get efuse block version +/// +/// see +#[instability::unstable] +pub fn block_version() -> (u8, u8) { + // see + // + ( + super::read_field_le::(BLK_VERSION_MAJOR), + super::read_field_le::(BLK_VERSION_MINOR), + ) +} + +/// Get version of RTC calibration block +/// +/// see +#[instability::unstable] +pub fn rtc_calib_version() -> u8 { + let (major, _minor) = block_version(); + + if major == 1 { 1 } else { 0 } +} + +/// Get ADC initial code for specified attenuation from efuse +/// +/// see +#[instability::unstable] +pub fn rtc_calib_init_code(unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + let adc_icode_diff: [u16; 4] = match unit { + AdcCalibUnit::ADC1 => [ + super::read_field_le(ADC1_INIT_CODE_ATTEN0), + super::read_field_le(ADC1_INIT_CODE_ATTEN1), + super::read_field_le(ADC1_INIT_CODE_ATTEN2), + super::read_field_le(ADC1_INIT_CODE_ATTEN3), + ], + AdcCalibUnit::ADC2 => [ + super::read_field_le(ADC2_INIT_CODE_ATTEN0), + super::read_field_le(ADC2_INIT_CODE_ATTEN1), + super::read_field_le(ADC2_INIT_CODE_ATTEN2), + super::read_field_le(ADC2_INIT_CODE_ATTEN3), + ], + }; + + // Version 1 logic for calculating ADC ICode based on EFUSE burnt value + + let mut adc_icode = [0; 4]; + match unit { + AdcCalibUnit::ADC1 => { + adc_icode[0] = adc_icode_diff[0] + 1850; + adc_icode[1] = adc_icode_diff[1] + adc_icode[0] + 90; + adc_icode[2] = adc_icode_diff[2] + adc_icode[1]; + adc_icode[3] = adc_icode_diff[3] + adc_icode[2] + 70; + } + AdcCalibUnit::ADC2 => { + adc_icode[0] = adc_icode_diff[0] + 2020; + adc_icode[1] = adc_icode_diff[1] + adc_icode[0]; + adc_icode[2] = adc_icode_diff[2] + adc_icode[1]; + adc_icode[3] = adc_icode_diff[3] + adc_icode[2]; + } + } + + Some( + adc_icode[match atten { + Attenuation::_0dB => 0, + Attenuation::_2p5dB => 1, + Attenuation::_6dB => 2, + Attenuation::_11dB => 3, + }], + ) +} + +/// Get ADC reference point voltage for specified attenuation in millivolts +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_mv(_unit: AdcCalibUnit, _atten: Attenuation) -> u16 { + 850 +} + +/// Get ADC reference point digital code for specified attenuation +/// +/// see +#[instability::unstable] +pub fn rtc_calib_cal_code(unit: AdcCalibUnit, atten: Attenuation) -> Option { + let version = rtc_calib_version(); + + if version != 1 { + return None; + } + + let adc_vol_diff: [u16; 8] = [ + super::read_field_le(ADC1_CAL_VOL_ATTEN0), + super::read_field_le(ADC1_CAL_VOL_ATTEN1), + super::read_field_le(ADC1_CAL_VOL_ATTEN2), + super::read_field_le(ADC1_CAL_VOL_ATTEN3), + super::read_field_le(ADC2_CAL_VOL_ATTEN0), + super::read_field_le(ADC2_CAL_VOL_ATTEN1), + super::read_field_le(ADC2_CAL_VOL_ATTEN2), + super::read_field_le(ADC2_CAL_VOL_ATTEN3), + ]; + + let mut adc1_vol = [0; 4]; + let mut adc2_vol = [0; 4]; + adc1_vol[3] = adc_vol_diff[3] + 900; + adc1_vol[2] = adc_vol_diff[2] + adc1_vol[3] + 800; + adc1_vol[1] = adc_vol_diff[1] + adc1_vol[2] + 700; + adc1_vol[0] = adc_vol_diff[0] + adc1_vol[1] + 800; + adc2_vol[3] = adc1_vol[3] - adc_vol_diff[7] + 15; + adc2_vol[2] = adc1_vol[2] - adc_vol_diff[6] + 20; + adc2_vol[1] = adc1_vol[1] - adc_vol_diff[5] + 10; + adc2_vol[0] = adc1_vol[0] - adc_vol_diff[4] + 40; + + let atten = match atten { + Attenuation::_0dB => 0, + Attenuation::_2p5dB => 1, + Attenuation::_6dB => 2, + Attenuation::_11dB => 3, + }; + + Some(match unit { + AdcCalibUnit::ADC1 => adc1_vol[atten], + AdcCalibUnit::ADC2 => adc2_vol[atten], + }) +} + +/// Returns the major hardware revision +#[instability::unstable] +pub fn major_chip_version() -> u8 { + super::read_field_le(WAFER_VERSION_MAJOR) +} + +/// Returns the minor hardware revision +#[instability::unstable] +pub fn minor_chip_version() -> u8 { + super::read_field_le::(WAFER_VERSION_MINOR_HI) << 3 + | super::read_field_le::(WAFER_VERSION_MINOR_LO) +} + +#[derive(Debug, Clone, Copy, strum::FromRepr)] +#[repr(u32)] +pub(crate) enum EfuseBlock { + Block0, + Block1, + Block2, + Block3, + Block4, + Block5, + Block6, + Block7, + Block8, + Block9, + Block10, +} + +impl EfuseBlock { + pub(crate) fn address(self) -> *const u32 { + let efuse = EFUSE::regs(); + match self { + Self::Block0 => efuse.rd_wr_dis().as_ptr(), + Self::Block1 => efuse.rd_mac_spi_sys_0().as_ptr(), + Self::Block2 => efuse.rd_sys_part1_data0().as_ptr(), + Self::Block3 => efuse.rd_usr_data0().as_ptr(), + Self::Block4 => efuse.rd_key0_data0().as_ptr(), + Self::Block5 => efuse.rd_key1_data0().as_ptr(), + Self::Block6 => efuse.rd_key2_data0().as_ptr(), + Self::Block7 => efuse.rd_key3_data0().as_ptr(), + Self::Block8 => efuse.rd_key4_data0().as_ptr(), + Self::Block9 => efuse.rd_key5_data0().as_ptr(), + Self::Block10 => efuse.rd_sys_part2_data0().as_ptr(), + } + } +} diff --git a/esp-hal/src/efuse/mod.rs b/esp-hal/src/efuse/mod.rs new file mode 100644 index 00000000000..5d8c0ed4c7d --- /dev/null +++ b/esp-hal/src/efuse/mod.rs @@ -0,0 +1,387 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "interface_mac" => { + cfg(soc_has_wifi) => "let mac = efuse::interface_mac_address(InterfaceMacAddress::Station);", + _ => "let mac = efuse::interface_mac_address(InterfaceMacAddress::Bluetooth);" + } +))] +//! # eFuse (one-time programmable configuration) +//! +//! ## Overview +//! +//! The `efuse` module provides functionality for reading eFuse data +//! from the chip, allowing access to various chip-specific +//! information such as: +//! +//! * MAC address +//! * Chip revision +//! +//! and more. It is useful for retrieving chip-specific configuration and +//! identification data during runtime. +//! +//! ## Examples +//! +//! ### Reading interface MAC addresses +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::efuse::{self, InterfaceMacAddress}; +//! +//! # {interface_mac} +//! println!("MAC: {mac}"); +//! println!("MAC bytes: {:02x?}", mac.as_bytes()); +//! # {after_snippet} +//! ``` + +use core::{cmp, mem, slice, sync::atomic::Ordering}; + +use bytemuck::AnyBitPattern; +use portable_atomic::AtomicU8; + +#[cfg_attr(esp32, path = "esp32/mod.rs")] +#[cfg_attr(esp32c2, path = "esp32c2/mod.rs")] +#[cfg_attr(esp32c3, path = "esp32c3/mod.rs")] +#[cfg_attr(esp32c5, path = "esp32c5/mod.rs")] +#[cfg_attr(esp32c6, path = "esp32c6/mod.rs")] +#[cfg_attr(esp32h2, path = "esp32h2/mod.rs")] +#[cfg_attr(esp32s2, path = "esp32s2/mod.rs")] +#[cfg_attr(esp32s3, path = "esp32s3/mod.rs")] +pub(crate) mod implem; + +pub use implem::*; + +/// The bit field for get access to efuse data +#[derive(Debug, Clone, Copy)] +#[instability::unstable] +pub struct EfuseField { + /// The block + pub(crate) block: EfuseBlock, + /// Word number - this is just informational + pub(crate) _word: u32, + /// Starting bit in the efuse block + pub(crate) bit_start: u32, + /// Number of bits + pub(crate) bit_count: u32, +} + +impl EfuseField { + pub(crate) const fn new(block: u32, word: u32, bit_start: u32, bit_count: u32) -> Self { + Self { + block: EfuseBlock::from_repr(block).unwrap(), + _word: word, + bit_start, + bit_count, + } + } +} + +/// Read field value in a little-endian order +#[inline(always)] +#[instability::unstable] +pub fn read_field_le(field: EfuseField) -> T { + let EfuseField { + block, + bit_start, + bit_count, + .. + } = field; + + // Represent output value as a bytes slice: + let mut output = mem::MaybeUninit::::uninit(); + let mut bytes = + unsafe { slice::from_raw_parts_mut(output.as_mut_ptr() as *mut u8, mem::size_of::()) }; + + let bit_off = bit_start as usize; + let bit_end = cmp::min(bit_count as usize, bytes.len() * 8) + bit_off; + + let mut last_word_off = bit_off / 32; + let mut last_word = unsafe { block.address().add(last_word_off).read_volatile() }; + + let word_bit_off = bit_off % 32; + let word_bit_ext = 32 - word_bit_off; + + let mut word_off = last_word_off; + for bit_off in (bit_off..bit_end).step_by(32) { + if word_off != last_word_off { + // Read a new word: + last_word_off = word_off; + last_word = unsafe { block.address().add(last_word_off).read_volatile() }; + } + + let mut word = last_word >> word_bit_off; + word_off += 1; + + let word_bit_len = cmp::min(bit_end - bit_off, 32); + if word_bit_len > word_bit_ext { + // Read the next word: + last_word_off = word_off; + last_word = unsafe { block.address().add(last_word_off).read_volatile() }; + // Append bits from a beginning of the next word: + word |= last_word.wrapping_shl((32 - word_bit_off) as u32); + }; + + if word_bit_len < 32 { + // Mask only needed bits of a word: + word &= u32::MAX >> (32 - word_bit_len); + } + + // Represent word as a byte slice: + let byte_len = word_bit_len.div_ceil(8); + let word_bytes = + unsafe { slice::from_raw_parts(&word as *const u32 as *const u8, byte_len) }; + + // Copy word bytes to output value bytes: + bytes[..byte_len].copy_from_slice(word_bytes); + + // Move read window forward: + bytes = &mut bytes[byte_len..]; + } + + // Fill untouched bytes with zeros: + bytes.fill(0); + + unsafe { output.assume_init() } +} + +/// Read bit value. +/// +/// This function panics if the field's bit length is not equal to 1. +#[inline(always)] +#[instability::unstable] +pub fn read_bit(field: EfuseField) -> bool { + assert_eq!(field.bit_count, 1); + read_field_le::(field) != 0 +} + +/// Overrides the base MAC address used by [`interface_mac_address`]. +/// +/// After a successful call, [`interface_mac_address`] will derive +/// per-interface addresses from the overridden base instead of the factory +/// eFuse MAC. [`base_mac_address`] is unaffected and continues to return the +/// factory eFuse value. +/// +/// The override does not persist across device resets. +/// Can only be called once. Returns `Err(SetMacError::AlreadySet)` +/// otherwise. +#[instability::unstable] +pub fn override_mac_address(mac: MacAddress) -> Result<(), SetMacError> { + if MAC_OVERRIDE_STATE + .compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + return Err(SetMacError::AlreadySet); + } + + unsafe { + MAC_OVERRIDE = mac; + } + + MAC_OVERRIDE_STATE.store(2, Ordering::Release); + + Ok(()) +} + +#[procmacros::doc_replace] +/// Returns the base MAC address programmed into eFuse during manufacturing. +/// +/// This always reads directly from the hardware eFuse storage. To get the +/// effective MAC for a specific radio interface (which may be overridden via +/// [`override_mac_address`]), use [`interface_mac_address`] instead. +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::efuse; +/// +/// let mac = efuse::base_mac_address(); +/// println!("Base MAC: {mac}"); +/// # {after_snippet} +/// ``` +#[instability::unstable] +pub fn base_mac_address() -> MacAddress { + let mut mac_addr = [0u8; 6]; + + let mac0 = read_field_le::<[u8; 4]>(crate::efuse::MAC0); + let mac1 = read_field_le::<[u8; 2]>(crate::efuse::MAC1); + + // MAC address is stored in big endian, so load the bytes in reverse: + mac_addr[0] = mac1[1]; + mac_addr[1] = mac1[0]; + mac_addr[2] = mac0[3]; + mac_addr[3] = mac0[2]; + mac_addr[4] = mac0[1]; + mac_addr[5] = mac0[0]; + + MacAddress::new_eui48(mac_addr) +} + +#[procmacros::doc_replace( + "interface_mac_example" => { + cfg(soc_has_wifi) => "let mac = efuse::interface_mac_address(InterfaceMacAddress::Station);", + _ => "let mac = efuse::interface_mac_address(InterfaceMacAddress::Bluetooth);" + } +)] +/// Returns the MAC address for a specific interface, derived from the base +/// MAC. +/// +/// By default, addresses are derived from the factory eFuse MAC returned +/// by [`base_mac_address`]. If [`override_mac_address`] has been called, the +/// overridden base address is used instead. +/// +/// Each chip is programmed with a unique base MAC address during manufacturing. +/// Different interfaces (Wi-Fi Station, SoftAP, Bluetooth, etc.) each use a +/// MAC address derived from this base address. The Station interface uses +/// the base MAC directly, while others use locally administered variants +/// produced by modifying the first octet to set the local-admin bit; some +/// interfaces (such as Bluetooth) additionally adjust the last octet to +/// obtain a distinct address. +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::efuse::{self, InterfaceMacAddress}; +/// +/// # {interface_mac_example} +/// println!("MAC: {mac}"); +/// # {after_snippet} +/// ``` +#[cfg(any(soc_has_wifi, soc_has_bt))] +pub fn interface_mac_address(kind: InterfaceMacAddress) -> MacAddress { + let mut mac = if MAC_OVERRIDE_STATE.load(Ordering::Acquire) == 2 { + unsafe { MAC_OVERRIDE } + } else { + base_mac_address() + }; + + match kind { + #[cfg(soc_has_wifi)] + InterfaceMacAddress::Station => { + // base MAC + } + #[cfg(soc_has_wifi)] + InterfaceMacAddress::AccessPoint => { + derive_local_mac(&mut mac); + } + #[cfg(soc_has_bt)] + InterfaceMacAddress::Bluetooth => { + derive_local_mac(&mut mac); + + mac.0[5] = mac.0[5].wrapping_add(1); + } + } + mac +} + +/// Returns the hardware revision +/// +/// The chip version is calculated using the following +/// formula: MAJOR * 100 + MINOR. (if the result is 1, then version is v0.1) +#[instability::unstable] +pub fn chip_revision() -> u16 { + major_chip_version() as u16 * 100 + minor_chip_version() as u16 +} + +// Indicates the state of setting the mac address +// 0 -- unset +// 1 -- in the process of being set +// 2 -- set +// +// Values other than 0 indicate that we cannot attempt setting the mac address +// again, and values other than 2 indicate that we should read the mac address +// from eFuse. +static MAC_OVERRIDE_STATE: AtomicU8 = AtomicU8::new(0); +static mut MAC_OVERRIDE: MacAddress = MacAddress::new_eui48([0; 6]); + +/// Error indicating issues with setting the MAC address. +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum SetMacError { + /// The MAC address has already been set and cannot be changed. + AlreadySet, +} + +impl core::fmt::Display for SetMacError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SetMacError::AlreadySet => write!(f, "The MAC address has already been set"), + } + } +} + +impl core::error::Error for SetMacError {} + +/// Helper function. +/// Serves to derive a local MAC by adjusting the first octet of the given base MAC. +/// See https://github.com/esp-rs/esp-hal/blob/0881d747c53e43ee847bef3068076a48ce8d27f0/esp-radio/src/common_adapter.rs#L151-L159 +#[cfg(any(soc_has_wifi, soc_has_bt))] +fn derive_local_mac(mac: &mut MacAddress) { + let bytes = &mut mac.0; + let base = bytes[0]; + + for i in 0..64 { + let derived = (base | 0x02) ^ (i << 2); + if derived != base { + bytes[0] = derived; + break; + } + } +} + +/// Interface selection for [`interface_mac_address`]. +/// +/// Each interface uses a distinct MAC address derived from either the base MAC or the overridden +/// MAC if [`override_mac_address`] has been called. +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(any(soc_has_wifi, soc_has_bt))] +#[non_exhaustive] +pub enum InterfaceMacAddress { + /// Wi-Fi station. Equivalent to the base MAC address or overridden via + /// [`override_mac_address`]. + #[cfg(soc_has_wifi)] + #[cfg_attr(soc_has_wifi, default)] + Station, + /// Wi-Fi SoftAP. + #[cfg(soc_has_wifi)] + AccessPoint, + /// Bluetooth (BT/BLE). + #[cfg(soc_has_bt)] + #[cfg_attr(not(soc_has_wifi), default)] + Bluetooth, +} + +/// Hardware (MAC) address. +/// +/// Use [`as_bytes`](Self::as_bytes) for raw access, or the +/// [`Display`](core::fmt::Display) impl for colon-separated hex +/// (e.g. `aa:bb:cc:dd:ee:ff`). +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MacAddress([u8; 6]); + +impl MacAddress { + /// Creates a new `MacAddress` from the given bytes. + #[instability::unstable] + pub const fn new_eui48(bytes: [u8; 6]) -> Self { + Self(bytes) + } + + /// Returns the address bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl core::fmt::Display for MacAddress { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for (i, b) in self.0.iter().enumerate() { + if i != 0 { + f.write_str(":")?; + } + write!(f, "{b:02x}")?; + } + Ok(()) + } +} diff --git a/esp-hal/src/etm.rs b/esp-hal/src/etm.rs new file mode 100644 index 00000000000..9a9026f7ce1 --- /dev/null +++ b/esp-hal/src/etm.rs @@ -0,0 +1,221 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Event Task Matrix (ETM) +//! +//! ## Overview +//! +//! Normally, if a peripheral X needs to notify peripheral Y of a particular +//! event, this could only be done via a CPU interrupt from peripheral X, where +//! the CPU notifies peripheral Y on behalf of peripheral X. However, in +//! time-critical applications, the latency introduced by CPU interrupts is +//! non-negligible. +//! +//! With the help of the Event Task Matrix (ETM) module, some peripherals can +//! directly notify other peripherals of events through pre-set connections +//! without the intervention of CPU interrupts. This allows precise and low +//! latency synchronization between peripherals, and lessens the CPU’s workload +//! as the CPU no longer needs to handle these events. +//! +//! The ETM module has multiple programmable channels, they are used to connect +//! a particular Event to a particular Task. When an event is activated, the ETM +//! channel will trigger the corresponding task automatically. +//! +//! For more information, please refer to the +#![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", chip!(), "/api-reference/peripherals/etm.html)")] +//! ## Examples +//! +//! ### Control LED by the button via ETM +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::etm::{Channels, InputConfig, OutputConfig}; +//! # use esp_hal::etm::Etm; +//! # use esp_hal::gpio::Pull; +//! # use esp_hal::gpio::Level; +//! +//! let led = peripherals.GPIO1; +//! let button = peripherals.GPIO9; +//! +//! // setup ETM +//! let gpio_ext = Channels::new(peripherals.GPIO_SD); +//! let led_task = gpio_ext.channel0_task.toggle( +//! led, +//! OutputConfig { +//! open_drain: false, +//! pull: Pull::None, +//! initial_state: Level::Low, +//! }, +//! ); +//! let button_event = gpio_ext +//! .channel0_event +//! .falling_edge(button, InputConfig { pull: Pull::Down }); +//! +//! let etm = Etm::new(peripherals.ETM); +//! let channel0 = etm.channel0; +//! +//! // make sure the configured channel doesn't get dropped - dropping it will +//! // disable the channel +//! let _configured_channel = channel0.setup(&button_event, &led_task); +//! +//! // the LED is controlled by the button without involving the CPU +//! loop {} +//! # } +//! ``` +//! +//! ### Control LED by the systimer via ETM +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::etm::{Channels, InputConfig, OutputConfig}; +//! # use esp_hal::etm::Etm; +//! # use esp_hal::gpio::Pull; +//! # use esp_hal::gpio::Level; +//! # use esp_hal::timer::systimer::{etm::Event, SystemTimer}; +//! # use esp_hal::timer::PeriodicTimer; +//! use esp_hal::time::Duration; +//! +//! let syst = SystemTimer::new(peripherals.SYSTIMER); +//! let mut alarm0 = syst.alarm0; +//! let mut timer = PeriodicTimer::new(alarm0.reborrow()); +//! timer.start(Duration::from_secs(1)); +//! +//! let led = peripherals.GPIO1; +//! +//! // setup ETM +//! let gpio_ext = Channels::new(peripherals.GPIO_SD); +//! let led_task = gpio_ext.channel0_task.toggle( +//! led, +//! OutputConfig { +//! open_drain: false, +//! pull: Pull::None, +//! initial_state: Level::Low, +//! }, +//! ); +//! +//! let timer_event = Event::new(&alarm0); +//! +//! let etm = Etm::new(peripherals.ETM); +//! let channel0 = etm.channel0; +//! +//! // make sure the configured channel doesn't get dropped - dropping it will +//! // disable the channel +//! let _configured_channel = channel0.setup(&timer_event, &led_task); +//! +//! // the LED is controlled by the timer without involving the CPU +//! loop {} +//! # } +//! ``` + +use crate::{peripherals::ETM, system::GenericPeripheralGuard}; + +/// Unconfigured EtmChannel. +#[non_exhaustive] +pub struct EtmChannel {} + +impl EtmChannel { + /// Setup the channel + /// + /// Enabled the channel and configures the assigned event and task. + pub fn setup<'a, E, T>(self, event: &'a E, task: &'a T) -> EtmConfiguredChannel<'a, E, T, C> + where + E: EtmEvent, + T: EtmTask, + { + let etm = ETM::regs(); + let guard = GenericPeripheralGuard::new(); + + etm.ch(C as usize) + .evt_id() + .modify(|_, w| unsafe { w.evt_id().bits(event.id()) }); + etm.ch(C as usize) + .task_id() + .modify(|_, w| unsafe { w.task_id().bits(task.id()) }); + if C < 32 { + etm.ch_ena_ad0_set().write(|w| w.ch_set(C).set_bit()); + } else { + etm.ch_ena_ad1_set().write(|w| w.ch_set(C - 32).set_bit()); + } + + EtmConfiguredChannel { + _event: event, + _task: task, + _guard: guard, + } + } +} + +fn disable_channel(channel: u8) { + if channel < 32 { + ETM::regs() + .ch_ena_ad0_clr() + .write(|w| w.ch_clr(channel).set_bit()); + } else { + ETM::regs() + .ch_ena_ad1_clr() + .write(|w| w.ch_clr(channel - 32).set_bit()); + } +} + +/// A readily configured channel +/// +/// The channel is enabled and event and task are configured. +#[non_exhaustive] +pub struct EtmConfiguredChannel<'a, E, T, const C: u8> +where + E: EtmEvent, + T: EtmTask, +{ + _event: &'a E, + _task: &'a T, + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Etm as u8 }>, +} + +impl Drop for EtmConfiguredChannel<'_, E, T, C> +where + E: EtmEvent, + T: EtmTask, +{ + fn drop(&mut self) { + debug!("Drop ETM channel {}", C); + disable_channel(C); + } +} + +macro_rules! create_etm { + ($($num:literal),+) => { + paste::paste! { + /// ETM Instance + /// + /// Provides access to all the [EtmChannel] + pub struct Etm<'d> { + _peripheral: crate::peripherals::ETM<'d>, + $( + /// An individual ETM channel, identified by its index number. + pub [< channel $num >]: EtmChannel<$num>, + )+ + } + + impl<'d> Etm<'d> { + /// Creates a new `Etm` instance. + pub fn new(peripheral: crate::peripherals::ETM<'d>) -> Self { + Self { + _peripheral: peripheral, + $([< channel $num >]: EtmChannel { },)+ + } + } + } + } + }; +} + +create_etm!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 +); + +#[doc(hidden)] +pub trait EtmEvent: crate::private::Sealed { + fn id(&self) -> u8; +} + +#[doc(hidden)] +pub trait EtmTask: crate::private::Sealed { + fn id(&self) -> u8; +} diff --git a/esp-hal/src/exception_handler/mod.rs b/esp-hal/src/exception_handler/mod.rs new file mode 100644 index 00000000000..8733ea2a7cf --- /dev/null +++ b/esp-hal/src/exception_handler/mod.rs @@ -0,0 +1,116 @@ +use crate::trapframe::TrapFrame; + +#[cfg(xtensa)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".rwtext")] +unsafe extern "C" fn __user_exception( + cause: xtensa_lx_rt::exception::ExceptionCause, + context: &TrapFrame, +) { + panic!( + "\n\nException occurred on {:?} '{:?}'\n{:?}", + crate::system::Cpu::current(), + cause, + context + ); +} + +#[cfg(all(xtensa, stack_guard_monitoring))] +pub(crate) fn breakpoint_interrupt(context: &TrapFrame) { + let mut dbgcause: u32; + unsafe { + core::arch::asm!( + "rsr.debugcause {0}", + out(reg) dbgcause, options(nostack) + ); + } + + #[cfg(stack_guard_monitoring)] + if dbgcause & 4 != 0 && dbgcause & 0b1111_0000_0000 == 0 { + panic!( + "\n\nDetected a write to the stack guard value on {:?}\n{:?}", + crate::system::Cpu::current(), + context + ); + } + + panic!( + "\n\nBreakpoint on {:?}\n{:?}\nDebug cause: {}", + crate::system::Cpu::current(), + context, + dbgcause + ); +} + +#[cfg(riscv)] +#[unsafe(no_mangle)] +unsafe extern "C" fn ExceptionHandler(context: &TrapFrame) -> ! { + let mepc = riscv::register::mepc::read(); + let code = riscv::register::mcause::read().code(); + let mtval = riscv::register::mtval::read(); + + unsafe extern "C" { + static mut __stack_chk_guard: u32; + } + + if code == 14 { + panic!( + "Stack overflow detected at 0x{:x}, possibly called by 0x{:x}", + mepc, context.ra + ); + } + + #[cfg(stack_guard_monitoring)] + if code == 3 { + let guard_addr = core::ptr::addr_of_mut!(__stack_chk_guard) as *mut _ as usize; + + if mtval == guard_addr { + panic!( + "Detected a write to the main stack's guard value at 0x{:x}, possibly called by 0x{:x}", + mepc, context.ra + ) + } else { + if unsafe { crate::debugger::watchpoint_hit(1) } { + panic!( + "Detected a write to the trap/rwtext segment at 0x{:x}, possibly called by 0x{:x}", + mepc, context.ra + ); + } else if unsafe { crate::debugger::watchpoint_hit(0) } { + panic!( + "Detected a write to a stack guard value at 0x{:x}, possibly called by 0x{:x}", + mepc, context.ra + ); + } else { + panic!( + "Breakpoint exception at 0x{:08x}, mtval=0x{:08x}\n{:?}", + mepc, mtval, context + ); + } + } + } + + let code = match code { + 0 => "Instruction address misaligned", + 1 => "Instruction access fault", + 2 => "Illegal instruction", + 3 => "Breakpoint", + 4 => "Load address misaligned", + 5 => "Load access fault", + 6 => "Store/AMO address misaligned", + 7 => "Store/AMO access fault", + 8 => "Environment call from U-mode", + 9 => "Environment call from S-mode", + 10 => "Reserved", + 11 => "Environment call from M-mode", + 12 => "Instruction page fault", + 13 => "Load page fault", + 14 => "Reserved", + 15 => "Store/AMO page fault", + _ => "UNKNOWN", + }; + + panic!( + "Exception '{}' mepc=0x{:08x}, mtval=0x{:08x}\n{:?}", + code, mepc, mtval, context + ); +} diff --git a/esp-hal/src/fmt.rs b/esp-hal/src/fmt.rs new file mode 100644 index 00000000000..dbec78406d2 --- /dev/null +++ b/esp-hal/src/fmt.rs @@ -0,0 +1,332 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert!($($x)*); + } else { + ::core::assert!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert_eq!($($x)*); + } else { + ::core::assert_eq!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::assert_ne!($($x)*); + } else { + ::core::assert_ne!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert!($($x)*); + } else { + ::core::debug_assert!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert_eq!($($x)*); + } else { + ::core::debug_assert_eq!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug_assert_ne!($($x)*); + } else { + ::core::debug_assert_ne!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::todo!($($x)*); + } else { + ::core::todo!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::unreachable!($($x)*); + } else { + ::core::unreachable!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::panic!($($x)*); + } else { + ::core::panic!($($x)*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::trace!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::trace!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::debug!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::debug!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::info!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::info!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::warn!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::warn!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + cfg_if::cfg_if! { + if #[cfg(feature = "defmt")] { + ::defmt::error!($s $(, $x)*); + } else if #[cfg(feature = "log-04")] { + ::log_04::error!($s $(, $x)*); + } else { + let _ = ($( & $x ),*); + } + } + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cold] +#[inline(never)] +#[cfg(not(feature = "defmt"))] +pub(crate) fn __unwrap_failed(arg: &str, e: impl ::core::fmt::Debug) -> ! { + ::core::panic!("unwrap of `{}` failed: {:?}", arg, e); +} + +#[cold] +#[inline(never)] +#[cfg(not(feature = "defmt"))] +pub(crate) fn __unwrap_failed_with_message( + arg: &str, + e: impl core::fmt::Debug, + msg: impl core::fmt::Display, +) -> ! { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", arg, msg, e); +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed(::core::stringify!($arg), e) } + } + }; + ($arg:expr, $msg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed_with_message(::core::stringify!($arg), e, $msg) } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { $crate::fmt::__unwrap_failed_with_message(::core::stringify!($arg), e, ::core::format_args!($($msg,)*)) } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + #[allow(unused)] + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +/// A way to `{:x?}` format a byte slice which is compatible with `defmt` +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl Debug for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl Display for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl LowerHex for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Bytes<'_> { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/esp-hal/src/gpio/asynch.rs b/esp-hal/src/gpio/asynch.rs new file mode 100644 index 00000000000..5a55f5e9a17 --- /dev/null +++ b/esp-hal/src/gpio/asynch.rs @@ -0,0 +1,278 @@ +use core::{ + sync::atomic::Ordering, + task::{Context, Poll}, +}; + +use crate::gpio::{Event, Flex, GpioBank, Input, InputPin}; + +impl Flex<'_> { + /// Wait until the pin experiences a particular [`Event`]. + /// + /// The GPIO driver will disable listening for the event once it occurs, + /// or if the `Future` is dropped - which also means this method is **not** + /// cancellation-safe, it will always wait for a future event. + /// + /// Note that calling this function will overwrite previous + /// [`listen`][Self::listen] operations for this pin. + #[inline] + #[instability::unstable] + pub async fn wait_for(&mut self, event: Event) { + // Make sure this pin is not being processed by an interrupt handler. We need to + // always take a critical section even if the pin is not listening, because the + // interrupt handler may be running on another core and the interrupt handler + // may be in the process of processing the pin if the interrupt status is set - + // regardless of the pin actually listening or not. + if self.is_listening() || self.is_interrupt_set() { + self.unlisten_and_clear(); + } + + // At this point the pin is no longer listening, and not being processed, so we + // can safely do our setup. + + // Mark pin as async. The interrupt handler clears this bit before processing a + // pin and unlistens it, so this call will not race with the interrupt + // handler (because it must have finished before `unlisten` above, or the + // handler no longer ) + self.pin + .bank() + .async_operations() + .fetch_or(self.pin.mask(), Ordering::Relaxed); + + // Start listening for the event. We only need to do this once, as disabling + // the interrupt will signal the future to complete. + self.listen(event); + + PinFuture { pin: self }.await + } + + /// Wait until the pin is high. + /// + /// See [Self::wait_for] for more information. + #[inline] + #[instability::unstable] + pub async fn wait_for_high(&mut self) { + self.wait_for(Event::HighLevel).await + } + + /// Wait until the pin is low. + /// + /// See [Self::wait_for] for more information. + #[inline] + #[instability::unstable] + pub async fn wait_for_low(&mut self) { + self.wait_for(Event::LowLevel).await + } + + /// Wait for the pin to undergo a transition from low to high. + /// + /// See [Self::wait_for] for more information. + #[inline] + #[instability::unstable] + pub async fn wait_for_rising_edge(&mut self) { + self.wait_for(Event::RisingEdge).await + } + + /// Wait for the pin to undergo a transition from high to low. + /// + /// See [Self::wait_for] for more information. + #[inline] + #[instability::unstable] + pub async fn wait_for_falling_edge(&mut self) { + self.wait_for(Event::FallingEdge).await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high + /// to low. + /// + /// See [Self::wait_for] for more information. + #[inline] + #[instability::unstable] + pub async fn wait_for_any_edge(&mut self) { + self.wait_for(Event::AnyEdge).await + } +} + +impl Input<'_> { + #[procmacros::doc_replace] + /// Wait until the pin experiences a particular [`Event`]. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for(Event::LowLevel).await; + /// # {after_snippet} + /// ``` + /// + /// ## Cancellation + /// + /// This function is not cancellation-safe. + /// + /// - Calling this function will overwrite previous [`listen`][Self::listen] operations for this + /// pin, making it side-effectful. + /// - Dropping the [`Future`] returned by this function will cancel the wait operation. If the + /// event occurs after the future is dropped, a consequent wait operation will ignore the + /// event. + #[inline] + #[instability::unstable] + pub async fn wait_for(&mut self, event: Event) { + self.pin.wait_for(event).await + } + + #[procmacros::doc_replace] + /// Wait until the pin is high. + /// + /// See [Self::wait_for] for more information. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for_high().await; + /// # {after_snippet} + /// ``` + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + #[procmacros::doc_replace] + /// Wait until the pin is low. + /// + /// See [Self::wait_for] for more information. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for_low().await; + /// # {after_snippet} + /// ``` + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + #[procmacros::doc_replace] + /// Wait for the pin to undergo a transition from low to high. + /// + /// See [Self::wait_for] for more information. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for_rising_edge().await; + /// # {after_snippet} + /// ``` + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + #[procmacros::doc_replace] + /// Wait for the pin to undergo a transition from high to low. + /// + /// See [Self::wait_for] for more information. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for_falling_edge().await; + /// # {after_snippet} + /// ``` + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + #[procmacros::doc_replace] + /// Wait for the pin to undergo any transition, i.e low to high OR high + /// to low. + /// + /// See [Self::wait_for] for more information. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig}; + /// let mut input_pin = Input::new(peripherals.GPIO4, InputConfig::default()); + /// + /// input_pin.wait_for_any_edge().await; + /// # {after_snippet} + /// ``` + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct PinFuture<'f, 'd> { + pin: &'f mut Flex<'d>, +} + +impl PinFuture<'_, '_> { + fn bank(&self) -> GpioBank { + self.pin.pin.bank() + } + + fn mask(&self) -> u32 { + self.pin.pin.mask() + } + + fn is_done(&self) -> bool { + // Only the interrupt handler should clear the async bit, and only if the + // specific pin is handling an interrupt. This way the user may clear the + // interrupt status without worrying about the async bit being cleared. + self.bank().async_operations().load(Ordering::Acquire) & self.mask() == 0 + } +} + +impl core::future::Future for PinFuture<'_, '_> { + type Output = (); + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.pin.pin.waker().register(cx.waker()); + + if self.is_done() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl Drop for PinFuture<'_, '_> { + fn drop(&mut self) { + // If the future has completed, unlistening and removing the async bit will have + // been done by the interrupt handler. + + if !self.is_done() { + self.pin.unlisten_and_clear(); + + // Unmark pin as async so that a future listen call doesn't wake a waker for no + // reason. + self.bank() + .async_operations() + .fetch_and(!self.mask(), Ordering::Relaxed); + } + } +} diff --git a/esp-hal/src/gpio/dedicated.rs b/esp-hal/src/gpio/dedicated.rs new file mode 100644 index 00000000000..7f18894a86a --- /dev/null +++ b/esp-hal/src/gpio/dedicated.rs @@ -0,0 +1,2038 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! Dedicated GPIO access. +//! +//! This module implements fast GPIO access using special-purpose CPU features. +//! Dedicated GPIO access works by connecting peripheral signals between the CPU +//! and the GPIO pins, instead of using the GPIO registers via the peripheral bus. +//! +//! To use dedicated GPIOs, first you must obtain the channel objects. The channels +//! are created by calling the [`DedicatedGpio::new`] function. The fields of the +//! [`DedicatedGpio`] struct represent the channels. The channels are [`DedicatedGpioChannel`] +//! objects, which by default represent both input and output channels. The channels can be split +//! into [`DedicatedGpioInputChannel`] and [`DedicatedGpioOutputChannel`] halves, or may be +//! used unsplit. +#![doc = concat!(r#" + + > "#, chip!() , r#" specific: There are "#, property!("dedicated_gpio.channel_count", str) , r#" dedicated GPIO channels available. + + "#)] +//! Next, configure the pins you want to use as normal GPIO pin drivers. Then you can wrap them +//! into the dedicated GPIO drivers: +//! - [`DedicatedGpioInput`] +//! - [`DedicatedGpioOutput`] +//! - [`DedicatedGpioFlex`] +//! +//! The drivers can take channels and pins by value or by reference. Pass these objects by reference +//! if you plan on reusing them again, after dropping the dedicated driver. +//! +//! Due to how the hardware works, [`DedicatedGpioOutput`] can drive any number of GPIO pins. +//! +//! ## Bundles +//! +//! If you need to read or update multiple channels together, you can use the bundle helpers: +//! - [`DedicatedGpioInputBundle`] +//! - [`DedicatedGpioOutputBundle`] +//! - [`DedicatedGpioFlexBundle`] +//! +//! Bundles are lightweight objects that precompute a channel mask from one or more drivers, +//! allowing multi-channel operations with a single low-level read/write. +//! +//! ## Low-level functions +//! +//! This module also exposes low-level helpers for direct, channel-bitmask-based access: +//! - [`write_ll`]: write output levels for a selected set of channels in one operation +//! - [`read_all_ll`]: read the current input levels of all channels +#![cfg_attr( + not(esp32s3), + doc = r#"- [`output_levels_ll`]: read the current output levels of all channels"# +)] +#![cfg_attr( + esp32s3, + doc = r#"- `output_levels_ll`: read the current output levels of all channels (not available on ESP32-S3 due to an LLVM bug, see )"# +)] +//! These functions operate purely on channel bitmasks (bit 0 -> channel 0, bit 1 -> channel 1, ...) +//! and do not track pin configuration. Prefer the higher-level drivers and bundles unless you +//! specifically need the lowest overhead. + +#![cfg_attr( + multi_core, + doc = r#" + +

    +Dedicated GPIOs are tied to CPU cores and a driver must only be used on the core that has created it. +Do not send the drivers to another core, either directly, or indirectly via a thread that is not pinned to a core. +
    +"# +)] +//! ## Examples +//! ### sharing drivers across multiple bundles +//! +//! The same driver can be borrowed by multiple bundles at the same time. +//! +//! In this example, we build two *input* bundles: +//! - `bundle_a` reads channels 0, 1, 2 +//! - `bundle_b` reads channels 0, 2, 4 +//! +//! Both bundles share the same `in0` and `in2` drivers, and we use +//! [`DedicatedGpioInputBundle::all_high`] to check if all channels in each bundle are currently +//! high. +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::gpio::{ +//! Input, +//! InputConfig, +//! dedicated::{DedicatedGpio, DedicatedGpioInput, DedicatedGpioInputBundle}, +//! }; +//! +//! // Create channels. +//! let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +//! +//! // Configure GPIO inputs +//! let p0 = Input::new(peripherals.GPIO0, InputConfig::default()); +//! let p1 = Input::new(peripherals.GPIO1, InputConfig::default()); +//! let p2 = Input::new(peripherals.GPIO2, InputConfig::default()); +//! let p4 = Input::new(peripherals.GPIO4, InputConfig::default()); +//! +//! let in0 = DedicatedGpioInput::new(channels.channel0.input, p0); +//! let in1 = DedicatedGpioInput::new(channels.channel1.input, p1); +//! let in2 = DedicatedGpioInput::new(channels.channel2.input, p2); +//! let in4 = DedicatedGpioInput::new(channels.channel4.input, p4); +//! +//! // Bundle A reads channels 0, 1, 2. +//! let mut bundle_a = DedicatedGpioInputBundle::new(); +//! bundle_a +//! .enable_input(&in0) +//! .enable_input(&in1) +//! .enable_input(&in2); +//! +//! // Bundle B reads channels 0, 2, 4. +//! // Note: `in0` and `in2` are *shared* with bundle A. +//! let mut bundle_b = DedicatedGpioInputBundle::new(); +//! bundle_b +//! .enable_input(&in0) +//! .enable_input(&in2) +//! .enable_input(&in4); +//! +//! // Check whether all channels in each bundle are currently high. +//! let a_all_high = bundle_a.all_high(); // true if ch0, ch1, ch2 are all high +//! let b_all_high = bundle_b.all_high(); // true if ch0, ch2, ch4 are all high +//! +//! # {after_snippet} +//! ``` +//! +//! This pattern is useful when different subsystems want different views of the same set of +//! input channels without duplicating driver setup. + +use core::{convert::Infallible, marker::PhantomData}; + +use procmacros::doc_replace; +use strum::EnumCount as _; + +use crate::{ + gpio::{ + AnyPin, + GpioBank, + InputSignal, + Level, + OutputSignal, + interconnect::{PeripheralOutput, PeripheralSignal}, + }, + peripherals::GPIO_DEDICATED, + private::Sealed, + system::Cpu, +}; + +for_each_dedicated_gpio!( + (channels $(($ch:literal)),*) => { + paste::paste!( + /// Dedicated GPIO channels. + pub struct DedicatedGpio<'lt> { + $( + #[doc = concat!("Channel ", stringify!($ch))] + pub [< channel $ch >]: DedicatedGpioChannel<'lt, $ch>, + )* + } + ); + + impl<'lt> DedicatedGpio<'lt> { + /// Initializes the dedicated GPIO features and returns the channels. + pub fn new(_: GPIO_DEDICATED<'lt>) -> Self { + #[cfg(dedicated_gpio_needs_initialization)] + Self::initialize(); + + paste::paste!( + Self { + $([]: DedicatedGpioChannel { + input: DedicatedGpioInputChannel { _marker: PhantomData }, + output: DedicatedGpioOutputChannel { _marker: PhantomData }, + },)* + } + ) + } + } + }; +); + +impl DedicatedGpio<'_> { + #[cfg(dedicated_gpio_needs_initialization)] + fn initialize() { + crate::system::PeripheralClockControl::enable(crate::system::Peripheral::DedicatedGpio); + + #[cfg(esp32s2)] + { + // Allow instruction access, which has better performance than register access. + let regs = unsafe { esp32s2::DEDICATED_GPIO::steal() }; + regs.out_cpu() + .write(|w| unsafe { w.bits((1 << property!("dedicated_gpio.channel_count")) - 1) }); + } + } +} + +// Channel traits allow us to implement DedicatedInputOutput, and users can choose to work with 8 IO +// channels for inputs/outputs instead of 8+8. + +/// Marker trait for dedicated GPIO input channels. +pub trait InputChannel: Sealed { + #[doc(hidden)] + const CH: u8; + + #[doc(hidden)] + fn mask(&self) -> u32 { + 1 << Self::CH + } + + #[doc(hidden)] + fn input_signal(&self) -> InputSignal { + for_each_dedicated_gpio!( + (signals $( ($cpu:literal, $signal_idx:literal, $signal_name:ident) ),*) => { + const CHANNEL_COUNT: usize = property!("dedicated_gpio.channel_count"); + const SIGNALS: [[InputSignal; CHANNEL_COUNT]; Cpu::COUNT] = const { + let mut signals = [[core::mem::MaybeUninit::uninit(); CHANNEL_COUNT]; Cpu::COUNT]; + + $( + signals[$cpu][$signal_idx].write(InputSignal::$signal_name); + )* + + unsafe { core::mem::transmute(signals) } + }; + }; + ); + SIGNALS[Cpu::current() as usize][Self::CH as usize] + } +} + +/// Marker trait for dedicated GPIO output channels. +pub trait OutputChannel: Sealed { + #[doc(hidden)] + const CH: u8; + + #[doc(hidden)] + fn mask(&self) -> u32 { + 1 << Self::CH + } + + #[doc(hidden)] + fn output_signal(&self) -> OutputSignal { + for_each_dedicated_gpio!( + (signals $( ($cpu:literal, $signal_idx:literal, $signal_name:ident) ),* ) => { + const CHANNEL_COUNT: usize = property!("dedicated_gpio.channel_count"); + const SIGNALS: [[OutputSignal; CHANNEL_COUNT]; Cpu::COUNT] = const { + let mut signals = [[core::mem::MaybeUninit::uninit(); CHANNEL_COUNT]; Cpu::COUNT]; + + $( + signals[$cpu][$signal_idx].write(OutputSignal::$signal_name); + )* + + unsafe { core::mem::transmute(signals) } + }; + }; + ); + SIGNALS[Cpu::current() as usize][Self::CH as usize] + } +} + +// Driver traits, to allow working with Flex. These are implemented for both owned types and +// references, so that the user is not forced to give up the pin driver, and that we're not forced +// to figure out Drop semantics with `reborrow`, and the pins aren't remembered by the dedicated +// drivers anyway. +// We can't use the interconnect traits, because we want them to be implemented for references, too. + +/// Marker trait for GPIO input drivers. +pub trait InputDriver: Sealed { + #[doc(hidden)] + fn pin(&self) -> u8; + + /// Connects the pin's peripheral input to an input signal source. + #[doc(hidden)] + fn set_input_connection(&self, signal: InputSignal); +} + +#[instability::unstable] +impl InputDriver for super::Input<'_> { + fn pin(&self) -> u8 { + self.pin.pin.pin + } + + fn set_input_connection(&self, signal: InputSignal) { + self.connect_input_to_peripheral(signal); + } +} + +#[instability::unstable] +impl InputDriver for &mut super::Input<'_> { + fn pin(&self) -> u8 { + self.pin.pin.pin + } + + fn set_input_connection(&self, signal: InputSignal) { + self.connect_input_to_peripheral(signal); + } +} + +#[instability::unstable] +impl InputDriver for super::Flex<'_> { + fn pin(&self) -> u8 { + self.pin.pin + } + + fn set_input_connection(&self, signal: InputSignal) { + self.connect_input_to_peripheral(signal); + } +} + +#[instability::unstable] +impl InputDriver for &mut super::Flex<'_> { + fn pin(&self) -> u8 { + self.pin.pin + } + + fn set_input_connection(&self, signal: InputSignal) { + self.connect_input_to_peripheral(signal); + } +} + +/// Marker trait for GPIO output drivers. +pub trait OutputDriver: Sealed { + #[doc(hidden)] + fn pin(&self) -> u8; + + #[doc(hidden)] + fn set_output_connection(&self, signal: OutputSignal); +} + +#[instability::unstable] +impl OutputDriver for super::Output<'_> { + fn pin(&self) -> u8 { + self.pin.pin.pin + } + + fn set_output_connection(&self, signal: OutputSignal) { + self.pin.pin.connect_peripheral_to_output(signal); + } +} + +#[instability::unstable] +impl OutputDriver for &mut super::Output<'_> { + fn pin(&self) -> u8 { + self.pin.pin.pin + } + + fn set_output_connection(&self, signal: OutputSignal) { + self.pin.pin.connect_peripheral_to_output(signal); + } +} + +#[instability::unstable] +impl OutputDriver for super::Flex<'_> { + fn pin(&self) -> u8 { + self.pin.pin + } + + fn set_output_connection(&self, signal: OutputSignal) { + self.pin.connect_peripheral_to_output(signal); + } +} + +#[instability::unstable] +impl OutputDriver for &mut super::Flex<'_> { + fn pin(&self) -> u8 { + self.pin.pin + } + + fn set_output_connection(&self, signal: OutputSignal) { + self.pin.connect_peripheral_to_output(signal); + } +} + +/// A single dedicated GPIO channel, both input and output. +/// +/// You can split the channel by moving its fields out into separate input/output channel variables. +pub struct DedicatedGpioChannel<'lt, const CH: u8> { + /// Channel input + pub input: DedicatedGpioInputChannel<'lt, CH>, + + /// Channel output + pub output: DedicatedGpioOutputChannel<'lt, CH>, +} + +impl Sealed for DedicatedGpioChannel<'_, CH> {} +impl Sealed for &mut DedicatedGpioChannel<'_, CH> {} + +impl DedicatedGpioChannel<'_, CH> { + /// Conjures a new dedicated GPIO channel out of thin air. + /// + /// # Safety + /// + /// The [`DedicatedGpio`] struct must be initialized before this function is called. There + /// should only be one reference to the channel in use at one time. + pub unsafe fn steal() -> Self { + unsafe { + Self { + input: DedicatedGpioInputChannel::steal(), + output: DedicatedGpioOutputChannel::steal(), + } + } + } +} + +impl InputChannel for DedicatedGpioChannel<'_, CH> { + const CH: u8 = CH; +} + +impl OutputChannel for DedicatedGpioChannel<'_, CH> { + const CH: u8 = CH; +} + +impl InputChannel for &mut DedicatedGpioChannel<'_, CH> { + const CH: u8 = CH; +} + +impl OutputChannel for &mut DedicatedGpioChannel<'_, CH> { + const CH: u8 = CH; +} + +/// A single dedicated GPIO input channel. +pub struct DedicatedGpioInputChannel<'lt, const CH: u8> { + _marker: PhantomData<&'lt mut ()>, +} + +impl Sealed for DedicatedGpioInputChannel<'_, CH> {} +impl Sealed for &mut DedicatedGpioInputChannel<'_, CH> {} + +impl DedicatedGpioInputChannel<'_, CH> { + /// Conjures a new dedicated GPIO input channel out of thin air. + /// + /// # Safety + /// + /// The [`DedicatedGpio`] struct must be initialized before this function is called. There + /// should only be one reference to the channel in use at one time. + pub unsafe fn steal() -> Self { + Self { + _marker: PhantomData, + } + } +} + +impl InputChannel for DedicatedGpioInputChannel<'_, CH> { + const CH: u8 = CH; +} + +impl InputChannel for &mut DedicatedGpioInputChannel<'_, CH> { + const CH: u8 = CH; +} + +/// A single dedicated GPIO output channel. +pub struct DedicatedGpioOutputChannel<'lt, const CH: u8> { + _marker: PhantomData<&'lt mut ()>, +} + +impl Sealed for DedicatedGpioOutputChannel<'_, CH> {} +impl Sealed for &mut DedicatedGpioOutputChannel<'_, CH> {} + +impl DedicatedGpioOutputChannel<'_, CH> { + /// Conjures a new dedicated GPIO output channel out of thin air. + /// + /// # Safety + /// + /// The [`DedicatedGpio`] struct must be initialized before this function is called. There + /// should only be one reference to the channel in use at one time. + pub unsafe fn steal() -> Self { + Self { + _marker: PhantomData, + } + } +} + +impl OutputChannel for DedicatedGpioOutputChannel<'_, CH> { + const CH: u8 = CH; +} + +impl OutputChannel for &mut DedicatedGpioOutputChannel<'_, CH> { + const CH: u8 = CH; +} + +#[doc_replace] +/// A dedicated GPIO input driver. +#[cfg_attr( + multi_core, + doc = r#" + +
    +Note that the driver must only be used on the core that has created it. Do not send the driver to +another core, either directly, or indirectly via a thread that is not pinned to a core. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Input, +/// InputConfig, +/// Level, +/// dedicated::{DedicatedGpio, DedicatedGpioInput}, +/// }; +/// +/// // Create an input pin driver: +/// let input = Input::new(peripherals.GPIO0, InputConfig::default()); +/// +/// // Create a dedicated GPIO driver: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// let mut dedicated_input = DedicatedGpioInput::new(channels.channel0, input); +/// +/// // Now you can use the pin: +/// let level = dedicated_input.level(); +/// # +/// # {after_snippet} +/// ``` +pub struct DedicatedGpioInput<'lt> { + _marker: PhantomData<&'lt mut ()>, + mask: u32, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioInput<'lt> { + /// Creates a new dedicated GPIO input driver. + pub fn new(channel: CH, pin: P) -> Self + where + CH: InputChannel + 'lt, + P: InputDriver + 'lt, + { + pin.set_input_connection(channel.input_signal()); + Self { + mask: channel.mask(), + _marker: PhantomData, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + /// Read the current state of the GPIO pins. + #[inline(always)] + pub fn level(&self) -> Level { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + let bits = ll::read_in(); + Level::from(bits & self.mask != 0) + } +} + +impl embedded_hal::digital::ErrorType for DedicatedGpioInput<'_> { + type Error = Infallible; +} + +impl embedded_hal::digital::InputPin for DedicatedGpioInput<'_> { + #[inline(always)] + fn is_high(&mut self) -> Result { + Ok(self.level() == Level::High) + } + + #[inline(always)] + fn is_low(&mut self) -> Result { + Ok(self.level() == Level::Low) + } +} + +#[doc_replace] +/// A dedicated GPIO output driver. +/// +/// Due to how the hardware works, [`DedicatedGpioOutput`] can drive any number of GPIO pins. To +/// create a driver, you can use the [`DedicatedGpioOutput::new`] method, then +/// [`DedicatedGpioOutput::with_pin`] to add output drivers. +#[cfg_attr( + esp32s3, + doc = r#" + +
    +"# +)] +#[cfg_attr( + multi_core, + doc = r#" + +
    +Note that the driver must only be used on the core that has created it. Do not send the driver to +another core, either directly, or indirectly via a thread that is not pinned to a core. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Level, +/// Output, +/// OutputConfig, +/// dedicated::{DedicatedGpio, DedicatedGpioOutput}, +/// }; +/// +/// // Create a dedicated GPIO driver: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// let dedicated_output = DedicatedGpioOutput::new(channels.channel0); +/// +/// // Add any number of output pin drivers: +/// let output = Output::new(peripherals.GPIO0, Level::Low, OutputConfig::default()); +/// let mut dedicated_output = dedicated_output.with_pin(output); +/// +/// // Now you can use the pin. +/// dedicated_output.set_level(Level::High); +/// # +/// # {after_snippet} +/// ``` +pub struct DedicatedGpioOutput<'lt> { + _marker: PhantomData<&'lt mut ()>, + mask: u32, + signal: OutputSignal, + contained_gpios: [u32; GpioBank::COUNT], + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioOutput<'lt> { + /// Creates a new dedicated GPIO output driver. + /// + /// This function returns an empty driver. You will need to add output drivers to it using the + /// [`Self::with_pin`] method. + pub fn new(channel: CH) -> Self + where + CH: OutputChannel + 'lt, + { + ll::set_output_enabled(channel.mask(), true); + Self { + mask: channel.mask(), + signal: channel.output_signal(), + contained_gpios: [0; GpioBank::COUNT], + _marker: PhantomData, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + /// Adds a new output driver to the GPIO pins. + /// + /// A dedicated GPIO output driver can control any number of GPIO pins. The pins will be + /// released when the driver is dropped. This function does not change the state of the newly + /// added GPIO pin. + pub fn with_pin(mut self, pin: impl OutputDriver + 'lt) -> Self { + pin.set_output_connection(self.signal); + + let bank = pin.pin() / 32; + let pin_in_bank = pin.pin() % 32; + self.contained_gpios[bank as usize] |= 1 << pin_in_bank; + + self + } + + /// Changes the current state of the GPIO pins. + #[inline(always)] + pub fn set_level(&mut self, level: Level) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + if level == Level::High { + ll::write(self.mask, self.mask); + } else { + ll::write(self.mask, 0); + } + } + + /// Returns the current output state of the GPIO pins. + /// + /// Returns [`Level::High`] if any of the GPIO pins are set high, otherwise [`Level::Low`]. + #[cfg(not(esp32s3))] + #[inline(always)] + pub fn output_level(&self) -> Level { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + Level::from(ll::read_out() & self.mask != 0) + } +} + +impl Drop for DedicatedGpioOutput<'_> { + fn drop(&mut self) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + // Set the contained GPIOs back to GPIO mode. + for (bank, mut pins) in self.contained_gpios.into_iter().enumerate() { + let bank_offset = bank as u8 * 32; + while pins != 0 { + let pin = pins.trailing_zeros() as u8; + pins &= !(1 << pin); + + let gpio = unsafe { AnyPin::steal(bank_offset + pin) }; + gpio.disconnect_from_peripheral_output(); + gpio.connect_peripheral_to_output(OutputSignal::GPIO); + } + } + } +} + +impl embedded_hal::digital::ErrorType for DedicatedGpioOutput<'_> { + type Error = Infallible; +} + +impl embedded_hal::digital::OutputPin for DedicatedGpioOutput<'_> { + #[inline(always)] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::Low); + Ok(()) + } + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::High); + Ok(()) + } +} + +#[doc_replace] +/// A dedicated GPIO input and output driver. +#[cfg_attr( + xtensa, + doc = r#" + +On ESP32-S2 and ESP32-S3, the GPIO's output is always enabled. +"# +)] +#[cfg_attr( + multi_core, + doc = r#" + +
    +Note that the driver must only be used on the core that has created it. Do not send the driver to +another core, either directly, or indirectly via a thread that is not pinned to a core. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Flex, +/// Input, +/// Level, +/// dedicated::{DedicatedGpio, DedicatedGpioFlex}, +/// }; +/// +/// // Create a pin driver: +/// let flex = Flex::new(peripherals.GPIO0); +/// +/// // Create a dedicated GPIO driver: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// let mut dedicated_io = DedicatedGpioFlex::new(channels.channel0, flex); +/// +/// // Now you can use the pin: +/// let level = dedicated_io.level(); +/// # +/// # {after_snippet} +/// ``` +pub struct DedicatedGpioFlex<'lt> { + _marker: PhantomData<&'lt mut ()>, + mask: u32, + pin: u8, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioFlex<'lt> { + /// Creates a new dedicated GPIO input/output driver. + pub fn new(channel: CH, pin: P) -> Self + where + CH: InputChannel + OutputChannel + 'lt, + P: InputDriver + OutputDriver + 'lt, + { + pin.set_input_connection(channel.input_signal()); + pin.set_output_connection(channel.output_signal()); + Self { + mask: ::mask(&channel), + pin:

    ::pin(&pin), + _marker: PhantomData, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + /// Enables or disables the output buffer of the GPIO pin. + #[cfg(riscv)] // Xtensas always have the output enabled. + pub fn set_output_enabled(&mut self, enabled: bool) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + ll::set_output_enabled(self.mask, enabled); + } + + /// Change the current state of the GPIO pin. + #[inline(always)] + pub fn set_level(&mut self, level: Level) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + if level == Level::High { + ll::write(self.mask, self.mask); + } else { + ll::write(self.mask, 0); + } + } + + /// Returns the current output state of the GPIO pin. + #[cfg(not(esp32s3))] + #[inline(always)] + pub fn output_level(&self) -> Level { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + Level::from(ll::read_out() & self.mask != 0) + } + + /// Read the current state of the GPIO pins. + #[inline(always)] + pub fn level(&self) -> Level { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + let bits = ll::read_in(); + Level::from(bits & self.mask != 0) + } +} + +impl Drop for DedicatedGpioFlex<'_> { + fn drop(&mut self) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + let gpio = unsafe { AnyPin::steal(self.pin) }; + gpio.disconnect_from_peripheral_output(); + gpio.connect_peripheral_to_output(OutputSignal::GPIO); + } +} + +#[doc_replace] +/// Low-level function to write any number of dedicated output channels at once. +/// +/// The `mask` selects which channels to write to. Channel positions are in +/// order: bit 0 marks channel 0, bit 1 marks channel 1, etc. +/// +/// `value` is the value to write to the selected channels. A 0 bit sets the +/// channel's output to low, a 1 bit sets it to high. +/// +/// # Example +/// +/// ```rust,no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Level, +/// Output, +/// OutputConfig, +/// dedicated::{DedicatedGpio, DedicatedGpioOutput, write_ll}, +/// }; +/// +/// // Create dedicated GPIO drivers: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// let dedicated_output0 = DedicatedGpioOutput::new(channels.channel0); +/// let dedicated_output1 = DedicatedGpioOutput::new(channels.channel1); +/// let dedicated_output2 = DedicatedGpioOutput::new(channels.channel2); +/// +/// // Configure output drivers: +/// let output0 = Output::new(peripherals.GPIO0, Level::Low, OutputConfig::default()); +/// let output1 = Output::new(peripherals.GPIO1, Level::Low, OutputConfig::default()); +/// let output2 = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); +/// let output3 = Output::new(peripherals.GPIO3, Level::Low, OutputConfig::default()); +/// +/// // Configure dedicated output drivers: +/// let dedicated_output0 = dedicated_output0.with_pin(output0).with_pin(output1); +/// let dedicated_output1 = dedicated_output1.with_pin(output2); +/// let dedicated_output2 = dedicated_output2.with_pin(output3); +/// +/// // Write all pins at once: +/// const MASK: u32 = 0b00000111; +/// const VALUE: u32 = 0b00000101; // Set GPIOs 0, 1, and 3 to high, 2 to low. +/// write_ll(MASK, VALUE); +/// # +/// # {after_snippet} +/// ``` +#[cfg_attr( + multi_core, + doc = r#" + +

    +Only channels configured on the current CPU core can be used. +
    +"# +)] +#[inline(always)] +pub fn write_ll(mask: u32, value: u32) { + ll::write(mask, value); +} + +/// Low-level function to read the current state of all dedicated input channels. +/// +/// The returned value is a bitmask where each bit represents the state of a +/// channel. Bit 0 represents channel 0, bit 1 represents channel 1, etc. +#[inline(always)] +pub fn read_all_ll() -> u32 { + ll::read_in() +} + +/// Low-level function to read the current output levels of all dedicated GPIO channels. +/// +/// The returned value is a bitmask where each bit represents the output level of a channel: +/// bit 0 -> channel 0, bit 1 -> channel 1, etc. A bit value of 1 means the channel output is high. +#[cfg(not(esp32s3))] +#[inline(always)] +pub fn output_levels_ll() -> u32 { + ll::read_out() +} + +#[doc_replace] +/// A bundle of dedicated GPIO output drivers. +/// +/// An output bundle precomputes a channel mask from one or more +/// [`DedicatedGpioOutput`] drivers. This lets you update multiple dedicated output +/// channels with a single low-level write. +/// +/// Attaching a driver does **not** change any output state. Dropping the output +/// drivers still controls when the underlying GPIO pins are released back to +/// normal GPIO mode. +/// +/// ## Relationship to pins +/// +/// Dedicated output channels can be connected to multiple GPIO pins via +/// [`DedicatedGpioOutput::with_pin`]. The bundle operates on *channels*, not +/// individual pins: writing channel bits affects every pin connected to that +/// channel. +#[cfg_attr( + esp32s3, + doc = r#" + +
    +The method output_levels is currently not available on ESP32-S3 due to +an LLVM bug. See https://github.com/espressif/llvm-project/issues/120 for details. +
    +"# +)] +#[cfg_attr( + multi_core, + doc = r#" + +
    +Dedicated GPIO channels are tied to CPU cores. All output drivers attached to a bundle must be +configured on the same core, and the bundle must only be used on the core that created it. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Level, +/// Output, +/// OutputConfig, +/// dedicated::{DedicatedGpio, DedicatedGpioOutput, DedicatedGpioOutputBundle}, +/// }; +/// +/// // Create channels: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// +/// // Create output drivers for three channels: +/// let out0 = DedicatedGpioOutput::new(channels.channel0); +/// let out1 = DedicatedGpioOutput::new(channels.channel1); +/// let out2 = DedicatedGpioOutput::new(channels.channel2); +/// +/// // Attach GPIO pins to the dedicated outputs (any number of pins per channel): +/// let p0 = Output::new(peripherals.GPIO0, Level::Low, OutputConfig::default()); +/// let p1 = Output::new(peripherals.GPIO1, Level::Low, OutputConfig::default()); +/// let p2 = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); +/// +/// let out0 = out0.with_pin(p0); +/// let out1 = out1.with_pin(p1); +/// let out2 = out2.with_pin(p2); +/// +/// // Build a bundle that controls channels 0..=2: +/// let mut bundle = DedicatedGpioOutputBundle::new(); +/// bundle +/// .enable_output(&out0) +/// .enable_output(&out1) +/// .enable_output(&out2); +/// +/// // Set channel 0 and 2 high, keep channel 1 unchanged: +/// bundle.set_high(0b101); +/// +/// // Now drive all channels in the bundle at once: +/// // - ch0 = 0 +/// // - ch1 = 1 +/// // - ch2 = 0 +/// bundle.write_bits(0b010); +/// # +/// # {after_snippet} +/// ``` +pub struct DedicatedGpioOutputBundle<'lt> { + _marker: PhantomData<&'lt ()>, + mask: u32, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioOutputBundle<'lt> { + /// Creates a new, empty dedicated GPIO output bundle. + /// + /// A bundle is a *logical* grouping of one or more [`DedicatedGpioOutput`] drivers. + /// Internally, it stores a precomputed channel mask (see [`Self::mask`]) which allows + /// writing multiple dedicated GPIO channels efficiently. + /// + /// The returned bundle initially contains no channels. Add outputs using + /// [`Self::enable_output`]. + /// + /// ## Notes + /// + /// - Creating a bundle does **not** configure any hardware by itself. + pub fn new() -> Self { + Self { + _marker: PhantomData, + mask: 0, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + #[doc_replace] + /// Returns the channel mask of this bundle. + /// + /// Each bit corresponds to one dedicated GPIO channel: + /// - bit 0 -> channel 0 + /// - bit 1 -> channel 1 + /// - ... + /// + /// A bit is set to 1 if that channel is currently included in the bundle. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// + /// use esp_hal::gpio::{ + /// Level, + /// Output, + /// OutputConfig, + /// dedicated::{DedicatedGpio, DedicatedGpioOutput, DedicatedGpioOutputBundle}, + /// }; + /// + /// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); + /// let out0 = DedicatedGpioOutput::new(channels.channel0).with_pin(Output::new( + /// peripherals.GPIO0, + /// Level::Low, + /// OutputConfig::default(), + /// )); + /// let out2 = DedicatedGpioOutput::new(channels.channel2).with_pin(Output::new( + /// peripherals.GPIO2, + /// Level::Low, + /// OutputConfig::default(), + /// )); + /// let mut bundle = DedicatedGpioOutputBundle::new(); + /// bundle.enable_output(&out0).enable_output(&out2); + /// + /// let mask = bundle.mask(); // bit 0 -> ch0, bit 2 -> ch2, ... + /// // For this bundle: mask == 0b0000_0101 (channels 0 and 2). + /// + /// # {after_snippet} + /// ``` + #[inline(always)] + pub fn mask(&self) -> u32 { + self.mask + } + + /// Attaches (enables) a dedicated output driver in this bundle. After enabling, you will + /// be able to control the output channels of `out` via this bundle. + /// + /// This method logically borrows the provided [`DedicatedGpioOutput`] for the lifetime `'lt`. + /// Multiple bundles may borrow the same output driver at the same time. Check examples in the + /// module-level documentation for more. + /// + /// ## Notes + /// + /// - This function does not change any GPIO output state. + #[cfg_attr( + multi_core, + doc = r#" +
    +All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself +
    + "# + )] + pub fn enable_output<'d>(&mut self, out: &'lt DedicatedGpioOutput<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + out.core, self.core, + "Trying to enable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask |= out.mask; + self + } + + /// Disables a dedicated output driver in this bundle. + /// + /// This updates the internal mask by clearing the channel bit(s) of `out`. After disabling, + /// future *bundle* operations will no longer touch those channels. + /// + /// ## Notes + /// + /// - This does **not** affect `out` itself. It only changes which channels future *bundle* + /// operations will touch. + /// - This does **not** end the lifetime-based borrow of `out`. Even after disabling, `out` + /// still cannot be moved or dropped while this bundle exists. + /// - You can re-enable it later via [`Self::enable_output`]. + #[cfg_attr( + multi_core, + doc = r#" +
    +You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself. +
    +"# + )] + pub fn disable_output<'d>(&mut self, out: &'lt DedicatedGpioOutput<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + out.core, self.core, + "Trying to disable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask &= !out.mask; + self + } + + /// Sets selected channels **high**. + /// + /// For every bit set to 1 in `bits`, the corresponding dedicated output channel is driven + /// high. Bits set to 0 are left unchanged. + /// + /// ## Example + /// + /// `bundle.set_high(0b1011_0001)` sets channels 0, 4, 5, and 7 high. + /// + ///
    + /// + /// The caller must ensure that `bits` only contains channels included in this bundle, + /// i.e. `bits & !self.mask() == 0`. + /// + /// For example, if the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then `bits` + /// must not set bit 2 (e.g. `0b0000_0100`), or you would modify channel 2 outside the bundle. + /// + /// When compiled with `debug-assertions`, this condition is checked at runtime. + ///
    + #[inline(always)] + pub fn set_high(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + debug_assert!( + (bits & !self.mask) == 0, + "Trying to set bits outside of the bundle mask" + ); + ll::write(bits, bits); // or ll::write(self.mask, bits); + } + + /// Sets selected channels **low**. + /// + /// For every bit set to 1 in `bits`, the corresponding dedicated output channel is driven + /// low. Bits set to 0 are left unchanged. + /// + /// ## Example + /// + /// `bundle.set_low(0b1011_0001)` sets channels 0, 4, 5, and 7 low. + /// + ///
    + /// + /// The caller must ensure that `bits` only contains channels included in this bundle, + /// i.e. `bits & !self.mask() == 0`. + /// + /// For example, if the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then `bits` + /// must not set bit 7 (e.g. `0b1000_0000`), or you would clear channel 7 outside the bundle. + /// + /// When compiled with `debug-assertions`, this condition is checked at runtime. + ///
    + #[inline(always)] + pub fn set_low(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + debug_assert!( + (bits & !self.mask) == 0, + "Trying to clear bits outside of the bundle mask" + ); + ll::write(bits, 0); + // or ll::write(bits & self.mask, 0); + // the latter is safer (preventing writing to channels outside of the bundle) but slower + } + + /// Writes output levels for **all channels included by this bundle**. + /// + /// This method updates the output state of every channel whose bit is set in + /// [`Self::mask`]. Channels not included in the bundle are not modified. + /// + /// `bits` provides the level for each channel in the bundle: + /// - bit = 0 -> low + /// - bit = 1 -> high + /// + /// ## Example + /// + /// If `self.mask()` is `0b1111_0000` (bundle contains channels 4..=7), then + /// `bundle.write_bits(0b0001_0000)` sets channel 4 high and channels 5..=7 low, + /// while leaving channels 0..=3 unchanged. + /// + ///
    + /// + /// This function only writes channels selected by [`Self::mask`]. It will **not** + /// change channels outside the mask, even if `bits` contains 1s there. + /// + /// Make sure this "masked write" behavior matches what you intend. + /// + ///
    + #[inline(always)] + pub fn write_bits(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + ll::write(self.mask, bits); + } + + /// Returns the current output levels of the channels included by this bundle. + /// + /// For channels outside the bundle mask, the corresponding bits are always 0. + /// + /// ## Example + /// + /// If the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then + /// `output_levels()` will only contain bits 0, 1, and 3, regardless of the output + /// state of other channels. + #[cfg(not(esp32s3))] + #[inline(always)] + pub fn output_levels(&self) -> u32 { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + ll::read_out() & self.mask + } +} + +impl<'lt> Default for DedicatedGpioOutputBundle<'lt> { + fn default() -> Self { + Self::new() + } +} + +#[doc_replace] +/// A bundle of dedicated GPIO input drivers. +/// +/// An input bundle precomputes a channel mask from one or more [`DedicatedGpioInput`] +/// drivers. This lets you read multiple dedicated input channels with a single +/// low-level read (see [`DedicatedGpioInputBundle::levels`]). +/// +/// Attaching a driver does **not** change any pin state. The bundle only stores a +/// channel mask; it does not own pins or remember which GPIOs were connected. +/// +/// ## Relationship to pins +/// +/// Dedicated input channels are fed by GPIO pins that were connected when creating +/// [`DedicatedGpioInput`]. The bundle operates on *channels*, not individual pins: +/// each bit in the returned mask corresponds to a dedicated input channel. +/// +/// If you later reconfigure a GPIO pin (e.g. connect it to a different peripheral +/// input), that changes what the channel reads. The bundle does not prevent such +/// reconfiguration. +#[cfg_attr( + multi_core, + doc = r#" + +
    +Dedicated GPIO channels are tied to CPU cores. All input drivers attached to a bundle must be +configured on the same core, and the bundle must only be used on the core that created it. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Input, +/// InputConfig, +/// dedicated::{DedicatedGpio, DedicatedGpioInput, DedicatedGpioInputBundle}, +/// }; +/// +/// // Create channels: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// +/// // Create input drivers for two channels: +/// let p0 = Input::new(peripherals.GPIO0, InputConfig::default()); +/// let p1 = Input::new(peripherals.GPIO1, InputConfig::default()); +/// let in0 = DedicatedGpioInput::new(channels.channel0, p0); +/// let in1 = DedicatedGpioInput::new(channels.channel1, p1); +/// +/// // Build a bundle that reads channels 0 and 1: +/// let mut bundle = DedicatedGpioInputBundle::new(); +/// bundle.enable_input(&in0).enable_input(&in1); +/// +/// // Read both channels at once: +/// let bits = bundle.levels(); +/// // bit 0 -> channel 0, bit 1 -> channel 1, ... +/// # +/// # {after_snippet} +/// ``` +pub struct DedicatedGpioInputBundle<'lt> { + _marker: PhantomData<&'lt ()>, + mask: u32, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioInputBundle<'lt> { + /// Creates a new, empty dedicated GPIO input bundle. + /// + /// A bundle is a *logical* grouping of one or more [`DedicatedGpioInput`] drivers. + /// Internally, it stores a precomputed channel mask which allows reading multiple + /// dedicated input channels efficiently. + /// + /// The returned bundle initially contains no channels. Add inputs using + /// [`Self::enable_input`]. + /// + /// ## Notes + /// + /// - Creating a bundle does **not** configure any hardware by itself. + pub fn new() -> Self { + Self { + _marker: PhantomData, + mask: 0, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + #[doc_replace] + /// Returns the channel mask of this bundle. + /// + /// Each bit corresponds to one dedicated GPIO channel: + /// - bit 0 -> channel 0 + /// - bit 1 -> channel 1 + /// - ... + /// + /// A bit is set to 1 if that channel is currently included in the bundle. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// + /// use esp_hal::gpio::{ + /// Input, + /// InputConfig, + /// dedicated::{DedicatedGpio, DedicatedGpioInput, DedicatedGpioInputBundle}, + /// }; + /// + /// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); + /// let in0 = DedicatedGpioInput::new( + /// channels.channel0, + /// Input::new(peripherals.GPIO0, InputConfig::default()), + /// ); + /// let in2 = DedicatedGpioInput::new( + /// channels.channel2, + /// Input::new(peripherals.GPIO2, InputConfig::default()), + /// ); + /// + /// let mut bundle = DedicatedGpioInputBundle::new(); + /// bundle.enable_input(&in0).enable_input(&in2); + /// + /// let mask = bundle.mask(); // bit 0 -> ch0, bit 2 -> ch2, ... + /// // For this bundle: mask == 0b0000_0101 (channels 0 and 2). + /// + /// # {after_snippet} + /// ``` + #[inline(always)] + pub fn mask(&self) -> u32 { + self.mask + } + + /// Attaches (enables) an already-configured dedicated input driver to this bundle. After + /// enabling, you will be able to read the input channels of `inp` via this bundle. + /// + /// This method logically borrows the provided [`DedicatedGpioInput`] for the lifetime `'lt`. + /// Multiple bundles may borrow the same input driver at the same time. Check examples in the + /// module-level documentation for more. + /// + /// ## Notes + /// + /// - This function does not change any input state. + #[cfg_attr( + multi_core, + doc = r#" +
    +All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself. +
    +"# + )] + pub fn enable_input<'d>(&mut self, inp: &'lt DedicatedGpioInput<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + inp.core, self.core, + "Trying to enable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask |= inp.mask; + self + } + + /// Disables a dedicated input driver in this bundle. + /// + /// This updates the internal mask by clearing the channel bit(s) of `inp`. After disabling, + /// future *bundle* operations will no longer touch those channels. + /// + /// ## Notes + /// + /// - This does **not** affect `inp` itself. It only changes which channels future *bundle* + /// operations will touch. + /// - This does **not** end the lifetime-based borrow of `inp`. Even after disabling, `inp` + /// still cannot be moved or dropped while this bundle exists. + /// - You can re-enable it later via [`Self::enable_input`]. + #[cfg_attr( + multi_core, + doc = r#" +
    +You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself. +
    +"# + )] + pub fn disable_input<'d>(&mut self, inp: &'lt DedicatedGpioInput<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + inp.core, self.core, + "Trying to disable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask &= !inp.mask; + self + } + + /// Reads the current state of the channels included by this bundle. + /// + /// For channels outside the bundle mask, the corresponding bits are always 0. + /// + /// ## Example + /// + /// If the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then + /// `levels()` will only contain bits 0, 1, and 3, regardless of the state + /// of other channels. + #[inline(always)] + pub fn levels(&self) -> u32 { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + ll::read_in() & self.mask + } + + /// Returns `true` if all channels in this bundle are currently high. + #[inline(always)] + pub fn all_high(&self) -> bool { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + (ll::read_in() & self.mask) == self.mask + } + + /// Returns `true` if all channels in this bundle are currently low. + #[inline(always)] + pub fn all_low(&self) -> bool { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + (ll::read_in() & self.mask) == 0 + } +} + +impl<'lt> Default for DedicatedGpioInputBundle<'lt> { + fn default() -> Self { + Self::new() + } +} + +#[doc_replace] +/// A bundle of dedicated GPIO flex drivers (input + output). +/// +/// A flex bundle precomputes a channel mask from one or more [`DedicatedGpioFlex`] +/// drivers. This lets you: +/// - update multiple dedicated output channels with a single low-level write, and +/// - read multiple dedicated input channels with a single low-level read. +/// +/// Attaching a driver does **not** change any pin state. The bundle only stores a +/// channel mask; it does not own pins or remember which GPIOs were connected. +/// +/// ## Relationship to pins +/// +/// A [`DedicatedGpioFlex`] connects one dedicated channel to one GPIO pin for both +/// input and output. The bundle operates on *channels*, not pins: writing channel bits +/// affects the pins currently connected to those channels. +#[cfg_attr( + esp32s3, + doc = r#" + +
    +The method output_levels is currently not available on ESP32-S3 due to +an LLVM bug. See https://github.com/espressif/llvm-project/issues/120 for details. +
    +"# +)] +#[cfg_attr( + multi_core, + doc = r#" + +
    +Dedicated GPIO channels are tied to CPU cores. All flex drivers attached to a bundle must be +configured on the current and same core, and the bundle must only be used on the core that created it. + +For example, do not create the bundle on core 0 and then call write_bits from a task +running on core 1. +
    +"# +)] +#[doc = ""] +/// ## Examples +/// +/// ```rust, no_run +/// # {before_snippet} +/// # +/// use esp_hal::gpio::{ +/// Flex, +/// Level, +/// dedicated::{DedicatedGpio, DedicatedGpioFlex, DedicatedGpioFlexBundle}, +/// }; +/// +/// // Create channels: +/// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); +/// +/// // Create flex drivers for two channels: +/// let p0 = Flex::new(peripherals.GPIO0); +/// let p1 = Flex::new(peripherals.GPIO1); +/// let f0 = DedicatedGpioFlex::new(channels.channel0, p0); +/// let f1 = DedicatedGpioFlex::new(channels.channel1, p1); +/// +/// // Build a bundle that controls channels 0 and 1: +/// let mut bundle = DedicatedGpioFlexBundle::new(); +/// bundle.enable_flex(&f0).enable_flex(&f1); +/// +/// // Set channel 0 high: +/// bundle.set_high(0b0000_0001); +/// +/// // Drive both channels at once: +/// bundle.write_bits(0b0000_0010); // ch0 low, ch1 high +/// +/// // Read both channels at once: +/// let in_bits = bundle.levels(); +/// # +/// # {after_snippet} +/// ``` +/// ... +pub struct DedicatedGpioFlexBundle<'lt> { + _marker: PhantomData<&'lt ()>, + mask: u32, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu, +} + +impl<'lt> DedicatedGpioFlexBundle<'lt> { + /// Creates a new, empty dedicated GPIO flex bundle. + /// + /// A bundle is a *logical* grouping of one or more [`DedicatedGpioFlex`] drivers. + /// Internally, it stores a precomputed channel mask (see [`Self::mask`]) which allows + /// writing multiple dedicated GPIO channels efficiently. + /// + /// The returned bundle initially contains no channels. Add flex drivers using + /// [`Self::enable_flex`]. + /// + /// ## Notes + /// + /// - Creating a bundle does **not** configure any hardware by itself. + pub fn new() -> Self { + Self { + _marker: PhantomData, + mask: 0, + #[cfg(all(debug_assertions, multi_core))] + core: Cpu::current(), + } + } + + #[doc_replace] + /// Returns the channel mask of this bundle. + /// + /// Each bit corresponds to one dedicated GPIO channel: + /// - bit 0 -> channel 0 + /// - bit 1 -> channel 1 + /// - ... + /// + /// A bit is set to 1 if that channel is currently included in the bundle. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// + /// use esp_hal::gpio::{ + /// Flex, + /// dedicated::{DedicatedGpio, DedicatedGpioFlex, DedicatedGpioFlexBundle}, + /// }; + /// + /// let channels = DedicatedGpio::new(peripherals.GPIO_DEDICATED); + /// let f0 = DedicatedGpioFlex::new(channels.channel0, Flex::new(peripherals.GPIO0)); + /// let f2 = DedicatedGpioFlex::new(channels.channel2, Flex::new(peripherals.GPIO2)); + /// + /// let mut bundle = DedicatedGpioFlexBundle::new(); + /// bundle.enable_flex(&f0).enable_flex(&f2); + /// + /// let mask = bundle.mask(); // bit 0 -> ch0, bit 2 -> ch2, ... + /// // For this bundle: mask == 0b0000_0101 (channels 0 and 2). + /// + /// # {after_snippet} + /// ``` + #[inline(always)] + pub fn mask(&self) -> u32 { + self.mask + } + + /// Attaches (enables) an already-configured dedicated flex driver to this bundle. After + /// enabling, you will be able to control the input/output channels of `flex` via this + /// bundle. + /// + /// This method logically borrows the provided [`DedicatedGpioFlex`] for the lifetime `'lt`. + /// Multiple bundles may borrow the same flex driver at the same time. Check examples in the + /// module-level documentation for more. + /// + /// ## Notes + /// + /// - This function does not change any input/output state. + #[cfg_attr( + multi_core, + doc = r#" +
    +All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself. +
    + "# + )] + pub fn enable_flex<'d>(&mut self, flex: &'lt DedicatedGpioFlex<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + flex.core, self.core, + "Trying to enable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask |= flex.mask; + self + } + + /// Disables a dedicated flex driver in this bundle. + /// + /// This updates the internal mask by clearing the channel bit(s) of `flex`. After disabling, + /// future *bundle* operations will no longer touch those channels. + /// + /// ## Notes + /// + /// - This does **not** affect `flex` itself. It only changes which channels future *bundle* + /// operations will touch. + /// - This does **not** end the lifetime-based borrow of `flex`. Even after disabling, `flex` + /// still cannot be moved or dropped while this bundle exists. + /// - You can re-enable it later via [`Self::enable_flex`]. + #[cfg_attr( + multi_core, + doc = r#" +
    +You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself. +
    +"# + )] + pub fn disable_flex<'d>(&mut self, flex: &'lt DedicatedGpioFlex<'d>) -> &mut Self { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + flex.core, self.core, + "Trying to disable a dedicated GPIO driver configured on a different core from the bundle." + ); + self.mask &= !flex.mask; + self + } + + /// Sets selected channels **high**. + /// + /// For every bit set to 1 in `bits`, the corresponding channel is driven high. + /// Bits set to 0 are left unchanged. + /// + /// ## Example + /// + /// If the bundle contains channels 0 and 2, then `bundle.set_high(0b0000_0101)` sets + /// channel 0 and 2 high. + /// + ///
    + /// + /// The caller must ensure that `bits` only contains channels included in this bundle, + /// i.e. `bits & !self.mask() == 0`. + /// + /// For example, if the bundle mask is `0b0000_0011` (channels 0 and 1), then `bits` must not + /// set bit 2 (e.g. `0b0000_0100`), or you would modify channel 2 outside the bundle. + /// + /// When compiled with `debug-assertions`, this condition is checked at runtime. + ///
    + #[inline(always)] + pub fn set_high(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + debug_assert!( + (bits & !self.mask) == 0, + "Trying to set bits outside of the bundle mask" + ); + ll::write(bits, bits); // or ll::write(self.mask, bits); + } + + /// Sets selected channels **low**. + /// + /// For every bit set to 1 in `bits`, the corresponding channel is driven low. + /// Bits set to 0 are left unchanged. + /// + /// ## Example + /// + /// If the bundle contains channels 0 and 2, then `bundle.set_low(0b0000_0101)` sets + /// channel 0 and 2 low. + /// + ///
    + /// + /// The caller must ensure that `bits` only contains channels included in this bundle, + /// i.e. `bits & !self.mask() == 0`. + /// + /// For example, if the bundle mask is `0b0000_0011` (channels 0 and 1), then `bits` must not + /// set bit 7 (e.g. `0b1000_0000`), or you would modify channel 7 outside the bundle. + /// + /// When compiled with `debug-assertions`, this condition is checked at runtime. + ///
    + #[inline(always)] + pub fn set_low(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + debug_assert!( + (bits & !self.mask) == 0, + "Trying to clear bits outside of the bundle mask" + ); + ll::write(bits, 0); // or ll::write(bits & self.mask, 0); the latter is safer but slower + } + + /// Writes output levels for **all channels included by this bundle**. + /// + /// This method updates the output state of every channel whose bit is set in + /// [`Self::mask`]. Channels not included in the bundle are not modified. + /// + /// `bits` provides the level for each channel in the bundle: + /// - bit = 0 -> low + /// - bit = 1 -> high + /// + /// ## Example + /// + /// If `self.mask()` is `0b1111_0000` (bundle contains channels 4..=7), then + /// `bundle.write_bits(0b0001_0000)` sets channel 4 high and channels 5..=7 low, + /// while leaving channels 0..=3 unchanged. + /// + ///
    + /// + /// This function only writes channels selected by [`Self::mask`]. It will **not** + /// change channels outside the mask, even if `bits` contains 1s there. + /// + /// Make sure this "masked write" behavior matches what you intend. + /// + ///
    + #[inline(always)] + pub fn write_bits(&mut self, bits: u32) { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + ll::write(self.mask, bits); + } + + /// Returns the current output levels of the channels included by this bundle. + /// + /// For channels outside the bundle mask, the corresponding bits are always 0. + /// + /// ## Example + /// + /// If the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then + /// `output_levels()` will only contain bits 0, 1, and 3, regardless of the output + /// state of other channels. + #[cfg(not(esp32s3))] + #[inline(always)] + pub fn output_levels(&self) -> u32 { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + ll::read_out() & self.mask + } + + /// Reads the current state of the channels included by this bundle. + /// + /// For channels outside the bundle mask, the corresponding bits are always 0. + /// + /// ## Example + /// + /// If the bundle mask is `0b0000_1011` (channels 0, 1, and 3), then + /// `levels()` will only contain bits 0, 1, and 3, regardless of the state + /// of other channels. + #[inline(always)] + pub fn levels(&self) -> u32 { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + ll::read_in() & self.mask + } + + /// Returns `true` if all channels in this bundle are currently high. + #[inline(always)] + pub fn all_high(&self) -> bool { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + (ll::read_in() & self.mask) == self.mask + } + + /// Returns `true` if all channels in this bundle are currently low. + #[inline(always)] + pub fn all_low(&self) -> bool { + #[cfg(all(debug_assertions, multi_core))] + debug_assert_eq!( + self.core, + Cpu::current(), + "Dedicated GPIO used on a different CPU core than it was created on" + ); + + (ll::read_in() & self.mask) == 0 + } +} + +impl<'lt> Default for DedicatedGpioFlexBundle<'lt> { + fn default() -> Self { + Self::new() + } +} + +#[cfg(esp32s2)] +mod ll { + #[inline(always)] + pub(super) fn set_output_enabled(_mask: u32, _en: bool) { + // nothing to do + } + + #[inline(always)] + pub(super) fn read_in() -> u32 { + let val; + unsafe { core::arch::asm!("get_gpio_in {0}", out(reg) val) }; + val + } + + #[inline(always)] + pub(super) fn read_out() -> u32 { + let val; + unsafe { core::arch::asm!("rur.gpio_out {0}", out(reg) val) }; + val + } + + #[inline(always)] + pub(super) fn write(mask: u32, value: u32) { + unsafe { core::arch::asm!("wr_mask_gpio_out {0}, {1}", in(reg) mask, in(reg) value) } + } +} + +#[cfg(esp32s3)] +mod ll { + #[inline(always)] + pub(super) fn set_output_enabled(_mask: u32, _en: bool) { + // nothing to do + } + + #[inline(always)] + pub(super) fn read_in() -> u32 { + let val; + unsafe { core::arch::asm!("ee.get_gpio_in {0}", out(reg) val) }; + val + } + + #[cfg(not(esp32s3))] + #[inline(always)] + pub(super) fn read_out() -> u32 { + // currently unavailable due to an LLVM bug, see https://github.com/espressif/llvm-project/issues/120 + let val; + unsafe { core::arch::asm!("rur.gpio_out {0}", out(reg) val) }; + val + } + + #[inline(always)] + pub(super) fn write(mask: u32, value: u32) { + unsafe { core::arch::asm!("ee.wr_mask_gpio_out {0}, {1}", in(reg) mask, in(reg) value) } + } +} + +#[cfg(riscv)] +mod ll { + // CSR_GPIO_OEN_USER 0x803 + // CSR_GPIO_IN_USER 0x804 + // CSR_GPIO_OUT_USER 0x805 + + #[inline(always)] + pub(super) fn set_output_enabled(mask: u32, en: bool) { + riscv::read_csr!(0x803); + riscv::write_csr!(0x803); + + unsafe { + let bits = _read(); + if en { + _write(bits | mask as usize) + } else { + _write(bits & !mask as usize) + } + } + } + + #[inline(always)] + pub(super) fn read_in() -> u32 { + riscv::read_csr!(0x804); + unsafe { _read() as u32 } + } + + #[inline(always)] + pub(super) fn read_out() -> u32 { + riscv::read_csr!(0x805); + unsafe { _read() as u32 } + } + + #[inline(always)] + pub(super) fn write(mask: u32, value: u32) { + riscv::set!(0x805); + riscv::clear!(0x805); + unsafe { + _set((mask & value) as usize); + _clear((mask & !value) as usize); + } + } +} diff --git a/esp-hal/src/gpio/embedded_hal_impls.rs b/esp-hal/src/gpio/embedded_hal_impls.rs new file mode 100644 index 00000000000..24c8fab495c --- /dev/null +++ b/esp-hal/src/gpio/embedded_hal_impls.rs @@ -0,0 +1,141 @@ +use embedded_hal::digital; +use embedded_hal_async::digital::Wait; + +#[cfg(feature = "unstable")] +use super::Flex; +use super::{Input, Output}; + +impl digital::ErrorType for Input<'_> { + type Error = core::convert::Infallible; +} + +impl digital::InputPin for Input<'_> { + fn is_high(&mut self) -> Result { + Ok(Self::is_high(self)) + } + + fn is_low(&mut self) -> Result { + Ok(Self::is_low(self)) + } +} + +impl digital::ErrorType for Output<'_> { + type Error = core::convert::Infallible; +} + +impl digital::OutputPin for Output<'_> { + fn set_low(&mut self) -> Result<(), Self::Error> { + Self::set_low(self); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + Self::set_high(self); + Ok(()) + } +} + +impl digital::StatefulOutputPin for Output<'_> { + fn is_set_high(&mut self) -> Result { + Ok(Self::is_set_high(self)) + } + + fn is_set_low(&mut self) -> Result { + Ok(Self::is_set_low(self)) + } +} + +#[instability::unstable] +impl digital::InputPin for Flex<'_> { + fn is_high(&mut self) -> Result { + Ok(Self::is_high(self)) + } + + fn is_low(&mut self) -> Result { + Ok(Self::is_low(self)) + } +} + +#[instability::unstable] +impl digital::ErrorType for Flex<'_> { + type Error = core::convert::Infallible; +} + +#[instability::unstable] +impl digital::OutputPin for Flex<'_> { + fn set_low(&mut self) -> Result<(), Self::Error> { + Self::set_low(self); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + Self::set_high(self); + Ok(()) + } +} + +#[instability::unstable] +impl digital::StatefulOutputPin for super::Flex<'_> { + fn is_set_high(&mut self) -> Result { + Ok(Self::is_set_high(self)) + } + + fn is_set_low(&mut self) -> Result { + Ok(Self::is_set_low(self)) + } +} + +#[instability::unstable] +impl Wait for Flex<'_> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Self::wait_for_high(self).await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Self::wait_for_low(self).await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_rising_edge(self).await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_falling_edge(self).await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_any_edge(self).await; + Ok(()) + } +} + +impl Wait for Input<'_> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Self::wait_for_high(self).await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Self::wait_for_low(self).await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_rising_edge(self).await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_falling_edge(self).await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Self::wait_for_any_edge(self).await; + Ok(()) + } +} diff --git a/esp-hal/src/gpio/etm.rs b/esp-hal/src/gpio/etm.rs new file mode 100644 index 00000000000..262346e42b5 --- /dev/null +++ b/esp-hal/src/gpio/etm.rs @@ -0,0 +1,362 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Event Task Matrix (ETM) +//! +//! ## Overview +//! GPIO supports ETM function, that is, the ETM task of GPIO can be +//! triggered by the ETM event of any peripheral, or the ETM task of any +//! peripheral can be triggered by the ETM event of GPIO. +//! +//! ## Configuration +//! The GPIO ETM provides several task channels. The ETM tasks that each task +//! channel can receive are: +//! - SET: GPIO goes high when triggered +//! - CLEAR: GPIO goes low when triggered +//! - TOGGLE: GPIO toggle level when triggered. +//! +//! GPIO has several event channels, and the ETM events that each event +//! channel can generate are: +//! - RISE_EDGE: Indicates that the output signal of the corresponding GPIO has a rising edge +//! - FALL_EDGE: Indicates that the output signal of the corresponding GPIO has a falling edge +//! - ANY_EDGE: Indicates that the output signal of the corresponding GPIO is reversed +//! +//! ## Examples +//! ### Toggle an LED When a Button is Pressed +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::etm::Channels; +//! # use esp_hal::etm::Etm; +//! # use esp_hal::gpio::etm::InputConfig; +//! # use esp_hal::gpio::etm::OutputConfig; +//! # use esp_hal::gpio::Pull; +//! # use esp_hal::gpio::Level; +//! # +//! # let led = peripherals.GPIO1; +//! # let button = peripherals.GPIO9; +//! +//! let gpio_ext = Channels::new(peripherals.GPIO_SD); +//! let led_task = gpio_ext.channel0_task.toggle( +//! led, +//! OutputConfig { +//! open_drain: false, +//! pull: Pull::None, +//! initial_state: Level::Low, +//! }, +//! ); +//! let button_event = gpio_ext +//! .channel0_event +//! .falling_edge(button, InputConfig { pull: Pull::Down }); +//! # {after_snippet} +//! ``` + +use core::marker::PhantomData; + +use crate::{ + gpio::{ + Level, + Pull, + interconnect::{InputSignal, OutputSignal}, + }, + peripherals::GPIO_SD, + private, +}; + +/// All the GPIO ETM channels +#[non_exhaustive] +pub struct Channels<'d> { + _gpio_sd: GPIO_SD<'d>, + /// Task channel 0 for triggering GPIO tasks. + pub channel0_task: TaskChannel<0>, + /// Event channel 0 for handling GPIO events. + pub channel0_event: EventChannel<0>, + /// Task channel 1 for triggering GPIO tasks. + pub channel1_task: TaskChannel<1>, + /// Event channel 1 for handling GPIO events. + pub channel1_event: EventChannel<1>, + /// Task channel 2 for triggering GPIO tasks. + pub channel2_task: TaskChannel<2>, + /// Event channel 2 for handling GPIO events. + pub channel2_event: EventChannel<2>, + /// Task channel 3 for triggering GPIO tasks. + pub channel3_task: TaskChannel<3>, + /// Event channel 3 for handling GPIO events. + pub channel3_event: EventChannel<3>, + /// Task channel 4 for triggering GPIO tasks. + pub channel4_task: TaskChannel<4>, + /// Event channel 4 for handling GPIO events. + pub channel4_event: EventChannel<4>, + /// Task channel 5 for triggering GPIO tasks. + pub channel5_task: TaskChannel<5>, + /// Event channel 5 for handling GPIO events. + pub channel5_event: EventChannel<5>, + /// Task channel 6 for triggering GPIO tasks. + pub channel6_task: TaskChannel<6>, + /// Event channel 6 for handling GPIO events. + pub channel6_event: EventChannel<6>, + /// Task channel 7 for triggering GPIO tasks. + pub channel7_task: TaskChannel<7>, + /// Event channel 7 for handling GPIO events. + pub channel7_event: EventChannel<7>, +} + +impl<'d> Channels<'d> { + /// Create a new instance + pub fn new(peripheral: GPIO_SD<'d>) -> Self { + Self { + _gpio_sd: peripheral, + channel0_task: TaskChannel {}, + channel0_event: EventChannel {}, + channel1_task: TaskChannel {}, + channel1_event: EventChannel {}, + channel2_task: TaskChannel {}, + channel2_event: EventChannel {}, + channel3_task: TaskChannel {}, + channel3_event: EventChannel {}, + channel4_task: TaskChannel {}, + channel4_event: EventChannel {}, + channel5_task: TaskChannel {}, + channel5_event: EventChannel {}, + channel6_task: TaskChannel {}, + channel6_event: EventChannel {}, + channel7_task: TaskChannel {}, + channel7_event: EventChannel {}, + } + } +} + +/// Configuration for an ETM controlled GPIO input pin +// TODO: remove this +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InputConfig { + /// Configuration for the internal pull-up resistors + pub pull: Pull, +} + +impl Default for InputConfig { + fn default() -> Self { + Self { pull: Pull::None } + } +} + +/// An ETM controlled GPIO event +pub struct EventChannel {} + +impl EventChannel { + /// Trigger at rising edge + pub fn rising_edge<'d>( + self, + pin: impl Into>, + pin_config: InputConfig, + ) -> Event<'d> { + self.into_event(pin, pin_config, EventKind::Rising) + } + + /// Trigger at falling edge + pub fn falling_edge<'d>( + self, + pin: impl Into>, + pin_config: InputConfig, + ) -> Event<'d> { + self.into_event(pin, pin_config, EventKind::Falling) + } + + /// Trigger at any edge + pub fn any_edge<'d>( + self, + pin: impl Into>, + pin_config: InputConfig, + ) -> Event<'d> { + self.into_event(pin, pin_config, EventKind::Any) + } + + fn into_event<'d>( + self, + pin: impl Into>, + pin_config: InputConfig, + kind: EventKind, + ) -> Event<'d> { + let pin = pin.into(); + if let Some(number) = pin.gpio_number() { + pin.apply_input_config(&crate::gpio::InputConfig::default().with_pull(pin_config.pull)); + pin.set_input_enable(true); + + enable_event_channel(C, number); + } + Event { + id: kind.id() + C, + _pin: PhantomData, + } + } +} + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum EventKind { + Rising, + Falling, + Any, +} + +impl EventKind { + fn id(&self) -> u8 { + match self { + EventKind::Rising => 1, + EventKind::Falling => 9, + EventKind::Any => 17, + } + } +} + +/// Event for rising edge +pub struct Event<'d> { + _pin: PhantomData<&'d mut ()>, + id: u8, +} + +impl private::Sealed for Event<'_> {} + +impl crate::etm::EtmEvent for Event<'_> { + fn id(&self) -> u8 { + self.id + } +} + +/// Configuration for an ETM controlled GPIO output pin +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OutputConfig { + /// Set to open-drain output + pub open_drain: bool, + /// Only used when open-drain + pub pull: Pull, + /// Initial pin state + pub initial_state: Level, +} + +impl Default for OutputConfig { + fn default() -> Self { + Self { + open_drain: false, + pull: Pull::None, + initial_state: Level::Low, + } + } +} + +/// An ETM controlled GPIO task +pub struct TaskChannel {} + +impl TaskChannel { + // In theory we could have multiple pins assigned to the same task. Not sure how + // useful that would be. If we want to support it, the easiest would be + // to offer additional functions like `set2`, `set3` etc. where the + // number is the pin-count + + /// Task to set a high level + pub fn set<'d>(self, pin: impl Into>, pin_config: OutputConfig) -> Task<'d> { + self.into_task(pin, pin_config, TaskKind::Set) + } + + /// Task to set a low level + pub fn clear<'d>(self, pin: impl Into>, pin_config: OutputConfig) -> Task<'d> { + self.into_task(pin, pin_config, TaskKind::Clear) + } + + /// Task to toggle the level + pub fn toggle<'d>( + self, + pin: impl Into>, + pin_config: OutputConfig, + ) -> Task<'d> { + self.into_task(pin, pin_config, TaskKind::Toggle) + } + + fn into_task<'d>( + self, + pin: impl Into>, + pin_config: OutputConfig, + kind: TaskKind, + ) -> Task<'d> { + let pin = pin.into(); + + if let Some(number) = pin.gpio_number() { + let config = if pin_config.open_drain { + super::OutputConfig::default() + .with_drive_mode(super::DriveMode::OpenDrain) + .with_pull(pin_config.pull) + } else { + super::OutputConfig::default() + }; + + pin.set_output_high(pin_config.initial_state.into()); + pin.apply_output_config(&config); + pin.set_output_enable(true); + + // TODO: what should we do if the user passes a Level/NoPin? + enable_task_channel(C, number); + } + Task { + id: kind.id() + C, + _pin: PhantomData, + } + } +} + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum TaskKind { + Set, + Clear, + Toggle, +} + +impl TaskKind { + fn id(&self) -> u8 { + match self { + TaskKind::Set => 1, + TaskKind::Clear => 9, + TaskKind::Toggle => 17, + } + } +} + +/// Task for set operation +pub struct Task<'d> { + _pin: PhantomData<&'d mut ()>, + id: u8, +} + +impl private::Sealed for Task<'_> {} + +impl crate::etm::EtmTask for Task<'_> { + fn id(&self) -> u8 { + self.id + } +} + +fn enable_task_channel(channel: u8, pin: u8) { + let gpio_sd = GPIO_SD::regs(); + let ptr = unsafe { gpio_sd.etm_task_p0_cfg().as_ptr().add(pin as usize / 4) }; + let shift = 8 * (pin as usize % 4); + // bit 0 = en, bit 1-3 = channel + unsafe { + ptr.write_volatile( + ptr.read_volatile() & !(0xf << shift) + | (1 << shift) + | ((channel as u32) << (shift + 1)), + ); + } +} + +fn enable_event_channel(channel: u8, pin: u8) { + let gpio_sd = GPIO_SD::regs(); + gpio_sd + .etm_event_ch_cfg(channel as usize) + .modify(|_, w| w.event_en().clear_bit()); + gpio_sd + .etm_event_ch_cfg(channel as usize) + .modify(|_, w| unsafe { w.event_sel().bits(pin) }); + gpio_sd + .etm_event_ch_cfg(channel as usize) + .modify(|_, w| w.event_en().set_bit()); +} diff --git a/esp-hal/src/gpio/interconnect.rs b/esp-hal/src/gpio/interconnect.rs new file mode 100644 index 00000000000..2a62193b7b0 --- /dev/null +++ b/esp-hal/src/gpio/interconnect.rs @@ -0,0 +1,947 @@ +//! # Peripheral signal interconnect using the GPIO matrix. +//! +//! The GPIO matrix offers flexible connection options between GPIO pins and +//! peripherals. This module offers capabilities not covered by GPIO pin types +//! and drivers, like routing fixed logic levels to peripheral inputs, or +//! inverting input and output signals. +//! +//! > Note that routing a signal through the GPIO matrix adds some latency to +//! > the signal. This is not a problem for most peripherals, but it can be an +//! > issue for high-speed peripherals like SPI or I2S. `esp-hal` tries to +//! > bypass the GPIO matrix when possible (e.g. when the pin can be configured +//! > as a suitable Alternate Function for the peripheral signal, and other +//! > settings are compatible), but silently falls back to the GPIO matrix for +//! > flexibility. +#![doc = concat!("## Relation to the ", crate::trm_markdown_link!("iomuxgpio"))] +//! The GPIO drivers implement IO MUX and pin functionality (input/output +//! buffers, pull resistors, etc.). The GPIO matrix is represented by signals +//! and the [`PeripheralInput`] and [`PeripheralOutput`] traits. There is some +//! overlap between them: signal routing depends on what type is passed to a +//! peripheral driver's pin setter functions. +//! +//! ## Signals +//! +//! GPIO signals are represented by the [`InputSignal`] and [`OutputSignal`] +//! structs. Peripheral drivers accept [`PeripheralInput`] and +//! [`PeripheralOutput`] implementations which are implemented for anything that +//! can be converted into the signal types: +//! - GPIO pins and drivers +//! - A fixed logic [`Level`] +//! - [`NoPin`] +//! +//! Note that some of these exist for convenience only. `Level` is meaningful as +//! a peripheral input, but not as a peripheral output. `NoPin` is a placeholder +//! for when a peripheral driver does not require a pin, but the API requires +//! one. It is equivalent to [`Level::Low`]. +//! +//! ### Splitting drivers into signals +//! +//! Each GPIO pin driver such as [`Input`], can be converted +//! into input or output signals. [`Flex`], which can be either input or output, +//! can be [`split`](Flex::split) into both signals at once. These signals can +//! then be individually connected to a peripheral input or output signal. This +//! allows for flexible routing of signals between peripherals and GPIO pins. +//! +//! Note that only configured GPIO drivers can be safely turned into signals. +//! This conversion freezes the pin configuration, otherwise it would be +//! possible for multiple peripheral drivers to configure the same GPIO pin at +//! the same time, which is undefined behavior. +//! +//! ### Splitting pins into signals +//! +//! GPIO pin types such as [`GPIO0`] or [`AnyPin`] can be **unsafely** +//! [split](AnyPin::split) into signals. In this case you need to carefully +//! ensure that only a single driver configures the split pin, by selectively +//! [freezing](`InputSignal::freeze`) the signals. +//! +//! For example, if you want to route GPIO3 to both a Pulse Counter +//! input and a [UART](crate::uart::Uart) RX line, you will need to make sure +//! one of the signals is frozen, otherwise the driver that is configured later +//! will overwrite the other driver's configuration. Configuring the signals on +//! multiple cores is undefined behaviour unless you ensure the configuration +//! does not happen at the same time. +//! +//! ### Using pins and signals +//! +//! A GPIO pin can be configured either with a GPIO driver such as [`Input`], or +//! by a peripheral driver using a pin assignment method such as +//! [`Spi::with_mosi`]. The peripheral drivers' preferences can be overridden by +//! passing a pin driver to the peripheral driver. When converting a driver to +//! signals, the underlying signals will be initially +//! [frozen](InputSignal::freeze) to support this use case. +//! +//! ## Inverting inputs and outputs +//! +//! The GPIO matrix allows for inverting the input and output signals. This can +//! be configured via [`InputSignal::with_input_inverter`] and +//! [`OutputSignal::with_input_inverter`]. The hardware is configured +//! accordingly when the signal is connected to a peripheral input or output. +//! +//! ## Connection rules +//! +//! Peripheral signals and GPIOs can be connected with the following +//! constraints: +//! +//! - A peripheral input signal must be driven by exactly one signal, which can be a GPIO input or a +//! constant level. +//! - A peripheral output signal can be connected to any number of GPIOs. These GPIOs can be +//! configured differently. The peripheral drivers will only support a single connection (that is, +//! they disconnect previously configured signals on repeat calls to the same function), but you +//! can use `esp_hal::gpio::OutputSignal::connect_to` (note that the type is currently hidden from +//! the documentation) to connect multiple GPIOs to the same output signal. +//! - A GPIO input signal can be connected to any number of peripheral inputs. +//! - A GPIO output can be driven by only one peripheral output. +//! +//! [`GPIO0`]: crate::peripherals::GPIO0 +//! [`Spi::with_mosi`]: crate::spi::master::Spi::with_mosi + +#[cfg(feature = "unstable")] +use crate::gpio::{Input, Output}; +use crate::{ + gpio::{ + self, + AlternateFunction, + AnyPin, + Flex, + InputPin, + Level, + NoPin, + OutputPin, + Pin, + PinGuard, + }, + peripherals::GPIO, + private::{self, Sealed}, +}; + +/// The base of all peripheral signals. +/// +/// This trait represents a signal in the GPIO matrix. Signals are converted or +/// split from GPIO pins and can be connected to peripheral inputs and outputs. +/// +/// All signals can be peripheral inputs, but not all output-like types should +/// be allowed to be passed as inputs. This trait bridges this gap by defining +/// the logic, but not declaring the signal to be an actual Input signal. +pub trait PeripheralSignal<'d>: Sealed { + /// Connects the peripheral input to an input signal source. + #[doc(hidden)] // Considered unstable + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal); +} + +/// A signal that can be connected to a peripheral input. +/// +/// Peripheral drivers are encouraged to accept types that implement this and +/// [`PeripheralOutput`] as arguments instead of pin types. +#[allow( + private_bounds, + reason = "InputSignal is unstable, but the trait needs to be public" +)] +pub trait PeripheralInput<'d>: Into> + PeripheralSignal<'d> {} + +/// A signal that can be connected to a peripheral input and/or output. +/// +/// Peripheral drivers are encouraged to accept types that implement this and +/// [`PeripheralInput`] as arguments instead of pin types. +#[allow( + private_bounds, + reason = "OutputSignal is unstable, but the trait needs to be public" +)] +pub trait PeripheralOutput<'d>: Into> + PeripheralSignal<'d> { + /// Connects the peripheral output to an output signal target. + #[doc(hidden)] // Considered unstable + fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal); + + /// Disconnects the peripheral output from an output signal target. + /// + /// This function clears the entry in the IO MUX that + /// associates this output pin with a previously connected + /// [signal](`gpio::OutputSignal`). Any other outputs connected to the + /// peripheral remain intact. + #[doc(hidden)] // Considered unstable + fn disconnect_from_peripheral_output(&self); +} + +// Pins +impl<'d, P> PeripheralSignal<'d> for P +where + P: Pin + 'd, +{ + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + let pin = unsafe { AnyPin::steal(self.number()) }; + InputSignal::new(pin).connect_input_to_peripheral(signal); + } +} +impl<'d, P> PeripheralInput<'d> for P where P: InputPin + 'd {} + +impl<'d, P> PeripheralOutput<'d> for P +where + P: OutputPin + 'd, +{ + fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) { + let pin = unsafe { AnyPin::steal(self.number()) }; + OutputSignal::new(pin).connect_peripheral_to_output(signal); + } + fn disconnect_from_peripheral_output(&self) { + let pin = unsafe { AnyPin::steal(self.number()) }; + OutputSignal::new(pin).disconnect_from_peripheral_output(); + } +} + +// Pin drivers +#[instability::unstable] +impl<'d> PeripheralSignal<'d> for Flex<'d> { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + self.pin.connect_input_to_peripheral(signal); + } +} +#[instability::unstable] +impl<'d> PeripheralInput<'d> for Flex<'d> {} +#[instability::unstable] +impl<'d> PeripheralOutput<'d> for Flex<'d> { + fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) { + self.pin.connect_peripheral_to_output(signal); + } + fn disconnect_from_peripheral_output(&self) { + self.pin.disconnect_from_peripheral_output(); + } +} + +#[instability::unstable] +impl<'d> PeripheralSignal<'d> for Input<'d> { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + self.pin.connect_input_to_peripheral(signal); + } +} +#[instability::unstable] +impl<'d> PeripheralInput<'d> for Input<'d> {} + +#[instability::unstable] +impl<'d> PeripheralSignal<'d> for Output<'d> { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + self.pin.connect_input_to_peripheral(signal); + } +} +#[instability::unstable] +impl<'d> PeripheralOutput<'d> for Output<'d> { + fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) { + self.pin.connect_peripheral_to_output(signal); + } + fn disconnect_from_peripheral_output(&self) { + self.pin.disconnect_from_peripheral_output(); + } +} + +// Placeholders +impl PeripheralSignal<'_> for NoPin { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + // Arbitrary choice but we need to overwrite a previous signal input + // association. + Level::Low.connect_input_to_peripheral(signal); + } +} +impl PeripheralInput<'_> for NoPin {} +impl PeripheralOutput<'_> for NoPin { + fn connect_peripheral_to_output(&self, _: gpio::OutputSignal) { + // A peripheral's outputs may be connected to any number of GPIOs. + // Connecting to, and disconnecting from a NoPin is therefore a + // no-op, as we are adding and removing nothing from that list of + // connections. + } + fn disconnect_from_peripheral_output(&self) { + // A peripheral's outputs may be connected to any number of GPIOs. + // Connecting to, and disconnecting from a NoPin is therefore a + // no-op, as we are adding and removing nothing from that list of + // connections. + } +} + +impl PeripheralSignal<'_> for Level { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + Signal::Level(*self).connect_to_peripheral_input(signal, false, true); + } +} +impl PeripheralInput<'_> for Level {} +impl PeripheralOutput<'_> for Level { + fn connect_peripheral_to_output(&self, _: gpio::OutputSignal) { + // There is no such thing as a constant-high level peripheral output, + // the implementation just exists for convenience. + } + fn disconnect_from_peripheral_output(&self) { + // There is no such thing as a constant-high level peripheral output, + // the implementation just exists for convenience. + } +} + +// Split signals +impl<'d> PeripheralSignal<'d> for InputSignal<'d> { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + // Since there can only be one input signal connected to a peripheral + // at a time, this function will disconnect any previously + // connected input signals. + self.pin.connect_to_peripheral_input( + signal, + self.is_input_inverted(), + self.is_gpio_matrix_forced(), + ); + } +} +impl<'d> PeripheralInput<'d> for InputSignal<'d> {} + +impl<'d> PeripheralSignal<'d> for OutputSignal<'d> { + fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) { + self.pin.connect_to_peripheral_input( + signal, + self.is_input_inverted(), + self.is_gpio_matrix_forced(), + ); + } +} +impl<'d> PeripheralOutput<'d> for OutputSignal<'d> { + fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) { + self.pin.connect_peripheral_to_output( + signal, + self.is_output_inverted(), + self.is_gpio_matrix_forced(), + true, + false, + ); + } + fn disconnect_from_peripheral_output(&self) { + self.pin.disconnect_from_peripheral_output(); + } +} + +impl gpio::InputSignal { + fn can_use_gpio_matrix(self) -> bool { + self as usize <= property!("gpio.input_signal_max") + } + + /// Connects a peripheral input signal to a GPIO or a constant level. + /// + /// Note that connecting multiple GPIOs to a single peripheral input is not + /// possible and the previous connection will be replaced. + /// + /// Also note that a peripheral input must always be connected to something, + /// so if you want to disconnect it from GPIOs, you should connect it to a + /// constant level. + /// + /// This function allows connecting a peripheral input to either a + /// [`PeripheralInput`] or [`PeripheralOutput`] implementation. + #[inline] + #[instability::unstable] + pub fn connect_to<'a>(self, pin: &impl PeripheralSignal<'a>) { + pin.connect_input_to_peripheral(self); + } +} + +impl gpio::OutputSignal { + fn can_use_gpio_matrix(self) -> bool { + self as usize <= property!("gpio.output_signal_max") + } + + /// Connects a peripheral output signal to a GPIO. + /// + /// Note that connecting multiple output signals to a single GPIO is not + /// possible and the previous connection will be replaced. + /// + /// Also note that it is possible to connect a peripheral output signal to + /// multiple GPIOs, and old connections will not be cleared automatically. + #[inline] + #[instability::unstable] + pub fn connect_to<'d>(self, pin: &impl PeripheralOutput<'d>) { + pin.connect_peripheral_to_output(self); + } + + /// Disconnects a peripheral output signal from a GPIO. + #[inline] + #[instability::unstable] + pub fn disconnect_from<'d>(self, pin: &impl PeripheralOutput<'d>) { + pin.disconnect_from_peripheral_output(); + } +} + +enum Signal<'d> { + Pin(AnyPin<'d>), + Level(Level), +} +impl Signal<'_> { + fn gpio_number(&self) -> Option { + match &self { + Signal::Pin(pin) => Some(pin.number()), + Signal::Level(_) => None, + } + } + + unsafe fn clone_unchecked(&self) -> Self { + match self { + Signal::Pin(pin) => Signal::Pin(unsafe { pin.clone_unchecked() }), + Signal::Level(level) => Signal::Level(*level), + } + } + + fn is_set_high(&self) -> bool { + match &self { + Signal::Pin(signal) => signal.is_set_high(), + Signal::Level(level) => *level == Level::High, + } + } + + fn is_input_high(&self) -> bool { + match &self { + Signal::Pin(signal) => signal.is_input_high(), + Signal::Level(level) => *level == Level::High, + } + } + + fn connect_to_peripheral_input( + &self, + signal: gpio::InputSignal, + is_inverted: bool, + force_gpio: bool, + ) { + let use_gpio_matrix = match self { + Signal::Pin(pin) => { + let af = if is_inverted || force_gpio { + AlternateFunction::GPIO + } else { + pin.input_signals(private::Internal) + .iter() + .find(|(_af, s)| *s == signal) + .map(|(af, _)| *af) + .unwrap_or(AlternateFunction::GPIO) + }; + pin.disable_usb_pads(); + pin.set_alternate_function(af); + af == AlternateFunction::GPIO + } + Signal::Level(_) => true, + }; + + if !signal.can_use_gpio_matrix() { + assert!( + !use_gpio_matrix, + "{:?} cannot be routed through the GPIO matrix", + signal + ); + // At this point we have set up the AF. The signal does not have a `func_in_sel_cfg` + // register, and we must not try to write to it. + return; + } + + let input = match self { + Signal::Pin(pin) => pin.number(), + Signal::Level(Level::Low) => property!("gpio.constant_0_input"), + Signal::Level(Level::High) => property!("gpio.constant_1_input"), + }; + + // No need for a critical section, this is a write and not a modify operation. + let offset = property!("gpio.func_in_sel_offset"); + GPIO::regs() + .func_in_sel_cfg(signal as usize - offset) + .write(|w| unsafe { + w.sel().bit(use_gpio_matrix); + w.in_inv_sel().bit(is_inverted); + // Connect to GPIO or constant level + w.in_sel().bits(input) + }); + } + + fn connect_peripheral_to_output( + &self, + signal: gpio::OutputSignal, + is_inverted: bool, + force_gpio: bool, + peripheral_control_output_enable: bool, + invert_output_enable: bool, + ) { + let Signal::Pin(pin) = self else { + return; + }; + let af = if is_inverted || force_gpio { + AlternateFunction::GPIO + } else { + pin.output_signals(private::Internal) + .iter() + .find(|(_af, s)| *s == signal) + .map(|(af, _)| *af) + .unwrap_or(AlternateFunction::GPIO) + }; + pin.disable_usb_pads(); + pin.set_alternate_function(af); + + let use_gpio_matrix = af == AlternateFunction::GPIO; + + assert!( + signal.can_use_gpio_matrix() || !use_gpio_matrix, + "{:?} cannot be routed through the GPIO matrix", + signal + ); + + GPIO::regs() + .func_out_sel_cfg(pin.number() as usize) + .write(|w| unsafe { + if use_gpio_matrix { + // Ignored if the signal is not routed through the GPIO matrix - alternate + // function selects peripheral signal directly. + w.out_sel().bits(signal as _); + w.inv_sel().bit(is_inverted); + } + w.oen_sel().bit(!peripheral_control_output_enable); + w.oen_inv_sel().bit(invert_output_enable) + }); + } + + fn disconnect_from_peripheral_output(&self) { + let Some(number) = self.gpio_number() else { + return; + }; + GPIO::regs() + .func_out_sel_cfg(number as usize) + .modify(|_, w| unsafe { w.out_sel().bits(gpio::OutputSignal::GPIO as _) }); + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy)] + struct InputFlags: u8 { + const ForceGpioMatrix = 1 << 0; + const Frozen = 1 << 1; + const InvertInput = 1 << 2; + } +} + +/// An input signal between a peripheral and a GPIO pin. +/// +/// If the `InputSignal` was obtained from a pin driver such as +/// [`Input`](crate::gpio::Input::split), the GPIO driver will be responsible +/// for configuring the pin with the correct settings, peripheral drivers will +/// not be able to modify the pin settings. +/// +/// Multiple input signals can be connected to one pin. +#[instability::unstable] +pub struct InputSignal<'d> { + pin: Signal<'d>, + flags: InputFlags, +} + +impl From for InputSignal<'_> { + fn from(level: Level) -> Self { + InputSignal::new_level(level) + } +} + +impl From for InputSignal<'_> { + fn from(_pin: NoPin) -> Self { + InputSignal::new_level(Level::Low) + } +} + +impl<'d, P> From

    for InputSignal<'d> +where + P: Pin + 'd, +{ + fn from(input: P) -> Self { + InputSignal::new(input.degrade()) + } +} + +impl<'d> From> for InputSignal<'d> { + fn from(pin: Flex<'d>) -> Self { + pin.peripheral_input() + } +} + +#[instability::unstable] +impl<'d> From> for InputSignal<'d> { + fn from(pin: Input<'d>) -> Self { + pin.pin.into() + } +} + +impl Sealed for InputSignal<'_> {} + +impl Clone for InputSignal<'_> { + fn clone(&self) -> Self { + Self { + pin: unsafe { self.pin.clone_unchecked() }, + flags: self.flags, + } + } +} + +impl<'d> InputSignal<'d> { + fn new_inner(inner: Signal<'d>) -> Self { + Self { + pin: inner, + flags: InputFlags::empty(), + } + } + + pub(crate) fn new(pin: AnyPin<'d>) -> Self { + Self::new_inner(Signal::Pin(pin)) + } + + pub(crate) fn new_level(level: Level) -> Self { + Self::new_inner(Signal::Level(level)) + } + + /// Freezes the pin configuration. + /// + /// This will prevent peripheral drivers using this signal from modifying + /// the pin settings. + pub fn freeze(mut self) -> Self { + self.flags.insert(InputFlags::Frozen); + self + } + + /// Unfreezes the pin configuration. + /// + /// This will enable peripheral drivers to modify the pin settings + /// again. + /// + /// # Safety + /// + /// This function is unsafe because it allows peripherals to modify the pin + /// configuration again. This can lead to undefined behavior if the pin + /// is being configured by multiple peripherals at the same time. It can + /// also lead to surprising behavior if the pin is passed to multiple + /// peripherals that expect conflicting settings. + pub unsafe fn unfreeze(&mut self) { + self.flags.remove(InputFlags::Frozen); + } + + /// Returns the GPIO number of the underlying pin. + /// + /// Returns `None` if the signal is a constant level. + pub fn gpio_number(&self) -> Option { + self.pin.gpio_number() + } + + /// Returns `true` if the input signal is high. + /// + /// Note that this does not take [`Self::with_input_inverter`] into account. + pub fn is_input_high(&self) -> bool { + self.pin.is_input_high() + } + + /// Returns the current signal level. + /// + /// Note that this does not take [`Self::with_input_inverter`] into account. + pub fn level(&self) -> Level { + self.is_input_high().into() + } + + /// Returns `true` if the input signal is configured to be inverted. + /// + /// Note that the hardware is not configured until the signal is actually + /// connected to a peripheral. + pub fn is_input_inverted(&self) -> bool { + self.flags.contains(InputFlags::InvertInput) + } + + /// Consumes the signal and returns a new one that inverts the peripheral's + /// input signal. + pub fn with_input_inverter(mut self, invert: bool) -> Self { + self.flags.set(InputFlags::InvertInput, invert); + self + } + + /// Consumes the signal and returns a new one that forces the GPIO matrix + /// to be used. + pub fn with_gpio_matrix_forced(mut self, force: bool) -> Self { + self.flags.set(InputFlags::ForceGpioMatrix, force); + self + } + + /// Returns `true` if the input signal must be routed through the GPIO + /// matrix. + pub fn is_gpio_matrix_forced(&self) -> bool { + self.flags.contains(InputFlags::ForceGpioMatrix) + } + + delegate::delegate! { + #[instability::unstable] + #[doc(hidden)] + to match &self.pin { + Signal::Pin(signal) => signal, + Signal::Level(_) => NoOp, + } { + pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)]; + } + } + + delegate::delegate! { + #[instability::unstable] + #[doc(hidden)] + to match &self.pin { + Signal::Pin(_) if self.flags.contains(InputFlags::Frozen) => NoOp, + Signal::Pin(signal) => signal, + Signal::Level(_) => NoOp, + } { + pub fn apply_input_config(&self, _config: &gpio::InputConfig); + pub fn set_input_enable(&self, on: bool); + } + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy)] + struct OutputFlags: u8 { + const ForceGpioMatrix = 1 << 0; + const Frozen = 1 << 1; + const InvertInput = 1 << 2; + const InvertOutput = 1 << 3; + } +} + +/// An (input and) output signal between a peripheral and a GPIO pin. +/// +/// If the `OutputSignal` was obtained from a pin driver such as +/// [`Output`](crate::gpio::Output::split), the GPIO driver will be responsible +/// for configuring the pin with the correct settings, peripheral drivers will +/// not be able to modify the pin settings. +/// +/// Note that connecting this to a peripheral input will enable the input stage +/// of the GPIO pin. +/// +/// Multiple pins can be connected to one output signal. +#[instability::unstable] +pub struct OutputSignal<'d> { + pin: Signal<'d>, + flags: OutputFlags, +} + +impl Sealed for OutputSignal<'_> {} + +impl From for OutputSignal<'_> { + fn from(level: Level) -> Self { + OutputSignal::new_level(level) + } +} + +impl From for OutputSignal<'_> { + fn from(_pin: NoPin) -> Self { + OutputSignal::new_level(Level::Low) + } +} + +impl<'d, P> From

    for OutputSignal<'d> +where + P: OutputPin + 'd, +{ + fn from(output: P) -> Self { + OutputSignal::new(output.degrade()) + } +} + +impl<'d> From> for OutputSignal<'d> { + fn from(pin: Flex<'d>) -> Self { + pin.into_peripheral_output() + } +} + +#[instability::unstable] +impl<'d> From> for OutputSignal<'d> { + fn from(pin: Output<'d>) -> Self { + pin.pin.into() + } +} + +impl<'d> OutputSignal<'d> { + fn new_inner(inner: Signal<'d>) -> Self { + Self { + pin: inner, + flags: OutputFlags::empty(), + } + } + + pub(crate) fn new(pin: AnyPin<'d>) -> Self { + Self::new_inner(Signal::Pin(pin)) + } + + pub(crate) fn new_level(level: Level) -> Self { + Self::new_inner(Signal::Level(level)) + } + + /// Freezes the pin configuration. + /// + /// This will prevent peripheral drivers using this signal from + /// modifying the pin settings. + pub fn freeze(mut self) -> Self { + self.flags.insert(OutputFlags::Frozen); + self + } + + /// Unfreezes the pin configuration. + /// + /// This will enable peripheral drivers to modify the pin settings + /// again. + /// + /// # Safety + /// + /// This function is unsafe because it allows peripherals to modify the pin + /// configuration again. This can lead to undefined behavior if the pin + /// is being configured by multiple peripherals at the same time. + /// It can also lead to surprising behavior if the pin is passed to multiple + /// peripherals that expect conflicting settings. + pub unsafe fn unfreeze(&mut self) { + self.flags.remove(OutputFlags::Frozen); + } + + /// Returns the GPIO number of the underlying pin. + /// + /// Returns `None` if the signal is a constant level. + pub fn gpio_number(&self) -> Option { + self.pin.gpio_number() + } + + /// Returns `true` if the input signal is configured to be inverted. + /// + /// Note that the hardware is not configured until the signal is actually + /// connected to a peripheral. + pub fn is_input_inverted(&self) -> bool { + self.flags.contains(OutputFlags::InvertInput) + } + + /// Returns `true` if the output signal is configured to be inverted. + /// + /// Note that the hardware is not configured until the signal is actually + /// connected to a peripheral. + pub fn is_output_inverted(&self) -> bool { + self.flags.contains(OutputFlags::InvertOutput) + } + + /// Consumes the signal and returns a new one that inverts the peripheral's + /// output signal. + pub fn with_output_inverter(mut self, invert: bool) -> Self { + self.flags.set(OutputFlags::InvertOutput, invert); + self + } + + /// Consumes the signal and returns a new one that inverts the peripheral's + /// input signal. + pub fn with_input_inverter(mut self, invert: bool) -> Self { + self.flags.set(OutputFlags::InvertInput, invert); + self + } + + /// Consumes the signal and returns a new one that forces the GPIO matrix + /// to be used. + pub fn with_gpio_matrix_forced(mut self, force: bool) -> Self { + self.flags.set(OutputFlags::ForceGpioMatrix, force); + self + } + + /// Returns `true` if the input signal must be routed through the GPIO + /// matrix. + pub fn is_gpio_matrix_forced(&self) -> bool { + self.flags.contains(OutputFlags::ForceGpioMatrix) + } + + /// Returns `true` if the input signal is high. + /// + /// Note that this does not take [`Self::with_input_inverter`] into account. + pub fn is_input_high(&self) -> bool { + self.pin.is_input_high() + } + + /// Returns `true` if the output signal is set high. + /// + /// Note that this does not take [`Self::with_output_inverter`] into + /// account. + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + #[doc(hidden)] + #[instability::unstable] + pub(crate) fn connect_with_guard(self, signal: crate::gpio::OutputSignal) -> PinGuard { + signal.connect_to(&self); + match self.pin { + Signal::Pin(pin) => PinGuard::new(pin), + Signal::Level(_) => PinGuard::new_unconnected(), + } + } + + delegate::delegate! { + #[instability::unstable] + #[doc(hidden)] + to match &self.pin { + Signal::Pin(signal) => signal, + Signal::Level(_) => NoOp, + } { + pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)]; + pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)]; + } + } + + delegate::delegate! { + #[instability::unstable] + #[doc(hidden)] + to match &self.pin { + Signal::Pin(_) if self.flags.contains(OutputFlags::Frozen) => NoOp, + Signal::Pin(pin) => pin, + Signal::Level(_) => NoOp, + } { + pub fn apply_input_config(&self, _config: &gpio::InputConfig); + pub fn apply_output_config(&self, _config: &gpio::OutputConfig); + pub fn set_input_enable(&self, on: bool); + pub fn set_output_enable(&self, on: bool); + pub fn set_output_high(&self, on: bool); + } + } +} + +struct NoOp; + +impl NoOp { + fn set_input_enable(&self, _on: bool) {} + fn set_output_enable(&self, _on: bool) {} + fn set_output_high(&self, _on: bool) {} + fn apply_input_config(&self, _config: &gpio::InputConfig) {} + fn apply_output_config(&self, _config: &gpio::OutputConfig) {} + + fn input_signals( + &self, + _: private::Internal, + ) -> &'static [(AlternateFunction, gpio::InputSignal)] { + &[] + } + + fn output_signals( + &self, + _: private::Internal, + ) -> &'static [(AlternateFunction, gpio::OutputSignal)] { + &[] + } +} + +#[procmacros::doc_replace] +/// ```rust,compile_fail +/// // Regression test for +/// // This test case is expected to generate the following error: +/// // error[E0277]: the trait bound `Output<'_>: PeripheralInput<'_>` is not satisfied +/// // --> src\gpio\interconnect.rs:977:5 +/// // | +/// // 31 | function_expects_input( +/// // | ---------------------- required by a bound introduced by this call +/// // 32 | / Output::new(peripherals.GPIO0, +/// // 33 | | Level::Low, +/// // 34 | | Default::default()), +/// // | |_______________________^ the trait `InputPin` is not implemented for `Output<'_>` +/// // FIXME: due to this test may be ineffective. +/// // It can be manually verified by changing it to `no_run` for a `run-doc-tests` run. +/// # {before_snippet} +/// use esp_hal::gpio::{Output, Level, interconnect::PeripheralInput}; +/// +/// fn function_expects_input<'d>(_: impl PeripheralInput<'d>) {} +/// +/// function_expects_input( +/// Output::new(peripherals.GPIO0, +/// Level::Low, +/// Default::default()), +/// ); +/// +/// # {after_snippet} +/// ``` +fn _compile_tests() {} diff --git a/esp-hal/src/gpio/interrupt.rs b/esp-hal/src/gpio/interrupt.rs new file mode 100644 index 00000000000..df7c0451c3e --- /dev/null +++ b/esp-hal/src/gpio/interrupt.rs @@ -0,0 +1,335 @@ +//! GPIO interrupt handling +//! +//! ## Requirements +//! +//! - On devices other than the P4, there is a single interrupt handler. GPIO interrupt handling +//! must not interfere with the async API in this single handler. +//! - Async operations take pins by `&mut self`, so they can only be accessed after the operation is +//! complete, or cancelled. They may be defined to overwrite the configuration of the manual +//! interrupt API, but not affect the interrupt handler. +//! - Manual `listen` operations don't need to be prepared for async operations, but async +//! operations need to be prepared to handle cases where the pin was configured to listen for an +//! event - or even that the user unlistened the pin but left the interrupt status set. +//! +//! The user should be careful when using the async API and the manual interrupt +//! API together. For performance reasons, we will not prevent the user handler +//! from running in response to an async event. +//! +//! ## Single-shot interaction with user interrupt handlers +//! +//! The async API disables the pin's interrupt when triggered. This makes async +//! operations single-shot. If there is no user handler, the other GPIO +//! interrupts are also single-shot. This is because the user has no way to +//! handle multiple events in this case, the API only allows querying whether +//! the interrupt has fired or not. Disabling the interrupt also means that the +//! interrupt status bits are not cleared, so the `is_interrupt_set` works by +//! default as expected. +//! +//! When the user sets a custom interrupt handler, the built-in interrupt +//! handler will only disable the async interrupts. The user handler is +//! responsible for clearing the interrupt status bits or disabling the +//! interrupts, based on their needs. This is communicated to the user in the +//! documentation of the `Io::set_interrupt_handler` function. +//! +//! ## Critical sections +//! +//! The interrupt handler runs in a GPIO-specific critical section. The critical +//! section is required because interrupts are disabled by modifying the pin +//! register, which is not an atomic operation. The critical section also +//! ensures that a higher priority task waken by an async pin event (or the user +//! handler) will only run after the interrupt handler has finished. +//! +//! ## Signaling async completion +//! +//! The completion is signalled by clearing a flag in an AtomicU32. This flag is +//! set at the start of the async operation, and cleared when the interrupt +//! handler is called. The flag is not accessible by the user, so they can't +//! force-complete an async operation accidentally from the interrupt handler. +//! +//! We could technically use the interrupt status on single-core chips, but it +//! would be slightly more complicated to prevent the user from breaking things. +//! (If the user were to clear the interrupt status, we would need to re-enable +//! it, for PinFuture to detect the completion). +//! +//! TODO: currently, direct-binding a GPIO interrupt handler will completely +//! break the async API. We will need to expose a way to handle async events. + +use portable_atomic::{AtomicPtr, Ordering}; +use strum::EnumCount; + +use crate::{ + gpio::{AnyPin, GPIO_LOCK, GpioBank, InputPin, set_int_enable}, + interrupt::Priority, + peripherals::{GPIO, Interrupt}, + ram, +}; +#[cfg(feature = "rt")] +use crate::{ + handler, + interrupt::{self, DEFAULT_INTERRUPT_HANDLER}, +}; + +/// Convenience constant for `Option::None` pin +pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); + +pub(super) struct CFnPtr(AtomicPtr<()>); +impl CFnPtr { + pub const fn new() -> Self { + Self(AtomicPtr::new(core::ptr::null_mut())) + } + + pub fn store(&self, f: extern "C" fn()) { + self.0.store(f as *mut (), Ordering::Relaxed); + } + + pub fn call(&self) { + let ptr = self.0.load(Ordering::Relaxed); + if !ptr.is_null() { + unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() }; + } + } +} + +#[cfg(feature = "rt")] +pub(crate) fn bind_default_interrupt_handler() { + // We first check if a handler is set in the vector table. + if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) { + // We only allow binding the default handler if nothing else is bound. + // This prevents silently overwriting RTIC's interrupt handler, if using GPIO. + if handler != DEFAULT_INTERRUPT_HANDLER.handler() { + // The user has configured an interrupt handler they wish to use. + info!("Not using default GPIO interrupt handler: already bound in vector table"); + return; + } + } + + // The vector table doesn't contain a custom entry. Still, the + // peripheral interrupt may already be bound to something else. + for cpu in cores() { + if interrupt::mapped_to(cpu, Interrupt::GPIO).is_some() { + info!("Not using default GPIO interrupt handler: peripheral interrupt already in use"); + return; + } + } + + interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); + + // On ESP32, there are separate interrupt status registers for each core, we need to enable the + // interrupt handler on each core otherwise GPIOs listening on the App CPU will not receive + // interrupts. + #[cfg(esp32)] + crate::interrupt::enable_on_cpu( + crate::system::Cpu::AppCpu, + Interrupt::GPIO, + Priority::Priority1, + ); +} + +cfg_if::cfg_if! { + if #[cfg(esp32)] { + // On ESP32, the interrupt fires on the core that started listening for a pin event. + fn cores() -> impl Iterator { + crate::system::Cpu::all() + } + } else { + fn cores() -> [crate::system::Cpu; 1] { + [crate::system::Cpu::current()] + } + } +} + +/// Configures the given peripheral interrupt to trigger the vectored handler of given priority. +pub(super) fn set_interrupt_priority(interrupt: Interrupt, priority: Priority) { + for cpu in cores() { + // Only change priority if the interrupt is mapped to the core, otherwise we would enable + // the interrupt unconditionally, which we don't want to do. + if crate::interrupt::mapped_to(cpu, interrupt).is_some() { + crate::interrupt::enable_on_cpu(cpu, interrupt, priority); + } + } +} + +/// The default GPIO interrupt handler, when the user has not set one. +/// +/// This handler will disable all pending interrupts and leave the interrupt +/// status bits unchanged. This enables functions like `is_interrupt_set` to +/// work correctly. +#[ram] +#[handler] +#[cfg(feature = "rt")] +fn default_gpio_interrupt_handler() { + GPIO_LOCK.lock(|| { + let banks = interrupt_status(); + + // Handle the async interrupts + for (bank, intrs) in banks { + // Get the mask of active async pins and clear the relevant bits to signal + // completion. This way the user may clear the interrupt status + // without worrying about the async bit being cleared. + let async_pins = bank.async_operations().load(Ordering::Relaxed); + + // Wake up the tasks + handle_async_pins(bank, async_pins, intrs); + + // Disable the remaining interrupts. + let mut intrs = intrs & !async_pins; + while intrs != 0 { + let pin_pos = intrs.trailing_zeros(); + intrs -= 1 << pin_pos; + + let pin_nr = pin_pos as u8 + bank.offset(); + + // The remaining interrupts are not async, we treat them as single-shot. + set_int_enable(pin_nr, Some(0), 0, false); + } + } + }); +} + +/// The user GPIO interrupt handler, when the user has set one. +/// +/// This handler only disables interrupts associated with async pins. The user +/// handler is responsible for clearing the interrupt status bits or disabling +/// the interrupts. +#[ram] +pub(super) extern "C" fn user_gpio_interrupt_handler() { + GPIO_LOCK.lock(|| { + // Read interrupt status before the user has a chance to modify them. + let banks = interrupt_status(); + + // Call the user handler before clearing interrupts. The user can use the enable + // bits to determine which interrupts they are interested in. Clearing the + // interrupt status or enable bits have no effect on the rest of the + // interrupt handler. + USER_INTERRUPT_HANDLER.call(); + + // Handle the async interrupts + for (bank, intrs) in banks { + // Get the mask of active async pins and clear the relevant bits to signal + // completion. This way the user may clear the interrupt status + // without worrying about the async bit being cleared. + let async_pins = bank.async_operations().load(Ordering::Relaxed); + + // Wake up the tasks + handle_async_pins(bank, async_pins, intrs); + } + }); +} + +#[derive(Clone, Copy)] +pub(crate) enum InterruptStatusRegisterAccess { + Bank0, + #[cfg(gpio_has_bank_1)] + Bank1, +} + +impl InterruptStatusRegisterAccess { + pub(crate) fn interrupt_status_read(self) -> u32 { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + match self { + Self::Bank0 => GPIO::regs().status().read().bits(), + Self::Bank1 => GPIO::regs().status1().read().bits(), + } + } else { + match self { + Self::Bank0 => GPIO::regs().pcpu_int().read().bits(), + #[cfg(gpio_has_bank_1)] + Self::Bank1 => GPIO::regs().pcpu_int1().read().bits(), + } + } + } + } +} + +fn interrupt_status() -> [(GpioBank, u32); GpioBank::COUNT] { + let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read(); + + #[cfg(gpio_has_bank_1)] + let intrs_bank1 = InterruptStatusRegisterAccess::Bank1.interrupt_status_read(); + + [ + (GpioBank::_0, intrs_bank0), + #[cfg(gpio_has_bank_1)] + (GpioBank::_1, intrs_bank1), + ] +} + +// We have separate variants for single-core and multi-core async pin handling. +// Single core can be much simpler because no code is running in parallel, so we +// don't have to be so careful with the order of operations. On multi-core, +// however, the order can actually break things, regardless of the critical +// section (because tasks on the other core may be waken in inappropriate +// times). + +#[cfg(single_core)] +fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) { + let mut async_intrs = async_pins & intrs; + while async_intrs != 0 { + let pin_pos = async_intrs.trailing_zeros(); + async_intrs -= 1 << pin_pos; + + let pin_nr = pin_pos as u8 + bank.offset(); + + // Disable the interrupt for this pin. + set_int_enable(pin_nr, Some(0), 0, false); + + unsafe { AnyPin::steal(pin_nr) }.waker().wake(); + } + + // This is an optimization (in case multiple pin interrupts are handled at once) + // so that PinFuture doesn't have to clear interrupt status bits one by one + // for each pin. We need to clear after disabling the interrupt, so that a pin + // event can't re-set the bit. + bank.write_interrupt_status_clear(async_pins & intrs); + + // On a single-core chip, the lock around `handle_async_pins` ensures + // that the interrupt handler will not be interrupted by other code, + // so we can safely write back without an atomic CAS. + bank.async_operations() + .store(async_pins & !intrs, Ordering::Relaxed); +} + +#[cfg(multi_core)] +fn handle_async_pins(bank: GpioBank, async_pins: u32, intrs: u32) { + // First, disable pin interrupts. If we were to do this after clearing the async + // flags, the PinFuture destructor may try to take a critical section which + // isn't necessary. + let mut async_intrs = async_pins & intrs; + while async_intrs != 0 { + let pin_pos = async_intrs.trailing_zeros(); + async_intrs -= 1 << pin_pos; + + let pin_nr = pin_pos as u8 + bank.offset(); + + // Disable the interrupt for this pin. + set_int_enable(pin_nr, Some(0), 0, false); + } + + // This is an optimization (in case multiple pin interrupts are handled at once) + // so that PinFuture doesn't have to clear interrupt status bits one by one + // for each pin. We need to clear after disabling the interrupt, so that a pin + // event can't re-set the bit. + bank.write_interrupt_status_clear(async_pins & intrs); + + // Clearing the async bit needs to be the last state change, as this signals + // completion. + // On multi-core chips, we need to use a CAS to ensure that only + // the handled async bits are cleared. + bank.async_operations().fetch_and(!intrs, Ordering::Relaxed); + + // Now we can wake the tasks. This needs to happen after completion - waking an + // already running task isn't a big issue, but not waking a waiting one is. + // Doing it sooner would mean that on a multi-core chip a task could be + // waken, but the Future wouldn't actually be resolved, and so it might + // never wake again. + let mut async_intrs = async_pins & intrs; + while async_intrs != 0 { + let pin_pos = async_intrs.trailing_zeros(); + async_intrs -= 1 << pin_pos; + + let pin_nr = pin_pos as u8 + bank.offset(); + + unsafe { AnyPin::steal(pin_nr) }.waker().wake(); + } +} diff --git a/esp-hal/src/gpio/lp_io.rs b/esp-hal/src/gpio/lp_io.rs new file mode 100644 index 00000000000..a48deb6dc53 --- /dev/null +++ b/esp-hal/src/gpio/lp_io.rs @@ -0,0 +1,269 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! Low Power IO (LP_IO) +//! +//! # Overview +//! The hardware provides a couple of GPIO pins with low power (LP) +//! capabilities and analog functions. +//! +//! ## Configuration +//! These pins can be controlled by either IO MUX or LP IO MUX. +//! +//! If controlled by LP IO MUX, these pins will bypass IO MUX and GPIO +//! matrix for the use by ULP and peripherals in LP system. +//! +//! When configured as LP GPIOs, the pins can still be controlled by ULP or +//! the peripherals in LP system during chip Deep-sleep, and wake up the +//! chip from Deep-sleep. +//! +//! ## Examples +//! +//! ## Configure a LP Pin as Output +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::gpio::lp_io::LowPowerOutput; +//! // configure GPIO 1 as LP output pin +//! let lp_pin: LowPowerOutput<'_, 1> = LowPowerOutput::new(peripherals.GPIO1); +//! # {after_snippet} +//! ``` + +use core::marker::PhantomData; + +use super::{InputPin, OutputPin, RtcPin}; +use crate::peripherals::{GPIO, LP_AON, LP_IO}; + +/// A GPIO output pin configured for low power operation +pub struct LowPowerOutput<'d, const PIN: u8> { + phantom: PhantomData<&'d mut ()>, +} + +impl<'d, const PIN: u8> LowPowerOutput<'d, PIN> { + /// Create a new output pin for use by the low-power core + #[instability::unstable] + pub fn new

    (_pin: P) -> Self + where + P: OutputPin + RtcPin + 'd, + { + init_low_power_pin(PIN); + + let this = Self { + phantom: PhantomData, + }; + this.output_enable(true); + + this + } + + fn output_enable(&self, enable: bool) { + let lp_io = LP_IO::regs(); + if enable { + lp_io + .out_enable_w1ts() + .write(|w| unsafe { w.enable_w1ts().bits(1 << PIN) }); + } else { + lp_io + .out_enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << PIN) }); + } + } +} + +/// A GPIO input pin configured for low power operation +pub struct LowPowerInput<'d, const PIN: u8> { + phantom: PhantomData<&'d mut ()>, +} + +impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { + /// Create a new input pin for use by the low-power core + #[instability::unstable] + pub fn new

    (_pin: P) -> Self + where + P: InputPin + RtcPin + 'd, + { + init_low_power_pin(PIN); + + let this = Self { + phantom: PhantomData, + }; + this.input_enable(true); + this.pullup_enable(false); + this.pulldown_enable(false); + + this + } + + fn input_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_ie().bit(enable)); + } + + /// Sets pull-up enable for the pin + pub fn pullup_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_wpu().bit(enable)); + } + + /// Sets pull-down enable for the pin + pub fn pulldown_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_wpd().bit(enable)); + } +} + +/// A GPIO open-drain output pin configured for low power operation +pub struct LowPowerOutputOpenDrain<'d, const PIN: u8> { + phantom: PhantomData<&'d mut ()>, +} + +impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { + /// Create a new output pin for use by the low-power core + #[instability::unstable] + pub fn new

    (_pin: P) -> Self + where + P: InputPin + OutputPin + RtcPin + 'd, + { + init_low_power_pin(PIN); + + let this = Self { + phantom: PhantomData, + }; + + this.set_open_drain_output(true); + this.input_enable(true); + this.pullup_enable(true); + this.pulldown_enable(false); + this.output_enable(true); + + this + } + + fn output_enable(&self, enable: bool) { + let lp_io = LP_IO::regs(); + if enable { + lp_io + .out_enable_w1ts() + .write(|w| unsafe { w.enable_w1ts().bits(1 << PIN) }); + } else { + lp_io + .out_enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << PIN) }); + } + } + + fn input_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_ie().bit(enable)); + } + + /// Sets pull-up enable for the pin + pub fn pullup_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_wpu().bit(enable)); + } + + /// Sets pull-down enable for the pin + pub fn pulldown_enable(&self, enable: bool) { + LP_IO::regs() + .gpio(PIN as usize) + .modify(|_, w| w.fun_wpd().bit(enable)); + } + + fn set_open_drain_output(&self, enable: bool) { + GPIO::regs() + .pin(PIN as usize) + .modify(|_, w| w.pad_driver().bit(enable)); + } +} + +pub(crate) fn init_low_power_pin(pin: u8) { + LP_AON::regs() + .gpio_mux() + .modify(|r, w| unsafe { w.sel().bits(r.sel().bits() | (1 << pin)) }); + + LP_IO::regs() + .gpio(pin as usize) + .modify(|_, w| unsafe { w.mcu_sel().bits(0) }); +} + +#[doc(hidden)] +macro_rules! lp_gpio { + ( + $($gpionum:literal)+ + ) => { + $( + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for paste::paste!($crate::peripherals::[]<'_>) { + unsafe fn apply_wakeup(&self, wakeup: bool, level: u8) { + let lp_io = $crate::peripherals::LP_IO::regs(); + lp_io.pin($gpionum).modify(|_, w| { + unsafe { + w.wakeup_enable().bit(wakeup).int_type().bits(level) + } + }); + } + + fn rtcio_pad_hold(&self, enable: bool) { + let mask = 1 << $gpionum; + unsafe { + let lp_aon = $crate::peripherals::LP_AON::regs(); + + lp_aon.gpio_hold0().modify(|r, w| { + if enable { + w.gpio_hold0().bits(r.gpio_hold0().bits() | mask) + } else { + w.gpio_hold0().bits(r.gpio_hold0().bits() & !mask) + } + }); + } + } + + /// Set the LP properties of the pin. If `mux` is true then then pin is + /// routed to LP_IO, when false it is routed to IO_MUX. + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: $crate::gpio::RtcFunction) { + let mask = 1 << $gpionum; + unsafe { + let lp_aon = $crate::peripherals::LP_AON::regs(); + // Select LP_IO + lp_aon + .gpio_mux() + .modify(|r, w| { + if mux { + w.sel().bits(r.sel().bits() | mask) + } else { + w.sel().bits(r.sel().bits() & !mask) + } + }); + + // Configure input, function and select normal operation registers + let lp_io = $crate::peripherals::LP_IO::regs(); + lp_io.gpio($gpionum).modify(|_, w| { + w.slp_sel().bit(false); + w.fun_ie().bit(input_enable); + w.mcu_sel().bits(func as u8) + }); + } + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPinWithResistors for paste::paste!($crate::peripherals::[]<'_>) { + fn rtcio_pullup(&self, enable: bool) { + let lp_io = $crate::peripherals::LP_IO::regs(); + lp_io.gpio($gpionum).modify(|_, w| w.fun_wpu().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + let lp_io = $crate::peripherals::LP_IO::regs(); + lp_io.gpio($gpionum).modify(|_, w| w.fun_wpd().bit(enable)); + } + } + )+ + } +} + +pub(crate) use lp_gpio; diff --git a/esp-hal/src/gpio/mod.rs b/esp-hal/src/gpio/mod.rs new file mode 100644 index 00000000000..f21704a2ade --- /dev/null +++ b/esp-hal/src/gpio/mod.rs @@ -0,0 +1,2408 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "etm_availability" => { + cfg(etm_driver_supported) => "The GPIO pins also provide tasks and events via the ETM interconnect system. For more information, see the [etm] module." + } +))] +//! # General Purpose Input/Output (GPIO) +//! +//! ## Overview +//! +//! Each pin can be used as a general-purpose I/O, or be connected to one or +//! more internal peripheral signals. +//! # {etm_availability} +//! ## Working with pins +//! +//! After initializing the HAL, you can access the individual pins using the +//! [`crate::Peripherals`] struct. These pins can then be used as general +//! purpose digital IO using pin drivers, or they can be passed to peripherals +//! (such as SPI, UART, I2C, etc.), or can be [`split`] +//! into peripheral signals for advanced use. +//! +//! Pin drivers can be created using [`Flex::new`], [`Input::new`] and +//! [`Output::new`]. +//! +//! Output pins can be configured to either push-pull or open-drain (active low) +//! mode, with configurable drive strength and pull-up/pull-down resistors. +//! +//! Each pin is a different type initially. Internally, `esp-hal` will erase +//! their types automatically, but they can also be converted into [`AnyPin`] +//! manually by calling [`Pin::degrade`]. +//! +//! The [`Io`] struct can also be used to configure the interrupt handler for +//! GPIO interrupts. For more information, see the +//! [`InterruptConfigurable::set_interrupt_handler`](crate::interrupt::InterruptConfigurable::set_interrupt_handler). +//! +//! This driver also implements pin-related traits from [embedded-hal] and +//! [Wait](embedded_hal_async::digital::Wait) trait from [embedded-hal-async]. +//! +//! ## GPIO interconnect +//! +//! Sometimes you may want to connect peripherals together without using +//! external hardware. The [`interconnect`] module provides tools to achieve +//! this using GPIO pins. +//! +//! To obtain peripheral signals, use the [`split`] method to split a +//! pin into an input and output signal. Alternatively, you may use +//! [`Flex::split`], [`Flex::into_peripheral_output`], +//! [`Flex::peripheral_input`], and similar methods to split a pin driver into +//! an input and output signal. You can then pass these signals to the +//! peripheral drivers similar to how you would pass a pin. +//! +//! [embedded-hal]: embedded_hal +//! [embedded-hal-async]: embedded_hal_async +//! [`split`]: crate::peripherals::GPIO0::split + +crate::unstable_module! { + pub mod interconnect; + + #[cfg(etm_driver_supported)] + pub mod etm; + + #[cfg(soc_has_lp_io)] + pub mod lp_io; + + #[cfg(all(soc_has_rtc_io, not(esp32)))] + pub mod rtc_io; + + #[cfg(dedicated_gpio_driver_supported)] + pub mod dedicated; +} +use interconnect::PeripheralOutput; + +mod asynch; +mod embedded_hal_impls; +pub(crate) mod interrupt; +use interrupt::*; + +mod placeholder; + +use core::fmt::Display; + +use esp_sync::RawMutex; +pub use placeholder::NoPin; +use portable_atomic::AtomicU32; +use strum::EnumCount; + +use crate::{ + asynch::AtomicWaker, + interrupt::{InterruptHandler, Priority}, + peripherals::{GPIO, IO_MUX, Interrupt}, + private::{self, Sealed}, +}; + +define_io_mux_signals!(); + +pub(crate) static GPIO_LOCK: RawMutex = RawMutex::new(); + +/// Represents a pin-peripheral connection that, when dropped, disconnects the +/// peripheral from the pin. +/// +/// This only needs to be applied to output signals, as it's not possible to +/// connect multiple inputs to the same peripheral signal. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct PinGuard { + pin: u8, +} + +impl crate::private::Sealed for PinGuard {} + +impl PinGuard { + // This must only be used with a pin currently configured for output, and the PinGuard must be + // dropped before the pin can be reconfigured (e.g. for input). + fn new(pin: AnyPin<'_>) -> Self { + Self { pin: pin.number() } + } + + pub(crate) fn new_unconnected() -> Self { + Self { pin: u8::MAX } + } + + #[allow(unused)] + pub(crate) fn pin_number(&self) -> Option { + if self.pin == u8::MAX { + None + } else { + Some(self.pin) + } + } +} + +impl Drop for PinGuard { + fn drop(&mut self) { + if self.pin != u8::MAX { + let pin = unsafe { AnyPin::steal(self.pin) }; + pin.disconnect_from_peripheral_output(); + } + } +} + +/// Event used to trigger interrupts. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum Event { + /// Interrupts trigger on rising pin edge. + RisingEdge = 1, + /// Interrupts trigger on falling pin edge. + FallingEdge = 2, + /// Interrupts trigger on either rising or falling pin edges. + AnyEdge = 3, + /// Interrupts trigger on low level + LowLevel = 4, + /// Interrupts trigger on high level + HighLevel = 5, +} + +impl From for Event { + fn from(value: WakeEvent) -> Self { + match value { + WakeEvent::LowLevel => Event::LowLevel, + WakeEvent::HighLevel => Event::HighLevel, + } + } +} + +/// Event used to wake up from light sleep. +#[instability::unstable] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WakeEvent { + /// Wake on low level + LowLevel = 4, + /// Wake on high level + HighLevel = 5, +} + +/// Digital input or output level. +/// +/// `Level` can be used to control a GPIO output, and it can act as a peripheral +/// signal and be connected to peripheral inputs and outputs. +/// +/// When connected to a peripheral +/// input, the peripheral will read the corresponding level from that signal. +/// +/// When connected to a peripheral output, the level will be ignored. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Low + Low, + /// High + High, +} + +impl Sealed for Level {} + +impl core::ops::Not for Level { + type Output = Self; + + fn not(self) -> Self { + match self { + Self::Low => Self::High, + Self::High => Self::Low, + } + } +} + +impl Level { + /// Create a [`Level`] from [`bool`]. + /// + /// Like `>::from(val)`, but `const`. + pub(crate) const fn const_from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } + + /// Convert a [`Level`] to [`bool`]. + /// + /// Like `>::from(self)`, but `const`. + pub(crate) const fn const_into(self) -> bool { + match self { + Level::Low => false, + Level::High => true, + } + } +} + +impl From for Level { + fn from(val: bool) -> Self { + Self::const_from(val) + } +} + +impl From for bool { + fn from(level: Level) -> bool { + level.const_into() + } +} + +/// Errors that can occur when configuring a pin to be a wakeup source. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +#[non_exhaustive] +pub enum WakeConfigError { + /// Returned when trying to configure a pin to wake up from light sleep on + /// an edge trigger, which is not supported. + EdgeTriggeringNotSupported, +} + +impl Display for WakeConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + WakeConfigError::EdgeTriggeringNotSupported => { + write!( + f, + "Edge triggering is not supported for wake-up from light sleep" + ) + } + } + } +} + +impl core::error::Error for WakeConfigError {} + +/// Pull setting for a GPIO. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + /// No pull + None, + /// Pull up + Up, + /// Pull down + Down, +} + +/// Drive strength (values are approximates) +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DriveStrength { + /// Drive strength of approximately 5mA. + _5mA = 0, + /// Drive strength of approximately 10mA. + _10mA = 1, + /// Drive strength of approximately 20mA. + _20mA = 2, + /// Drive strength of approximately 40mA. + _40mA = 3, +} + +/// Alternate functions +/// +/// GPIO pins can be configured for various functions, such as GPIO +/// or being directly connected to a peripheral's signal like UART, SPI, etc. +/// The `AlternateFunction` enum allows selecting one of several functions that +/// a pin can perform, rather than using it as a general-purpose input or +/// output. +/// +/// The different variants correspond to different functionality depending on +/// the chip and the specific pin. For more information, refer to your chip's +#[doc(hidden)] +#[doc = crate::trm_markdown_link!("iomuxgpio")] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AlternateFunction { + /// Alternate function 0. + _0 = 0, + /// Alternate function 1. + _1 = 1, + /// Alternate function 2. + _2 = 2, + /// Alternate function 3. + _3 = 3, + /// Alternate function 4. + _4 = 4, + /// Alternate function 5. + _5 = 5, +} + +impl AlternateFunction { + const GPIO: Self = match Self::const_try_from(property!("gpio.gpio_function")) { + Ok(func) => func, + Err(_) => ::core::panic!("Invalid GPIO function"), + }; + + const fn const_try_from(value: usize) -> Result { + match value { + 0 => Ok(Self::_0), + 1 => Ok(Self::_1), + 2 => Ok(Self::_2), + 3 => Ok(Self::_3), + 4 => Ok(Self::_4), + 5 => Ok(Self::_5), + _ => Err(()), + } + } +} + +impl TryFrom for AlternateFunction { + type Error = (); + + fn try_from(value: usize) -> Result { + Self::const_try_from(value) + } +} + +/// RTC function +#[instability::unstable] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(not(any(esp32h2, esp32c5)))] +pub enum RtcFunction { + /// RTC mode. + Rtc = 0, + /// Digital mode. + Digital = 1, + /// RTC_I2C mode. + #[cfg(soc_has_rtc_i2c)] + I2c = 3, +} + +/// Trait implemented by RTC pins +#[instability::unstable] +#[cfg(not(esp32c5))] +pub trait RtcPin: Pin { + /// RTC number of the pin + #[cfg(any(xtensa, esp32h2))] + fn rtc_number(&self) -> u8; + + /// Configure the pin + #[cfg(any(xtensa, esp32c6))] + #[doc(hidden)] + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: RtcFunction); + + /// Enable or disable PAD_HOLD + #[doc(hidden)] + fn rtcio_pad_hold(&self, enable: bool); + + /// # Safety + /// + /// The `level` argument needs to be a valid setting for the + /// `rtc_cntl.gpio_wakeup.gpio_pinX_int_type`. + #[cfg(any(esp32c3, esp32c2, esp32c6))] + #[doc(hidden)] + unsafe fn apply_wakeup(&self, wakeup: bool, level: u8); +} + +/// Trait implemented by RTC pins which support internal pull-up / pull-down +/// resistors. +#[instability::unstable] +#[cfg(not(esp32c5))] +pub trait RtcPinWithResistors: RtcPin { + /// Enable/disable the internal pull-up resistor + #[cfg(not(esp32h2))] + #[doc(hidden)] + fn rtcio_pullup(&self, enable: bool); + /// Enable/disable the internal pull-down resistor + #[cfg(not(esp32h2))] + #[doc(hidden)] + fn rtcio_pulldown(&self, enable: bool); +} + +/// Common trait implemented by pins +pub trait Pin: Sealed { + /// GPIO number + fn number(&self) -> u8; + + #[procmacros::doc_replace] + /// Type-erase this pin into an [`AnyPin`]. + /// + /// This function converts pin singletons (`GPIO0<'_>`, …), which are all + /// different types, into the same type. It is useful for creating + /// arrays of pins, or avoiding generics. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// delay::Delay, + /// gpio::{AnyPin, Level, Output, OutputConfig, Pin}, + /// }; + /// + /// fn toggle_pins(pins: [AnyPin; 2], delay: &mut Delay) { + /// let [red, blue] = pins; + /// let mut red = Output::new(red, Level::High, OutputConfig::default()); + /// let mut blue = Output::new(blue, Level::Low, OutputConfig::default()); + /// + /// loop { + /// red.toggle(); + /// blue.toggle(); + /// delay.delay_millis(500); + /// } + /// } + /// + /// let pins: [AnyPin; 2] = [peripherals.GPIO5.degrade(), peripherals.GPIO4.degrade()]; + /// + /// let mut delay = Delay::new(); + /// toggle_pins(pins, &mut delay); + /// # {after_snippet} + /// ``` + fn degrade<'d>(self) -> AnyPin<'d> + where + Self: Sized + 'd, + { + unsafe { AnyPin::steal(self.number()) } + } + + #[doc(hidden)] + fn output_signals(&self, _: private::Internal) -> &'static [(AlternateFunction, OutputSignal)]; + + #[doc(hidden)] + fn input_signals(&self, _: private::Internal) -> &'static [(AlternateFunction, InputSignal)]; +} + +/// Trait implemented by pins which can be used as inputs. +pub trait InputPin: Pin { + #[doc(hidden)] + fn waker(&self) -> &'static AtomicWaker; +} + +/// Trait implemented by pins which can be used as outputs. +pub trait OutputPin: Pin {} + +/// Trait implemented by pins which can be used as analog pins +#[instability::unstable] +pub trait AnalogPin: Pin { + /// Configure the pin for analog operation + #[doc(hidden)] + fn set_analog(&self, _: private::Internal); +} + +/// Trait implemented by pins which can be used as Touchpad pins +#[cfg(touch)] +#[instability::unstable] +pub trait TouchPin: Pin { + /// Configure the pin for analog operation + #[doc(hidden)] + fn set_touch(&self, _: private::Internal); + + /// Reads the pin's touch measurement register + #[doc(hidden)] + fn touch_measurement(&self, _: private::Internal) -> u16; + + /// Maps the pin nr to the touch pad nr + #[doc(hidden)] + fn touch_nr(&self, _: private::Internal) -> u8; + + /// Set a pins touch threshold for interrupts. + #[doc(hidden)] + fn set_threshold(&self, threshold: u16, _: private::Internal); +} + +#[doc(hidden)] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, EnumCount)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum GpioBank { + _0, + #[cfg(gpio_has_bank_1)] + _1, +} + +impl GpioBank { + fn async_operations(self) -> &'static AtomicU32 { + static FLAGS: [AtomicU32; GpioBank::COUNT] = [const { AtomicU32::new(0) }; GpioBank::COUNT]; + + &FLAGS[self as usize] + } + + fn offset(self) -> u8 { + match self { + Self::_0 => 0, + #[cfg(gpio_has_bank_1)] + Self::_1 => 32, + } + } + + fn write_out_en(self, word: u32, enable: bool) { + if enable { + self.write_out_en_set(word); + } else { + self.write_out_en_clear(word); + } + } + + fn write_out_en_clear(self, word: u32) { + match self { + Self::_0 => GPIO::regs() + .enable_w1tc() + .write(|w| unsafe { w.bits(word) }), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs() + .enable1_w1tc() + .write(|w| unsafe { w.bits(word) }), + }; + } + + fn write_out_en_set(self, word: u32) { + match self { + Self::_0 => GPIO::regs() + .enable_w1ts() + .write(|w| unsafe { w.bits(word) }), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs() + .enable1_w1ts() + .write(|w| unsafe { w.bits(word) }), + }; + } + + fn read_input(self) -> u32 { + match self { + Self::_0 => GPIO::regs().in_().read().bits(), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs().in1().read().bits(), + } + } + + fn read_output(self) -> u32 { + match self { + Self::_0 => GPIO::regs().out().read().bits(), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs().out1().read().bits(), + } + } + + fn read_interrupt_status(self) -> u32 { + match self { + Self::_0 => GPIO::regs().status().read().bits(), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs().status1().read().bits(), + } + } + + fn write_interrupt_status_clear(self, word: u32) { + match self { + Self::_0 => GPIO::regs() + .status_w1tc() + .write(|w| unsafe { w.bits(word) }), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs() + .status1_w1tc() + .write(|w| unsafe { w.bits(word) }), + }; + } + + fn write_output(self, word: u32, set: bool) { + if set { + self.write_output_set(word); + } else { + self.write_output_clear(word); + } + } + + fn write_output_set(self, word: u32) { + match self { + Self::_0 => GPIO::regs().out_w1ts().write(|w| unsafe { w.bits(word) }), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs().out1_w1ts().write(|w| unsafe { w.bits(word) }), + }; + } + + fn write_output_clear(self, word: u32) { + match self { + Self::_0 => GPIO::regs().out_w1tc().write(|w| unsafe { w.bits(word) }), + #[cfg(gpio_has_bank_1)] + Self::_1 => GPIO::regs().out1_w1tc().write(|w| unsafe { w.bits(word) }), + }; + } +} + +/// Any GPIO pin. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyPin<'lt> { + pub(crate) pin: u8, + pub(crate) _lifetime: core::marker::PhantomData<&'lt mut ()>, +} + +/// General Purpose Input/Output driver +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub struct Io<'d> { + _io_mux: IO_MUX<'d>, +} + +impl<'d> Io<'d> { + /// Initialize the I/O driver. + #[instability::unstable] + pub fn new(_io_mux: IO_MUX<'d>) -> Self { + Io { _io_mux } + } + + /// Set the interrupt priority for GPIO interrupts. + #[instability::unstable] + pub fn set_interrupt_priority(&self, prio: Priority) { + // FIXME: this sets priority on all cores where the handler may be running. Should we only + // change it on the current core? Should that enable the interrupt if it's not already + // enabled? + interrupt::set_interrupt_priority(Interrupt::GPIO, prio); + } + + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for all GPIO pins." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for all GPIO pins. Enables the interrupt on the current core." + )] + #[doc = ""] + /// Note that when using interrupt handlers registered by this function, or + /// by defining a `#[no_mangle] unsafe extern "C" fn GPIO()` function, we do + /// **not** clear the interrupt status register or the interrupt enable + /// setting for you. Based on your use case, you need to do one of this + /// yourself: + /// + /// - Disabling the interrupt enable setting for the GPIO pin allows you to handle an event once + /// per call to [`listen()`]. Using this method, the [`is_interrupt_set()`] method will return + /// `true` if the interrupt is set even after your handler has finished running. + /// - Clearing the interrupt status register allows you to handle an event repeatedly after + /// [`listen()`] is called. Using this method, [`is_interrupt_set()`] will return `false` + /// after your handler has finished running. + /// + /// [`listen()`]: Input::listen + /// [`is_interrupt_set()`]: Input::is_interrupt_set + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::GPIO); + } + USER_INTERRUPT_HANDLER.store(handler.handler().callback()); + + crate::interrupt::bind_handler( + Interrupt::GPIO, + InterruptHandler::new(user_gpio_interrupt_handler, handler.priority()), + ); + } +} + +impl crate::private::Sealed for Io<'_> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Io<'_> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +for_each_analog_function! { + (($_ch:ident, ADCn_CHm, $_n:literal, $_m:literal), $gpio:ident) => { + #[instability::unstable] + impl $crate::gpio::AnalogPin for crate::peripherals::$gpio<'_> { + #[cfg(riscv)] + fn set_analog(&self, _: private::Internal) { + io_mux_reg(self.number()).modify(|_, w| unsafe { + w.mcu_sel().bits(1); + w.fun_ie().clear_bit(); + w.fun_wpu().clear_bit(); + w.fun_wpd().clear_bit() + }); + + GPIO::regs() + .enable_w1tc() + .write(|w| unsafe { w.bits(1 << self.number()) }); + } + + #[cfg(not(riscv))] + fn set_analog(&self, _: private::Internal) { + self.set_analog_impl(); + } + } + }; +} + +/// The drive mode of the output pin. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DriveMode { + /// Push-pull output. + /// + /// The driver actively sets the output voltage level for both high and low + /// logical [`Level`]s. + PushPull, + + /// Open drain output. + /// + /// The driver actively pulls the output voltage level low for the low + /// logical [`Level`], but leaves the high level floating, which is then + /// determined by external hardware, or internal pull-up/pull-down + /// resistors. + #[cfg_attr( + feature = "unstable", + doc = "\n\nEnable the input related functionality by using [Output::into_flex] and enabling input via [Flex::set_input_enable]" + )] + OpenDrain, +} + +/// Output pin configuration. +/// +/// This struct is used to configure the drive mode, drive strength, and pull +/// direction of an output pin. By default, the configuration is set to: +/// - Drive mode: [`DriveMode::PushPull`] +/// - Drive strength: [`DriveStrength::_20mA`] +/// - Pull direction: [`Pull::None`] (no pull resistors connected) +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, procmacros::BuilderLite)] +#[non_exhaustive] +pub struct OutputConfig { + /// Output drive mode. + drive_mode: DriveMode, + + /// Pin drive strength. + drive_strength: DriveStrength, + + /// Pin pull direction. + pull: Pull, +} + +impl Default for OutputConfig { + fn default() -> Self { + Self { + drive_mode: DriveMode::PushPull, + drive_strength: DriveStrength::_20mA, + pull: Pull::None, + } + } +} + +/// Push-pull digital output. +/// +/// This driver configures the GPIO pin to be an output driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Output<'d> { + pin: Flex<'d>, +} + +impl private::Sealed for Output<'_> {} +impl private::Sealed for &mut Output<'_> {} + +impl<'d> Output<'d> { + #[procmacros::doc_replace] + /// Creates a new GPIO output driver. + /// + /// The `initial_level` parameter sets the initial output level of the pin. + /// The `config` parameter sets the drive mode, drive strength, and pull + /// direction of the pin. + /// + /// ## Example + /// + /// The following example configures `GPIO5` to pulse a LED once. The + /// example assumes that the LED is connected such that it is on when + /// the pin is low. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// delay::Delay, + /// gpio::{Level, Output, OutputConfig}, + /// }; + /// + /// fn blink_once(led: &mut Output<'_>, delay: &mut Delay) { + /// led.set_low(); + /// delay.delay_millis(500); + /// led.set_high(); + /// } + /// + /// let config = OutputConfig::default(); + /// let mut led = Output::new(peripherals.GPIO5, Level::High, config); + /// let mut delay = Delay::new(); + /// + /// blink_once(&mut led, &mut delay); + /// # {after_snippet} + /// ``` + #[inline] + pub fn new(pin: impl OutputPin + 'd, initial_level: Level, config: OutputConfig) -> Self { + // Set up the pin + let mut this = Self { + pin: Flex::new(pin), + }; + this.set_level(initial_level); + this.apply_config(&config); + this.pin.pin.set_output_enable(true); + + this + } + + #[procmacros::doc_replace] + /// Turns the pin object into a peripheral + /// [output][interconnect::OutputSignal]. + /// + /// The output signal can be passed to peripherals in place of an output + /// pin. + /// + /// Note that the signal returned by this function is + /// [frozen](interconnect::OutputSignal::freeze). + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// # let config = OutputConfig::default(); + /// let pin1_gpio = Output::new(peripherals.GPIO1, Level::High, config); + /// let output = pin1_gpio.into_peripheral_output(); + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub fn into_peripheral_output(self) -> interconnect::OutputSignal<'d> { + self.pin.into_peripheral_output() + } + + #[procmacros::doc_replace] + /// Change the configuration. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{DriveMode, Level, Output, OutputConfig}; + /// let mut pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// + /// pin.apply_config(&OutputConfig::default().with_drive_mode(DriveMode::OpenDrain)); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn apply_config(&mut self, config: &OutputConfig) { + self.pin.apply_output_config(config) + } + #[procmacros::doc_replace] + /// Set the output as high. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let mut pin = Output::new(peripherals.GPIO5, Level::Low, OutputConfig::default()); + /// pin.set_high(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn set_high(&mut self) { + self.set_level(Level::High) + } + + #[procmacros::doc_replace] + /// Set the output as low. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let mut pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// pin.set_low(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn set_low(&mut self) { + self.set_level(Level::Low) + } + + #[procmacros::doc_replace] + /// Set the output level.ç + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let mut pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// pin.set_level(Level::Low); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + #[procmacros::doc_replace] + /// Returns whether the pin is set to high level. + /// + /// This function reads back the value set using `set_level`, `set_high` or + /// `set_low`. It does not need the input stage to be enabled. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// let is_high = pin.is_set_high(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn is_set_high(&self) -> bool { + self.output_level() == Level::High + } + + #[procmacros::doc_replace] + /// Returns whether the pin is set to low level. + /// + /// This function reads back the value set using `set_level`, `set_high` or + /// `set_low`. It does not need the input stage to be enabled. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// let is_low = pin.is_set_low(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn is_set_low(&self) -> bool { + self.output_level() == Level::Low + } + + #[procmacros::doc_replace] + /// Returns which level the pin is set to. + /// + /// This function reads back the value set using `set_level`, `set_high` or + /// `set_low`. It does not need the input stage to be enabled. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// let level = pin.output_level(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn output_level(&self) -> Level { + self.pin.output_level() + } + + #[procmacros::doc_replace] + /// Toggles the pin output. + /// + /// If the pin was previously set to high, it will be set to low, and vice + /// versa. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Level, Output, OutputConfig}; + /// let mut pin = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + /// pin.toggle(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle(); + } + + /// Converts the pin driver into a [`Flex`] driver. + #[inline] + #[instability::unstable] + pub fn into_flex(self) -> Flex<'d> { + self.pin + } +} + +/// Input pin configuration. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, procmacros::BuilderLite)] +#[non_exhaustive] +pub struct InputConfig { + /// Initial pull of the pin. + pull: Pull, +} + +impl Default for InputConfig { + fn default() -> Self { + Self { pull: Pull::None } + } +} + +/// Digital input. +/// +/// This driver configures the GPIO pin to be an input. Input drivers read the +/// voltage of their pins and convert it to a logical [`Level`]. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl private::Sealed for Input<'_> {} +impl private::Sealed for &mut Input<'_> {} + +impl<'d> Input<'d> { + #[procmacros::doc_replace] + /// Creates a new GPIO input. + /// + /// The `pull` parameter configures internal pull-up or pull-down + /// resistors. + /// + /// ## Example + /// + /// The following example configures `GPIO5` to read a button press. The + /// example assumes that the button is connected such that the pin is low + /// when the button is pressed. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// delay::Delay, + /// gpio::{Input, InputConfig, Level, Pull}, + /// }; + /// + /// fn print_when_pressed(button: &mut Input<'_>, delay: &mut Delay) { + /// let mut was_pressed = false; + /// loop { + /// let is_pressed = button.is_low(); + /// if is_pressed && !was_pressed { + /// println!("Button pressed!"); + /// } + /// was_pressed = is_pressed; + /// delay.delay_millis(100); + /// } + /// } + /// + /// let config = InputConfig::default().with_pull(Pull::Up); + /// let mut button = Input::new(peripherals.GPIO5, config); + /// let mut delay = Delay::new(); + /// + /// print_when_pressed(&mut button, &mut delay); + /// # {after_snippet} + /// ``` + #[inline] + pub fn new(pin: impl InputPin + 'd, config: InputConfig) -> Self { + let mut pin = Flex::new(pin); + + pin.set_output_enable(false); + pin.set_input_enable(true); + pin.apply_input_config(&config); + + Self { pin } + } + + #[procmacros::doc_replace] + /// Returns a peripheral [input][interconnect::InputSignal] connected to + /// this pin. + /// + /// The input signal can be passed to peripherals in place of an input pin. + /// + /// Note that the signal returned by this function is + /// [frozen](interconnect::InputSignal::freeze). + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// use esp_hal::gpio::{Input, InputConfig, Pull}; + /// let config = InputConfig::default().with_pull(Pull::Up); + /// let pin1_gpio = Input::new(peripherals.GPIO1, config); + /// let pin1 = pin1_gpio.peripheral_input(); + /// # + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub fn peripheral_input(&self) -> interconnect::InputSignal<'d> { + self.pin.peripheral_input() + } + + #[procmacros::doc_replace] + /// Get whether the pin input level is high. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Input, InputConfig}; + /// let pin = Input::new(peripherals.GPIO5, InputConfig::default()); + /// let is_high = pin.is_high(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn is_high(&self) -> bool { + self.level() == Level::High + } + + #[procmacros::doc_replace] + /// Get whether the pin input level is low. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Input, InputConfig}; + /// let pin = Input::new(peripherals.GPIO5, InputConfig::default()); + /// let is_low = pin.is_low(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn is_low(&self) -> bool { + self.level() == Level::Low + } + + #[procmacros::doc_replace] + /// Get the current pin input level. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Input, InputConfig, Level}; + /// let pin = Input::new(peripherals.GPIO5, InputConfig::default()); + /// let level = pin.level(); + /// + /// # {after_snippet} + /// ``` + #[inline] + pub fn level(&self) -> Level { + self.pin.level() + } + + #[procmacros::doc_replace] + /// Change the configuration. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Input, InputConfig, Level, Pull}; + /// let mut pin = Input::new(peripherals.GPIO5, InputConfig::default()); + /// pin.apply_config(&InputConfig::default().with_pull(Pull::Up)); + /// + /// # {after_snippet} + /// ``` + pub fn apply_config(&mut self, config: &InputConfig) { + self.pin.apply_input_config(config) + } + + #[procmacros::doc_replace] + /// Listen for interrupts. + /// + /// The interrupts will be handled by the handler set using + /// [`Io::set_interrupt_handler`]. All GPIO pins share the same + /// interrupt handler. + /// + /// Note that [`Event::LowLevel`] and [`Event::HighLevel`] are fired + /// continuously when the pin is low or high, respectively. You must use + /// a custom interrupt handler to stop listening for these events, + /// otherwise your program will be stuck in a loop as long as the pin is + /// reading the corresponding level. + /// + /// ## Examples + /// + /// ### Print something when a button is pressed. + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{Event, Input, InputConfig, Io, Pull}; + /// + /// let mut io = Io::new(peripherals.IO_MUX); + /// io.set_interrupt_handler(handler); + /// + /// // Set up the input and store it in the static variable. + /// // This example uses a push button that is high when not + /// // pressed and low when pressed. + /// let config = InputConfig::default().with_pull(Pull::Up); + /// let mut button = Input::new(peripherals.GPIO5, config); + /// + /// critical_section::with(|cs| { + /// // Here we are listening for a low level to demonstrate + /// // that you need to stop listening for level interrupts, + /// // but usually you'd probably use `FallingEdge`. + /// button.listen(Event::LowLevel); + /// BUTTON.borrow_ref_mut(cs).replace(button); + /// }); + /// # {after_snippet} + /// + /// // Outside of your `main` function: + /// + /// use core::cell::RefCell; + /// + /// use critical_section::Mutex; + /// use esp_hal::gpio::Input; + /// + /// // You will need to store the `Input` object in a static variable so + /// // that the interrupt handler can access it. + /// static BUTTON: Mutex>> = Mutex::new(RefCell::new(None)); + /// + /// #[esp_hal::handler] + /// fn handler() { + /// critical_section::with(|cs| { + /// let mut button = BUTTON.borrow_ref_mut(cs); + /// let Some(button) = button.as_mut() else { + /// // Some other interrupt has occurred + /// // before the button was set up. + /// return; + /// }; + /// + /// if button.is_interrupt_set() { + /// print!("Button pressed"); + /// + /// // If you want to stop listening for interrupts, you need to + /// // call `unlisten` here. If you comment this line, the + /// // interrupt will fire continuously while the button + /// // is pressed. + /// button.unlisten(); + /// } + /// }); + /// } + /// ``` + #[inline] + #[instability::unstable] + pub fn listen(&mut self, event: Event) { + self.pin.listen(event); + } + + /// Stop listening for interrupts + #[inline] + #[instability::unstable] + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } + + /// Clear the interrupt status bit for this Pin + #[inline] + #[instability::unstable] + pub fn clear_interrupt(&mut self) { + self.pin.clear_interrupt(); + } + + /// Checks if the interrupt status bit for this Pin is set + #[inline] + #[instability::unstable] + pub fn is_interrupt_set(&self) -> bool { + self.pin.is_interrupt_set() + } + + /// Enable as a wake-up source. + /// + /// This will unlisten for interrupts + /// + /// # Error + /// Configuring pin to wake up from light sleep on an edge + /// trigger is currently not supported, corresponding variant of + /// [`WakeConfigError`] will be returned. + #[instability::unstable] + #[inline] + pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) -> Result<(), WakeConfigError> { + self.pin.wakeup_enable(enable, event) + } + + /// Converts the pin driver into a [`Flex`] driver. + #[inline] + #[instability::unstable] + pub fn into_flex(self) -> Flex<'d> { + self.pin + } +} + +/// Flexible pin driver. +/// +/// This pin driver can act as either input, or output, or both at the same +/// time. The input and output are (not counting the shared pull direction) +/// separately configurable, and they have independent enable states. +/// +/// Enabling the input stage does not change the output stage, and vice versa. +/// Disabling the input or output stages don't forget their configuration. +/// Disabling the output stage will not change the output level, but it will +/// disable the driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub struct Flex<'d> { + pin: AnyPin<'d>, +} + +impl private::Sealed for Flex<'_> {} +impl private::Sealed for &mut Flex<'_> {} + +impl<'d> Flex<'d> { + /// Create flexible pin driver for a [Pin]. + /// No mode change happens. + #[inline] + #[instability::unstable] + pub fn new(pin: impl Pin + 'd) -> Self { + let pin = pin.degrade(); + + // Before each use, reset the GPIO to a known state. + pin.init_gpio(); + + Self { pin } + } + + // Input functions + + /// Applies the given input configuration to the pin. + /// + /// This function does not set the pin as input (i.e. it does not enable the + /// input buffer). Note that the pull direction is common between the + /// input and output configuration. + #[inline] + #[instability::unstable] + pub fn apply_input_config(&mut self, config: &InputConfig) { + self.pin.apply_input_config(config); + } + + /// Enable or disable the GPIO pin input buffer. + #[inline] + #[instability::unstable] + pub fn set_input_enable(&mut self, enable_input: bool) { + self.pin.set_input_enable(enable_input); + } + + /// Get whether the pin input level is high. + #[inline] + #[instability::unstable] + pub fn is_high(&self) -> bool { + self.level() == Level::High + } + + /// Get whether the pin input level is low. + #[inline] + #[instability::unstable] + pub fn is_low(&self) -> bool { + self.level() == Level::Low + } + + /// Get the current pin input level. + #[inline] + #[instability::unstable] + pub fn level(&self) -> Level { + self.pin.is_input_high().into() + } + + /// Listen for interrupts. + /// + /// See [`Input::listen`] for more information and an example. + #[inline] + #[instability::unstable] + pub fn listen(&mut self, event: Event) { + // Unwrap can't fail currently as listen_with_options is only supposed to return + // an error if wake_up_from_light_sleep is true. + unwrap!(self.pin.listen_with_options(event, true, false, false)); + } + + /// Stop listening for interrupts. + #[inline] + #[instability::unstable] + pub fn unlisten(&mut self) { + GPIO_LOCK.lock(|| { + set_int_enable(self.pin.number(), Some(0), 0, false); + }); + } + + fn unlisten_and_clear(&mut self) { + GPIO_LOCK.lock(|| { + set_int_enable(self.pin.number(), Some(0), 0, false); + self.clear_interrupt(); + }); + } + + /// Check if the pin is listening for interrupts. + #[inline] + #[instability::unstable] + pub fn is_listening(&self) -> bool { + is_int_enabled(self.pin.number()) + } + + /// Clear the interrupt status bit for this Pin + #[inline] + #[instability::unstable] + pub fn clear_interrupt(&mut self) { + self.pin + .bank() + .write_interrupt_status_clear(self.pin.mask()); + } + + /// Checks if the interrupt status bit for this Pin is set + #[inline] + #[instability::unstable] + pub fn is_interrupt_set(&self) -> bool { + self.pin.bank().read_interrupt_status() & self.pin.mask() != 0 + } + + /// Enable as a wake-up source. + /// + /// This will unlisten for interrupts + /// + /// # Error + /// Configuring pin to wake up from light sleep on an edge + /// trigger is currently not supported, corresponding variant of + /// [`WakeConfigError`] will be returned. + #[inline] + #[instability::unstable] + pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) -> Result<(), WakeConfigError> { + self.pin + .listen_with_options(event.into(), false, false, enable) + } + + // Output functions + + /// Applies the given output configuration to the pin. + /// + /// This function does not set the pin to output (i.e. it does not enable + /// the output driver). Note that the pull direction is common between + /// the input and output configuration. + #[inline] + #[instability::unstable] + pub fn apply_output_config(&mut self, config: &OutputConfig) { + self.pin.apply_output_config(config); + } + + /// Enable or disable the GPIO pin output driver. + /// + /// The output level will be set to the last value. Use [`Self::set_high`], + /// [`Self::set_low`] or [`Self::set_level`] to set the output level before + /// enabling the output. + /// + /// This function does not disable the input buffer. + #[inline] + #[instability::unstable] + pub fn set_output_enable(&mut self, enable_output: bool) { + self.pin.set_output_enable(enable_output); + } + + /// Set the output as high. + #[inline] + #[instability::unstable] + pub fn set_high(&mut self) { + self.set_level(Level::High) + } + + /// Set the output as low. + #[inline] + #[instability::unstable] + pub fn set_low(&mut self) { + self.set_level(Level::Low) + } + + /// Set the output level. + #[inline] + #[instability::unstable] + pub fn set_level(&mut self, level: Level) { + self.pin.set_output_high(level.into()); + } + + /// Is the output pin set as high? + #[inline] + #[instability::unstable] + pub fn is_set_high(&self) -> bool { + self.output_level() == Level::High + } + + /// Is the output pin set as low? + #[inline] + #[instability::unstable] + pub fn is_set_low(&self) -> bool { + self.output_level() == Level::Low + } + + /// What level output is set to + #[inline] + #[instability::unstable] + pub fn output_level(&self) -> Level { + self.pin.is_set_high().into() + } + + /// Toggle pin output + #[inline] + #[instability::unstable] + pub fn toggle(&mut self) { + let level = self.output_level(); + self.set_level(!level); + } + + // Other/common functions + + #[procmacros::doc_replace] + /// Returns a peripheral [input][interconnect::InputSignal] connected to + /// this pin. + /// + /// The input signal can be passed to peripherals in place of an input pin. + /// + /// Note that the signal returned by this function is + /// [frozen](interconnect::InputSignal::freeze). + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::Flex; + /// let pin1_gpio = Flex::new(peripherals.GPIO1); + /// // Can be passed as an input. + /// let pin1 = pin1_gpio.peripheral_input(); + /// // You can keep using the Flex, as well as connect the pin to a + /// // peripheral input. + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub fn peripheral_input(&self) -> interconnect::InputSignal<'d> { + self.pin.set_input_enable(true); + unsafe { + // Safety: the signal is frozen by this function. + self.pin.clone_unchecked().split_no_init().0.freeze() + } + } + + #[procmacros::doc_replace] + /// Split the pin into an input and output signal pair. + /// + /// Peripheral signals allow connecting peripherals together without using + /// external hardware. + /// + /// Note that the signals returned by this function is + /// [frozen](interconnect::InputSignal::freeze). + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::Flex; + /// let pin1 = Flex::new(peripherals.GPIO1); + /// let (input, output) = pin1.split(); + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub fn split( + self, + ) -> ( + interconnect::InputSignal<'d>, + interconnect::OutputSignal<'d>, + ) { + let input = self.peripheral_input(); + let output = self.into_peripheral_output(); + + (input, output) + } + + /// Split the pin into an [Input] and an [Output] driver pair. + /// + /// Note that the signal returned by this function is + /// [frozen](interconnect::InputSignal::freeze). On the other hand, + /// the pin driver is free to change settings. + /// + /// This function allows you to configure an input-output pin, then keep + /// working with the output half. This is mainly intended for testing, + /// allowing you to drive a peripheral from a signal generated by + /// software. + /// + /// # Safety + /// + /// The caller must ensure that the pins are not being configured via their + /// `apply_config` functions in the same time in multiple places. The pin + /// drivers must not be turned back into `Flex`, unless one of the + /// drivers is dropped first. + // TODO is this enough? Register-wise config is the only non-atomic operation, but is it + // actually safe to have two drivers on the same pin, otherwise? Perhaps it would be better + // to implement ehal traits for signals? + #[inline] + #[instability::unstable] + pub unsafe fn split_into_drivers(self) -> (Input<'d>, Output<'d>) { + self.pin.set_input_enable(true); + let input = Input { + pin: Flex { + pin: unsafe { self.pin.clone_unchecked() }, + }, + }; + let output = Output { pin: self }; + + (input, output) + } + + #[procmacros::doc_replace] + /// Turns the pin object into a peripheral + /// [output][interconnect::OutputSignal]. + /// + /// The output signal can be passed to peripherals in place of an output + /// pin. + /// + /// Note that the signal returned by this function is + /// [frozen](interconnect::OutputSignal::freeze). + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::Flex; + /// let pin1_gpio = Flex::new(peripherals.GPIO1); + /// // Can be passed as an output. + /// let pin1 = pin1_gpio.into_peripheral_output(); + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub fn into_peripheral_output(self) -> interconnect::OutputSignal<'d> { + unsafe { + // Safety: the signals are frozen by this function. + self.pin.split_no_init().1.freeze() + } + } +} + +impl private::Sealed for AnyPin<'_> {} + +impl<'lt> AnyPin<'lt> { + fn bank(&self) -> GpioBank { + #[cfg(gpio_has_bank_1)] + if self.number() >= 32 { + return GpioBank::_1; + } + + GpioBank::_0 + } + + pub(crate) fn disable_usb_pads(&self) { + #[cfg(soc_has_usb_device)] + { + /// Workaround to make D+ and D- work when the pin is assigned to + /// the `USB_SERIAL_JTAG` peripheral by default. + fn disable_usb_pads(_gpionum: u8) { + crate::peripherals::USB_DEVICE::regs() + .conf0() + .modify(|_, w| { + w.usb_pad_enable().clear_bit(); + w.dm_pullup().clear_bit(); + w.dm_pulldown().clear_bit(); + w.dp_pullup().clear_bit(); + w.dp_pulldown().clear_bit() + }); + } + + macro_rules! disable_usb_pads { + ($gpio:ident) => { + if self.number() == crate::peripherals::$gpio::NUMBER { + disable_usb_pads(crate::peripherals::$gpio::NUMBER); + } + }; + } + + for_each_analog_function! { + (USB_DM, $gpio:ident) => { disable_usb_pads!($gpio) }; + (USB_DP, $gpio:ident) => { disable_usb_pads!($gpio) }; + } + } + } + + #[inline] + /// Resets the GPIO to a known state. + /// + /// This function needs to be called before using the GPIO pin: + /// - Before converting it into signals + /// - Before using it as an input or output + pub(crate) fn init_gpio(&self) { + self.set_output_enable(false); + self.disable_usb_pads(); + + GPIO::regs() + .func_out_sel_cfg(self.number() as usize) + .modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as _) }); + + // Use RMW to not overwrite sleep configuration + io_mux_reg(self.number()).modify(|_, w| unsafe { + w.mcu_sel().bits(AlternateFunction::GPIO as u8); + w.fun_ie().clear_bit(); + w.slp_sel().clear_bit() + }); + } + + #[procmacros::doc_replace] + /// Split the pin into an input and output signal. + /// + /// Peripheral signals allow connecting peripherals together without + /// using external hardware. + /// + /// Creating an input signal enables the pin's input buffer. + /// + /// # Safety + /// + /// The caller must ensure that peripheral drivers don't configure the same + /// GPIO at the same time in multiple places. This includes clones of the + /// `InputSignal` struct, as well as the `OutputSignal` struct. + /// + /// # Panics + /// + /// This function panics if the pin is not an output pin. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::gpio::{AnyPin, Pin}; + /// let pin1 = peripherals.GPIO1.degrade(); + /// let (input, output) = unsafe { pin1.split() }; + /// # {after_snippet} + /// ``` + #[inline] + #[instability::unstable] + pub unsafe fn split( + self, + ) -> ( + interconnect::InputSignal<'lt>, + interconnect::OutputSignal<'lt>, + ) { + assert!(self.is_output()); + + // Before each use, reset the GPIO to a known state. + self.init_gpio(); + self.set_input_enable(true); + + let (input, output) = unsafe { self.split_no_init() }; + + // We don't know if the input signal(s) will support bypassing the GPIO matrix. + // Since the bypass option is common between input and output halves of + // a single GPIO, we can't assume anything about the output, either. + let output = output.with_gpio_matrix_forced(true); + + (input, output) + } + + /// Convert the pin into an input signal. + /// + /// Peripheral signals allow connecting peripherals together without + /// using external hardware. + /// + /// Creating an input signal enables the pin's input buffer. + /// + /// # Safety + /// + /// The caller must ensure that peripheral drivers don't configure the same + /// GPIO at the same time in multiple places. This includes clones of the + /// `InputSignal` struct. + #[inline] + #[instability::unstable] + pub unsafe fn into_input_signal(self) -> interconnect::InputSignal<'lt> { + // Before each use, reset the GPIO to a known state. + self.init_gpio(); + self.set_input_enable(true); + + let (input, _) = unsafe { self.split_no_init() }; + + input + } + + /// Convert the pin into an output signal. + /// + /// Peripheral signals allow connecting peripherals together without + /// using external hardware. + /// + /// # Panics + /// + /// This function panics if the pin is not an output pin. + #[inline] + #[instability::unstable] + pub fn into_output_signal(self) -> interconnect::OutputSignal<'lt> { + assert!(self.is_output()); + + // Before each use, reset the GPIO to a known state. + self.init_gpio(); + + // AnyPin is used as output only, we can allow bypassing the GPIO matrix. + let (_, output) = unsafe { self.split_no_init() }; + + output + } + + unsafe fn split_no_init( + self, + ) -> ( + interconnect::InputSignal<'lt>, + interconnect::OutputSignal<'lt>, + ) { + let input = interconnect::InputSignal::new(unsafe { self.clone_unchecked() }); + let output = interconnect::OutputSignal::new(self); + + // Since InputSignal can be cloned, we have no way of knowing how many signals + // end up being configured, and in what order. If multiple signals are + // passed to peripherals, and one of them would allow GPIO alternate + // function configurations, it would mean that the GPIO MCU_SEL bit's + // final value would depend on the order of operations. + let input = input.with_gpio_matrix_forced(true); + + (input, output) + } + + #[inline] + pub(crate) fn set_alternate_function(&self, alternate: AlternateFunction) { + io_mux_reg(self.number()).modify(|_, w| unsafe { w.mcu_sel().bits(alternate as u8) }); + } + + // /// Enable/disable sleep-mode + // #[inline] + // fn sleep_mode(&mut self, on: bool, _: private::Internal) { + // io_mux_reg(self.number()).modify(|_, w| w.slp_sel().bit(on)); + // } + + /// Enable or disable the GPIO pin output buffer. + #[inline] + pub(crate) fn set_output_enable(&self, enable: bool) { + assert!(self.is_output() || !enable); + self.bank().write_out_en(self.mask(), enable); + } + + /// Enable input for the pin + #[inline] + pub(crate) fn set_input_enable(&self, on: bool) { + io_mux_reg(self.number()).modify(|_, w| w.fun_ie().bit(on)); + } + + #[inline] + pub(crate) fn apply_input_config(&self, config: &InputConfig) { + let pull_up = config.pull == Pull::Up; + let pull_down = config.pull == Pull::Down; + + #[cfg(esp32)] + crate::soc::gpio::errata36(unsafe { self.clone_unchecked() }, pull_up, pull_down); + + io_mux_reg(self.number()).modify(|_, w| { + w.fun_wpd().bit(pull_down); + w.fun_wpu().bit(pull_up) + }); + } + + fn clear_interrupt(&self) { + self.bank().write_interrupt_status_clear(self.mask()); + } + + fn with_gpio_lock(&self, f: F) -> R + where + F: FnOnce() -> R, + { + // If the pin is listening, we need to take a critical section to prevent racing + // with the interrupt handler. + if is_int_enabled(self.number()) { + GPIO_LOCK.lock(f) + } else { + f() + } + } + + fn listen_with_options( + &self, + event: Event, + int_enable: bool, + nmi_enable: bool, + wake_up_from_light_sleep: bool, + ) -> Result<(), WakeConfigError> { + /// Assembles a valid value for the int_ena pin register field. + fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + match crate::system::Cpu::current() { + crate::system::Cpu::AppCpu => int_enable as u8 | ((nmi_enable as u8) << 1), + crate::system::Cpu::ProCpu => ((int_enable as u8) << 2) | ((nmi_enable as u8) << 3), + } + } else { + // ESP32 and ESP32-C3 have separate bits for maskable and NMI interrupts. + int_enable as u8 | ((nmi_enable as u8) << 1) + } + } + } + + if wake_up_from_light_sleep { + match event { + Event::AnyEdge | Event::RisingEdge | Event::FallingEdge => { + return Err(WakeConfigError::EdgeTriggeringNotSupported); + } + _ => {} + } + } + + self.with_gpio_lock(|| { + // Clear the interrupt status bit for this Pin, just in case the user forgot. + // Since we disabled the interrupt in the handler, it's not possible to + // trigger a new interrupt before we re-enable it here. + self.clear_interrupt(); + + set_int_enable( + self.number(), + Some(gpio_intr_enable(int_enable, nmi_enable)), + event as u8, + wake_up_from_light_sleep, + ); + }); + Ok(()) + } + + #[inline] + fn apply_output_config(&self, config: &OutputConfig) { + let pull_up = config.pull == Pull::Up; + let pull_down = config.pull == Pull::Down; + + #[cfg(esp32)] + crate::soc::gpio::errata36(unsafe { self.clone_unchecked() }, pull_up, pull_down); + + io_mux_reg(self.number()).modify(|_, w| { + unsafe { w.fun_drv().bits(config.drive_strength as u8) }; + w.fun_wpu().bit(pull_up); + w.fun_wpd().bit(pull_down); + w + }); + + self.with_gpio_lock(|| { + GPIO::regs().pin(self.number() as usize).modify(|_, w| { + w.pad_driver() + .bit(config.drive_mode == DriveMode::OpenDrain) + }); + }); + } + + #[inline] + fn mask(&self) -> u32 { + 1 << (self.number() % 32) + } + + /// The current state of the input + #[inline] + pub(crate) fn is_input_high(&self) -> bool { + self.bank().read_input() & self.mask() != 0 + } + + /// Set the pin's level to high or low + #[inline] + pub(crate) fn set_output_high(&self, high: bool) { + self.bank().write_output(self.mask(), high); + } + + /// Is the output set to high + #[inline] + pub(crate) fn is_set_high(&self) -> bool { + self.bank().read_output() & self.mask() != 0 + } +} + +impl Pin for AnyPin<'_> { + #[inline(always)] + fn number(&self) -> u8 { + self.pin + } + + fn output_signals( + &self, + private: private::Internal, + ) -> &'static [(AlternateFunction, OutputSignal)] { + for_each_gpio! { + (all $( ($n:literal, $gpio:ident $in_afs:tt $out_afs:tt ($input:tt [$($is_output:ident)?]) ) ),* ) => { + match self.number() { + $($( + $n => { + crate::ignore!($is_output); + let inner = unsafe { crate::peripherals::$gpio::steal() }; + return Pin::output_signals(&inner, private); + } + )?)* + other => panic!("Pin {} is not an OutputPin", other) + } + }; + } + } + + fn input_signals( + &self, + private: private::Internal, + ) -> &'static [(AlternateFunction, InputSignal)] { + for_each_gpio! { + (all $( ($n:literal, $gpio:ident $in_afs:tt $out_afs:tt ([$($is_input:ident)?] $output:tt) ) ),* ) => { + match self.number() { + $($( + $n => { + crate::ignore!($is_input); + let inner = unsafe { crate::peripherals::$gpio::steal() }; + return Pin::input_signals(&inner, private); + } + )?)* + other => panic!("Pin {} is not an InputPin", other) + } + }; + } + } +} + +impl InputPin for AnyPin<'_> { + fn waker(&self) -> &'static AtomicWaker { + for_each_gpio! { + (all $( ($n:literal, $gpio:ident $in_afs:tt $out_afs:tt ([$($is_input:ident)?] $output:tt) ) ),* ) => { + match self.number() { + $($( + $n => { + crate::ignore!($is_input); + let inner = unsafe { crate::peripherals::$gpio::steal() }; + return InputPin::waker(&inner); + } + )?)* + other => panic!("Pin {} is not an InputPin", other) + } + }; + } + } +} +impl OutputPin for AnyPin<'_> {} + +for_each_gpio! { + ($n:literal, $gpio:ident $($_rest:tt)*) => { + impl<'lt> TryFrom> for crate::peripherals::$gpio<'lt> { + type Error = AnyPin<'lt>; + + fn try_from(any_pin: AnyPin<'lt>) -> Result { + if any_pin.number() == $n { + Ok(unsafe { Self::steal() }) + } else { + Err(any_pin) + } + } + } + }; +} + +impl AnyPin<'_> { + #[procmacros::doc_replace] + /// Attempts to downcast the pin into the underlying GPIO instance. + /// + /// ## Example + /// + /// ```rust,no_run + /// # {before_snippet} + /// # + /// use esp_hal::{ + /// gpio::AnyPin, + /// peripherals::{GPIO2, GPIO4}, + /// }; + /// + /// let any_pin2 = AnyPin::from(peripherals.GPIO2); + /// let any_pin3 = AnyPin::from(peripherals.GPIO3); + /// + /// let gpio2 = any_pin2 + /// .downcast::() + /// .expect("This downcast succeeds because AnyPin was created from GPIO2"); + /// let gpio4 = any_pin3 + /// .downcast::() + /// .expect_err("This AnyPin was created from GPIO3, it cannot be downcast to GPIO4"); + /// # + /// # {after_snippet} + /// ``` + #[inline] + pub fn downcast(self) -> Result + where + Self: TryInto, + { + self.try_into() + } + + #[procmacros::doc_replace] + /// Conjure a new GPIO pin out of thin air. + /// + /// # Safety + /// + /// The caller must ensure that only one instance of a pin is in use at one time. + /// + /// # Panics + /// + /// Panics if the pin with the given number does not exist. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// use esp_hal::gpio::AnyPin; + /// let pin = unsafe { AnyPin::steal(1) }; + /// # + /// # {after_snippet} + /// ``` + pub unsafe fn steal(pin: u8) -> Self { + for_each_gpio! { + (all $( ($n:literal $($any:tt)*) ),*) => { const PINS: &[u8] = &[ $($n),* ]; }; + }; + assert!(PINS.contains(&pin), "Pin {} does not exist", pin); + Self { + pin, + _lifetime: core::marker::PhantomData, + } + } + + #[procmacros::doc_replace] + /// Unsafely clone the pin. + /// + /// # Safety + /// + /// Ensure that only one instance of a pin is in use at one time. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// use esp_hal::gpio::{AnyPin, Pin}; + /// let pin = peripherals.GPIO1.degrade(); + /// let pin_cloned = unsafe { pin.clone_unchecked() }; + /// # + /// # {after_snippet} + /// ``` + pub unsafe fn clone_unchecked(&self) -> Self { + Self { + pin: self.pin, + _lifetime: core::marker::PhantomData, + } + } + + #[procmacros::doc_replace] + /// Create a new AnyPin object that is limited to the lifetime of the + /// passed reference. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// use esp_hal::gpio::{AnyPin, Pin}; + /// let mut pin = peripherals.GPIO1.degrade(); + /// let pin_reborrowed = pin.reborrow(); + /// # + /// # {after_snippet} + /// ``` + pub fn reborrow(&mut self) -> AnyPin<'_> { + unsafe { self.clone_unchecked() } + } + + pub(crate) fn is_output(&self) -> bool { + for_each_gpio! { + (all $( ($n:literal, $gpio:ident $in_afs:tt $out_afs:tt ($input:tt [$($is_output:ident)?]) ) ),* ) => { + return match self.number() { + $($( + // This code is generated if the Output attribute is present + $n => { + crate::ignore!($is_output); + true + } + )?)* + other => false, + }; + }; + } + } +} + +#[cold] +#[allow(unused)] +fn pin_does_not_support_function(pin: u8, function: &str) { + panic!("Pin {} is not an {}", pin, function) +} + +#[cfg(not(esp32c5))] +macro_rules! for_each_rtcio_pin { + (@impl $ident:ident, $target:ident, $gpio:ident, $code:tt) => { + if $ident.number() == $crate::peripherals::$gpio::NUMBER { + #[allow(unused_mut)] + let mut $target = unsafe { $crate::peripherals::$gpio::steal() }; + return $code; + } + }; + + (($ident:ident, $target:ident) => $code:tt;) => { + for_each_lp_function! { + (($_sig:ident, RTC_GPIOn, $_n:literal), $gpio:ident) => { + for_each_rtcio_pin!(@impl $ident, $target, $gpio, $code) + }; + (($_sig:ident, LP_GPIOn, $_n:literal), $gpio:ident) => { + for_each_rtcio_pin!(@impl $ident, $target, $gpio, $code) + }; + } + unreachable!(); + }; +} + +#[cfg(not(any(esp32h2, esp32c5)))] +macro_rules! for_each_rtcio_output_pin { + (@impl $ident:ident, $target:ident, $gpio:ident, $code:tt, $kind:literal) => { + if $ident.number() == $crate::peripherals::$gpio::NUMBER { + for_each_gpio! { + // If the pin is an output pin, generate $code + ($n:tt, $gpio $in_afs:tt $out_afs:tt ($input:tt [Output])) => { + #[allow(unused_mut)] + let mut $target = unsafe { $crate::peripherals::$gpio::steal() }; + return $code; + }; + // If the pin is not an output pin, generate a panic + ($n:tt, $gpio $in_afs:tt $out_afs:tt ($input:tt [])) => { + pin_does_not_support_function($crate::peripherals::$gpio::NUMBER, $kind) + }; + } + } + }; + + (($ident:ident, $target:ident) => $code:tt;) => { + for_each_lp_function! { + (($_sig:ident, RTC_GPIOn, $_n:literal), $gpio:ident) => { + for_each_rtcio_output_pin!(@impl $ident, $target, $gpio, $code, "RTC_IO output") + }; + (($_sig:ident, LP_GPIOn, $_n:literal), $gpio:ident) => { + for_each_rtcio_output_pin!(@impl $ident, $target, $gpio, $code, "LP_IO output") + }; + } + unreachable!(); + }; +} + +#[cfg(not(esp32c5))] +impl RtcPin for AnyPin<'_> { + #[cfg(any(xtensa, esp32h2))] + fn rtc_number(&self) -> u8 { + for_each_rtcio_pin! { + (self, target) => { RtcPin::rtc_number(&target) }; + } + } + + #[cfg(any(xtensa, esp32c6))] + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: RtcFunction) { + for_each_rtcio_pin! { + (self, target) => { RtcPin::rtc_set_config(&target, input_enable, mux, func) }; + } + } + + fn rtcio_pad_hold(&self, enable: bool) { + for_each_rtcio_pin! { + (self, target) => { RtcPin::rtcio_pad_hold(&target, enable) }; + } + } + + #[cfg(any(esp32c2, esp32c3, esp32c6))] + unsafe fn apply_wakeup(&self, wakeup: bool, level: u8) { + for_each_rtcio_pin! { + (self, target) => { unsafe { RtcPin::apply_wakeup(&target, wakeup, level) } }; + } + } +} + +#[cfg(not(esp32c5))] +impl RtcPinWithResistors for AnyPin<'_> { + #[cfg(not(esp32h2))] + fn rtcio_pullup(&self, enable: bool) { + for_each_rtcio_output_pin! { + (self, target) => { RtcPinWithResistors::rtcio_pullup(&target, enable) }; + } + } + + #[cfg(not(esp32h2))] + fn rtcio_pulldown(&self, enable: bool) { + for_each_rtcio_output_pin! { + (self, target) => { RtcPinWithResistors::rtcio_pulldown(&target, enable) }; + } + } +} + +/// Set GPIO event listening. +/// +/// - `gpio_num`: the pin to configure +/// - `int_ena`: maskable and non-maskable CPU interrupt bits. None to leave unchanged. +/// - `int_type`: interrupt type, see [Event] (or 0 to disable) +/// - `wake_up_from_light_sleep`: whether to wake up from light sleep +fn set_int_enable(gpio_num: u8, int_ena: Option, int_type: u8, wake_up_from_light_sleep: bool) { + GPIO::regs().pin(gpio_num as usize).modify(|_, w| unsafe { + if let Some(int_ena) = int_ena { + w.int_ena().bits(int_ena); + } + w.int_type().bits(int_type); + w.wakeup_enable().bit(wake_up_from_light_sleep) + }); +} + +fn is_int_enabled(gpio_num: u8) -> bool { + GPIO::regs().pin(gpio_num as usize).read().int_ena().bits() != 0 +} + +for_each_gpio! { + ($n:literal, $gpio:ident $af_ins:tt $af_outs:tt ([Input] $output:tt)) => { + impl InputPin for crate::peripherals::$gpio<'_> { + #[doc(hidden)] + #[inline] + fn waker(&self) -> &'static $crate::asynch::AtomicWaker { + static WAKER: $crate::asynch::AtomicWaker = $crate::asynch::AtomicWaker::new(); + &WAKER + } + } + }; +} +for_each_gpio! { + ($n:literal, $gpio:ident $af_ins:tt $af_outs:tt ($input:tt [Output])) => { + impl OutputPin for crate::peripherals::$gpio<'_> {} + }; +} +for_each_gpio! { + ($n:literal, $gpio:ident ($( $af_input_num:ident => $af_input_signal:ident )*) ($( $af_output_num:ident => $af_output_signal:ident )*) $attrs:tt) => { + impl<'d> crate::peripherals::$gpio<'d> { + #[allow(unused)] + pub(crate) const NUMBER: u8 = $n; + + #[procmacros::doc_replace] + /// Split the pin into an input and output signal. + /// + /// Peripheral signals allow connecting peripherals together without using + /// external hardware. + /// + /// # Safety + /// + /// The caller must ensure that peripheral drivers don't configure the same + /// GPIO at the same time in multiple places. This includes clones of the + /// `InputSignal` struct, as well as the `OutputSignal` struct. + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// let (rx, tx) = unsafe { peripherals.GPIO2.split() }; + /// // rx and tx can then be passed to different peripherals to connect them. + /// # + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub unsafe fn split(self) -> (interconnect::InputSignal<'d>, interconnect::OutputSignal<'d>) { + // FIXME: we should implement this in the gpio macro for output pins, but we + // should also have an input-only alternative for pins that can't be used as + // outputs. + + // This goes through AnyPin which calls `init_gpio` as needed. + unsafe { self.degrade().split() } + } + } + + impl Pin for crate::peripherals::$gpio<'_> { + #[inline(always)] + fn number(&self) -> u8 { + $n + } + + fn output_signals(&self, _: crate::private::Internal) -> &'static [(AlternateFunction, OutputSignal)] { + &[$( + (AlternateFunction::$af_output_num, OutputSignal::$af_output_signal), + )*] + } + + fn input_signals(&self, _: crate::private::Internal) -> &'static [(AlternateFunction, InputSignal)] { + &[$( + (AlternateFunction::$af_input_num, InputSignal::$af_input_signal), + )*] + } + } + + impl<'lt> From> for AnyPin<'lt> { + fn from(pin: crate::peripherals::$gpio<'lt>) -> Self { + Pin::degrade(pin) + } + } + }; +} + +define_io_mux_reg!(); diff --git a/esp-hal/src/gpio/placeholder.rs b/esp-hal/src/gpio/placeholder.rs new file mode 100644 index 00000000000..3161ffb4226 --- /dev/null +++ b/esp-hal/src/gpio/placeholder.rs @@ -0,0 +1,40 @@ +//! Placeholder pins/signals. +//! +//! These are useful to pass them into peripheral drivers where you don't want +//! an actual pin but one is required. +// This module also contains peripheral signal impls for `Level` to avoid +// polluting the main module. + +use super::*; + +/// Placeholder pin, used when no pin is required when using a peripheral. +/// +/// When used as a peripheral signal, `NoPin` is equivalent to [`Level::Low`]. +#[derive(Default, Clone, Copy)] +pub struct NoPin; + +impl private::Sealed for NoPin {} + +impl embedded_hal::digital::ErrorType for NoPin { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::OutputPin for NoPin { + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for NoPin { + fn is_set_high(&mut self) -> Result { + Ok(false) + } + + fn is_set_low(&mut self) -> Result { + Ok(false) + } +} diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs new file mode 100644 index 00000000000..084f5236a5b --- /dev/null +++ b/esp-hal/src/gpio/rtc_io.rs @@ -0,0 +1,185 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! RTC IO +//! +//! # Overview +//! +//! The hardware provides a couple of GPIO pins with low power (LP) +//! capabilities and analog functions. +//! +//! ## Configuration +//! +//! These pins can be controlled by either IO MUX or RTC IO. +//! +//! If controlled by RTC IO, these pins will bypass IO MUX and GPIO +//! matrix for the use by ULP and peripherals in RTC system. +//! +//! When configured as RTC GPIOs, the pins can still be controlled by ULP or +//! the peripherals in RTC system during chip Deep-sleep, and wake up the +//! chip from Deep-sleep. +//! +//! ## Examples +//! +//! ### Configure a ULP Pin as Output +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::rtc_io::LowPowerOutput; +//! // configure GPIO 1 as ULP output pin +//! let lp_pin = LowPowerOutput::<'static, 1>::new(peripherals.GPIO1); +//! # {after_snippet} +//! ``` + +use core::marker::PhantomData; + +use super::{InputPin, OutputPin, RtcPin}; +use crate::{ + gpio::RtcFunction, + peripherals::{GPIO, RTC_IO}, +}; + +/// A GPIO output pin configured for low power operation +pub struct LowPowerOutput<'d, const PIN: u8> { + phantom: PhantomData<&'d ()>, +} + +impl<'d, const PIN: u8> LowPowerOutput<'d, PIN> { + /// Create a new output pin for use by the low-power core + pub fn new

    (pin: P) -> Self + where + P: OutputPin + RtcPin + 'd, + { + pin.rtc_set_config(false, true, RtcFunction::Rtc); + + let this = Self { + phantom: PhantomData, + }; + this.output_enable(true); + + this + } + + fn output_enable(&self, enable: bool) { + let rtc_io = RTC_IO::regs(); + + if enable { + rtc_io + .rtc_gpio_enable_w1ts() + .write(|w| unsafe { w.rtc_gpio_enable_w1ts().bits(1 << PIN) }); + } else { + rtc_io + .enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << PIN) }); + } + } +} + +/// A GPIO input pin configured for low power operation +pub struct LowPowerInput<'d, const PIN: u8> { + phantom: PhantomData<&'d mut ()>, +} + +impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { + /// Create a new input pin for use by the low-power core + pub fn new

    (pin: P) -> Self + where + P: InputPin + RtcPin + 'd, + { + pin.rtc_set_config(true, true, RtcFunction::Rtc); + + let this = Self { + phantom: PhantomData, + }; + this.input_enable(true); + this.pullup_enable(false); + this.pulldown_enable(false); + + this + } + + fn input_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.fun_ie().bit(enable)); + } + + /// Sets pull-up enable for the pin + pub fn pullup_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.rue().bit(enable)); + } + + /// Sets pull-down enable for the pin + pub fn pulldown_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.rde().bit(enable)); + } +} + +/// A GPIO open-drain output pin configured for low power operation +pub struct LowPowerOutputOpenDrain<'d, const PIN: u8> { + phantom: PhantomData<&'d ()>, +} + +impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { + /// Create a new output pin for use by the low-power core + pub fn new

    (pin: P) -> Self + where + P: InputPin + OutputPin + RtcPin + 'd, + { + pin.rtc_set_config(true, true, RtcFunction::Rtc); + + let this = Self { + phantom: PhantomData, + }; + + this.set_open_drain_output(true); + this.input_enable(true); + this.pullup_enable(true); + this.pulldown_enable(false); + this.output_enable(true); + + this + } + + fn output_enable(&self, enable: bool) { + let rtc_io = RTC_IO::regs(); + + if enable { + rtc_io + .rtc_gpio_enable_w1ts() + .write(|w| unsafe { w.rtc_gpio_enable_w1ts().bits(1 << PIN) }); + } else { + rtc_io + .enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << PIN) }); + } + } + + fn input_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.fun_ie().bit(enable)); + } + + /// Sets pull-up enable for the pin + pub fn pullup_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.rue().bit(enable)); + } + + /// Sets pull-down enable for the pin + pub fn pulldown_enable(&self, enable: bool) { + RTC_IO::regs() + .touch_pad(PIN as usize) + .modify(|_, w| w.rde().bit(enable)); + } + + fn set_open_drain_output(&self, enable: bool) { + GPIO::regs() + .pin(PIN as usize) + .modify(|_, w| w.pad_driver().bit(enable)); + } +} diff --git a/esp-hal/src/hmac.rs b/esp-hal/src/hmac.rs new file mode 100644 index 00000000000..8b499479920 --- /dev/null +++ b/esp-hal/src/hmac.rs @@ -0,0 +1,348 @@ +//! # Hash-based Message Authentication Code (HMAC) Accelerator +//! +//! ## Overview +//! HMAC is a secure authentication technique that verifies the authenticity and +//! integrity of a message with a pre-shared key. This module provides hardware +//! acceleration for SHA256-HMAC generation using a key burned into an eFuse +//! block. +//! +//! Main features: +//! +//! - Standard HMAC-SHA-256 algorithm. +//! - Hash result only accessible by configurable hardware peripheral (in downstream mode). +//! - Compatible to challenge-response authentication algorithm. +//! - Generates required keys for the Digital Signature (DS) peripheral (in downstream mode). +//! - Re-enables soft-disabled JTAG (in downstream mode). +//! +//! ## Configuration +//! The HMAC module can be used in two modes - in ”upstream” mode the HMAC +//! message is supplied by the user and the calculation result is read back by +//! the user. In ”downstream” mode the HMAC module is used as a Key Derivation +//! Function (KDF) for other internal hardwares. +//! +//! ### HMAC padding +//! +//! The HMAC padding is handled by the driver. In downstream mode, users do not +//! need to input any message or apply padding. The HMAC module uses a default +//! 32-byte pattern of 0x00 for re-enabling JTAG and a 32-byte pattern of 0xff +//! for deriving the AES key for the DS module. +//! +//! ## Examples +//! Visit the [HMAC] example to learn how to use the HMAC accelerator +//! +//! [HMAC]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/hmac/src/main.rs + +use core::convert::Infallible; + +use crate::{ + pac, + peripherals::HMAC, + reg_access::{AlignmentHelper, SocDependentEndianess}, + system::{GenericPeripheralGuard, Peripheral as PeripheralEnable}, +}; + +/// Provides an interface for interacting with the HMAC hardware peripheral. +/// It allows users to compute HMACs for cryptographic purposes, ensuring data +/// integrity and authenticity. +pub struct Hmac<'d> { + hmac: HMAC<'d>, + alignment_helper: AlignmentHelper, + byte_written: usize, + next_command: NextCommand, + _guard: GenericPeripheralGuard<{ PeripheralEnable::Hmac as u8 }>, +} + +/// HMAC interface error +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// It means the purpose of the selected block does not match the + /// configured key purpose and the calculation will not proceed. + KeyPurposeMismatch, +} + +/// The peripheral can be configured to deliver its output directly to the +/// user. It can also deliver to other peripherals. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")] +pub enum HmacPurpose { + /// HMAC is used to re-enable JTAG after soft-disabling it. + ToJtag = 6, + /// HMAC is provided to the digital signature peripheral to decrypt the + /// private key. + ToDs = 7, + /// Let the user provide a message and read the result. + ToUser = 8, + /// HMAC is used for both the digital signature or JTAG. + ToDsOrJtag = 5, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the key identifiers for the HMAC peripheral. +pub enum KeyId { + /// Key 0. + Key0 = 0, + /// Key 1. + Key1 = 1, + /// Key 2. + Key2 = 2, + /// Key 3. + Key3 = 3, + /// Key 4. + Key4 = 4, + /// Key 5. + Key5 = 5, +} + +enum NextCommand { + None, + MessageIng, + MessagePad, +} + +impl<'d> Hmac<'d> { + /// Creates a new instance of the HMAC peripheral. + pub fn new(hmac: HMAC<'d>) -> Self { + let guard = GenericPeripheralGuard::new(); + + Self { + hmac, + alignment_helper: AlignmentHelper::default(), + byte_written: 64, + next_command: NextCommand::None, + _guard: guard, + } + } + + fn regs(&self) -> &pac::hmac::RegisterBlock { + self.hmac.register_block() + } + + /// Step 1. Enable HMAC module. + /// + /// Before these steps, the user shall set the peripheral clocks bits for + /// HMAC and SHA peripherals and clear the corresponding peripheral + /// reset bits. + pub fn init(&mut self) { + self.regs().set_start().write(|w| w.set_start().set_bit()); + self.alignment_helper.reset(); + self.byte_written = 64; + self.next_command = NextCommand::None; + } + + /// Step 2. Configure HMAC keys and key purposes. + pub fn configure(&mut self, m: HmacPurpose, key_id: KeyId) -> nb::Result<(), Error> { + self.regs() + .set_para_purpose() + .write(|w| unsafe { w.purpose_set().bits(m as u8) }); + self.regs() + .set_para_key() + .write(|w| unsafe { w.key_set().bits(key_id as u8) }); + self.regs() + .set_para_finish() + .write(|w| w.set_para_end().set_bit()); + + if self.regs().query_error().read().query_check().bit_is_set() { + return Err(nb::Error::Other(Error::KeyPurposeMismatch)); + } + + Ok(()) + } + + /// Process the msg block after block + /// + /// Call this function as many times as necessary (msg.len() > 0) + pub fn update<'a>(&mut self, msg: &'a [u8]) -> nb::Result<&'a [u8], Infallible> { + if self.is_busy() { + return Err(nb::Error::WouldBlock); + } + + self.next_command(); + + let remaining = self.write_data(msg).unwrap(); + + Ok(remaining) + } + + /// Finalizes the HMAC computation and retrieves the resulting hash output. + pub fn finalize(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> { + if self.is_busy() { + return Err(nb::Error::WouldBlock); + } + + self.next_command(); + + let msg_len = self.byte_written as u64; + + nb::block!(self.write_data(&[0x80])).unwrap(); + nb::block!(self.flush_data()).unwrap(); + self.next_command(); + debug_assert!(self.byte_written.is_multiple_of(4)); + + self.padding(msg_len); + + // Checking if the message is one block including padding + if msg_len < 64 + 56 { + self.regs() + .one_block() + .write(|w| w.set_one_block().set_bit()); + + while self.is_busy() {} + } + + self.alignment_helper.volatile_read_regset( + #[cfg(esp32s2)] + self.regs().rd_result_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().rd_result_mem(0).as_ptr(), + output, + core::cmp::min(output.len(), 32), + ); + + self.regs() + .set_result_finish() + .write(|w| w.set_result_end().set_bit()); + self.byte_written = 64; + self.next_command = NextCommand::None; + Ok(()) + } + + fn is_busy(&mut self) -> bool { + self.regs().query_busy().read().busy_state().bit_is_set() + } + + fn next_command(&mut self) { + match self.next_command { + NextCommand::MessageIng => { + self.regs() + .set_message_ing() + .write(|w| w.set_text_ing().set_bit()); + } + NextCommand::MessagePad => { + self.regs() + .set_message_pad() + .write(|w| w.set_text_pad().set_bit()); + } + NextCommand::None => {} + } + self.next_command = NextCommand::None; + } + + fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> { + let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy( + #[cfg(esp32s2)] + self.regs().wr_message_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().wr_message_mem(0).as_ptr(), + incoming, + 64, + self.byte_written % 64, + ); + + self.byte_written = self + .byte_written + .wrapping_add(incoming.len() - remaining.len()); + + if bound_reached { + self.regs() + .set_message_one() + .write(|w| w.set_text_one().set_bit()); + + if remaining.len() >= 56 { + self.next_command = NextCommand::MessageIng; + } else { + self.next_command = NextCommand::MessagePad; + } + } + + Ok(remaining) + } + + fn flush_data(&mut self) -> nb::Result<(), Infallible> { + if self.is_busy() { + return Err(nb::Error::WouldBlock); + } + + let flushed = self.alignment_helper.flush_to( + #[cfg(esp32s2)] + self.regs().wr_message_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().wr_message_mem(0).as_ptr(), + self.byte_written % 64, + ); + + self.byte_written = self.byte_written.wrapping_add(flushed); + if flushed > 0 && self.byte_written.is_multiple_of(64) { + self.regs() + .set_message_one() + .write(|w| w.set_text_one().set_bit()); + while self.is_busy() {} + self.next_command = NextCommand::MessagePad; + } + + Ok(()) + } + + fn padding(&mut self, msg_len: u64) { + let mod_cursor = self.byte_written % 64; + + // The padding will be spanned over 2 blocks + if mod_cursor > 56 { + let pad_len = 64 - mod_cursor; + self.alignment_helper.volatile_write( + #[cfg(esp32s2)] + self.regs().wr_message_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().wr_message_mem(0).as_ptr(), + 0_u8, + pad_len, + mod_cursor, + ); + self.regs() + .set_message_one() + .write(|w| w.set_text_one().set_bit()); + self.byte_written = self.byte_written.wrapping_add(pad_len); + debug_assert!(self.byte_written.is_multiple_of(64)); + while self.is_busy() {} + self.next_command = NextCommand::MessagePad; + self.next_command(); + } + + let mod_cursor = self.byte_written % 64; + let pad_len = 64 - mod_cursor - core::mem::size_of::(); + + self.alignment_helper.volatile_write( + #[cfg(esp32s2)] + self.regs().wr_message_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().wr_message_mem(0).as_ptr(), + 0_u8, + pad_len, + mod_cursor, + ); + + self.byte_written = self.byte_written.wrapping_add(pad_len); + + assert_eq!(self.byte_written % 64, 64 - core::mem::size_of::()); + + // Add padded key + let len_mem = (msg_len * 8).to_be_bytes(); + + self.alignment_helper.aligned_volatile_copy( + #[cfg(esp32s2)] + self.regs().wr_message_(0).as_ptr(), + #[cfg(not(esp32s2))] + self.regs().wr_message_mem(0).as_ptr(), + &len_mem, + 64, + 64 - core::mem::size_of::(), + ); + self.regs() + .set_message_one() + .write(|w| w.set_text_one().set_bit()); + + while self.is_busy() {} + } +} diff --git a/esp-hal/src/i2c/lp_i2c.rs b/esp-hal/src/i2c/lp_i2c.rs new file mode 100644 index 00000000000..b412d9308da --- /dev/null +++ b/esp-hal/src/i2c/lp_i2c.rs @@ -0,0 +1,367 @@ +//! Low-power I2C driver + +use crate::{ + gpio::lp_io::LowPowerOutputOpenDrain, + peripherals::{LP_AON, LP_I2C0, LP_IO, LP_PERI, LPWR}, + time::Rate, +}; + +const LP_I2C_FILTER_CYC_NUM_DEF: u8 = 7; + +/// I2C-specific transmission errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The transmission exceeded the FIFO size. + ExceedingFifo, + /// The acknowledgment check failed. + AckCheckFailed, + /// A timeout occurred during transmission. + TimeOut, + /// The arbitration for the bus was lost. + ArbitrationLost, + /// The execution of the I2C command was incomplete. + ExecIncomplete, + /// The number of commands issued exceeded the limit. + CommandNrExceeded, + /// The response received from the I2C device was invalid. + InvalidResponse, +} + +#[allow(unused)] +enum OperationType { + Write = 0, + Read = 1, +} + +#[allow(unused)] +#[derive(Eq, PartialEq, Copy, Clone)] +enum Ack { + Ack, + Nack, +} + +#[derive(PartialEq)] +#[allow(unused)] +enum Command { + Start, + Stop, + End, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp + /// value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is + /// 255, while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte + /// has been received. + ack_value: Ack, + /// Length of data (in bytes) to be read. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, +} + +// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122 +// TX/RX RAM size is 16*8 bit +// TX RX FIFO has 16 bit depth +// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST. +// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK. +// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source, +// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source. +// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK. +// Adjust the timing registers accordingly when the clock frequency changes. + +/// Represents a Low-Power I2C peripheral. +pub struct LpI2c { + i2c: LP_I2C0<'static>, +} + +impl LpI2c { + /// Creates a new instance of the `LpI2c` peripheral. + pub fn new( + i2c: LP_I2C0<'static>, + _sda: LowPowerOutputOpenDrain<'_, 6>, + _scl: LowPowerOutputOpenDrain<'_, 7>, + frequency: Rate, + ) -> Self { + let me = Self { i2c }; + + // Configure LP I2C GPIOs + // Initialize IO Pins + let lp_io = LP_IO::regs(); + let lp_aon = LP_AON::regs(); + let lp_peri = LP_PERI::regs(); + + unsafe { + // FIXME: use GPIO APIs to configure pins + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 6))); + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 7))); + lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO + + lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); + + // Set output mode to Normal + lp_io.pin(6).modify(|_, w| w.pad_driver().set_bit()); + // Enable output (writing to write-1-to-set register, then internally the + // `GPIO_OUT_REG` will be set) + lp_io + .out_enable_w1ts() + .write(|w| w.enable_w1ts().bits(1 << 6)); + // Enable input + lp_io.gpio(6).modify(|_, w| w.fun_ie().set_bit()); + + // Disable pulldown (enable internal weak pull-down) + lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit()); + // Enable pullup + lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit()); + + // Same process for SCL pin + lp_io.pin(7).modify(|_, w| w.pad_driver().set_bit()); + // Enable output (writing to write-1-to-set register, then internally the + // `GPIO_OUT_REG` will be set) + lp_io + .out_enable_w1ts() + .write(|w| w.enable_w1ts().bits(1 << 7)); + // Enable input + lp_io.gpio(7).modify(|_, w| w.fun_ie().set_bit()); + // Disable pulldown (enable internal weak pull-down) + lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit()); + // Enable pullup + lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit()); + + // Select LP I2C function for the SDA and SCL pins + lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); + lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); + } + + // Initialize LP I2C HAL */ + me.i2c + .register_block() + .clk_conf() + .modify(|_, w| w.sclk_active().set_bit()); + + // Enable LP I2C controller clock + lp_peri + .clk_en() + .modify(|_, w| w.lp_ext_i2c_ck_en().set_bit()); + + lp_peri + .reset_en() + .modify(|_, w| w.lp_ext_i2c_reset_en().set_bit()); + lp_peri + .reset_en() + .modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit()); + + // Set LP I2C source clock + LPWR::regs() + .lpperi() + .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); + + // Initialize LP I2C Master mode + me.i2c.register_block().ctr().modify(|_, w| unsafe { + // Clear register + w.bits(0); + // Use open drain output for SDA and SCL + w.sda_force_out().set_bit(); + w.scl_force_out().set_bit(); + // Ensure that clock is enabled + w.clk_en().set_bit() + }); + + // First, reset the fifo buffers + me.i2c + .register_block() + .fifo_conf() + .modify(|_, w| w.nonfifo_en().clear_bit()); + + me.i2c.register_block().ctr().modify(|_, w| { + w.tx_lsb_first().clear_bit(); + w.rx_lsb_first().clear_bit() + }); + + me.reset_fifo(); + + // Set LP I2C source clock + LPWR::regs() + .lpperi() + .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); + + // Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this + // call + + let source_clk = 16_000_000; + let bus_freq = frequency.as_hz(); + + let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; + let sclk_freq: u32 = source_clk / clkm_div; + let half_cycle: u32 = sclk_freq / bus_freq / 2; + + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + // Make 80KHz as a boundary here, because when working at lower frequency, too + // much scl_wait_high will faster the frequency according to some + // hardware behaviors. + let scl_wait_high = if bus_freq >= 80 * 1000 { + half_cycle / 2 - 2 + } else { + half_cycle / 4 + }; + let scl_high = half_cycle - scl_wait_high; + let sda_hold = half_cycle / 4; + let sda_sample = half_cycle / 2; // TODO + scl_wait_high; + let setup = half_cycle; + let hold = half_cycle; + // default we set the timeout value to about 10 bus cycles + // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) + let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2; + + // According to the Technical Reference Manual, the following timings must be + // subtracted by 1. However, according to the practical measurement and + // some hardware behaviour, if wait_high_period and scl_high minus one. + // The SCL frequency would be a little higher than expected. Therefore, the + // solution here is not to minus scl_high as well as scl_wait high, and + // the frequency will be absolutely accurate to all frequency + // to some extent. + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold - 1; + let sda_sample_time = sda_sample - 1; + // setup + let scl_rstart_setup_time = setup - 1; + let scl_stop_setup_time = setup - 1; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold - 1; + let time_out_value = tout; + let time_out_en = true; + + // Write data to registers + unsafe { + me.i2c.register_block().clk_conf().modify(|_, w| { + w.sclk_sel().clear_bit(); + w.sclk_div_num().bits((clkm_div - 1) as u8) + }); + + // scl period + me.i2c + .register_block() + .scl_low_period() + .write(|w| w.scl_low_period().bits(scl_low_period as u16)); + + me.i2c.register_block().scl_high_period().write(|w| { + w.scl_high_period().bits(scl_high_period as u16); + w.scl_wait_high_period().bits(scl_wait_high_period as u8) + }); + // sda sample + me.i2c + .register_block() + .sda_hold() + .write(|w| w.time().bits(sda_hold_time as u16)); + me.i2c + .register_block() + .sda_sample() + .write(|w| w.time().bits(sda_sample_time as u16)); + + // setup + me.i2c + .register_block() + .scl_rstart_setup() + .write(|w| w.time().bits(scl_rstart_setup_time as u16)); + me.i2c + .register_block() + .scl_stop_setup() + .write(|w| w.time().bits(scl_stop_setup_time as u16)); + + // hold + me.i2c + .register_block() + .scl_start_hold() + .write(|w| w.time().bits(scl_start_hold_time as u16)); + me.i2c + .register_block() + .scl_stop_hold() + .write(|w| w.time().bits(scl_stop_hold_time as u16)); + + me.i2c.register_block().to().write(|w| { + w.time_out_en().bit(time_out_en); + w.time_out_value().bits(time_out_value.try_into().unwrap()) + }); + } + + // Enable SDA and SCL filtering. This configuration matches the HP I2C filter + // config + + me.i2c + .register_block() + .filter_cfg() + .modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); + me.i2c + .register_block() + .filter_cfg() + .modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); + + me.i2c + .register_block() + .filter_cfg() + .modify(|_, w| w.sda_filter_en().set_bit()); + me.i2c + .register_block() + .filter_cfg() + .modify(|_, w| w.scl_filter_en().set_bit()); + + // Configure the I2C master to send a NACK when the Rx FIFO count is full + me.i2c + .register_block() + .ctr() + .modify(|_, w| w.rx_full_ack_level().set_bit()); + + // Synchronize the config register values to the LP I2C peripheral clock + me.lp_i2c_update(); + + me + } + + /// Update I2C configuration + fn lp_i2c_update(&self) { + self.i2c + .register_block() + .ctr() + .modify(|_, w| w.conf_upgate().set_bit()); + } + + /// Resets the transmit and receive FIFO buffers. + fn reset_fifo(&self) { + self.i2c + .register_block() + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().set_bit()); + + self.i2c + .register_block() + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().clear_bit()); + + self.i2c + .register_block() + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().set_bit()); + + self.i2c + .register_block() + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().clear_bit()); + } +} diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs new file mode 100644 index 00000000000..ddddbafcdc1 --- /dev/null +++ b/esp-hal/src/i2c/master/mod.rs @@ -0,0 +1,3396 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Inter-Integrated Circuit (I2C) - Master mode +//! +//! ## Overview +//! +//! This driver implements the I2C Master mode. In this mode, the MCU initiates +//! and controls the I2C communication with one or more slave devices. Slave +//! devices are identified by their unique I2C addresses. +//! +//! ## Configuration +//! +//! The driver can be configured using the [`Config`] struct. To create a +//! configuration, you can use the [`Config::default()`] method, and then modify +//! the individual settings as needed, by calling `with_*` methods on the +//! [`Config`] struct. +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::{i2c::master::Config, time::Rate}; +//! +//! let config = Config::default().with_frequency(Rate::from_khz(100)); +//! # {after_snippet} +//! ``` +//! +//! You will then need to pass the configuration to [`I2c::new`], and you can +//! also change the configuration later by calling [`I2c::apply_config`]. +//! +//! You will also need to specify the SDA and SCL pins when you create the +//! driver instance. +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::i2c::master::I2c; +//! # use esp_hal::{i2c::master::Config, time::Rate}; +//! # +//! # let config = Config::default(); +//! # +//! // You need to configure the driver during initialization: +//! let mut i2c = I2c::new(peripherals.I2C0, config)? +//! .with_sda(peripherals.GPIO2) +//! .with_scl(peripherals.GPIO3); +//! +//! // You can change the configuration later: +//! let new_config = config.with_frequency(Rate::from_khz(400)); +//! i2c.apply_config(&new_config)?; +//! # {after_snippet} +//! ``` +//! +//! ## Usage +//! +//! The master communicates with slave devices using I2C transactions. A +//! transaction can be a write, a read, or a combination of both. The +//! [`I2c`] driver provides methods for performing these transactions: +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::i2c::master::{I2c, Config, Operation}; +//! # let config = Config::default(); +//! # let mut i2c = I2c::new(peripherals.I2C0, config)?; +//! # +//! // `u8` is automatically converted to `I2cAddress::SevenBit`. The device +//! // address does not contain the `R/W` bit! +//! const DEVICE_ADDR: u8 = 0x77; +//! let write_buffer = [0xAA]; +//! let mut read_buffer = [0u8; 22]; +//! +//! i2c.write(DEVICE_ADDR, &write_buffer)?; +//! i2c.write_read(DEVICE_ADDR, &write_buffer, &mut read_buffer)?; +//! i2c.read(DEVICE_ADDR, &mut read_buffer)?; +//! i2c.transaction( +//! DEVICE_ADDR, +//! &mut [ +//! Operation::Write(&write_buffer), +//! Operation::Read(&mut read_buffer), +//! ], +//! )?; +//! # {after_snippet} +//! ``` +//! If you configure the driver to `async` mode, the driver also provides +//! asynchronous versions of these methods: +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::i2c::master::{I2c, Config, Operation}; +//! # let config = Config::default(); +//! # let mut i2c = I2c::new(peripherals.I2C0, config)?; +//! # +//! # const DEVICE_ADDR: u8 = 0x77; +//! # let write_buffer = [0xAA]; +//! # let mut read_buffer = [0u8; 22]; +//! # +//! // Reconfigure the driver to use async mode. +//! let mut i2c = i2c.into_async(); +//! +//! i2c.write_async(DEVICE_ADDR, &write_buffer).await?; +//! i2c.write_read_async(DEVICE_ADDR, &write_buffer, &mut read_buffer) +//! .await?; +//! i2c.read_async(DEVICE_ADDR, &mut read_buffer).await?; +//! i2c.transaction_async( +//! DEVICE_ADDR, +//! &mut [ +//! Operation::Write(&write_buffer), +//! Operation::Read(&mut read_buffer), +//! ], +//! ) +//! .await?; +//! +//! // You should still be able to use the blocking methods, if you need to: +//! i2c.write(DEVICE_ADDR, &write_buffer)?; +//! +//! # {after_snippet} +//! ``` +//! +//! The I2C driver also implements [embedded-hal] and [embedded-hal-async] +//! traits, so you can use it with any crate that supports these traits. +//! +//! [embedded-hal]: embedded_hal::i2c +//! [embedded-hal-async]: embedded_hal_async::i2c + +use core::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use embedded_hal::i2c::Operation as EhalOperation; +use enumset::{EnumSet, EnumSetType}; + +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + gpio::{ + DriveMode, + InputSignal, + OutputConfig, + OutputSignal, + PinGuard, + Pull, + interconnect::{self, PeripheralInput, PeripheralOutput}, + }, + handler, + interrupt::InterruptHandler, + pac::i2c0::{COMD, RegisterBlock}, + private, + ram, + system::PeripheralGuard, + time::{Duration, Instant, Rate}, +}; + +const I2C_FIFO_SIZE: usize = property!("i2c_master.fifo_size"); +// Chunk writes/reads by this size +const I2C_CHUNK_SIZE: usize = I2C_FIFO_SIZE - 1; +const CLEAR_BUS_TIMEOUT_MS: Duration = Duration::from_millis(50); + +/// Representation of I2C address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum I2cAddress { + /// 7-bit address mode type. + /// + /// Note that 7-bit addresses are specified in **right-aligned** form, e.g. + /// in the range `0x00..=0x7F`. + /// + /// For example, a device that has the seven bit address of `0b011_0010`, + /// and therefore is addressed on the wire using: + /// + /// * `0b0110010_0` or `0x64` for *writes* + /// * `0b0110010_1` or `0x65` for *reads* + /// + /// The above address is specified as 0b0011_0010 or 0x32, NOT 0x64 or 0x65. + SevenBit(u8), +} + +impl I2cAddress { + fn validate(&self) -> Result<(), Error> { + match self { + I2cAddress::SevenBit(addr) => { + if *addr > 0x7F { + return Err(Error::AddressInvalid(*self)); + } + } + } + + Ok(()) + } + + fn bytes(self) -> usize { + match self { + I2cAddress::SevenBit(_) => 1, + } + } +} + +impl From for I2cAddress { + fn from(value: u8) -> Self { + I2cAddress::SevenBit(value) + } +} + +/// I2C SCL timeout period. +/// +/// When the level of SCL remains unchanged for more than `timeout` bus +/// clock cycles, the bus goes to idle state. +/// +/// Default value is `BusCycles(10)`. +#[doc = ""] +#[cfg_attr( + i2c_master_bus_timeout_is_exponential, + doc = "Note that the effective timeout may be longer than the value configured here." +)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, strum::Display)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum BusTimeout { + /// Use the maximum timeout value. + Maximum, + + /// Disable timeout control. + #[cfg(i2c_master_has_bus_timeout_enable)] + Disabled, + + /// Timeout in bus clock cycles. + BusCycles(u32), +} + +impl BusTimeout { + /// Returns the timeout in APB cycles, or `None` if the timeout is disabled. + /// + /// Newer devices only support power-of-two timeouts, so we'll have to take + /// the logarithm of the timeout value. This may cause considerably + /// longer (at most ~double) timeouts than configured. We may provide an + /// `ApbCycles` variant in the future to allow specifying the timeout in + /// APB cycles directly. + fn apb_cycles(self, half_bus_cycle: u32) -> Result, ConfigError> { + match self { + BusTimeout::Maximum => Ok(Some(property!("i2c_master.max_bus_timeout"))), + + #[cfg(i2c_master_has_bus_timeout_enable)] + BusTimeout::Disabled => Ok(None), + + BusTimeout::BusCycles(cycles) => { + let raw = if cfg!(i2c_master_bus_timeout_is_exponential) { + let to_peri = (cycles * 2 * half_bus_cycle).max(1); + let log2 = to_peri.ilog2(); + // If not a power of 2, round up so that we don't shorten timeouts. + if to_peri != 1 << log2 { log2 + 1 } else { log2 } + } else { + cycles * 2 * half_bus_cycle + }; + + if raw <= property!("i2c_master.max_bus_timeout") { + Ok(Some(raw)) + } else { + Err(ConfigError::TimeoutTooLong) + } + } + } + } +} + +/// Software timeout for I2C operations. +/// +/// This timeout is used to limit the duration of I2C operations in software. +/// Note that using this in conjunction with `async` operations will cause the +/// task to be woken up continuously until the operation completes or the +/// timeout is reached. You should prefer using an asynchronous +/// timeout mechanism (like [`embassy_time::with_timeout`]) for better +/// efficiency. +/// +/// [`embassy_time::with_timeout`]: https://docs.rs/embassy-time/0.4.0/embassy_time/fn.with_timeout.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SoftwareTimeout { + /// No software timeout is set. + None, + + /// Define a fixed timeout for I2C operations. + Transaction(Duration), + + /// Define a data length dependent timeout for I2C operations. + /// + /// The applied timeout is calculated as `data_length * duration_per_byte`. + /// In [`I2c::transaction`] and [`I2c::transaction_async`], the timeout is + /// applied separately for each operation. + PerByte(Duration), +} + +/// When the FSM remains unchanged for more than the 2^ the given amount of bus +/// clock cycles a timeout will be triggered. +/// +/// The default value is 23 (2^23 clock cycles). +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(i2c_master_has_fsm_timeouts)] +pub struct FsmTimeout { + value: u8, +} + +#[cfg(i2c_master_has_fsm_timeouts)] +impl FsmTimeout { + const FSM_TIMEOUT_MAX: u8 = 23; + + /// Creates a new timeout. + /// + /// The meaning of the value and the allowed range of values is different + /// for different chips. + #[instability::unstable] + pub const fn new_const() -> Self { + const { + core::assert!(VALUE <= Self::FSM_TIMEOUT_MAX, "Invalid timeout value"); + } + Self { value: VALUE } + } + + /// Creates a new timeout. + /// + /// The meaning of the value and the allowed range of values is different + /// for different chips. + #[instability::unstable] + pub fn new(value: u8) -> Result { + if value > Self::FSM_TIMEOUT_MAX { + return Err(ConfigError::TimeoutTooLong); + } + + Ok(Self { value }) + } + + fn value(&self) -> u8 { + self.value + } +} + +#[cfg(i2c_master_has_fsm_timeouts)] +impl Default for FsmTimeout { + fn default() -> Self { + Self::new_const::<{ Self::FSM_TIMEOUT_MAX }>() + } +} + +/// I2C-specific transmission errors +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The transmission exceeded the FIFO size. + FifoExceeded, + /// The acknowledgment check failed. + AcknowledgeCheckFailed(AcknowledgeCheckFailedReason), + /// A timeout occurred during transmission. + Timeout, + /// The arbitration for the bus was lost. + ArbitrationLost, + /// The execution of the I2C command was incomplete. + ExecutionIncomplete, + /// The number of commands issued exceeded the limit. + CommandNumberExceeded, + /// Zero length read or write operation. + ZeroLengthInvalid, + /// The given address is invalid. + AddressInvalid(I2cAddress), +} + +/// I2C no acknowledge error reason. +/// +/// Consider this as a hint and make sure to always handle all cases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum AcknowledgeCheckFailedReason { + /// The device did not acknowledge its address. The device may be missing. + Address, + + /// The device did not acknowledge the data. It may not be ready to process + /// requests at the moment. + Data, + + /// Either the device did not acknowledge its address or the data, but it is + /// unknown which. + Unknown, +} + +impl core::fmt::Display for AcknowledgeCheckFailedReason { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + AcknowledgeCheckFailedReason::Address => write!(f, "Address"), + AcknowledgeCheckFailedReason::Data => write!(f, "Data"), + AcknowledgeCheckFailedReason::Unknown => write!(f, "Unknown"), + } + } +} + +impl From<&AcknowledgeCheckFailedReason> for embedded_hal::i2c::NoAcknowledgeSource { + fn from(value: &AcknowledgeCheckFailedReason) -> Self { + match value { + AcknowledgeCheckFailedReason::Address => { + embedded_hal::i2c::NoAcknowledgeSource::Address + } + AcknowledgeCheckFailedReason::Data => embedded_hal::i2c::NoAcknowledgeSource::Data, + AcknowledgeCheckFailedReason::Unknown => { + embedded_hal::i2c::NoAcknowledgeSource::Unknown + } + } + } +} + +impl core::error::Error for Error {} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Error::FifoExceeded => write!(f, "The transmission exceeded the FIFO size"), + Error::AcknowledgeCheckFailed(reason) => { + write!(f, "The acknowledgment check failed. Reason: {reason}") + } + Error::Timeout => write!(f, "A timeout occurred during transmission"), + Error::ArbitrationLost => write!(f, "The arbitration for the bus was lost"), + Error::ExecutionIncomplete => { + write!(f, "The execution of the I2C command was incomplete") + } + Error::CommandNumberExceeded => { + write!(f, "The number of commands issued exceeded the limit") + } + Error::ZeroLengthInvalid => write!(f, "Zero length read or write operation"), + Error::AddressInvalid(address) => { + write!(f, "The given address ({address:?}) is invalid") + } + } + } +} + +/// I2C-specific configuration errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// Provided bus frequency is not valid for the current configuration. + FrequencyOutOfRange, + /// Provided timeout is not valid for the current configuration. + TimeoutTooLong, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::FrequencyOutOfRange => write!( + f, + "Provided bus frequency is invalid for the current configuration" + ), + ConfigError::TimeoutTooLong => write!( + f, + "Provided timeout is invalid for the current configuration" + ), + } + } +} + +// This enum is used to keep track of the last/next operation that was/will be +// performed in an embedded-hal(-async) I2c::transaction. It is used to +// determine whether a START condition should be issued at the start of the +// current operation and whether a read needs an ack or a nack for the final +// byte. +#[derive(PartialEq)] +enum OpKind { + Write, + Read, +} + +/// I2C operation. +/// +/// Several operations can be combined as part of a transaction. +#[derive(Debug, PartialEq, Eq, Hash, strum::Display)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Operation<'a> { + /// Write data from the provided buffer. + Write(&'a [u8]), + + /// Read data into the provided buffer. + Read(&'a mut [u8]), +} + +impl<'a, 'b> From<&'a mut embedded_hal::i2c::Operation<'b>> for Operation<'a> { + fn from(value: &'a mut embedded_hal::i2c::Operation<'b>) -> Self { + match value { + embedded_hal::i2c::Operation::Write(buffer) => Operation::Write(buffer), + embedded_hal::i2c::Operation::Read(buffer) => Operation::Read(buffer), + } + } +} + +impl<'a, 'b> From<&'a mut Operation<'b>> for Operation<'a> { + fn from(value: &'a mut Operation<'b>) -> Self { + match value { + Operation::Write(buffer) => Operation::Write(buffer), + Operation::Read(buffer) => Operation::Read(buffer), + } + } +} + +impl Operation<'_> { + fn is_write(&self) -> bool { + matches!(self, Operation::Write(_)) + } + + fn kind(&self) -> OpKind { + match self { + Operation::Write(_) => OpKind::Write, + Operation::Read(_) => OpKind::Read, + } + } + + fn is_empty(&self) -> bool { + match self { + Operation::Write(buffer) => buffer.is_empty(), + Operation::Read(buffer) => buffer.is_empty(), + } + } +} + +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + use embedded_hal::i2c::ErrorKind; + + match self { + Self::FifoExceeded => ErrorKind::Overrun, + Self::ArbitrationLost => ErrorKind::ArbitrationLoss, + Self::AcknowledgeCheckFailed(reason) => ErrorKind::NoAcknowledge(reason.into()), + _ => ErrorKind::Other, + } + } +} + +/// A generic I2C Command +#[derive(Debug)] +enum Command { + Start, + Stop, + End, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is + #[doc = property!("i2c_master.fifo_size", str)] + /// , while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte has + /// been received. + ack_value: Ack, + /// Length of data (in bytes) to be written. The maximum length is + #[doc = property!("i2c_master.fifo_size", str)] + /// , while the minimum is 1. + length: u8, + }, +} + +enum OperationType { + Write = 0, + Read = 1, +} + +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +enum Ack { + Ack = 0, + Nack = 1, +} + +/// I2C driver configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// The I2C clock frequency. + /// + /// Default value: 100 kHz. + frequency: Rate, + + /// I2C SCL timeout period. + /// + /// Default value: + #[cfg_attr(i2c_master_has_bus_timeout_enable, doc = "disabled")] + #[cfg_attr(not(i2c_master_has_bus_timeout_enable), doc = concat!(property!("i2c_master.max_bus_timeout", str), " bus cycles"))] + #[builder_lite(unstable)] + timeout: BusTimeout, + + /// Software timeout. + /// + /// Default value: disabled. + software_timeout: SoftwareTimeout, + + /// Sets the threshold value for the unchanged period of the SCL_FSM. + /// + /// Default value: 16. + #[cfg(i2c_master_has_fsm_timeouts)] + #[builder_lite(unstable)] + scl_st_timeout: FsmTimeout, + + /// Sets the threshold for the unchanged duration of the SCL_MAIN_FSM. + /// + /// Default value: 16. + #[cfg(i2c_master_has_fsm_timeouts)] + #[builder_lite(unstable)] + scl_main_st_timeout: FsmTimeout, +} + +impl Default for Config { + fn default() -> Self { + Config { + frequency: Rate::from_khz(100), + + #[cfg(i2c_master_has_bus_timeout_enable)] + timeout: BusTimeout::Disabled, + #[cfg(not(i2c_master_has_bus_timeout_enable))] + timeout: BusTimeout::Maximum, + + software_timeout: SoftwareTimeout::None, + + #[cfg(i2c_master_has_fsm_timeouts)] + scl_st_timeout: Default::default(), + #[cfg(i2c_master_has_fsm_timeouts)] + scl_main_st_timeout: Default::default(), + } + } +} + +#[procmacros::doc_replace] +/// I2C driver +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::i2c::master::{Config, I2c}; +/// # const DEVICE_ADDR: u8 = 0x77; +/// let mut i2c = I2c::new(peripherals.I2C0, Config::default())? +/// .with_sda(peripherals.GPIO1) +/// .with_scl(peripherals.GPIO2); +/// +/// let mut data = [0u8; 22]; +/// i2c.write_read(DEVICE_ADDR, &[0xaa], &mut data)?; +/// # {after_snippet} +/// ``` +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct I2c<'d, Dm: DriverMode> { + i2c: AnyI2c<'d>, + phantom: PhantomData, + guard: PeripheralGuard, + config: DriverConfig, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DriverConfig { + config: Config, + sda_pin: PinGuard, + scl_pin: PinGuard, +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for I2c<'_, Dm> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +impl embedded_hal::i2c::ErrorType for I2c<'_, Dm> { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2c<'_, Dm> { + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.driver() + .transaction_impl( + I2cAddress::SevenBit(address), + operations.iter_mut().map(Operation::from), + ) + .inspect_err(|error| self.internal_recover(error)) + } +} + +impl<'d> I2c<'d, Blocking> { + #[procmacros::doc_replace] + /// Create a new I2C instance. + /// + /// ## Errors + /// + /// A [`ConfigError`] variant will be returned if bus frequency or timeout + /// passed in config is invalid. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// let i2c = I2c::new(peripherals.I2C0, Config::default())? + /// .with_sda(peripherals.GPIO1) + /// .with_scl(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + pub fn new(i2c: impl Instance + 'd, config: Config) -> Result { + let guard = PeripheralGuard::new(i2c.info().peripheral); + + let sda_pin = PinGuard::new_unconnected(); + let scl_pin = PinGuard::new_unconnected(); + + let mut i2c = I2c { + i2c: i2c.degrade(), + phantom: PhantomData, + guard, + config: DriverConfig { + config, + sda_pin, + scl_pin, + }, + }; + + i2c.apply_config(&config)?; + + Ok(i2c) + } + + /// Reconfigures the driver to operate in [`Async`] mode. + /// + /// See the [`Async`] documentation for an example on how to use this + /// method. + pub fn into_async(mut self) -> I2c<'d, Async> { + self.set_interrupt_handler(self.driver().info.async_handler); + + I2c { + i2c: self.i2c, + phantom: PhantomData, + guard: self.guard, + config: self.config, + } + } + + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by passing + /// [DEFAULT_INTERRUPT_HANDLER][crate::interrupt::DEFAULT_INTERRUPT_HANDLER]. + /// + /// # Panics + /// + /// Panics if passed interrupt handler is invalid (e.g. has priority + /// `None`) + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.i2c.set_interrupt_handler(handler); + } + + /// Listen for the given interrupts + #[instability::unstable] + pub fn listen(&mut self, interrupts: impl Into>) { + self.i2c.info().enable_listen(interrupts.into(), true) + } + + /// Unlisten the given interrupts + #[instability::unstable] + pub fn unlisten(&mut self, interrupts: impl Into>) { + self.i2c.info().enable_listen(interrupts.into(), false) + } + + /// Gets asserted interrupts + #[instability::unstable] + pub fn interrupts(&mut self) -> EnumSet { + self.i2c.info().interrupts() + } + + /// Resets asserted interrupts + #[instability::unstable] + pub fn clear_interrupts(&mut self, interrupts: EnumSet) { + self.i2c.info().clear_interrupts(interrupts) + } +} + +impl private::Sealed for I2c<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for I2c<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.i2c.set_interrupt_handler(handler); + } +} + +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum Event { + /// Triggered when op_code of the master indicates an END command and an END + /// condition is detected. + EndDetect, + + /// Triggered when the I2C controller detects a STOP bit. + TxComplete, + + /// Triggered when the TX FIFO watermark check is enabled and the TX fifo + /// falls below the configured watermark. + #[cfg(i2c_master_has_tx_fifo_watermark)] + TxFifoWatermark, +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct I2cFuture<'a> { + events: EnumSet, + driver: Driver<'a>, + deadline: Option, + /// True if the Future has been polled to completion. + finished: bool, +} + +impl<'a> I2cFuture<'a> { + pub fn new(events: EnumSet, driver: Driver<'a>, deadline: Option) -> Self { + driver.regs().int_ena().modify(|_, w| { + for event in events { + match event { + Event::EndDetect => w.end_detect().set_bit(), + Event::TxComplete => w.trans_complete().set_bit(), + #[cfg(i2c_master_has_tx_fifo_watermark)] + Event::TxFifoWatermark => w.txfifo_wm().set_bit(), + }; + } + + w.arbitration_lost().set_bit(); + w.time_out().set_bit(); + w.nack().set_bit(); + #[cfg(i2c_master_has_fsm_timeouts)] + { + w.scl_main_st_to().set_bit(); + w.scl_st_to().set_bit(); + } + + w + }); + + Self::new_blocking(events, driver, deadline) + } + + pub fn new_blocking( + events: EnumSet, + driver: Driver<'a>, + deadline: Option, + ) -> Self { + Self { + events, + driver, + deadline, + finished: false, + } + } + + fn is_done(&self) -> bool { + !self.driver.info.interrupts().is_disjoint(self.events) + } + + fn poll_completion(&mut self) -> Poll> { + // Grab the current time before doing anything. This will ensure that a long + // interruption still allows the peripheral sufficient time to complete the + // operation (i.e. it ensures that the deadline is "at least", not "at most"). + let now = if self.deadline.is_some() { + Instant::now() + } else { + Instant::EPOCH + }; + let error = self.driver.check_errors(); + + let result = if self.is_done() { + // Even though we are done, we have to check for NACK and arbitration loss. + let result = if error == Err(Error::Timeout) { + // We are both done, and timed out. Likely the transaction has completed, but we + // checked too late? + Ok(()) + } else { + error + }; + Poll::Ready(result) + } else if error.is_err() { + Poll::Ready(error) + } else if let Some(deadline) = self.deadline + && now > deadline + { + // If the deadline is reached, we return an error. + Poll::Ready(Err(Error::Timeout)) + } else { + Poll::Pending + }; + + if result.is_ready() { + self.finished = true; + } + + result + } +} + +impl core::future::Future for I2cFuture<'_> { + type Output = Result<(), Error>; + + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + self.driver.state.waker.register(ctx.waker()); + + let result = self.poll_completion(); + + if result.is_pending() && self.deadline.is_some() { + ctx.waker().wake_by_ref(); + } + + result + } +} + +impl Drop for I2cFuture<'_> { + fn drop(&mut self) { + if !self.finished { + let result = self.poll_completion(); + if result.is_pending() || result == Poll::Ready(Err(Error::Timeout)) { + self.driver.reset_fsm(true); + } + } + } +} + +impl<'d> I2c<'d, Async> { + /// Reconfigures the driver to operate in [`Blocking`] mode. + /// + /// See the [`Blocking`] documentation for an example on how to use this + /// method. + pub fn into_blocking(self) -> I2c<'d, Blocking> { + self.i2c.disable_peri_interrupt_on_all_cores(); + + I2c { + i2c: self.i2c, + phantom: PhantomData, + guard: self.guard, + config: self.config, + } + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address`. + /// + /// Note that dropping the returned Future will abort the transfer, but doing so will + /// block while the driver is finishing clearing and releasing the bus. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new(peripherals.I2C0, Config::default())? + /// .with_sda(peripherals.GPIO1) + /// .with_scl(peripherals.GPIO2) + /// .into_async(); + /// + /// i2c.write_async(DEVICE_ADDR, &[0xaa]).await?; + /// # {after_snippet} + /// ``` + pub async fn write_async>( + &mut self, + address: A, + buffer: &[u8], + ) -> Result<(), Error> { + self.transaction_async(address, &mut [Operation::Write(buffer)]) + .await + } + + #[procmacros::doc_replace] + /// Reads enough bytes from slave with `address` to fill `buffer`. + /// + /// Note that dropping the returned Future will abort the transfer, but doing so will + /// block while the driver is finishing clearing and releasing the bus. + /// + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the + /// passed buffer has zero length. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new(peripherals.I2C0, Config::default())? + /// .with_sda(peripherals.GPIO1) + /// .with_scl(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut data = [0u8; 22]; + /// i2c.read_async(DEVICE_ADDR, &mut data).await?; + /// # {after_snippet} + /// ``` + pub async fn read_async>( + &mut self, + address: A, + buffer: &mut [u8], + ) -> Result<(), Error> { + self.transaction_async(address, &mut [Operation::Read(buffer)]) + .await + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address` and then reads enough + /// bytes to fill `buffer` *in a single transaction*. + /// + /// Note that dropping the returned Future will abort the transfer, but doing so will + /// block while the driver is finishing clearing and releasing the bus. + /// + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the + /// passed buffer has zero length. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new(peripherals.I2C0, Config::default())? + /// .with_sda(peripherals.GPIO1) + /// .with_scl(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut data = [0u8; 22]; + /// i2c.write_read_async(DEVICE_ADDR, &[0xaa], &mut data) + /// .await?; + /// # {after_snippet} + /// ``` + pub async fn write_read_async>( + &mut self, + address: A, + write_buffer: &[u8], + read_buffer: &mut [u8], + ) -> Result<(), Error> { + self.transaction_async( + address, + &mut [Operation::Write(write_buffer), Operation::Read(read_buffer)], + ) + .await + } + + #[procmacros::doc_replace] + /// Execute the provided operations on the I2C bus as a single transaction. + /// + /// Note that dropping the returned Future will abort the transfer, but doing so will + /// block while the driver is finishing clearing and releasing the bus. + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by + /// SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or + /// SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last + /// byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + #[cfg_attr( + any(esp32, esp32s2), + doc = "\n\nOn ESP32 and ESP32-S2 there might be issues combining large read/write operations with small (<3 bytes) read/write operations.\n\n" + )] + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the + /// buffer passed to an [`Operation`] has zero length. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c, Operation}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new(peripherals.I2C0, Config::default())? + /// .with_sda(peripherals.GPIO1) + /// .with_scl(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut data = [0u8; 22]; + /// i2c.transaction_async( + /// DEVICE_ADDR, + /// &mut [Operation::Write(&[0xaa]), Operation::Read(&mut data)], + /// ) + /// .await?; + /// # {after_snippet} + /// ``` + pub async fn transaction_async<'a, A: Into>( + &mut self, + address: A, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.driver() + .transaction_impl_async(address.into(), operations.into_iter().map(Operation::from)) + .await + .inspect_err(|error| self.internal_recover(error)) + } +} + +impl<'d, Dm> I2c<'d, Dm> +where + Dm: DriverMode, +{ + fn driver(&self) -> Driver<'_> { + Driver { + info: self.i2c.info(), + state: self.i2c.state(), + config: &self.config, + } + } + + fn internal_recover(&self, error: &Error) { + // Timeout errors mean our hardware is (possibly) working when it gets reset. Clear the bus + // in this case, to prevent leaving the I2C device mid-transfer. + self.driver().reset_fsm(*error == Error::Timeout) + } + + #[procmacros::doc_replace] + /// Connect a pin to the I2C SDA signal. + /// + /// This will replace previous pin assignments for this signal. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// + /// let i2c = I2c::new(peripherals.I2C0, Config::default())?.with_sda(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + pub fn with_sda(mut self, sda: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self { + let info = self.driver().info; + let input = info.sda_input; + let output = info.sda_output; + Driver::connect_pin(sda.into(), input, output, &mut self.config.sda_pin); + + self + } + + #[procmacros::doc_replace] + /// Connect a pin to the I2C SCL signal. + /// + /// This will replace previous pin assignments for this signal. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// + /// let i2c = I2c::new(peripherals.I2C0, Config::default())?.with_scl(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + pub fn with_scl(mut self, scl: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self { + let info = self.driver().info; + let input = info.scl_input; + let output = info.scl_output; + Driver::connect_pin(scl.into(), input, output, &mut self.config.scl_pin); + + self + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address` + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// # let mut i2c = I2c::new( + /// # peripherals.I2C0, + /// # Config::default(), + /// # )?; + /// # const DEVICE_ADDR: u8 = 0x77; + /// i2c.write(DEVICE_ADDR, &[0xaa])?; + /// # {after_snippet} + /// ``` + pub fn write>(&mut self, address: A, buffer: &[u8]) -> Result<(), Error> { + self.transaction(address, &mut [Operation::Write(buffer)]) + } + + #[procmacros::doc_replace] + /// Reads enough bytes from slave with `address` to fill `buffer` + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// # let mut i2c = I2c::new( + /// # peripherals.I2C0, + /// # Config::default(), + /// # )?; + /// # const DEVICE_ADDR: u8 = 0x77; + /// let mut data = [0u8; 22]; + /// i2c.read(DEVICE_ADDR, &mut data)?; + /// # {after_snippet} + /// ``` + /// + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the passed buffer has + /// zero length. + pub fn read>( + &mut self, + address: A, + buffer: &mut [u8], + ) -> Result<(), Error> { + self.transaction(address, &mut [Operation::Read(buffer)]) + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address` and then reads enough bytes + /// to fill `buffer` *in a single transaction* + /// + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the passed buffer has + /// zero length. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// # let mut i2c = I2c::new( + /// # peripherals.I2C0, + /// # Config::default(), + /// # )?; + /// # const DEVICE_ADDR: u8 = 0x77; + /// let mut data = [0u8; 22]; + /// i2c.write_read(DEVICE_ADDR, &[0xaa], &mut data)?; + /// # {after_snippet} + /// ``` + pub fn write_read>( + &mut self, + address: A, + write_buffer: &[u8], + read_buffer: &mut [u8], + ) -> Result<(), Error> { + self.transaction( + address, + &mut [Operation::Write(write_buffer), Operation::Read(read_buffer)], + ) + } + + #[procmacros::doc_replace] + /// Execute the provided operations on the I2C bus. + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by + /// SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or + /// SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last + /// byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c, Operation}; + /// # let mut i2c = I2c::new( + /// # peripherals.I2C0, + /// # Config::default(), + /// # )?; + /// # const DEVICE_ADDR: u8 = 0x77; + /// let mut data = [0u8; 22]; + /// i2c.transaction( + /// DEVICE_ADDR, + /// &mut [Operation::Write(&[0xaa]), Operation::Read(&mut data)], + /// )?; + /// # {after_snippet} + /// ``` + #[cfg_attr( + any(esp32, esp32s2), + doc = "\n\nOn ESP32 and ESP32-S2 it is advisable to not combine large read/write operations with small (<3 bytes) read/write operations.\n\n" + )] + /// ## Errors + /// + /// The corresponding error variant from [`Error`] will be returned if the + /// buffer passed to an [`Operation`] has zero length. + pub fn transaction<'a, A: Into>( + &mut self, + address: A, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.driver() + .transaction_impl(address.into(), operations.into_iter().map(Operation::from)) + .inspect_err(|error| self.internal_recover(error)) + } + + #[procmacros::doc_replace] + /// Applies a new configuration. + /// + /// ## Errors + /// + /// A [`ConfigError`] variant will be returned if bus frequency or timeout + /// passed in config is invalid. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::master::{Config, I2c}; + /// let mut i2c = I2c::new(peripherals.I2C0, Config::default())?; + /// + /// i2c.apply_config(&Config::default().with_frequency(Rate::from_khz(400)))?; + /// # {after_snippet} + /// ``` + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.config.config = *config; + self.driver().setup(config)?; + self.driver().reset_fsm(false); + Ok(()) + } +} + +impl embedded_hal_async::i2c::I2c for I2c<'_, Async> { + async fn transaction( + &mut self, + address: u8, + operations: &mut [EhalOperation<'_>], + ) -> Result<(), Self::Error> { + self.driver() + .transaction_impl_async(address.into(), operations.iter_mut().map(Operation::from)) + .await + .inspect_err(|error| self.internal_recover(error)) + } +} + +#[ram] +fn async_handler(info: &Info, state: &State) { + // Disable all interrupts. The I2C Future will check events based on the + // interrupt status bits. + info.regs().int_ena().write(|w| unsafe { w.bits(0) }); + + state.waker.wake(); +} + +/// Sets the filter with a supplied threshold in clock cycles for which a +/// pulse must be present to pass the filter +fn set_filter( + register_block: &RegisterBlock, + sda_threshold: Option, + scl_threshold: Option, +) { + cfg_if::cfg_if! { + if #[cfg(i2c_master_separate_filter_config_registers)] { + register_block.sda_filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()) + }); + register_block.scl_filter_cfg().modify(|_, w| { + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } else { + register_block.filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()); + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } + } +} + +#[expect(clippy::too_many_arguments)] +#[allow(unused)] +/// Configures the clock and timing parameters for the I2C peripheral. +fn configure_clock( + info: &Info, + sclk_div: u32, + scl_low_period: u32, + scl_high_period: u32, + scl_wait_high_period: u32, + sda_hold_time: u32, + sda_sample_time: u32, + scl_rstart_setup_time: u32, + scl_stop_setup_time: u32, + scl_start_hold_time: u32, + scl_stop_hold_time: u32, + timeout: Option, +) -> Result<(), ConfigError> { + unsafe { + cfg_if::cfg_if! { + if #[cfg(all(soc_has_pcr, soc_has_i2c1))] { + crate::peripherals::PCR::regs().i2c_sclk_conf(info.id as usize).modify(|_, w| { + w.i2c_sclk_sel().clear_bit(); + w.i2c_sclk_div_num().bits((sclk_div - 1) as u8); + w.i2c_sclk_en().set_bit() + }); + } else if #[cfg(soc_has_pcr)] { + crate::peripherals::PCR::regs().i2c_sclk_conf().modify(|_, w| { + w.i2c_sclk_sel().clear_bit(); + w.i2c_sclk_div_num().bits((sclk_div - 1) as u8); + w.i2c_sclk_en().set_bit() + }); + } else if #[cfg(not(any(esp32, esp32s2)))] { // TODO have a better cfg for this + info.regs().clk_conf().modify(|_, w| { + w.sclk_sel().clear_bit(); + w.sclk_div_num().bits((sclk_div - 1) as u8) + }); + } + } + + // scl period + info.regs() + .scl_low_period() + .write(|w| w.scl_low_period().bits(scl_low_period as u16)); + + #[cfg(not(esp32))] + let scl_wait_high_period = scl_wait_high_period + .try_into() + .map_err(|_| ConfigError::FrequencyOutOfRange)?; + + info.regs().scl_high_period().write(|w| { + #[cfg(not(esp32))] // ESP32 does not have a wait_high field + w.scl_wait_high_period().bits(scl_wait_high_period); + w.scl_high_period().bits(scl_high_period as u16) + }); + + // sda sample + info.regs() + .sda_hold() + .write(|w| w.time().bits(sda_hold_time as u16)); + info.regs() + .sda_sample() + .write(|w| w.time().bits(sda_sample_time as u16)); + + // setup + info.regs() + .scl_rstart_setup() + .write(|w| w.time().bits(scl_rstart_setup_time as u16)); + info.regs() + .scl_stop_setup() + .write(|w| w.time().bits(scl_stop_setup_time as u16)); + + // hold + info.regs() + .scl_start_hold() + .write(|w| w.time().bits(scl_start_hold_time as u16)); + info.regs() + .scl_stop_hold() + .write(|w| w.time().bits(scl_stop_hold_time as u16)); + + cfg_if::cfg_if! { + if #[cfg(i2c_master_has_bus_timeout_enable)] { + info.regs().to().write(|w| { + w.time_out_en().bit(timeout.is_some()); + w.time_out_value().bits(timeout.unwrap_or(1) as _) + }); + } else { + info.regs() + .to() + .write(|w| w.time_out().bits(timeout.unwrap_or(1))); + } + } + } + Ok(()) +} + +/// Peripheral data describing a particular I2C instance. +#[doc(hidden)] +#[derive(Debug)] +#[non_exhaustive] +#[allow(private_interfaces, reason = "Unstable details")] +pub struct Info { + /// Numeric instance id (0 = I2C0, 1 = I2C1, ...) + #[cfg(soc_has_i2c1)] + pub id: u8, + + /// Pointer to the register block for this I2C instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, + + /// System peripheral marker. + pub peripheral: crate::system::Peripheral, + + /// Interrupt handler for the asynchronous operations of this I2C instance. + pub async_handler: InterruptHandler, + + /// SCL output signal. + pub scl_output: OutputSignal, + + /// SCL input signal. + pub scl_input: InputSignal, + + /// SDA output signal. + pub sda_output: OutputSignal, + + /// SDA input signal. + pub sda_input: InputSignal, +} + +impl Info { + /// Returns the register block for this I2C instance. + pub fn regs(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } + + /// Listen for the given interrupts + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + let reg_block = self.regs(); + + reg_block.int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + Event::EndDetect => w.end_detect().bit(enable), + Event::TxComplete => w.trans_complete().bit(enable), + #[cfg(i2c_master_has_tx_fifo_watermark)] + Event::TxFifoWatermark => w.txfifo_wm().bit(enable), + }; + } + w + }); + } + + fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + let reg_block = self.regs(); + + let ints = reg_block.int_raw().read(); + + if ints.end_detect().bit_is_set() { + res.insert(Event::EndDetect); + } + if ints.trans_complete().bit_is_set() { + res.insert(Event::TxComplete); + } + #[cfg(i2c_master_has_tx_fifo_watermark)] + if ints.txfifo_wm().bit_is_set() { + res.insert(Event::TxFifoWatermark); + } + + res + } + + fn clear_interrupts(&self, interrupts: EnumSet) { + let reg_block = self.regs(); + + reg_block.int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + Event::EndDetect => w.end_detect().clear_bit_by_one(), + Event::TxComplete => w.trans_complete().clear_bit_by_one(), + #[cfg(i2c_master_has_tx_fifo_watermark)] + Event::TxFifoWatermark => w.txfifo_wm().clear_bit_by_one(), + }; + } + w + }); + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + core::ptr::eq(self.register_block, other.register_block) + } +} + +unsafe impl Sync for Info {} + +#[derive(Clone, Copy)] +enum Deadline { + None, + Fixed(Instant), + PerByte(Duration), +} + +impl Deadline { + fn start(self, data_len: usize) -> Option { + match self { + Deadline::None => None, + Deadline::Fixed(deadline) => Some(deadline), + Deadline::PerByte(duration) => Some(Instant::now() + duration * data_len as u32), + } + } +} + +#[allow(dead_code)] // Some versions don't need `state` +#[derive(Clone, Copy)] +struct Driver<'a> { + info: &'a Info, + state: &'a State, + config: &'a DriverConfig, +} + +impl Driver<'_> { + fn regs(&self) -> &RegisterBlock { + self.info.regs() + } + + fn connect_pin( + pin: crate::gpio::interconnect::OutputSignal<'_>, + input: InputSignal, + output: OutputSignal, + guard: &mut PinGuard, + ) { + // avoid the pin going low during configuration + pin.set_output_high(true); + + pin.apply_output_config( + &OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::Up), + ); + pin.set_output_enable(true); + pin.set_input_enable(true); + + input.connect_to(&pin); + + *guard = interconnect::OutputSignal::connect_with_guard(pin, output); + } + + fn init_master(&self) { + self.regs().ctr().write(|w| { + // Set I2C controller to master mode + w.ms_mode().set_bit(); + // Use open drain output for SDA and SCL + w.sda_force_out().set_bit(); + w.scl_force_out().set_bit(); + // Use Most Significant Bit first for sending and receiving data + w.tx_lsb_first().clear_bit(); + w.rx_lsb_first().clear_bit(); + + #[cfg(i2c_master_has_arbitration_en)] + w.arbitration_en().clear_bit(); + + #[cfg(esp32s2)] + w.ref_always_on().set_bit(); + + // Ensure that clock is enabled + w.clk_en().set_bit() + }); + } + + /// Configures the I2C peripheral with the specified frequency, clocks, and + /// optional timeout. + fn setup(&self, config: &Config) -> Result<(), ConfigError> { + self.init_master(); + + // Configure filter + // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 + set_filter(self.regs(), Some(7), Some(7)); + + // Configure frequency + self.set_frequency(config)?; + + // Configure additional timeouts + #[cfg(i2c_master_has_fsm_timeouts)] + { + self.regs() + .scl_st_time_out() + .write(|w| unsafe { w.scl_st_to().bits(config.scl_st_timeout.value()) }); + self.regs() + .scl_main_st_time_out() + .write(|w| unsafe { w.scl_main_st_to().bits(config.scl_main_st_timeout.value()) }); + } + + self.update_registers(); + + Ok(()) + } + + fn do_fsm_reset(&self) { + cfg_if::cfg_if! { + if #[cfg(i2c_master_has_reliable_fsm_reset)] { + // Device has a working FSM reset mechanism + self.regs().ctr().modify(|_, w| w.fsm_rst().set_bit()); + } else { + // Even though C2 and C3 have a FSM reset bit, esp-idf does not + // define I2C_LL_SUPPORT_HW_FSM_RST for them, so include them in the fallback impl. + + crate::system::PeripheralClockControl::reset(self.info.peripheral); + + // Restore configuration. This operation has succeeded once, so we can + // assume that the config is valid and we can ignore the result. + self.setup(&self.config.config).ok(); + } + } + } + + /// Resets the I2C controller (FIFO + FSM + command list) + // This function implements esp-idf's `s_i2c_hw_fsm_reset` + // https://github.com/espressif/esp-idf/blob/27d68f57e6bdd3842cd263585c2c352698a9eda2/components/esp_driver_i2c/i2c_master.c#L115 + // + // Make sure you don't call this function in the middle of a transaction. If the + // first command in the command list is not a START, the hardware will hang + // with no timeouts. + fn reset_fsm(&self, clear_bus: bool) { + if clear_bus { + self.clear_bus_blocking(true); + } else { + self.do_fsm_reset(); + } + } + + fn bus_busy(&self) -> bool { + self.regs().sr().read().bus_busy().bit_is_set() + } + + fn ensure_idle_blocking(&self) { + if self.bus_busy() { + // If the bus is busy, we need to clear it. + self.clear_bus_blocking(false); + } + } + + async fn ensure_idle(&self) { + if self.bus_busy() { + // If the bus is busy, we need to clear it. + self.clear_bus().await; + } + } + + fn reset_before_transmission(&self) { + // Clear all I2C interrupts + self.clear_all_interrupts(); + + // Reset fifo + self.reset_fifo(); + + // Reset the command list + self.reset_command_list(); + } + + /// Implements s_i2c_master_clear_bus + /// + /// If a transaction ended incorrectly for some reason, the slave may drive + /// SDA indefinitely. This function forces the slave to release the + /// bus by sending 9 clock pulses. + fn clear_bus_blocking(&self, reset_fsm: bool) { + let mut future = ClearBusFuture::new(*self, reset_fsm); + let start = Instant::now(); + while future.poll_completion().is_pending() { + if start.elapsed() > CLEAR_BUS_TIMEOUT_MS { + break; + } + } + } + + async fn clear_bus(&self) { + let clear_bus = ClearBusFuture::new(*self, true); + let start = Instant::now(); + + embassy_futures::select::select(clear_bus, async { + while start.elapsed() < CLEAR_BUS_TIMEOUT_MS { + embassy_futures::yield_now().await; + } + }) + .await; + } + + /// Resets the I2C peripheral's command registers. + fn reset_command_list(&self) { + for cmd in self.regs().comd_iter() { + cmd.reset(); + } + } + + #[cfg(esp32)] + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + fn set_frequency(&self, clock_config: &Config) -> Result<(), ConfigError> { + let timeout = clock_config.timeout; + + let source_clk = crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::apb_clk_frequency(clocks) + }); + + let bus_freq = clock_config.frequency.as_hz(); + + let half_cycle: u32 = source_clk / bus_freq / 2; + let scl_low = half_cycle; + let scl_high = half_cycle; + let sda_hold = half_cycle / 2; + let sda_sample = scl_high / 2; + let setup = half_cycle; + let hold = half_cycle; + + // SCL period. According to the TRM, we should always subtract 1 to SCL low + // period + let scl_low = scl_low - 1; + // Still according to the TRM, if filter is not enbled, we have to subtract 7, + // if SCL filter is enabled, we have to subtract: + // 8 if SCL filter is between 0 and 2 (included) + // 6 + SCL threshold if SCL filter is between 3 and 7 (included) + // to SCL high period + let mut scl_high = scl_high; + // In the "worst" case, we will subtract 13, make sure the result will still be + // correct + + // FIXME since we always set the filter threshold to 7 we don't need conditional + // code here once that changes we need the conditional code here + scl_high -= 7 + 6; + + // if (filter_cfg_en) { + // if (thres <= 2) { + // scl_high -= 8; + // } else { + // assert(hw->scl_filter_cfg.thres <= 7); + // scl_high -= thres + 6; + // } + // } else { + // scl_high -= 7; + //} + + let scl_high_period = scl_high; + let scl_low_period = scl_low; + // sda sample + let sda_hold_time = sda_hold; + let sda_sample_time = sda_sample; + // setup + let scl_rstart_setup_time = setup; + let scl_stop_setup_time = setup; + // hold + let scl_start_hold_time = hold; + let scl_stop_hold_time = hold; + + configure_clock( + self.info, + 0, + scl_low_period, + scl_high_period, + 0, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + timeout.apb_cycles(half_cycle)?, + )?; + + Ok(()) + } + + #[cfg(esp32s2)] + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + fn set_frequency(&self, clock_config: &Config) -> Result<(), ConfigError> { + let timeout = clock_config.timeout; + + // TODO: could be REF_TICK + let source_clk = crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::apb_clk_frequency(clocks) + }); + + let bus_freq = clock_config.frequency.as_hz(); + + let half_cycle: u32 = source_clk / bus_freq / 2; + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + let scl_high = half_cycle / 2 + 2; + let scl_wait_high = half_cycle - scl_high; + let sda_hold = half_cycle / 2; + // scl_wait_high < sda_sample <= scl_high + let sda_sample = half_cycle / 2 - 1; + let setup = half_cycle; + let hold = half_cycle; + + // scl period + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold; + let sda_sample_time = sda_sample; + // setup + let scl_rstart_setup_time = setup; + let scl_stop_setup_time = setup; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold; + + configure_clock( + self.info, + 0, + scl_low_period, + scl_high_period, + scl_wait_high_period, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + timeout.apb_cycles(half_cycle)?, + )?; + + Ok(()) + } + + #[cfg(not(any(esp32, esp32s2)))] + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + fn set_frequency(&self, clock_config: &Config) -> Result<(), ConfigError> { + let timeout = clock_config.timeout; + + let source_clk = crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::xtal_clk_frequency(clocks) + }); + + let bus_freq = clock_config.frequency.as_hz(); + + let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; + let sclk_freq: u32 = source_clk / clkm_div; + let half_cycle: u32 = sclk_freq / bus_freq / 2; + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + // Make 80KHz as a boundary here, because when working at lower frequency, too + // much scl_wait_high will faster the frequency according to some + // hardware behaviors. + let scl_wait_high = if bus_freq >= 80 * 1000 { + half_cycle / 2 - 2 + } else { + half_cycle / 4 + }; + let scl_high = half_cycle - scl_wait_high; + let sda_hold = half_cycle / 4; + let sda_sample = half_cycle / 2; + let setup = half_cycle; + let hold = half_cycle; + + // According to the Technical Reference Manual, the following timings must be + // subtracted by 1. However, according to the practical measurement and + // some hardware behaviour, if wait_high_period and scl_high minus one. + // The SCL frequency would be a little higher than expected. Therefore, the + // solution here is not to minus scl_high as well as scl_wait high, and + // the frequency will be absolutely accurate to all frequency + // to some extent. + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold - 1; + let sda_sample_time = sda_sample - 1; + // setup + let scl_rstart_setup_time = setup - 1; + let scl_stop_setup_time = setup - 1; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold - 1; + + configure_clock( + self.info, + clkm_div, + scl_low_period, + scl_high_period, + scl_wait_high_period, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + timeout.apb_cycles(half_cycle)?, + )?; + + Ok(()) + } + + /// Configures the I2C peripheral for a write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation will end with a STOP condition. + /// - `cmd_iterator` is an iterator over the command registers. + fn setup_write<'a, I>( + &self, + addr: I2cAddress, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut I, + ) -> Result<(), Error> + where + I: Iterator, + { + // If start is true we need to send the address, too, which takes up a data + // byte. + let max_len = if start { + I2C_CHUNK_SIZE + } else { + I2C_CHUNK_SIZE + 1 + }; + if bytes.len() > max_len { + return Err(Error::FifoExceeded); + } + + if start { + add_cmd(cmd_iterator, Command::Start)?; + } + + let write_len = if start { bytes.len() + 1 } else { bytes.len() }; + // don't issue write if there is no data to write + if write_len > 0 { + // ESP32 can't alter the position of END, so we need to split the chunk always into + // 3-command sequences. Chunking makes sure not to place a 1-byte + // command at the end, which would cause an arithmetic underflow. + // The sequences we can generate are: + // - START-WRITE-STOP + // - START-WRITE-END-WRITE-STOP + // - START-WRITE-END-(WRITE-WRITE-END)*-WRITE-STOP sequence. + if cfg!(esp32) && !(start || stop) { + // Chunks that do not have a START or STOP command need to be split into multiple + // commands. + add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: (write_len as u8) - 1, + }, + )?; + add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 1, + }, + )?; + } else { + add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: write_len as u8, + }, + )?; + } + } + + if start { + // Load address and R/W bit into FIFO + match addr { + I2cAddress::SevenBit(addr) => { + write_fifo(self.regs(), (addr << 1) | OperationType::Write as u8); + } + } + } + for b in bytes { + write_fifo(self.regs(), *b); + } + + Ok(()) + } + + /// Configures the I2C peripheral for a read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation will end with a STOP condition. + /// - `will_continue` indicates whether there is another read operation following this one and + /// we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + fn setup_read<'a, I>( + &self, + addr: I2cAddress, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut I, + ) -> Result<(), Error> + where + I: Iterator, + { + if buffer.is_empty() { + return Err(Error::ZeroLengthInvalid); + } + let (max_len, initial_len) = if will_continue { + (I2C_CHUNK_SIZE + 1, buffer.len()) + } else { + (I2C_CHUNK_SIZE, buffer.len() - 1) + }; + if buffer.len() > max_len { + return Err(Error::FifoExceeded); + } + + if start { + add_cmd(cmd_iterator, Command::Start)?; + // WRITE 7-bit address + add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 1, + }, + )?; + } + + if initial_len > 0 { + let extra_commands = if cfg!(esp32) { + match (start, will_continue) { + // No chunking (START-WRITE-READ-STOP) or first chunk (START-WRITE-READ-END) + (true, _) => 0, + // Middle chunk - (READ-READ-READ-END) + (false, true) => 2, + // Last chunk - (READ-READ-STOP-END) + (false, false) => 1 - stop as u8, + } + } else { + 0 + }; + + add_cmd( + cmd_iterator, + Command::Read { + ack_value: Ack::Ack, + length: initial_len as u8 - extra_commands, + }, + )?; + for _ in 0..extra_commands { + add_cmd( + cmd_iterator, + Command::Read { + ack_value: Ack::Ack, + length: 1, + }, + )?; + } + } + + if !will_continue { + // this is the last read so we need to nack the last byte + // READ w/o ACK + add_cmd( + cmd_iterator, + Command::Read { + ack_value: Ack::Nack, + length: 1, + }, + )?; + } + + self.update_registers(); + + if start { + // Load address and R/W bit into FIFO + match addr { + I2cAddress::SevenBit(addr) => { + write_fifo(self.regs(), (addr << 1) | OperationType::Read as u8); + } + } + } + Ok(()) + } + + /// Reads from RX FIFO into the given buffer. + fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { + if self.regs().sr().read().rxfifo_cnt().bits() < buffer.len() as u8 { + return Err(Error::ExecutionIncomplete); + } + + // Read bytes from FIFO + for byte in buffer.iter_mut() { + *byte = read_fifo(self.regs()); + } + + // The RX FIFO should be empty now. If it is not, it means we queued up reading + // more data than we read, which is an error. + debug_assert!(self.regs().sr().read().rxfifo_cnt().bits() == 0); + + Ok(()) + } + + /// Clears all pending interrupts for the I2C peripheral. + fn clear_all_interrupts(&self) { + self.regs() + .int_clr() + .write(|w| unsafe { w.bits(property!("i2c_master.ll_intr_mask")) }); + } + + async fn wait_for_completion(&self, deadline: Option) -> Result<(), Error> { + I2cFuture::new(Event::TxComplete | Event::EndDetect, *self, deadline).await?; + self.check_all_commands_done(deadline).await + } + + /// Waits for the completion of an I2C transaction. + fn wait_for_completion_blocking(&self, deadline: Option) -> Result<(), Error> { + let mut future = + I2cFuture::new_blocking(Event::TxComplete | Event::EndDetect, *self, deadline); + loop { + if let Poll::Ready(result) = future.poll_completion() { + result?; + return self.check_all_commands_done_blocking(deadline); + } + } + } + + fn all_commands_done(&self, deadline: Option) -> Result { + // NOTE: on esp32 executing the end command generates the end_detect interrupt + // but does not seem to clear the done bit! So we don't check the done + // status of an end command + let now = if deadline.is_some() { + Instant::now() + } else { + Instant::EPOCH + }; + + self.check_errors()?; + + for cmd_reg in self.regs().comd_iter() { + let cmd = cmd_reg.read(); + + // if there is a valid command which is not END, check if it's marked as done + if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() { + // Let's retry + if let Some(deadline) = deadline + && now > deadline + { + return Err(Error::ExecutionIncomplete); + } + + return Ok(false); + } + + // once we hit END or STOP we can break the loop + if cmd.opcode().is_end() { + break; + } + if cmd.opcode().is_stop() { + #[cfg(esp32)] + // wait for STOP - apparently on ESP32 we otherwise miss the ACK error for an + // empty write + if self.regs().sr().read().scl_state_last() == 6 { + self.check_errors()?; + } else { + continue; + } + break; + } + } + Ok(true) + } + + /// Checks whether all I2C commands have completed execution. + fn check_all_commands_done_blocking(&self, deadline: Option) -> Result<(), Error> { + // loop until commands are actually done + while !self.all_commands_done(deadline)? {} + self.check_errors()?; + + Ok(()) + } + + /// Checks whether all I2C commands have completed execution. + async fn check_all_commands_done(&self, deadline: Option) -> Result<(), Error> { + // loop until commands are actually done + while !self.all_commands_done(deadline)? { + embassy_futures::yield_now().await; + } + self.check_errors()?; + + Ok(()) + } + + /// Checks for I2C transmission errors and handles them. + /// + /// This function inspects specific I2C-related interrupts to detect errors + /// during communication, such as timeouts, failed acknowledgments, or + /// arbitration loss. If an error is detected, the function handles it + /// by resetting the I2C peripheral to clear the error condition and then + /// returns an appropriate error. + fn check_errors(&self) -> Result<(), Error> { + let r = self.regs().int_raw().read(); + + // Handle error cases + if r.nack().bit_is_set() { + return Err(Error::AcknowledgeCheckFailed(estimate_ack_failed_reason( + self.regs(), + ))); + } + if r.arbitration_lost().bit_is_set() { + return Err(Error::ArbitrationLost); + } + + #[cfg(not(esp32))] + if r.trans_complete().bit_is_set() && self.regs().sr().read().resp_rec().bit_is_clear() { + return Err(Error::AcknowledgeCheckFailed( + AcknowledgeCheckFailedReason::Data, + )); + } + + #[cfg(i2c_master_has_fsm_timeouts)] + { + if r.scl_st_to().bit_is_set() { + return Err(Error::Timeout); + } + if r.scl_main_st_to().bit_is_set() { + return Err(Error::Timeout); + } + } + if r.time_out().bit_is_set() { + return Err(Error::Timeout); + } + + Ok(()) + } + + /// Updates the configuration of the I2C peripheral. + /// + /// This function ensures that the configuration values, such as clock + /// settings, SDA/SCL filtering, timeouts, and other operational + /// parameters, which are configured in other functions, are properly + /// propagated to the I2C hardware. This step is necessary to synchronize + /// the software-configured settings with the peripheral's internal + /// registers, ensuring that the hardware behaves according to the + /// current configuration. + fn update_registers(&self) { + // Ensure that the configuration of the peripheral is correctly propagated + // (only necessary for C2, C3, C6, H2 and S3 variant) + #[cfg(i2c_master_has_conf_update)] + self.regs().ctr().modify(|_, w| w.conf_upgate().set_bit()); + } + + /// Starts an I2C transmission. + fn start_transmission(&self) { + // Start transmission + self.regs().ctr().modify(|_, w| w.trans_start().set_bit()); + } + + /// Resets the transmit and receive FIFO buffers + #[cfg(not(esp32))] + fn reset_fifo(&self) { + // First, reset the fifo buffers + self.regs().fifo_conf().write(|w| unsafe { + w.tx_fifo_rst().set_bit(); + w.rx_fifo_rst().set_bit(); + w.nonfifo_en().clear_bit(); + w.fifo_prt_en().set_bit(); + w.rxfifo_wm_thrhd().bits(1); + w.txfifo_wm_thrhd().bits(8) + }); + + self.regs().fifo_conf().modify(|_, w| { + w.tx_fifo_rst().clear_bit(); + w.rx_fifo_rst().clear_bit() + }); + + self.regs().int_clr().write(|w| { + w.rxfifo_wm().clear_bit_by_one(); + w.txfifo_wm().clear_bit_by_one() + }); + + self.update_registers(); + } + + /// Resets the transmit and receive FIFO buffers + #[cfg(esp32)] + fn reset_fifo(&self) { + // First, reset the fifo buffers + self.regs().fifo_conf().write(|w| unsafe { + w.tx_fifo_rst().set_bit(); + w.rx_fifo_rst().set_bit(); + w.nonfifo_en().clear_bit(); + w.nonfifo_rx_thres().bits(1); + w.nonfifo_tx_thres().bits(32) + }); + + self.regs().fifo_conf().modify(|_, w| { + w.tx_fifo_rst().clear_bit(); + w.rx_fifo_rst().clear_bit() + }); + + self.regs() + .int_clr() + .write(|w| w.rxfifo_full().clear_bit_by_one()); + } + + fn start_write_operation( + &self, + address: I2cAddress, + buffer: &[u8], + start: bool, + stop: bool, + deadline: Deadline, + ) -> Result, Error> { + let cmd_iterator = &mut self.regs().comd_iter(); + + self.setup_write(address, buffer, start, stop, cmd_iterator)?; + + if stop { + add_cmd(cmd_iterator, Command::Stop)?; + } + if !(start && stop) { + // Multi-chunk write, terminate with END. ESP32 TRM suggests a write should work with + // only a STOP at the end, but STOP does not seem to generate a TX_COMPLETE interrupt + // without END. + add_cmd(cmd_iterator, Command::End)?; + } + + self.start_transmission(); + + Ok(deadline.start(buffer.len() + address.bytes())) + } + + /// Executes an I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation should end with a STOP condition. + /// - `will_continue` indicates whether there is another read operation following this one and + /// we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + fn start_read_operation( + &self, + address: I2cAddress, + buffer: &mut [u8], + start: bool, + will_continue: bool, + stop: bool, + deadline: Deadline, + ) -> Result, Error> { + // We don't support single I2C reads larger than the FIFO. This should be + // enforced by `VariableChunkIterMut` used in `Driver::read` and + // `Driver::read_async`. + debug_assert!(buffer.len() <= I2C_FIFO_SIZE); + + let cmd_iterator = &mut self.regs().comd_iter(); + + self.setup_read(address, buffer, start, stop, will_continue, cmd_iterator)?; + + if stop { + add_cmd(cmd_iterator, Command::Stop)?; + } + if !(start && stop) { + // Multi-chunk read, terminate with END. On ESP32, assume same limitation as writes. + add_cmd(cmd_iterator, Command::End)?; + } + + self.start_transmission(); + + Ok(deadline.start(buffer.len() + address.bytes())) + } + + /// Executes an I2C write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation should end with a STOP condition. + /// - `cmd_iterator` is an iterator over the command registers. + fn write_operation_blocking( + &self, + address: I2cAddress, + bytes: &[u8], + start: bool, + stop: bool, + deadline: Deadline, + ) -> Result<(), Error> { + address.validate()?; + + self.reset_before_transmission(); + + // Short circuit for zero length writes without start or end as that would be an + // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 + if bytes.is_empty() && !start && !stop { + return Ok(()); + } + + let deadline = self.start_write_operation(address, bytes, start, stop, deadline)?; + self.wait_for_completion_blocking(deadline)?; + + Ok(()) + } + + /// Executes an I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation should end with a STOP condition. + /// - `will_continue` indicates whether there is another read operation following this one and + /// we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + fn read_operation_blocking( + &self, + address: I2cAddress, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + deadline: Deadline, + ) -> Result<(), Error> { + address.validate()?; + self.reset_before_transmission(); + + // Short circuit for zero length reads as that would be an invalid operation + // read lengths in the TRM (at least for ESP32-S3) are 1-255 + if buffer.is_empty() { + return Ok(()); + } + + let deadline = + self.start_read_operation(address, buffer, start, will_continue, stop, deadline)?; + self.wait_for_completion_blocking(deadline)?; + self.read_all_from_fifo(buffer)?; + + Ok(()) + } + + /// Executes an async I2C write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation should end with a STOP condition. + /// - `cmd_iterator` is an iterator over the command registers. + async fn write_operation( + &self, + address: I2cAddress, + bytes: &[u8], + start: bool, + stop: bool, + deadline: Deadline, + ) -> Result<(), Error> { + address.validate()?; + self.reset_before_transmission(); + + // Short circuit for zero length writes without start or end as that would be an + // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 + if bytes.is_empty() && !start && !stop { + return Ok(()); + } + + let deadline = self.start_write_operation(address, bytes, start, stop, deadline)?; + self.wait_for_completion(deadline).await?; + + Ok(()) + } + + /// Executes an async I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START condition and sending the + /// address. + /// - `stop` indicates whether the operation should end with a STOP condition. + /// - `will_continue` indicates whether there is another read operation following this one and + /// we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + async fn read_operation( + &self, + address: I2cAddress, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + deadline: Deadline, + ) -> Result<(), Error> { + address.validate()?; + self.reset_before_transmission(); + + // Short circuit for zero length reads as that would be an invalid operation + // read lengths in the TRM (at least for ESP32-S3) are 1-255 + if buffer.is_empty() { + return Ok(()); + } + + let deadline = + self.start_read_operation(address, buffer, start, will_continue, stop, deadline)?; + self.wait_for_completion(deadline).await?; + self.read_all_from_fifo(buffer)?; + + Ok(()) + } + + fn read_blocking( + &self, + address: I2cAddress, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + deadline: Deadline, + ) -> Result<(), Error> { + let chunk_count = VariableChunkIterMut::new(buffer).count(); + for (idx, chunk) in VariableChunkIterMut::new(buffer).enumerate() { + self.read_operation_blocking( + address, + chunk, + start && idx == 0, + stop && idx == chunk_count - 1, + will_continue || idx < chunk_count - 1, + deadline, + )?; + } + + Ok(()) + } + + fn write_blocking( + &self, + address: I2cAddress, + buffer: &[u8], + start: bool, + stop: bool, + deadline: Deadline, + ) -> Result<(), Error> { + if buffer.is_empty() { + return self.write_operation_blocking(address, &[], start, stop, deadline); + } + + let chunk_count = VariableChunkIter::new(buffer).count(); + for (idx, chunk) in VariableChunkIter::new(buffer).enumerate() { + self.write_operation_blocking( + address, + chunk, + start && idx == 0, + stop && idx == chunk_count - 1, + deadline, + )?; + } + + Ok(()) + } + + async fn read( + &self, + address: I2cAddress, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + deadline: Deadline, + ) -> Result<(), Error> { + let chunk_count = VariableChunkIterMut::new(buffer).count(); + for (idx, chunk) in VariableChunkIterMut::new(buffer).enumerate() { + self.read_operation( + address, + chunk, + start && idx == 0, + stop && idx == chunk_count - 1, + will_continue || idx < chunk_count - 1, + deadline, + ) + .await?; + } + + Ok(()) + } + + async fn write( + &self, + address: I2cAddress, + buffer: &[u8], + start: bool, + stop: bool, + deadline: Deadline, + ) -> Result<(), Error> { + if buffer.is_empty() { + return self + .write_operation(address, &[], start, stop, deadline) + .await; + } + + let chunk_count = VariableChunkIter::new(buffer).count(); + for (idx, chunk) in VariableChunkIter::new(buffer).enumerate() { + self.write_operation( + address, + chunk, + start && idx == 0, + stop && idx == chunk_count - 1, + deadline, + ) + .await?; + } + + Ok(()) + } + + fn transaction_impl<'a>( + &self, + address: I2cAddress, + operations: impl Iterator>, + ) -> Result<(), Error> { + address.validate()?; + self.ensure_idle_blocking(); + + let mut deadline = Deadline::None; + + if let SoftwareTimeout::Transaction(timeout) = self.config.config.software_timeout { + deadline = Deadline::Fixed(Instant::now() + timeout); + } + + let mut last_op: Option = None; + // filter out 0 length read operations + let mut op_iter = operations + .filter(|op| op.is_write() || !op.is_empty()) + .peekable(); + + while let Some(op) = op_iter.next() { + let next_op = op_iter.peek().map(|v| v.kind()); + let kind = op.kind(); + match op { + Operation::Write(buffer) => { + // execute a write operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout { + deadline = Deadline::PerByte(timeout); + } + self.write_blocking( + address, + buffer, + !matches!(last_op, Some(OpKind::Write)), + next_op.is_none(), + deadline, + )?; + } + Operation::Read(buffer) => { + if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout { + deadline = Deadline::PerByte(timeout); + } + // execute a read operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + // - will_continue is true if there is another read operation next + self.read_blocking( + address, + buffer, + !matches!(last_op, Some(OpKind::Read)), + next_op.is_none(), + matches!(next_op, Some(OpKind::Read)), + deadline, + )?; + } + } + + last_op = Some(kind); + } + + Ok(()) + } + + async fn transaction_impl_async<'a>( + &self, + address: I2cAddress, + operations: impl Iterator>, + ) -> Result<(), Error> { + address.validate()?; + self.ensure_idle().await; + + let mut deadline = Deadline::None; + + if let SoftwareTimeout::Transaction(timeout) = self.config.config.software_timeout { + deadline = Deadline::Fixed(Instant::now() + timeout); + } + + let mut last_op: Option = None; + // filter out 0 length read operations + let mut op_iter = operations + .filter(|op| op.is_write() || !op.is_empty()) + .peekable(); + + while let Some(op) = op_iter.next() { + let next_op = op_iter.peek().map(|v| v.kind()); + let kind = op.kind(); + match op { + Operation::Write(buffer) => { + if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout { + deadline = Deadline::PerByte(timeout); + } + // execute a write operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + self.write( + address, + buffer, + !matches!(last_op, Some(OpKind::Write)), + next_op.is_none(), + deadline, + ) + .await?; + } + Operation::Read(buffer) => { + if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout { + deadline = Deadline::PerByte(timeout); + } + // execute a read operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + // - will_continue is true if there is another read operation next + self.read( + address, + buffer, + !matches!(last_op, Some(OpKind::Read)), + next_op.is_none(), + matches!(next_op, Some(OpKind::Read)), + deadline, + ) + .await?; + } + } + + last_op = Some(kind); + } + + Ok(()) + } +} + +/// Chunks a slice by I2C_CHUNK_SIZE in a way to avoid the last chunk being +/// sized smaller than 2 +struct VariableChunkIterMut<'a, T> { + buffer: &'a mut [T], +} + +impl<'a, T> VariableChunkIterMut<'a, T> { + fn new(buffer: &'a mut [T]) -> Self { + Self { buffer } + } +} + +impl<'a, T> Iterator for VariableChunkIterMut<'a, T> { + type Item = &'a mut [T]; + + fn next(&mut self) -> Option { + if self.buffer.is_empty() { + return None; + } + + let s = calculate_chunk_size(self.buffer.len()); + let (chunk, remaining) = core::mem::take(&mut self.buffer).split_at_mut(s); + self.buffer = remaining; + Some(chunk) + } +} + +/// Chunks a slice by I2C_CHUNK_SIZE in a way to avoid the last chunk being +/// sized smaller than 2 +struct VariableChunkIter<'a, T> { + buffer: &'a [T], +} + +impl<'a, T> VariableChunkIter<'a, T> { + fn new(buffer: &'a [T]) -> Self { + Self { buffer } + } +} + +impl<'a, T> Iterator for VariableChunkIter<'a, T> { + type Item = &'a [T]; + + fn next(&mut self) -> Option { + if self.buffer.is_empty() { + return None; + } + + let s = calculate_chunk_size(self.buffer.len()); + let (chunk, remaining) = core::mem::take(&mut self.buffer).split_at(s); + self.buffer = remaining; + Some(chunk) + } +} + +fn calculate_chunk_size(remaining: usize) -> usize { + if remaining <= I2C_CHUNK_SIZE { + remaining + } else if remaining > I2C_CHUNK_SIZE + 2 { + I2C_CHUNK_SIZE + } else { + I2C_CHUNK_SIZE - 2 + } +} + +#[cfg(i2c_master_has_hw_bus_clear)] +mod bus_clear { + use esp_rom_sys::rom::ets_delay_us; + + use super::*; + + pub struct ClearBusFuture<'a> { + driver: Driver<'a>, + } + + impl<'a> ClearBusFuture<'a> { + // Number of SCL pulses to clear the bus + const BUS_CLEAR_BITS: u8 = 9; + const DELAY_US: u32 = 5; // 5us -> 100kHz + + pub fn new(driver: Driver<'a>, reset_fsm: bool) -> Self { + // If we have a HW implementation, reset FSM to make sure it's not trying to transmit + // while we clear the bus. + if reset_fsm { + // Resetting the FSM may still generate a short SCL pulse, but I don't know how to + // work around it - just waiting doesn't solve anything if the hardware is running. + driver.do_fsm_reset(); + } + + let mut this = Self { driver }; + + // Prevent SCL from going low immediately after FSM reset/previous operation has set + // it high + ets_delay_us(Self::DELAY_US); + + this.configure(Self::BUS_CLEAR_BITS); + this + } + + fn configure(&mut self, bits: u8) { + self.driver.regs().scl_sp_conf().modify(|_, w| { + unsafe { w.scl_rst_slv_num().bits(bits) }; + w.scl_rst_slv_en().bit(bits > 0) + }); + self.driver.update_registers(); + } + + fn is_done(&self) -> bool { + self.driver + .regs() + .scl_sp_conf() + .read() + .scl_rst_slv_en() + .bit_is_clear() + } + + pub fn poll_completion(&mut self) -> Poll<()> { + if self.is_done() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + + impl Drop for ClearBusFuture<'_> { + fn drop(&mut self) { + use crate::gpio::AnyPin; + if !self.is_done() { + self.configure(0); + } + + // Generate a stop condition + let sda = self + .driver + .config + .sda_pin + .pin_number() + .map(|n| unsafe { AnyPin::steal(n) }); + let scl = self + .driver + .config + .scl_pin + .pin_number() + .map(|n| unsafe { AnyPin::steal(n) }); + + if let (Some(sda), Some(scl)) = (sda, scl) { + // Prevent short SCL pulse right after HW clearing completes + ets_delay_us(Self::DELAY_US); + + sda.set_output_high(true); + scl.set_output_high(false); + + self.driver.info.scl_output.disconnect_from(&scl); + self.driver.info.sda_output.disconnect_from(&sda); + + // Set SDA low - whatever state it was in, we need a low -> high transition. + sda.set_output_high(false); + ets_delay_us(Self::DELAY_US); + + // Set SCL high to prepare for STOP condition + scl.set_output_high(true); + ets_delay_us(Self::DELAY_US); + + // STOP + sda.set_output_high(true); + ets_delay_us(Self::DELAY_US); + + self.driver.info.sda_output.connect_to(&sda); + self.driver.info.scl_output.connect_to(&scl); + } + + // We don't care about errors during bus clearing + self.driver.clear_all_interrupts(); + } + } +} + +#[cfg(not(i2c_master_has_hw_bus_clear))] +mod bus_clear { + use super::*; + use crate::gpio::AnyPin; + + /// State of the bus clearing operation. + /// + /// Pins are changed on the start of the state, and a wait is scheduled + /// for the end of the state. At the end of the wait, the state is + /// updated to the next state. + enum State { + Idle, + SendStop, + + // Number of SCL pulses left to send, and the last SCL level. + // + // Our job is to send 9 high->low SCL transitions, followed by a STOP condition. + SendClock(u8, bool), + } + + pub struct ClearBusFuture<'a> { + driver: Driver<'a>, + wait: Instant, + state: State, + reset_fsm: bool, + pins: Option<(AnyPin<'static>, AnyPin<'static>)>, + } + + impl<'a> ClearBusFuture<'a> { + // Number of SCL pulses to clear the bus (max 8 data bits sent by the device, + NACK) + const BUS_CLEAR_BITS: u8 = 9; + // use standard 100kHz data rate + const SCL_DELAY: Duration = Duration::from_micros(5); + + pub fn new(driver: Driver<'a>, reset_fsm: bool) -> Self { + let sda = driver + .config + .sda_pin + .pin_number() + .map(|n| unsafe { AnyPin::steal(n) }); + let scl = driver + .config + .scl_pin + .pin_number() + .map(|n| unsafe { AnyPin::steal(n) }); + + let (Some(sda), Some(scl)) = (sda, scl) else { + // If we don't have the pins, we can't clear the bus. + if reset_fsm { + driver.do_fsm_reset(); + } + return Self { + driver, + wait: Instant::now(), + state: State::Idle, + reset_fsm: false, + pins: None, + }; + }; + + sda.set_output_high(true); + scl.set_output_high(false); + + driver.info.scl_output.disconnect_from(&scl); + driver.info.sda_output.disconnect_from(&sda); + + // Starting from (9, false), becase: + // - we start with SCL low + // - a complete SCL cycle consists of a high period and a low period + // - we decrement the remaining counter at the beginning of a cycle, which gives us 9 + // complete SCL cycles. + let state = State::SendClock(Self::BUS_CLEAR_BITS, false); + + Self { + driver, + wait: Instant::now() + Self::SCL_DELAY, + state, + reset_fsm, + pins: Some((sda, scl)), + } + } + } + + impl ClearBusFuture<'_> { + pub fn poll_completion(&mut self) -> Poll<()> { + let now = Instant::now(); + + match self.state { + State::Idle => { + if let Some((sda, _scl)) = self.pins.as_ref() { + // Pins are disconnected from the peripheral, we can't use `bus_busy`. + if !sda.is_input_high() { + return Poll::Pending; + } + } + return Poll::Ready(()); + } + _ if now < self.wait => { + // Still waiting for the end of the SCL pulse + return Poll::Pending; + } + State::SendStop => { + if let Some((sda, _scl)) = self.pins.as_ref() { + sda.set_output_high(true); // STOP, SDA low -> high while SCL is HIGH + } + self.state = State::Idle; + return Poll::Pending; + } + State::SendClock(0, false) => { + if let Some((sda, scl)) = self.pins.as_ref() { + // Set up for STOP condition + sda.set_output_high(false); + scl.set_output_high(true); + } + self.state = State::SendStop; + } + State::SendClock(n, false) => { + if let Some((sda, scl)) = self.pins.as_ref() { + scl.set_output_high(true); + if sda.is_input_high() { + sda.set_output_high(false); + // The device has released SDA, we can move on to generating a STOP + // condition + self.wait = Instant::now() + Self::SCL_DELAY; + self.state = State::SendStop; + return Poll::Pending; + } + } + self.state = State::SendClock(n - 1, true); + } + State::SendClock(n, true) => { + if let Some((_sda, scl)) = self.pins.as_ref() { + scl.set_output_high(false); + } + self.state = State::SendClock(n, false); + } + } + self.wait = Instant::now() + Self::SCL_DELAY; + + Poll::Pending + } + } + + impl Drop for ClearBusFuture<'_> { + fn drop(&mut self) { + if let Some((sda, scl)) = self.pins.take() { + // Make sure _we_ release the bus. + scl.set_output_high(true); + sda.set_output_high(true); + + // If we don't have a HW implementation, reset the peripheral after clearing the + // bus, but before we reconnect the pins in Drop. This should prevent glitches. + if self.reset_fsm { + self.driver.do_fsm_reset(); + } + + self.driver.info.sda_output.connect_to(&sda); + self.driver.info.scl_output.connect_to(&scl); + + // We don't care about errors during bus clearing. There shouldn't be any, + // anyway. + self.driver.clear_all_interrupts(); + } + } + } +} + +use bus_clear::ClearBusFuture; + +impl Future for ClearBusFuture<'_> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let pending = self.poll_completion(); + if pending.is_pending() { + ctx.waker().wake_by_ref(); + } + pending + } +} + +/// Peripheral state for an I2C instance. +#[doc(hidden)] +#[non_exhaustive] +pub struct State { + /// Waker for the asynchronous operations. + pub waker: AtomicWaker, +} + +/// A peripheral singleton compatible with the I2C master driver. +pub trait Instance: crate::private::Sealed + any::Degrade { + #[doc(hidden)] + /// Returns the peripheral data and state describing this instance. + fn parts(&self) -> (&Info, &State); + + /// Returns the peripheral data describing this instance. + #[doc(hidden)] + #[inline(always)] + fn info(&self) -> &Info { + self.parts().0 + } + + /// Returns the peripheral state for this instance. + #[doc(hidden)] + #[inline(always)] + fn state(&self) -> &State { + self.parts().1 + } +} + +/// Adds a command to the I2C command sequence. +/// +/// Make sure the first command after a FSM reset is a START, otherwise +/// the hardware will hang with no timeouts. +fn add_cmd<'a, I>(cmd_iterator: &mut I, command: Command) -> Result<(), Error> +where + I: Iterator, +{ + let cmd = cmd_iterator.next().ok_or(Error::CommandNumberExceeded)?; + + cmd.write(|w| match command { + Command::Start => w.opcode().rstart(), + Command::Stop => w.opcode().stop(), + Command::End => w.opcode().end(), + Command::Write { + ack_exp, + ack_check_en, + length, + } => unsafe { + w.opcode().write(); + w.ack_exp().bit(ack_exp == Ack::Nack); + w.ack_check_en().bit(ack_check_en); + w.byte_num().bits(length); + w + }, + Command::Read { ack_value, length } => unsafe { + w.opcode().read(); + w.ack_value().bit(ack_value == Ack::Nack); + w.byte_num().bits(length); + w + }, + }); + + Ok(()) +} + +fn read_fifo(register_block: &RegisterBlock) -> u8 { + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + // Apparently the ESO can read just fine using DPORT, + // so use this workaround on S2 only. + let peri_offset = register_block as *const _ as usize - crate::peripherals::I2C0::ptr() as usize; + let fifo_ptr = (property!("i2c_master.i2c0_data_register_ahb_address") + peri_offset) as *mut u32; + unsafe { (fifo_ptr.read_volatile() & 0xff) as u8 } + } else { + register_block.data().read().fifo_rdata().bits() + } + } +} + +fn write_fifo(register_block: &RegisterBlock, data: u8) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + let peri_offset = register_block as *const _ as usize - crate::peripherals::I2C0::ptr() as usize; + let fifo_ptr = (property!("i2c_master.i2c0_data_register_ahb_address") + peri_offset) as *mut u32; + unsafe { + fifo_ptr.write_volatile(data as u32); + } + } else { + register_block + .data() + .write(|w| unsafe { w.fifo_rdata().bits(data) }); + } + } +} + +// Estimate the reason for an acknowledge check failure on a best effort basis. +// When in doubt it's better to return `Unknown` than to return a wrong reason. +fn estimate_ack_failed_reason(_register_block: &RegisterBlock) -> AcknowledgeCheckFailedReason { + cfg_if::cfg_if! { + if #[cfg(i2c_master_can_estimate_nack_reason)] { + // this is based on observations rather than documented behavior + if _register_block.fifo_st().read().txfifo_raddr().bits() <= 1 { + AcknowledgeCheckFailedReason::Address + } else { + AcknowledgeCheckFailedReason::Data + } + } else { + AcknowledgeCheckFailedReason::Unknown + } + } +} + +for_each_i2c_master!( + ($id:literal, $inst:ident, $peri:ident, $scl:ident, $sda:ident) => { + impl Instance for crate::peripherals::$inst<'_> { + fn parts(&self) -> (&Info, &State) { + #[handler] + #[ram] + pub(super) fn irq_handler() { + async_handler(&PERIPHERAL, &STATE); + } + + static STATE: State = State { + waker: AtomicWaker::new(), + }; + + static PERIPHERAL: Info = Info { + #[cfg(soc_has_i2c1)] + id: $id, + register_block: crate::peripherals::$inst::ptr(), + peripheral: crate::system::Peripheral::$peri, + async_handler: irq_handler, + scl_output: OutputSignal::$scl, + scl_input: InputSignal::$scl, + sda_output: OutputSignal::$sda, + sda_input: InputSignal::$sda, + }; + (&PERIPHERAL, &STATE) + } + } + }; +); + +crate::any_peripheral! { + /// Any I2C peripheral. + pub peripheral AnyI2c<'d> { + #[cfg(i2c_master_i2c0)] + I2c0(crate::peripherals::I2C0<'d>), + #[cfg(i2c_master_i2c1)] + I2c1(crate::peripherals::I2C1<'d>), + } +} + +impl Instance for AnyI2c<'_> { + fn parts(&self) -> (&Info, &State) { + any::delegate!(self, i2c => { i2c.parts() }) + } +} + +impl AnyI2c<'_> { + fn bind_peri_interrupt(&self, handler: InterruptHandler) { + any::delegate!(self, i2c => { i2c.bind_peri_interrupt(handler) }) + } + + fn disable_peri_interrupt_on_all_cores(&self) { + any::delegate!(self, i2c => { i2c.disable_peri_interrupt_on_all_cores() }) + } + + fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.disable_peri_interrupt_on_all_cores(); + + self.info().enable_listen(EnumSet::all(), false); + self.info().clear_interrupts(EnumSet::all()); + + self.bind_peri_interrupt(handler); + } +} diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs new file mode 100644 index 00000000000..48aa5eba85a --- /dev/null +++ b/esp-hal/src/i2c/mod.rs @@ -0,0 +1,23 @@ +//! # Inter-Integrated Circuit (I2C) +//! +//! I2C is a serial, synchronous, multi-device, half-duplex communication +//! protocol that allows co-existence of multiple masters and slaves on the +//! same bus. I2C uses two bidirectional open-drain lines: serial data line +//! (SDA) and serial clock line (SCL), pulled up by resistors. +//! +//! For more information, see +#![doc = crate::trm_markdown_link!("i2c")] + +#[cfg(i2c_master_driver_supported)] +pub mod master; + +#[cfg(soc_has_lp_i2c0)] +crate::unstable_module! { + pub mod lp_i2c; +} + +#[cfg(esp32s3)] // Only support ESP32-S3 for now. +#[cfg(soc_has_rtc_i2c)] +crate::unstable_module! { + pub mod rtc; +} diff --git a/esp-hal/src/i2c/rtc.rs b/esp-hal/src/i2c/rtc.rs new file mode 100644 index 00000000000..e82c324240d --- /dev/null +++ b/esp-hal/src/i2c/rtc.rs @@ -0,0 +1,791 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # RTC I2C driver +//! +//! ## Overview +//! +//! This is the host driver for the RTC_I2C peripheral which is primarily for the ULP. +//! +//! The RTC I2C peripheral always expects a slave sub-register address to be provided when reading +//! or writing. +//! This could make the RTC I2C peripheral incompatible with certain I2C devices or sensors which +//! do not need any sub-register to be programmed. +//! It also means a `embedded_hal::i2c::I2c` implementation cannot be provided. +//! +//! ## Configuration +//! +//! The driver can be configured using the [`Config`] struct. To create a +//! configuration, you can use the [`Config::default()`] method, and then modify +//! the individual settings as needed, by calling `with_*` methods on the +//! [`Config`] struct. +//! +//! ```rust, no_run +//! # {before_snippet} +//! use core::time::Duration; +//! +//! use esp_hal::i2c::rtc::Config; +//! +//! let config = Config::default().with_timeout(Duration::from_micros(100)); +//! # {after_snippet} +//! ``` +//! +//! You will then need to pass the configuration to [`I2c::new`], and you can +//! also change the configuration later by calling [`I2c::apply_config`]. +//! +//! You will also need to specify the SDA and SCL pins when you create the +//! driver instance. +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::i2c::rtc::I2c; +//! # use core::time::Duration; +//! # use esp_hal::i2c::rtc::Config; +//! # +//! # let config = Config::default(); +//! # +//! // You need to configure the driver during initialization: +//! let mut i2c = I2c::new( +//! peripherals.RTC_I2C, +//! config, +//! peripherals.GPIO3, +//! peripherals.GPIO2, +//! )?; +//! +//! // You can change the configuration later: +//! let new_config = config.with_timeout(Duration::from_micros(150)); +//! i2c.apply_config(&new_config)?; +//! # {after_snippet} +//! ``` +//! +//! ## Usage +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::i2c::rtc::{I2c, Config}; +//! # let config = Config::default(); +//! # let mut i2c = I2c::new(peripherals.RTC_I2C, config, peripherals.GPIO3, peripherals.GPIO2)?; +//! # +//! // `u8` is automatically converted to `I2cAddress::SevenBit`. The device +//! // address does not contain the `R/W` bit! +//! const DEVICE_ADDR: u8 = 0x77; +//! const DEVICE_REG: u8 = 0x01; +//! let write_buffer = [0xAA]; +//! let mut read_buffer = [0u8; 22]; +//! +//! i2c.write(DEVICE_ADDR, DEVICE_REG, &write_buffer)?; +//! i2c.read(DEVICE_ADDR, DEVICE_REG, &mut read_buffer)?; +//! # {after_snippet} +//! ``` + +use core::time::Duration; + +use crate::{ + gpio::{InputPin, OutputPin, RtcFunction, RtcPin}, + peripherals::{GPIO, RTC_I2C, RTC_IO, SENS}, +}; + +const RC_FAST_CLK: u32 = property!("soc.rc_fast_clk_default"); + +/// Trait representing the RTC_I2C SDA pin. +pub trait Sda: RtcPin + OutputPin + InputPin { + #[doc(hidden)] + fn selector(&self) -> u8; +} + +/// Trait representing the RTC_I2C SCL pin. +pub trait Scl: RtcPin + OutputPin + InputPin { + #[doc(hidden)] + fn selector(&self) -> u8; +} + +for_each_lp_function! { + (($_func:ident, SAR_I2C_SCL_n, $n:literal), $gpio:ident) => { + impl Scl for crate::peripherals::$gpio<'_> { + fn selector(&self) -> u8 { + $n + } + } + }; + (($_func:ident, SAR_I2C_SDA_n, $n:literal), $gpio:ident) => { + impl Sda for crate::peripherals::$gpio<'_> { + fn selector(&self) -> u8 { + $n + } + } + }; +} + +#[procmacros::doc_replace] +/// I2C (RTC) driver +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::i2c::rtc::{Config, I2c}; +/// # const DEVICE_ADDR: u8 = 0x77; +/// let mut i2c = I2c::new( +/// peripherals.RTC_I2C, +/// Config::default(), +/// peripherals.GPIO1, +/// peripherals.GPIO2, +/// )?; +/// +/// let mut data = [0u8; 22]; +/// i2c.read(DEVICE_ADDR, 0xaa, &mut data)?; +/// # {after_snippet} +/// ``` +pub struct I2c<'d> { + i2c: RTC_I2C<'d>, + sda: u8, + scl: u8, +} + +impl<'d> I2c<'d> { + #[procmacros::doc_replace] + /// Create a new I2C (RTC) instance. + /// + /// ## Errors + /// + /// A [`crate::i2c::rtc::ConfigError`] variant will be returned if bus frequency or timeout + /// passed in config is invalid. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::rtc::{Config, I2c}; + /// let i2c = I2c::new( + /// peripherals.RTC_I2C, + /// Config::default(), + /// peripherals.GPIO1, + /// peripherals.GPIO2, + /// )?; + /// # {after_snippet} + /// ``` + pub fn new( + i2c: RTC_I2C<'d>, + config: Config, + sda: impl Sda + 'd, + scl: impl Scl + 'd, + ) -> Result { + // Clear any stale config registers + i2c.register_block().ctrl().reset(); + SENS::regs().sar_i2c_ctrl().reset(); + + fn bind_pin(pin: &impl RtcPin) { + GPIO::regs() + .pin(pin.number() as usize) + .modify(|_, w| w.pad_driver().bit(true)); + RTC_IO::regs() + .touch_pad(pin.number() as usize) + .modify(|_, w| w.fun_ie().bit(true).rue().bit(true).rde().bit(false)); + RTC_IO::regs() + .rtc_gpio_enable_w1ts() + .write(|w| unsafe { w.rtc_gpio_enable_w1ts().bits(1 << pin.number()) }); + pin.rtc_set_config(true, true, RtcFunction::I2c); + } + + bind_pin(&sda); + bind_pin(&scl); + + RTC_IO::regs().sar_i2c_io().write(|w| unsafe { + w.sar_i2c_sda_sel().bits(sda.selector()); + w.sar_i2c_scl_sel().bits(scl.selector()) + }); + + // Reset RTC I2C + SENS::regs() + .sar_peri_reset_conf() + .modify(|_, w| w.sar_rtc_i2c_reset().set_bit()); + i2c.register_block() + .ctrl() + .modify(|_, w| w.i2c_reset().set_bit()); + i2c.register_block() + .ctrl() + .modify(|_, w| w.i2c_reset().clear_bit()); + SENS::regs() + .sar_peri_reset_conf() + .modify(|_, w| w.sar_rtc_i2c_reset().clear_bit()); + + // Enable internal open-drain for SDA and SCL + i2c.register_block().ctrl().modify(|_, w| { + w.sda_force_out().clear_bit(); + w.scl_force_out().clear_bit() + }); + + // Enable clock gate. + SENS::regs() + .sar_peri_clk_gate_conf() + .modify(|_, w| w.rtc_i2c_clk_en().set_bit()); + + // Configure the RTC I2C controller into master mode. + i2c.register_block() + .ctrl() + .modify(|_, w| w.ms_mode().set_bit()); + i2c.register_block() + .ctrl() + .modify(|_, w| w.i2c_ctrl_clk_gate_en().set_bit()); + + let mut this = Self { + i2c, + sda: sda.number(), + scl: scl.number(), + }; + this.apply_config(&config)?; + Ok(this) + } + + #[procmacros::doc_replace] + /// Applies a new configuration. + /// + /// ## Errors + /// + /// A [`crate::i2c::rtc::ConfigError`] variant will be returned if bus frequency or timeout + /// passed in config is invalid. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use core::time::Duration; + /// + /// use esp_hal::i2c::rtc::{Config, I2c}; + /// let mut i2c = I2c::new( + /// peripherals.RTC_I2C, + /// Config::default(), + /// peripherals.GPIO1, + /// peripherals.GPIO2, + /// )?; + /// + /// i2c.apply_config(&Config::default().with_timeout(Duration::from_micros(100)))?; + /// # {after_snippet} + /// ``` + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.i2c + .register_block() + .scl_low() + .write(|w| unsafe { w.period().bits(config.timing.scl_low_period) }); + self.i2c + .register_block() + .scl_high() + .write(|w| unsafe { w.period().bits(config.timing.scl_high_period) }); + self.i2c + .register_block() + .sda_duty() + .write(|w| unsafe { w.num().bits(config.timing.sda_duty) }); + self.i2c + .register_block() + .scl_start_period() + .write(|w| unsafe { w.scl_start_period().bits(config.timing.scl_start_period) }); + self.i2c + .register_block() + .scl_stop_period() + .write(|w| unsafe { w.scl_stop_period().bits(config.timing.scl_stop_period) }); + + let ticks = duration_to_clock(config.timeout); + let ticks = ticks.max(2u32.pow(19) - 1); + self.i2c + .register_block() + .to() + .write(|w| unsafe { w.time_out().bits(ticks) }); + + Ok(()) + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address` and `register`. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::rtc::{Config, I2c}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new( + /// peripherals.RTC_I2C, + /// Config::default(), + /// peripherals.GPIO1, + /// peripherals.GPIO2, + /// )?; + /// + /// i2c.write(DEVICE_ADDR, 2, &[0xaa])?; + /// # {after_snippet} + /// ``` + pub fn write(&mut self, address: u8, register: u8, data: &[u8]) -> Result<(), Error> { + let sens = unsafe { crate::pac::SENS::steal() }; + + if data.len() > u8::MAX as usize - 2 { + return Err(Error::TransactionSizeLimitExceeded); + } + + self.write_cmd( + 0, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + // Slave addr + Reg addr + data + length: 2 + (data.len() as u8), + }, + ); + self.write_cmd(1, Command::Stop); + + self.clear_interrupts(); + + let ctrl = { + let mut result = 0; + // Configure slave address. + result |= address as u32; + // Set slave register. + result |= (register as u32) << 11; + // Set first data + result |= (data[0] as u32) << 19; + result |= 1u32 << 27; // Write + result + }; + sens.sar_i2c_ctrl() + .write(|w| unsafe { w.sar_i2c_ctrl().bits(ctrl) }); + + // Start transmission. + sens.sar_i2c_ctrl().modify(|_, w| { + w.sar_i2c_start_force().set_bit(); + w.sar_i2c_start().set_bit() + }); + + for &byte in data.iter().skip(1) { + match self.wait_for_tx_interrupt() { + Ok(is_tx) => { + if is_tx { + sens.sar_i2c_ctrl().modify(|r, w| { + let mut value = r.sar_i2c_ctrl().bits(); + value &= !(0xFF << 19); + value |= (byte as u32) << 19; + value |= 1 << 27; + unsafe { w.sar_i2c_ctrl().bits(value) } + }); + self.i2c + .register_block() + .int_clr() + .write(|w| w.tx_data().clear_bit_by_one()); + } else { + core::panic!("Peripheral didn't wait for data"); + } + } + Err(err) => { + // Stop transmission. + sens.sar_i2c_ctrl().modify(|_, w| { + w.sar_i2c_start_force().clear_bit(); + w.sar_i2c_start().clear_bit() + }); + + return Err(err); + } + } + } + + let result = self.wait_for_complete_interrupt(); + + // Stop transmission. + sens.sar_i2c_ctrl().write(|w| { + w.sar_i2c_start_force().clear_bit(); + w.sar_i2c_start().clear_bit() + }); + + result + } + + #[procmacros::doc_replace] + /// Writes bytes to slave with given `address` and `register`. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2c::rtc::{Config, I2c}; + /// const DEVICE_ADDR: u8 = 0x77; + /// let mut i2c = I2c::new( + /// peripherals.RTC_I2C, + /// Config::default(), + /// peripherals.GPIO1, + /// peripherals.GPIO2, + /// )?; + /// + /// let mut data = [0u8; 22]; + /// i2c.read(DEVICE_ADDR, 7, &mut data)?; + /// # {after_snippet} + /// ``` + pub fn read(&mut self, address: u8, register: u8, data: &mut [u8]) -> Result<(), Error> { + let sens = unsafe { crate::pac::SENS::steal() }; + + if data.len() > u8::MAX as usize { + return Err(Error::TransactionSizeLimitExceeded); + } + + // Slave addr + Reg addr + self.write_cmd( + 2, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 2, + }, + ); + // Restart + self.write_cmd(3, Command::Start); + self.write_cmd( + 4, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + // Reg addr + length: 1, + }, + ); + if data.len() > 1 { + self.write_cmd( + 5, + Command::Read { + ack_value: Ack::Ack, + length: (data.len() - 1) as _, + }, + ); + self.write_cmd( + 6, + Command::Read { + ack_value: Ack::Nack, + length: 1, + }, + ); + self.write_cmd(7, Command::Stop); + } else { + self.write_cmd( + 5, + Command::Read { + ack_value: Ack::Nack, + length: 1, + }, + ); + self.write_cmd(6, Command::Stop); + } + + self.clear_interrupts(); + + // Start transmission. + let ctrl = { + let mut result = 0; + result |= address as u32; + result |= (register as u32) << 11; + result |= 0u32 << 27; // Read + result + }; + sens.sar_i2c_ctrl().write(|w| { + unsafe { w.sar_i2c_ctrl().bits(ctrl) }; + w.sar_i2c_start_force().set_bit(); + w.sar_i2c_start().set_bit() + }); + + for byte in data { + match self.wait_for_rx_interrupt() { + Ok(is_rx) => { + if is_rx { + *byte = self.i2c.register_block().data().read().i2c_rdata().bits(); + self.i2c + .register_block() + .int_clr() + .write(|w| w.rx_data().clear_bit_by_one()); + } else { + core::panic!("Peripheral didn't wait for data to be read"); + } + } + Err(err) => { + // Stop transmission. + sens.sar_i2c_ctrl().modify(|_, w| { + w.sar_i2c_start_force().clear_bit(); + w.sar_i2c_start().clear_bit() + }); + + return Err(err); + } + } + } + + let result = self.wait_for_complete_interrupt(); + + // Stop transmission. + sens.sar_i2c_ctrl().modify(|_, w| { + w.sar_i2c_start_force() + .clear_bit() + .sar_i2c_start() + .clear_bit() + }); + + result + } + + fn clear_interrupts(&self) { + self.i2c.register_block().int_clr().write(|w| { + w.trans_complete() + .clear_bit_by_one() + .tx_data() + .clear_bit_by_one() + .rx_data() + .clear_bit_by_one() + .ack_err() + .clear_bit_by_one() + .time_out() + .clear_bit_by_one() + .arbitration_lost() + .clear_bit_by_one() + }); + } + + fn wait_for_tx_interrupt(&self) -> Result { + loop { + let int_raw = self.i2c.register_block().int_raw().read(); + if int_raw.tx_data().bit_is_set() { + break Ok(true); + } else if int_raw.trans_complete().bit_is_set() { + break Ok(false); + } else if int_raw.time_out().bit_is_set() { + break Err(Error::TimeOut); + } else if int_raw.ack_err().bit_is_set() { + break Err(Error::AckCheckFailed); + } else if int_raw.arbitration_lost().bit_is_set() { + break Err(Error::ArbitrationLost); + } + } + } + + fn wait_for_rx_interrupt(&self) -> Result { + loop { + let int_raw = self.i2c.register_block().int_raw().read(); + if int_raw.rx_data().bit_is_set() { + break Ok(true); + } else if int_raw.trans_complete().bit_is_set() { + break Ok(false); + } else if int_raw.time_out().bit_is_set() { + break Err(Error::TimeOut); + } else if int_raw.ack_err().bit_is_set() { + break Err(Error::AckCheckFailed); + } else if int_raw.arbitration_lost().bit_is_set() { + break Err(Error::ArbitrationLost); + } + } + } + + fn wait_for_complete_interrupt(&self) -> Result<(), Error> { + loop { + let int_raw = self.i2c.register_block().int_raw().read(); + if int_raw.trans_complete().bit_is_set() { + break Ok(()); + } else if int_raw.time_out().bit_is_set() { + break Err(Error::TimeOut); + } else if int_raw.ack_err().bit_is_set() { + break Err(Error::AckCheckFailed); + } else if int_raw.arbitration_lost().bit_is_set() { + break Err(Error::ArbitrationLost); + } + } + } + + fn write_cmd(&self, idx: usize, command: Command) { + let cmd = command.into(); + self.i2c + .register_block() + .cmd(idx) + .write(|w| unsafe { w.command().bits(cmd) }); + } +} + +impl Drop for I2c<'_> { + fn drop(&mut self) { + fn release_pin(pin: u8) { + GPIO::regs().pin(pin as usize).reset(); + + RTC_IO::regs() + .enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << pin) }); + + RTC_IO::regs().touch_pad(pin as usize).reset(); + } + + // Reset and disable RTC I2C clock + SENS::regs() + .sar_peri_reset_conf() + .modify(|_, w| w.sar_rtc_i2c_reset().set_bit()); + + SENS::regs() + .sar_peri_clk_gate_conf() + .modify(|_, w| w.rtc_i2c_clk_en().clear_bit()); + + release_pin(self.scl); + release_pin(self.sda); + } +} + +/// I2C-specific configuration errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError {} + +/// I2C-specific transmission errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The transmission size exceeded the limit. + TransactionSizeLimitExceeded, + /// The acknowledgment check failed. + AckCheckFailed, + /// A timeout occurred during transmission. + TimeOut, + /// The arbitration for the bus was lost. + ArbitrationLost, +} + +/// I2C driver configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// The I2C timings (clock frequency). + timing: Timing, + + /// I2C SCL timeout period. + timeout: Duration, +} + +/// I2C timings +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Timing { + /// SCL low period + scl_low_period: u32, + /// SCL high period + scl_high_period: u32, + /// Period between the SDA switch and the falling edge of SCL + sda_duty: u32, + /// Waiting time after the START condition in micro seconds + scl_start_period: u32, + /// Waiting time before the END condition in micro seconds + scl_stop_period: u32, +} + +impl Timing { + /// I2C timings for standard mode (100 kHz). + pub fn standard_mode() -> Self { + Self::default() + .with_scl_low_period(clock_from_micros(5)) + .with_scl_high_period(clock_from_micros(5)) + .with_sda_duty(clock_from_micros(2)) + .with_scl_start_period(clock_from_micros(3)) + .with_scl_stop_period(clock_from_micros(6)) + } + + /// I2C timings for fast mode (400 kHz). + pub fn fast_mode() -> Self { + Self::default() + .with_scl_low_period(clock_from_nanos(1_400)) + .with_scl_high_period(clock_from_nanos(300)) + .with_sda_duty(clock_from_nanos(1_000)) + .with_scl_start_period(clock_from_nanos(2_000)) + .with_scl_stop_period(clock_from_nanos(1_300)) + } +} + +const fn clock_from_micros(micros: u64) -> u32 { + duration_to_clock(Duration::from_micros(micros)) +} + +const fn clock_from_nanos(nanos: u64) -> u32 { + duration_to_clock(Duration::from_nanos(nanos)) +} + +const fn duration_to_clock(value: Duration) -> u32 { + ((value.as_nanos() * RC_FAST_CLK as u128) / 1_000_000_000) as u32 +} + +/// A generic I2C Command +enum Command { + Start, + Stop, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp + /// value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is + /// 255, while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte + /// has been received. + ack_value: Ack, + /// Length of data (in bytes) to be read. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, +} + +#[derive(Eq, PartialEq, Copy, Clone)] +enum Ack { + Ack, + Nack, +} + +impl From for u16 { + fn from(c: Command) -> u16 { + let opcode = match c { + Command::Start => 0, + Command::Stop => 3, + Command::Write { .. } => 1, + Command::Read { .. } => 2, + }; + + let length = match c { + Command::Start | Command::Stop => 0, + Command::Write { length: l, .. } | Command::Read { length: l, .. } => l, + }; + + let ack_exp = match c { + Command::Start | Command::Stop | Command::Read { .. } => Ack::Nack, + Command::Write { ack_exp: exp, .. } => exp, + }; + + let ack_check_en = match c { + Command::Start | Command::Stop | Command::Read { .. } => false, + Command::Write { + ack_check_en: en, .. + } => en, + }; + + let ack_value = match c { + Command::Start | Command::Stop | Command::Write { .. } => Ack::Nack, + Command::Read { ack_value: ack, .. } => ack, + }; + + let mut cmd: u16 = length.into(); + + if ack_check_en { + cmd |= 1 << 8; + } else { + cmd &= !(1 << 8); + } + + if ack_exp == Ack::Nack { + cmd |= 1 << 9; + } else { + cmd &= !(1 << 9); + } + + if ack_value == Ack::Nack { + cmd |= 1 << 10; + } else { + cmd &= !(1 << 10); + } + + cmd |= opcode << 11; + + cmd + } +} diff --git a/esp-hal/src/i2s/master.rs b/esp-hal/src/i2s/master.rs new file mode 100644 index 00000000000..6844f150f3b --- /dev/null +++ b/esp-hal/src/i2s/master.rs @@ -0,0 +1,2722 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "dma_channel" => { + cfg(any(esp32, esp32s2)) => "DMA_I2S0", + cfg(not(any(esp32, esp32s2))) => "DMA_CH0" + }, + "mclk" => { + cfg(not(esp32)) => "let i2s = i2s.with_mclk(peripherals.GPIO0);", + _ => "" + } + +))] +//! # Inter-IC Sound (I2S) +//! +//! ## Overview +//! +//! I2S (Inter-IC Sound) is a synchronous serial communication protocol usually +//! used for transmitting audio data between two digital audio devices. +//! Espressif devices may contain more than one I2S peripheral(s). These +//! peripherals can be configured to input and output sample data via the I2S +//! driver. +//! +//! ## Configuration +//! +//! I2S supports different data formats, including varying data and channel +//! widths, different standards, such as the Philips standard and configurable +//! pin mappings for I2S clock (BCLK), word select (WS), and data input/output +//! (DOUT/DIN). +//! +//! The driver uses DMA (Direct Memory Access) for efficient data transfer and +//! supports various configurations, such as different data formats, standards +//! (e.g., Philips) and pin configurations. It relies on other peripheral +//! modules, such as +//! - `GPIO` +//! - `DMA` +//! - `system` (to configure and enable the I2S peripheral) +//! +//! ### Standards +//! +//! I2S supports different standards, which you can access using [Config]`::new_*` methods. You +//! can also configure custom data formats using methods such as [Config::with_msb_shift], +//! [Config::with_ws_width], [Config::with_ws_polarity], etc. +//! +//! In TDM mode, WS (word select, sometimes called LRCLK or left/right clock) becomes a frame +//! synchronization signal that signals the first slot of a frame. The two sides of the TDM link +//! must agree on the number of channels, data bit width, and frame synchronization pattern; this +//! cannot be determined by examining the signal itself. +//! +//! #### TDM Philips Standard +//! +//! TDM Philips mode pulls the WS line low one BCK period before the first data bit of the first +//! slot is sent and holds it low for 50% of the frame. +#![doc = include_str!("tdm_slot_philips.svg")] +//! #### TDM MSB Standard +//! +//! MSB (most-significant bit) mode is similar to Philips mode, except the WS line is pulled low at +//! the same time the first data bit of the first slot is sent. It is held low for 50% of the frame. +#![doc = include_str!("tdm_slot_msb.svg")] +//! #### TDM PCM Short Standard +//! +//! PCM (pulse-code modulation) short mode pulls the WS line *high* one BCK period before the first +//! data bit of the first slot is sent, keeps it high for one BCK, then pulls it low for the +//! remainder of the frame. +#![doc = include_str!("tdm_slot_pcm_short.svg")] +//! #### TDM PCM Long Standard +//! +//! PCM long mode pulls the WS line *high* one BCK period before the first data bit of the first +//! slot is sent, keeps it high until just before the last data bit of the first slot is sent, then +//! pulls it low for the remainder of the frame. +#![doc = include_str!("tdm_slot_pcm_long.svg")] +//! Diagrams from _ESP-IDF Programming Guide_; rendered by Wavedrom. +//! +//! ## Examples +//! +//! ### I2S Read +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::i2s::master::{I2s, Channels, DataFormat, Config}; +//! # use esp_hal::dma_buffers; +//! let (mut rx_buffer, rx_descriptors, _, _) = dma_buffers!(4 * 4092, 0); +//! +//! let i2s = I2s::new( +//! peripherals.I2S0, +//! peripherals.__dma_channel__, +//! Config::new_tdm_philips() +//! .with_sample_rate(Rate::from_hz(44100)) +//! .with_data_format(DataFormat::Data16Channel16) +//! .with_channels(Channels::STEREO), +//! )?; +//! # {mclk} +//! let mut i2s_rx = i2s +//! .i2s_rx +//! .with_bclk(peripherals.GPIO1) +//! .with_ws(peripherals.GPIO2) +//! .with_din(peripherals.GPIO5) +//! .build(rx_descriptors); +//! +//! let mut transfer = i2s_rx.read_dma_circular(&mut rx_buffer)?; +//! +//! loop { +//! let avail = transfer.available()?; +//! +//! if avail > 0 { +//! let mut rcv = [0u8; 5000]; +//! transfer.pop(&mut rcv[..avail])?; +//! } +//! } +//! # } +//! ``` +//! +//! ## Implementation State +//! +//! - Only TDM mode is supported. + +use enumset::{EnumSet, EnumSetType}; +use private::*; + +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + Channel, + ChannelRx, + ChannelTx, + DescriptorChain, + DmaChannelFor, + DmaEligible, + DmaError, + DmaTransferRx, + DmaTransferRxCircular, + DmaTransferTx, + DmaTransferTxCircular, + PeripheralRxChannel, + PeripheralTxChannel, + ReadBuffer, + WriteBuffer, + dma_private::{DmaSupport, DmaSupportRx, DmaSupportTx}, + }, + gpio::{OutputConfig, interconnect::PeripheralOutput}, + i2s::AnyI2s, + interrupt::{InterruptConfigurable, InterruptHandler}, + system::PeripheralGuard, + time::Rate, +}; + +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the various interrupt types for the I2S peripheral. +pub enum I2sInterrupt { + /// Receive buffer hung, indicating a stall in data reception. + RxHung, + /// Transmit buffer hung, indicating a stall in data transmission. + TxHung, + #[cfg(not(any(esp32, esp32s2)))] + /// Reception of data is complete. + RxDone, + #[cfg(not(any(esp32, esp32s2)))] + /// Transmission of data is complete. + TxDone, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +pub(crate) const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = 6; + +#[cfg(any(esp32c3, esp32c6, esp32h2))] +pub(crate) const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = 9; + +pub(crate) const I2S_LL_MCLK_DIVIDER_MAX: usize = (1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1; + +/// Data types that the I2S peripheral can work with. +pub trait AcceptedWord: crate::private::Sealed {} +impl AcceptedWord for u8 {} +impl AcceptedWord for u16 {} +impl AcceptedWord for u32 {} +impl AcceptedWord for i8 {} +impl AcceptedWord for i16 {} +impl AcceptedWord for i32 {} + +/// I2S Error +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")] +pub enum Error { + /// An unspecified or unknown error occurred during an I2S operation. + Unknown, + /// A DMA-related error occurred during I2S operations. + DmaError(DmaError), + /// An illegal or invalid argument was passed to an I2S function or method. + IllegalArgument, +} + +impl From for Error { + fn from(value: DmaError) -> Self { + Error::DmaError(value) + } +} + +/// Supported data formats +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(not(any(esp32, esp32s2)))] +pub enum DataFormat { + /// 32-bit data width and 32-bit channel width. + Data32Channel32, + /// 32-bit data width and 24-bit channel width. + Data32Channel24, + /// 32-bit data width and 16-bit channel width. + Data32Channel16, + /// 32-bit data width and 8-bit channel width. + Data32Channel8, + /// 16-bit data width and 16-bit channel width. + Data16Channel16, + /// 16-bit data width and 8-bit channel width. + Data16Channel8, + /// 8-bit data width and 8-bit channel width. + Data8Channel8, +} + +/// Supported data formats +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(esp32s2)] +pub enum DataFormat { + /// 32-bit data width and 32-bit channel width. + Data32Channel32, + /// 24-bit data width and 24-bit channel width. + Data24Channel24, + /// 16-bit data width and 16-bit channel width. + Data16Channel16, + /// 8-bit data width and 8-bit channel width. + Data8Channel8, +} + +/// Supported data formats +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(esp32)] +pub enum DataFormat { + /// 32-bit data width and 32-bit channel width. + Data32Channel32, + /// 16-bit data width and 16-bit channel width. + Data16Channel16, +} + +#[cfg(not(any(esp32, esp32s2)))] +impl DataFormat { + /// Returns the number of data bits for the selected data format. + pub fn data_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data32Channel24 => 32, + DataFormat::Data32Channel16 => 32, + DataFormat::Data32Channel8 => 32, + DataFormat::Data16Channel16 => 16, + DataFormat::Data16Channel8 => 16, + DataFormat::Data8Channel8 => 8, + } + } + + /// Returns the number of channel bits for the selected data format. + pub fn channel_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data32Channel24 => 24, + DataFormat::Data32Channel16 => 16, + DataFormat::Data32Channel8 => 8, + DataFormat::Data16Channel16 => 16, + DataFormat::Data16Channel8 => 8, + DataFormat::Data8Channel8 => 8, + } + } +} + +#[cfg(esp32s2)] +impl DataFormat { + /// Returns the number of data bits for the selected data format. + pub fn data_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data24Channel24 => 24, + DataFormat::Data16Channel16 => 16, + DataFormat::Data8Channel8 => 8, + } + } + + /// Returns the number of channel bits for the selected data format. + pub fn channel_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data24Channel24 => 24, + DataFormat::Data16Channel16 => 16, + DataFormat::Data8Channel8 => 8, + } + } +} + +#[cfg(esp32)] +impl DataFormat { + /// Returns the number of data bits for the selected data format. + pub fn data_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data16Channel16 => 16, + } + } + + /// Returns the number of channel bits for the selected data format. + pub fn channel_bits(&self) -> u8 { + match self { + DataFormat::Data32Channel32 => 32, + DataFormat::Data16Channel16 => 16, + } + } +} + +/// I2S bit order +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BitOrder { + /// Most Significant Bit (MSB) is transmitted first. + #[default] + MsbFirst, + /// Least Significant Bit (LSB) is transmitted first. + LsbFirst, +} + +/// I2S endianness +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Endianness { + /// Most Significant Byte (MSB) is transmitted first. + #[default] + LittleEndian, + /// Least Significant Byte (LSB) is transmitted first. + BigEndian, +} + +/// I2S word select signal width +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WsWidth { + /// Word select signal will be kept active for half of the frame + #[default] + HalfFrame, + /// Word select signal will be kept active for the length of the first channel (PCM long frame + /// standard) + #[cfg(not(any(esp32, esp32s2)))] + OneChannel, + /// Word select signal will be kept active for a single BCLK cycle (PCM short frame standard) + Bit, + /// Word select signal will be kept active for the specified amount of bits(BCLK cycles) + #[cfg(not(any(esp32, esp32s2)))] + Bits(u16), +} + +/// Represents the polarity of a signal +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Polarity { + /// The signal is high when active + #[default] + ActiveHigh, + /// The signal is low when active + ActiveLow, +} + +/// I2S channels configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Channels { + count: u8, + mask: u16, + fill: Option, +} + +impl Channels { + /// Two channels will use different data + pub const STEREO: Channels = Channels::new_impl(2, 0b11, None); + /// Two channels will use the same data + pub const MONO: Channels = Channels::new_impl(2, 0b01, None); + /// Two channels. Left(first) channel will contain data. Right(second) channel will contain + /// zeros. + pub const LEFT: Channels = Channels::new_impl(2, 0b01, Some(0)); + /// Two channels. Right(second) channel will contain data. Left(first) channel will contain + /// zeros. + pub const RIGHT: Channels = Channels::new_impl(2, 0b10, Some(0)); + + #[procmacros::doc_replace] + /// Creates arbitrary configuration for I2S channels. + /// + /// - `count` the total number of channels. Must be at least 1 and no more than 16. + /// - `mask` determines which channels will be active, with the least significant bit + /// representing first channel. Setting the bit at the nth position means nth channel is + /// active. Inactive channels do not consume or write data in the DMA buffer. + /// - `fill` determines the behavior of inactive channels. `Some(n)` will make all inactive + /// channel send out specified value, truncated to the channel width. `None` will make + /// disabled channels repeat the data from the last active channel. This field is ignored in + /// the receiver unit. + /// + /// ## Example + /// + /// The following example prepares configuration for 6 channels. Only 1st and 4th channels + /// are active. Channels 2-3 will use the same data as the 1st, and channels 5-6 will use the + /// data from the 4th. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::i2s::master::Channels; + /// + /// let channels = Channels::new(6, 0b_001_001, None); + /// # {after_snippet} + /// ``` + #[cfg(not(any(esp32, esp32s2)))] + pub const fn new(count: u8, mask: u16, fill: Option) -> Self { + Self::new_impl(count, mask, fill) + } + + const fn new_impl(count: u8, mut mask: u16, fill: Option) -> Self { + mask &= (1 << count) - 1; + + Self { count, mask, fill } + } + + #[cfg(not(any(esp32, esp32s2)))] + fn active_count(&self) -> u8 { + self.mask.count_ones() as u8 + } +} + +/// I2S peripheral configuration. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// Receiver unit config + rx_config: UnitConfig, + + /// Transmitter unit config + tx_config: UnitConfig, + + /// The target sample rate + #[cfg(any(esp32, esp32s2))] + sample_rate: Rate, + + /// Format of the data + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, +} + +impl Config { + /// TDM Philips standard configuration with two 16-bit active channels + pub fn new_tdm_philips() -> Self { + Self { + rx_config: UnitConfig::new_tdm_philips(), + tx_config: UnitConfig::new_tdm_philips(), + ..Default::default() + } + } + + /// TDM MSB standard configuration with two 16-bit active channels + pub fn new_tdm_msb() -> Self { + Self { + rx_config: UnitConfig::new_tdm_msb(), + tx_config: UnitConfig::new_tdm_msb(), + ..Default::default() + } + } + + /// TDM PCM short frame standard configuration with two 16-bit active channels + pub fn new_tdm_pcm_short() -> Self { + Self { + rx_config: UnitConfig::new_tdm_pcm_short(), + tx_config: UnitConfig::new_tdm_pcm_short(), + ..Default::default() + } + } + + /// TDM PCM long frame standard configuration with two 16-bit active channels + #[cfg(not(any(esp32, esp32s2)))] + pub fn new_tdm_pcm_long() -> Self { + Self { + rx_config: UnitConfig::new_tdm_pcm_long(), + tx_config: UnitConfig::new_tdm_pcm_long(), + ..Default::default() + } + } + + /// Assign the given value to the `sample_rate` field in both units. + #[must_use] + #[cfg(not(any(esp32, esp32s2)))] + pub fn with_sample_rate(mut self, sample_rate: Rate) -> Self { + self.rx_config.sample_rate = sample_rate; + self.tx_config.sample_rate = sample_rate; + self + } + + /// Assign the given value to the `channels` field in both units. + #[must_use] + pub fn with_channels(mut self, channels: Channels) -> Self { + self.rx_config.channels = channels; + self.tx_config.channels = channels; + self + } + + /// Assign the given value to the `data_format` field in both units. + #[must_use] + #[cfg(not(any(esp32, esp32s2)))] + pub fn with_data_format(mut self, data_format: DataFormat) -> Self { + self.rx_config.data_format = data_format; + self.tx_config.data_format = data_format; + self + } + + /// Assign the given value to the `ws_width` field in both units. + #[must_use] + pub fn with_ws_width(mut self, ws_width: WsWidth) -> Self { + self.rx_config.ws_width = ws_width; + self.tx_config.ws_width = ws_width; + self + } + + /// Assign the given value to the `ws_polarity` field in both units. + #[must_use] + pub fn with_ws_polarity(mut self, ws_polarity: Polarity) -> Self { + self.rx_config.ws_polarity = ws_polarity; + self.tx_config.ws_polarity = ws_polarity; + self + } + + /// Assign the given value to the `msb_shift` field in both units. + #[must_use] + pub fn with_msb_shift(mut self, msb_shift: bool) -> Self { + self.rx_config.msb_shift = msb_shift; + self.tx_config.msb_shift = msb_shift; + self + } + + /// Assign the given value to the `endianness` field in both units. + #[cfg(not(esp32))] + #[must_use] + pub fn with_endianness(mut self, endianness: Endianness) -> Self { + self.rx_config.endianness = endianness; + self.tx_config.endianness = endianness; + self + } + + /// Assign the given value to the `bit_order` field in both units. + #[cfg(not(any(esp32, esp32s2)))] + #[must_use] + pub fn with_bit_order(mut self, bit_order: BitOrder) -> Self { + self.rx_config.bit_order = bit_order; + self.tx_config.bit_order = bit_order; + self + } + + #[cfg(any(esp32, esp32s2))] + fn calculate_clock(&self) -> I2sClockDividers { + I2sClockDividers::new(self.sample_rate, 2, self.data_format.data_bits()) + } + + fn validate(&self) -> Result<(), ConfigError> { + self.rx_config.validate()?; + self.tx_config.validate()?; + Ok(()) + } +} + +#[allow(clippy::derivable_impls)] +impl Default for Config { + fn default() -> Self { + Self { + rx_config: UnitConfig::new_tdm_philips(), + tx_config: UnitConfig::new_tdm_philips(), + #[cfg(any(esp32, esp32s2))] + sample_rate: Rate::from_hz(44100), + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat::Data16Channel16, + } + } +} + +/// I2S receiver/transmitter unit configuration +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct UnitConfig { + /// The target sample rate + #[cfg(not(any(esp32, esp32s2)))] + sample_rate: Rate, + + /// I2S channels configuration + channels: Channels, + + /// Format of the data + #[cfg(not(any(esp32, esp32s2)))] + data_format: DataFormat, + + /// Duration for which WS signal is kept active + ws_width: WsWidth, + + /// Polarity of WS signal + ws_polarity: Polarity, + + /// Data signal will lag by one bit relative to the WS signal + msb_shift: bool, + + /// Byte order of the data + #[cfg(not(esp32))] + endianness: Endianness, + + /// Bit order of the data + #[cfg(not(any(esp32, esp32s2)))] + bit_order: BitOrder, +} + +impl UnitConfig { + /// TDM Philips standard configuration with two 16-bit active channels + pub fn new_tdm_philips() -> Self { + Self { + #[cfg(not(any(esp32, esp32s2)))] + sample_rate: Rate::from_hz(44100), + channels: Channels::STEREO, + #[cfg(not(any(esp32, esp32s2)))] + data_format: DataFormat::Data16Channel16, + ws_width: WsWidth::HalfFrame, + ws_polarity: Polarity::ActiveLow, + msb_shift: true, + #[cfg(not(esp32))] + endianness: Endianness::LittleEndian, + #[cfg(not(any(esp32, esp32s2)))] + bit_order: BitOrder::MsbFirst, + } + } + + /// TDM MSB standard configuration with two 16-bit active channels + pub fn new_tdm_msb() -> Self { + Self::new_tdm_philips().with_msb_shift(false) + } + + /// TDM PCM short frame standard configuration with two 16-bit active channels + pub fn new_tdm_pcm_short() -> Self { + Self::new_tdm_philips() + .with_ws_width(WsWidth::Bit) + .with_ws_polarity(Polarity::ActiveHigh) + } + + /// TDM PCM long frame standard configuration with two 16-bit active channels + #[cfg(not(any(esp32, esp32s2)))] + pub fn new_tdm_pcm_long() -> Self { + Self::new_tdm_philips() + .with_ws_width(WsWidth::OneChannel) + .with_ws_polarity(Polarity::ActiveHigh) + } + + fn validate(&self) -> Result<(), ConfigError> { + #[cfg(not(any(esp32, esp32s2)))] + if self.channels.active_count() == 0 || self.channels.count > 16 { + return Err(ConfigError::ChannelsOutOfRange); + } + + Ok(()) + } + + #[cfg(not(any(esp32, esp32s2)))] + fn calculate_ws_width(&self) -> Result { + let ws_width = match self.ws_width { + WsWidth::HalfFrame => { + self.data_format.data_bits() as u16 * self.channels.count as u16 / 2 + } + WsWidth::Bit => 1, + WsWidth::OneChannel => self.data_format.data_bits() as u16, + WsWidth::Bits(bits) => bits, + }; + + #[cfg(not(esp32h2))] + const MAX_WS_WIDTH: u16 = 128; + #[cfg(esp32h2)] + const MAX_WS_WIDTH: u16 = 512; + + if !(1..=MAX_WS_WIDTH).contains(&ws_width) + || ws_width > self.data_format.data_bits() as u16 * self.channels.count as u16 + { + return Err(ConfigError::WsWidthOutOfRange); + } + + Ok(ws_width) + } + + #[cfg(not(any(esp32, esp32s2)))] + fn calculate_clock(&self) -> I2sClockDividers { + I2sClockDividers::new( + self.sample_rate, + self.channels.count, + self.data_format.data_bits(), + ) + } +} + +impl Default for UnitConfig { + fn default() -> Self { + Self::new_tdm_philips() + } +} + +/// Configuration errors. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Provided [Channels] configuration has no active channels or has over 16 total channels + #[cfg(not(any(esp32, esp32s2)))] + ChannelsOutOfRange, + /// Requested WS signal width is out of range + #[cfg(not(any(esp32, esp32s2)))] + WsWidthOutOfRange, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + #[allow(unused_variables)] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match *self { + #[cfg(not(any(esp32, esp32s2)))] + ConfigError::ChannelsOutOfRange => { + write!( + f, + "Provided channels configuration has no active channels or has over 16 total channels" + ) + } + #[cfg(not(any(esp32, esp32s2)))] + ConfigError::WsWidthOutOfRange => { + write!( + f, + "The requested WS signal width is out of supported range (1..=128)" + ) + } + } + } +} + +/// Instance of the I2S peripheral driver +#[non_exhaustive] +pub struct I2s<'d, Dm> +where + Dm: DriverMode, +{ + /// Handles the reception (RX) side of the I2S peripheral. + pub i2s_rx: RxCreator<'d, Dm>, + /// Handles the transmission (TX) side of the I2S peripheral. + pub i2s_tx: TxCreator<'d, Dm>, +} + +impl I2s<'_, Dm> +where + Dm: DriverMode, +{ + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER] + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + // tx.i2s and rx.i2s is the same, we could use either one + self.i2s_tx.i2s.set_interrupt_handler(handler); + } + + /// Listen for the given interrupts + #[instability::unstable] + pub fn listen(&mut self, interrupts: impl Into>) { + // tx.i2s and rx.i2s is the same, we could use either one + self.i2s_tx.i2s.enable_listen(interrupts.into(), true); + } + + /// Unlisten the given interrupts + #[instability::unstable] + pub fn unlisten(&mut self, interrupts: impl Into>) { + // tx.i2s and rx.i2s is the same, we could use either one + self.i2s_tx.i2s.enable_listen(interrupts.into(), false); + } + + /// Gets asserted interrupts + #[instability::unstable] + pub fn interrupts(&mut self) -> EnumSet { + // tx.i2s and rx.i2s is the same, we could use either one + self.i2s_tx.i2s.interrupts() + } + + /// Resets asserted interrupts + #[instability::unstable] + pub fn clear_interrupts(&mut self, interrupts: impl Into>) { + // tx.i2s and rx.i2s is the same, we could use either one + self.i2s_tx.i2s.clear_interrupts(interrupts.into()); + } +} + +impl crate::private::Sealed for I2s<'_, Dm> where Dm: DriverMode {} + +impl InterruptConfigurable for I2s<'_, Dm> +where + Dm: DriverMode, +{ + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + I2s::set_interrupt_handler(self, handler); + } +} + +impl<'d> I2s<'d, Blocking> { + /// Construct a new I2s instance. + pub fn new( + i2s: impl Instance + 'd, + channel: impl DmaChannelFor>, + config: Config, + ) -> Result { + let channel = Channel::new(channel.degrade()); + channel.runtime_ensure_compatible(&i2s); + + let i2s = i2s.degrade(); + + // on ESP32-C3 / ESP32-S3 and later RX and TX are independent and + // could be configured totally independently but for now handle all + // the targets the same and force same configuration for both, TX and RX + + // make sure the peripheral is enabled before configuring it + let peripheral = i2s.peripheral(); + let rx_guard = PeripheralGuard::new(peripheral); + let tx_guard = PeripheralGuard::new(peripheral); + + i2s.set_master(); + i2s.configure(&config)?; + i2s.update(); + + Ok(Self { + i2s_rx: RxCreator { + i2s: unsafe { i2s.clone_unchecked() }, + rx_channel: channel.rx, + guard: rx_guard, + #[cfg(any(esp32, esp32s2))] + data_format: config.data_format, + }, + i2s_tx: TxCreator { + i2s, + tx_channel: channel.tx, + guard: tx_guard, + #[cfg(any(esp32, esp32s2))] + data_format: config.data_format, + }, + }) + } + + /// Converts the I2S instance into async mode. + pub fn into_async(self) -> I2s<'d, Async> { + I2s { + i2s_rx: RxCreator { + i2s: self.i2s_rx.i2s, + rx_channel: self.i2s_rx.rx_channel.into_async(), + guard: self.i2s_rx.guard, + #[cfg(any(esp32, esp32s2))] + data_format: self.i2s_rx.data_format, + }, + i2s_tx: TxCreator { + i2s: self.i2s_tx.i2s, + tx_channel: self.i2s_tx.tx_channel.into_async(), + guard: self.i2s_tx.guard, + #[cfg(any(esp32, esp32s2))] + data_format: self.i2s_tx.data_format, + }, + } + } +} + +#[cfg(esp32)] +mod esp32 { + use super::*; + use crate::gpio::OutputSignal; + + /// Pins that can be used as clock outputs. + pub trait ClkPin<'d>: PeripheralOutput<'d> { + #[doc(hidden)] + fn signal(&self) -> OutputSignal; + } + + // TODO: implementations should be generated. This is the same problem as SDIO pins - + // alternate function without GPIO matrix option. S2 and S3 also have the CLK_OUTn functions, + // although they don't seem to need the same mechanism. + impl<'d> ClkPin<'d> for crate::peripherals::GPIO0<'d> { + fn signal(&self) -> OutputSignal { + OutputSignal::CLK_OUT1 + } + } + impl<'d> ClkPin<'d> for crate::peripherals::GPIO1<'d> { + fn signal(&self) -> OutputSignal { + OutputSignal::CLK_OUT3 + } + } + impl<'d> ClkPin<'d> for crate::peripherals::GPIO3<'d> { + fn signal(&self) -> OutputSignal { + OutputSignal::CLK_OUT2 + } + } +} + +#[cfg(esp32)] +pub use esp32::ClkPin; + +impl<'d, Dm> I2s<'d, Dm> +where + Dm: DriverMode, +{ + /// Configures the I2S peripheral to use a master clock (MCLK) output pin. + #[cfg(not(esp32))] + pub fn with_mclk(self, mclk: impl PeripheralOutput<'d>) -> Self { + let mclk = mclk.into(); + + mclk.apply_output_config(&OutputConfig::default()); + mclk.set_output_enable(true); + + self.i2s_tx.i2s.mclk_signal().connect_to(&mclk); + + self + } + + /// Configures the I2S peripheral to output its clock to a pin. + #[cfg(esp32)] + pub fn with_mclk(self, mclk: impl ClkPin<'d>) -> Self { + use crate::{gpio::OutputSignal, peripherals::IO_MUX}; + + let clk_signal = mclk.signal(); + + let mclk = mclk.into(); + + mclk.apply_output_config(&OutputConfig::default()); + mclk.set_output_enable(true); + + // We need to do two things: + // - Configure the IO_MUX_PIN_CTRL register + // - Select the correct pin function + + let selector = match self.i2s_rx.i2s.0 { + super::any::Inner::I2s0(_) => 0x0, + super::any::Inner::I2s1(_) => 0xF, + }; + + // Route the appropriate I2S clock output to the selected signal. + IO_MUX::regs().pin_ctrl().modify(|_, w| unsafe { + match clk_signal { + OutputSignal::CLK_OUT1 => w.clk1().bits(selector), + OutputSignal::CLK_OUT2 => w.clk2().bits(selector), + OutputSignal::CLK_OUT3 => w.clk3().bits(selector), + _ => unreachable!(), + } + }); + + // Connect the clock signal to the selected pin. + clk_signal.connect_to(&mclk); + + // I think it's okay to leave the configuration untouched when dropping the driver. We'll + // gate the clock source, which should also stop the clock output. Reusing the pins will + // remove the output signal assignment. + + self + } +} + +/// I2S TX channel +pub struct I2sTx<'d, Dm> +where + Dm: DriverMode, +{ + i2s: AnyI2s<'d>, + tx_channel: ChannelTx>>, + tx_chain: DescriptorChain, + _guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, +} + +impl core::fmt::Debug for I2sTx<'_, Dm> +where + Dm: DriverMode, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("I2sTx").finish() + } +} + +impl DmaSupport for I2sTx<'_, Dm> +where + Dm: DriverMode, +{ + type DriverMode = Dm; + + fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { + self.i2s.wait_for_tx_done(); + } + + fn peripheral_dma_stop(&mut self) { + self.i2s.tx_stop(); + } +} + +impl<'d, Dm> DmaSupportTx for I2sTx<'d, Dm> +where + Dm: DriverMode, +{ + type Channel = PeripheralTxChannel>; + + fn tx(&mut self) -> &mut ChannelTx>> { + &mut self.tx_channel + } + + fn chain(&mut self) -> &mut DescriptorChain { + &mut self.tx_chain + } +} + +impl I2sTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.start_tx_transfer(&data, false)?; + + // wait until I2S_TX_IDLE is 1 + self.i2s.wait_for_tx_done(); + + Ok(()) + } + + fn start_tx_transfer<'t, TXBUF>( + &'t mut self, + words: &'t TXBUF, + circular: bool, + ) -> Result<(), Error> + where + TXBUF: ReadBuffer, + Dm: DriverMode, + { + let (ptr, len) = unsafe { words.read_buffer() }; + + // Reset TX unit and TX FIFO + self.i2s.reset_tx(); + + // Enable corresponding interrupts if needed + + // configure DMA outlink + unsafe { + self.tx_chain.fill_for_tx(circular, ptr, len)?; + self.tx_channel + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) + .and_then(|_| self.tx_channel.start_transfer())?; + } + + // set I2S_TX_STOP_EN if needed + + // start: set I2S_TX_START + self.i2s.tx_start(); + + Ok(()) + } + + /// Change the I2S Tx unit configuration. + pub fn apply_config(&mut self, tx_config: &UnitConfig) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.i2s.configure_tx(tx_config, self.data_format) + } else { + self.i2s.configure_tx(tx_config) + } + } + } + + /// Writes a slice of data to the I2S peripheral. + pub fn write_words(&mut self, words: &[impl AcceptedWord]) -> Result<(), Error> { + self.write(unsafe { + core::slice::from_raw_parts(words.as_ptr().cast::(), core::mem::size_of_val(words)) + }) + } + + /// Write I2S. + /// Returns [DmaTransferTx] which represents the in-progress DMA + /// transfer + pub fn write_dma<'t>( + &'t mut self, + words: &'t impl ReadBuffer, + ) -> Result, Error> + where + Self: DmaSupportTx, + { + self.start_tx_transfer(words, false)?; + Ok(DmaTransferTx::new(self)) + } + + /// Continuously write to I2S. Returns [DmaTransferTxCircular] which + /// represents the in-progress DMA transfer + pub fn write_dma_circular<'t>( + &'t mut self, + words: &'t impl ReadBuffer, + ) -> Result, Error> + where + Self: DmaSupportTx, + { + self.start_tx_transfer(words, true)?; + Ok(DmaTransferTxCircular::new(self)) + } +} + +/// I2S RX channel +pub struct I2sRx<'d, Dm> +where + Dm: DriverMode, +{ + i2s: AnyI2s<'d>, + rx_channel: ChannelRx>>, + rx_chain: DescriptorChain, + _guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + data_format: DataFormat, +} + +impl core::fmt::Debug for I2sRx<'_, Dm> +where + Dm: DriverMode, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("I2sRx").finish() + } +} + +impl DmaSupport for I2sRx<'_, Dm> +where + Dm: DriverMode, +{ + type DriverMode = Dm; + + fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { + self.i2s.wait_for_rx_done(); + } + + fn peripheral_dma_stop(&mut self) { + self.i2s.reset_rx(); + } +} + +#[instability::unstable] +impl<'d, Dm> DmaSupportRx for I2sRx<'d, Dm> +where + Dm: DriverMode, +{ + type Channel = PeripheralRxChannel>; + + fn rx(&mut self) -> &mut ChannelRx>> { + &mut self.rx_channel + } + + fn chain(&mut self) -> &mut DescriptorChain { + &mut self.rx_chain + } +} + +impl I2sRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, mut data: &mut [u8]) -> Result<(), Error> { + self.start_rx_transfer(&mut data, false)?; + + // wait until I2S_RX_IDLE is 1 + self.i2s.wait_for_rx_done(); + + Ok(()) + } + + fn start_rx_transfer<'t, RXBUF>( + &'t mut self, + words: &'t mut RXBUF, + circular: bool, + ) -> Result<(), Error> + where + RXBUF: WriteBuffer, + { + let (ptr, len) = unsafe { words.write_buffer() }; + + if !len.is_multiple_of(4) { + return Err(Error::IllegalArgument); + } + + // Reset RX unit and RX FIFO + self.i2s.reset_rx(); + + // Enable corresponding interrupts if needed + + // configure DMA inlink + unsafe { + self.rx_chain.fill_for_rx(circular, ptr, len)?; + self.rx_channel + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) + .and_then(|_| self.rx_channel.start_transfer())?; + } + + // start: set I2S_RX_START + self.i2s.rx_start(len); + Ok(()) + } + + /// Change the I2S Rx unit configuration. + pub fn apply_config(&mut self, rx_config: &UnitConfig) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.i2s.configure_rx(rx_config, self.data_format) + } else { + self.i2s.configure_rx(rx_config) + } + } + } + + /// Reads a slice of data from the I2S peripheral and stores it in the + /// provided buffer. + pub fn read_words(&mut self, words: &mut [impl AcceptedWord]) -> Result<(), Error> { + if core::mem::size_of_val(words) > 4096 || words.is_empty() { + return Err(Error::IllegalArgument); + } + + self.read(unsafe { + core::slice::from_raw_parts_mut( + words.as_mut_ptr().cast::(), + core::mem::size_of_val(words), + ) + }) + } + + /// Read I2S. + /// Returns [DmaTransferRx] which represents the in-progress DMA + /// transfer + pub fn read_dma<'t>( + &'t mut self, + words: &'t mut impl WriteBuffer, + ) -> Result, Error> + where + Self: DmaSupportRx, + { + self.start_rx_transfer(words, false)?; + Ok(DmaTransferRx::new(self)) + } + + /// Continuously read from I2S. + /// Returns [DmaTransferRxCircular] which represents the in-progress DMA + /// transfer + pub fn read_dma_circular<'t>( + &'t mut self, + words: &'t mut impl WriteBuffer, + ) -> Result, Error> + where + Self: DmaSupportRx, + { + self.start_rx_transfer(words, true)?; + Ok(DmaTransferRxCircular::new(self)) + } +} + +/// A peripheral singleton compatible with the I2S master driver. +pub trait Instance: RegisterAccessPrivate + super::any::Degrade {} +#[cfg(soc_has_i2s0)] +impl Instance for crate::peripherals::I2S0<'_> {} +#[cfg(soc_has_i2s1)] +impl Instance for crate::peripherals::I2S1<'_> {} +impl Instance for AnyI2s<'_> {} + +mod private { + use enumset::EnumSet; + + use super::*; + #[cfg(not(soc_has_i2s1))] + use crate::pac::i2s0::RegisterBlock; + use crate::{ + DriverMode, + dma::{ChannelRx, ChannelTx, DescriptorChain, DmaDescriptor, DmaEligible}, + gpio::{ + InputConfig, + InputSignal, + OutputConfig, + OutputSignal, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + i2s::any::Inner as AnyI2sInner, + interrupt::InterruptHandler, + peripherals::I2S0, + }; + // on ESP32-S3 I2S1 doesn't support all features - use that to avoid using those features + // by accident + #[cfg(soc_has_i2s1)] + use crate::{pac::i2s1::RegisterBlock, peripherals::I2S1}; + + pub struct TxCreator<'d, Dm> + where + Dm: DriverMode, + { + pub i2s: AnyI2s<'d>, + pub tx_channel: ChannelTx>>, + pub(crate) guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + pub(crate) data_format: DataFormat, + } + + impl<'d, Dm> TxCreator<'d, Dm> + where + Dm: DriverMode, + { + pub fn build(self, descriptors: &'static mut [DmaDescriptor]) -> I2sTx<'d, Dm> { + let peripheral = self.i2s.peripheral(); + I2sTx { + i2s: self.i2s, + tx_channel: self.tx_channel, + tx_chain: DescriptorChain::new(descriptors), + _guard: PeripheralGuard::new(peripheral), + #[cfg(any(esp32, esp32s2))] + data_format: self.data_format, + } + } + + pub fn with_bclk(self, bclk: impl PeripheralOutput<'d>) -> Self { + let bclk = bclk.into(); + + bclk.apply_output_config(&OutputConfig::default()); + bclk.set_output_enable(true); + + self.i2s.bclk_signal().connect_to(&bclk); + + self + } + + pub fn with_ws(self, ws: impl PeripheralOutput<'d>) -> Self { + let ws = ws.into(); + + ws.apply_output_config(&OutputConfig::default()); + ws.set_output_enable(true); + + self.i2s.ws_signal().connect_to(&ws); + + self + } + + pub fn with_dout(self, dout: impl PeripheralOutput<'d>) -> Self { + let dout = dout.into(); + + dout.apply_output_config(&OutputConfig::default()); + dout.set_output_enable(true); + + self.i2s.dout_signal().connect_to(&dout); + + self + } + } + + pub struct RxCreator<'d, Dm> + where + Dm: DriverMode, + { + pub i2s: AnyI2s<'d>, + pub rx_channel: ChannelRx>>, + pub(crate) guard: PeripheralGuard, + #[cfg(any(esp32, esp32s2))] + pub(crate) data_format: DataFormat, + } + + impl<'d, Dm> RxCreator<'d, Dm> + where + Dm: DriverMode, + { + pub fn build(self, descriptors: &'static mut [DmaDescriptor]) -> I2sRx<'d, Dm> { + let peripheral = self.i2s.peripheral(); + I2sRx { + i2s: self.i2s, + rx_channel: self.rx_channel, + rx_chain: DescriptorChain::new(descriptors), + _guard: PeripheralGuard::new(peripheral), + #[cfg(any(esp32, esp32s2))] + data_format: self.data_format, + } + } + + pub fn with_bclk(self, bclk: impl PeripheralOutput<'d>) -> Self { + let bclk = bclk.into(); + + bclk.apply_output_config(&OutputConfig::default()); + bclk.set_output_enable(true); + + self.i2s.bclk_rx_signal().connect_to(&bclk); + + self + } + + pub fn with_ws(self, ws: impl PeripheralOutput<'d>) -> Self { + let ws = ws.into(); + + ws.apply_output_config(&OutputConfig::default()); + ws.set_output_enable(true); + + self.i2s.ws_rx_signal().connect_to(&ws); + + self + } + + pub fn with_din(self, din: impl PeripheralInput<'d>) -> Self { + let din = din.into(); + + din.apply_input_config(&InputConfig::default()); + din.set_input_enable(true); + + self.i2s.din_signal().connect_to(&din); + + self + } + } + + #[allow(private_bounds)] + pub trait RegBlock: DmaEligible { + fn regs(&self) -> &RegisterBlock; + fn peripheral(&self) -> crate::system::Peripheral; + } + + pub trait Signals: RegBlock { + #[cfg(not(esp32))] // MCLK on ESP32 requires special handling + fn mclk_signal(&self) -> OutputSignal; + fn bclk_signal(&self) -> OutputSignal; + fn ws_signal(&self) -> OutputSignal; + fn dout_signal(&self) -> OutputSignal; + fn bclk_rx_signal(&self) -> OutputSignal; + fn ws_rx_signal(&self) -> OutputSignal; + fn din_signal(&self) -> InputSignal; + } + + #[cfg(any(esp32, esp32s2))] + pub trait RegisterAccessPrivate: Signals + RegBlock { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + I2sInterrupt::RxHung => w.rx_hung().bit(enable), + I2sInterrupt::TxHung => w.tx_hung().bit(enable), + }; + } + w + }); + } + + fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + let ints = self.regs().int_st().read(); + + if ints.rx_hung().bit() { + res.insert(I2sInterrupt::RxHung); + } + if ints.tx_hung().bit() { + res.insert(I2sInterrupt::TxHung); + } + + res + } + + fn clear_interrupts(&self, interrupts: EnumSet) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + I2sInterrupt::RxHung => w.rx_hung().clear_bit_by_one(), + I2sInterrupt::TxHung => w.tx_hung().clear_bit_by_one(), + }; + } + w + }); + } + + fn set_clock(&self, clock_settings: I2sClockDividers) { + self.regs().clkm_conf().modify(|r, w| unsafe { + // select PLL_160M + w.bits(r.bits() | (crate::soc::constants::I2S_DEFAULT_CLK_SRC << 21)) + }); + + #[cfg(esp32)] + self.regs() + .clkm_conf() + .modify(|_, w| w.clka_ena().clear_bit()); + + self.regs().clkm_conf().modify(|_, w| unsafe { + w.clk_en().set_bit(); + w.clkm_div_num().bits(clock_settings.mclk_divider as u8); + w.clkm_div_a().bits(clock_settings.denominator as u8); + w.clkm_div_b().bits(clock_settings.numerator as u8) + }); + + self.regs().sample_rate_conf().modify(|_, w| unsafe { + w.tx_bck_div_num().bits(clock_settings.bclk_divider as u8); + w.rx_bck_div_num().bits(clock_settings.bclk_divider as u8) + }); + } + + fn configure(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + + self.configure_tx(&config.tx_config, config.data_format)?; + self.configure_rx(&config.rx_config, config.data_format)?; + + self.set_clock(config.calculate_clock()); + + self.regs().sample_rate_conf().modify(|_, w| unsafe { + // Having different data formats for each direction would make clock calculations + // more tricky + w.tx_bits_mod().bits(config.data_format.data_bits()); + w.rx_bits_mod().bits(config.data_format.data_bits()) + }); + + self.regs().conf().modify(|_, w| { + w.tx_slave_mod().clear_bit(); + w.rx_slave_mod().clear_bit(); + // Send MSB to the right channel to be consistent with ESP32-S3 et al. + w.tx_msb_right().set_bit(); + w.rx_msb_right().set_bit(); + // ESP32 generates two clock pulses first. If the WS is low, those first clock + // pulses are indistinguishable from real data, which corrupts the first few + // samples. So we send the right channel first (which means WS is high during + // the first sample) to prevent this issue. + w.tx_right_first().set_bit(); + w.rx_right_first().set_bit(); + w.tx_mono().clear_bit(); + w.rx_mono().clear_bit(); + w.sig_loopback().clear_bit() + }); + + self.regs().fifo_conf().modify(|_, w| w.dscr_en().set_bit()); + + self.regs().conf1().modify(|_, w| { + w.tx_pcm_bypass().set_bit(); + w.rx_pcm_bypass().set_bit() + }); + + self.regs().pd_conf().modify(|_, w| { + w.fifo_force_pu().set_bit(); + w.fifo_force_pd().clear_bit() + }); + + self.regs().conf2().modify(|_, w| { + w.camera_en().clear_bit(); + w.lcd_en().clear_bit() + }); + + Ok(()) + } + + fn configure_tx( + &self, + config: &UnitConfig, + data_format: DataFormat, + ) -> Result<(), ConfigError> { + config.validate()?; + + let chan_mod = match config.channels { + Channels::STEREO | Channels::MONO => 0, + Channels::LEFT => 3, + Channels::RIGHT => 4, + _ => unreachable!(), + }; + + let fifo_mod = match (data_format.data_bits(), config.channels == Channels::STEREO) { + (8 | 16, true) => 0, + (8 | 16, false) => 1, + (24 | 32, true) => 2, + (24 | 32, false) => 3, + _ => unreachable!(), + }; + + self.regs().conf().modify(|_, w| { + w.tx_msb_shift().bit(config.msb_shift); + // Short frame synchronization + w.tx_short_sync().bit(config.ws_width == WsWidth::Bit) + }); + + #[cfg(not(esp32))] + self.regs().conf().modify(|_, w| { + // Channel configurations other than Stereo should use same data from DMA + // for both channels + w.tx_dma_equal().bit(config.channels != Channels::STEREO); + // Byte endianness + w.tx_big_endian() + .bit(config.endianness == Endianness::BigEndian) + }); + + self.regs().fifo_conf().modify(|_, w| unsafe { + w.tx_fifo_mod().bits(fifo_mod); + w.tx_fifo_mod_force_en().set_bit() + }); + + self.regs() + .conf_sigle_data() + .modify(|_, w| unsafe { w.sigle_data().bits(config.channels.fill.unwrap_or(0)) }); + + self.regs() + .conf_chan() + .modify(|_, w| unsafe { w.tx_chan_mod().bits(chan_mod) }); + + Ok(()) + } + + fn configure_rx( + &self, + config: &UnitConfig, + data_format: DataFormat, + ) -> Result<(), ConfigError> { + config.validate()?; + + let chan_mod = match config.channels { + Channels::STEREO => 0, + Channels::LEFT | Channels::MONO => 1, + Channels::RIGHT => 2, + _ => unreachable!(), + }; + + let fifo_mod = match (data_format.data_bits(), config.channels == Channels::STEREO) { + (8 | 16, true) => 0, + (8 | 16, false) => 1, + (24 | 32, true) => 2, + (24 | 32, false) => 3, + _ => unreachable!(), + }; + + self.regs().conf().modify(|_, w| { + w.rx_msb_shift().bit(config.msb_shift); + // Short frame synchronization + w.rx_short_sync().bit(config.ws_width == WsWidth::Bit) + }); + + #[cfg(not(esp32))] + self.regs().conf().modify(|_, w| { + // Channel configurations other than Stereo should use same data from DMA + // for both channels + w.rx_dma_equal().bit(config.channels != Channels::STEREO); + // Byte endianness + w.rx_big_endian() + .bit(config.endianness == Endianness::BigEndian) + }); + + self.regs().fifo_conf().modify(|_, w| unsafe { + w.rx_fifo_mod().bits(fifo_mod); + w.rx_fifo_mod_force_en().set_bit() + }); + + self.regs() + .conf_chan() + .modify(|_, w| unsafe { w.rx_chan_mod().bits(chan_mod) }); + + Ok(()) + } + + fn set_master(&self) { + self.regs().conf().modify(|_, w| { + w.rx_slave_mod().clear_bit(); + w.tx_slave_mod().clear_bit() + }); + } + + fn update(&self) { + // nothing to do + } + + fn reset_tx(&self) { + self.regs().conf().modify(|_, w| { + w.tx_reset().set_bit(); + w.tx_fifo_reset().set_bit() + }); + self.regs().conf().modify(|_, w| { + w.tx_reset().clear_bit(); + w.tx_fifo_reset().clear_bit() + }); + + self.regs().lc_conf().modify(|_, w| w.out_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.out_rst().clear_bit()); + + self.regs().int_clr().write(|w| { + w.out_done().clear_bit_by_one(); + w.out_total_eof().clear_bit_by_one() + }); + } + + fn tx_start(&self) { + self.regs().conf().modify(|_, w| w.tx_start().set_bit()); + + while self.regs().state().read().tx_idle().bit_is_set() { + // wait + } + } + + fn tx_stop(&self) { + self.regs().conf().modify(|_, w| w.tx_start().clear_bit()); + } + + fn wait_for_tx_done(&self) { + while self.regs().state().read().tx_idle().bit_is_clear() { + // wait + } + + self.regs().conf().modify(|_, w| w.tx_start().clear_bit()); + } + + fn reset_rx(&self) { + self.regs().conf().modify(|_, w| { + w.rx_reset().set_bit(); + w.rx_fifo_reset().set_bit() + }); + self.regs().conf().modify(|_, w| { + w.rx_reset().clear_bit(); + w.rx_fifo_reset().clear_bit() + }); + + self.regs().lc_conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.in_rst().clear_bit()); + + self.regs().int_clr().write(|w| { + w.in_done().clear_bit_by_one(); + w.in_suc_eof().clear_bit_by_one() + }); + } + + fn rx_start(&self, len: usize) { + self.regs() + .int_clr() + .write(|w| w.in_suc_eof().clear_bit_by_one()); + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + // On ESP32, the eof_num count in words. + let eof_num = len / 4; + } else { + let eof_num = len - 1; + } + } + + self.regs() + .rxeof_num() + .modify(|_, w| unsafe { w.rx_eof_num().bits(eof_num as u32) }); + + self.regs().conf().modify(|_, w| w.rx_start().set_bit()); + } + + fn wait_for_rx_done(&self) { + while self.regs().int_raw().read().in_suc_eof().bit_is_clear() { + // wait + } + + self.regs() + .int_clr() + .write(|w| w.in_suc_eof().clear_bit_by_one()); + } + } + + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] + pub trait RegisterAccessPrivate: Signals + RegBlock { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + I2sInterrupt::RxHung => w.rx_hung().bit(enable), + I2sInterrupt::TxHung => w.tx_hung().bit(enable), + I2sInterrupt::RxDone => w.rx_done().bit(enable), + I2sInterrupt::TxDone => w.tx_done().bit(enable), + }; + } + w + }); + } + + fn listen(&self, interrupts: impl Into>) { + self.enable_listen(interrupts.into(), true); + } + + fn unlisten(&self, interrupts: impl Into>) { + self.enable_listen(interrupts.into(), false); + } + + fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + let ints = self.regs().int_st().read(); + + if ints.rx_hung().bit() { + res.insert(I2sInterrupt::RxHung); + } + if ints.tx_hung().bit() { + res.insert(I2sInterrupt::TxHung); + } + if ints.rx_done().bit() { + res.insert(I2sInterrupt::RxDone); + } + if ints.tx_done().bit() { + res.insert(I2sInterrupt::TxDone); + } + + res + } + + fn clear_interrupts(&self, interrupts: EnumSet) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + I2sInterrupt::RxHung => w.rx_hung().clear_bit_by_one(), + I2sInterrupt::TxHung => w.tx_hung().clear_bit_by_one(), + I2sInterrupt::RxDone => w.rx_done().clear_bit_by_one(), + I2sInterrupt::TxDone => w.tx_done().clear_bit_by_one(), + }; + } + w + }); + } + + #[cfg(any(esp32c3, esp32s3))] + fn set_tx_clock(&self, clock_settings: I2sClockDividers) { + let clkm_div = clock_settings.mclk_dividers(); + + self.regs().tx_clkm_div_conf().modify(|_, w| unsafe { + w.tx_clkm_div_x().bits(clkm_div.x as u16); + w.tx_clkm_div_y().bits(clkm_div.y as u16); + w.tx_clkm_div_yn1().bit(clkm_div.yn1); + w.tx_clkm_div_z().bits(clkm_div.z as u16) + }); + + self.regs().tx_clkm_conf().modify(|_, w| unsafe { + w.clk_en().set_bit(); + w.tx_clk_active().set_bit(); + // for now fixed at 160MHz + w.tx_clk_sel() + .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC); + w.tx_clkm_div_num().bits(clock_settings.mclk_divider as u8) + }); + + self.regs().tx_conf1().modify(|_, w| unsafe { + w.tx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + } + + #[cfg(any(esp32c3, esp32s3))] + fn set_rx_clock(&self, clock_settings: I2sClockDividers) { + let clkm_div = clock_settings.mclk_dividers(); + + self.regs().rx_clkm_div_conf().modify(|_, w| unsafe { + w.rx_clkm_div_x().bits(clkm_div.x as u16); + w.rx_clkm_div_y().bits(clkm_div.y as u16); + w.rx_clkm_div_yn1().bit(clkm_div.yn1); + w.rx_clkm_div_z().bits(clkm_div.z as u16) + }); + + self.regs().rx_clkm_conf().modify(|_, w| unsafe { + w.rx_clk_active().set_bit(); + // for now fixed at 160MHz + w.rx_clk_sel() + .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC); + w.rx_clkm_div_num().bits(clock_settings.mclk_divider as u8); + w.mclk_sel().bit(true) + }); + + self.regs().rx_conf1().modify(|_, w| unsafe { + w.rx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + } + + #[cfg(any(esp32c6, esp32h2))] + fn set_tx_clock(&self, clock_settings: I2sClockDividers) { + // I2S clocks are configured via PCR + use crate::peripherals::PCR; + + let clkm_div = clock_settings.mclk_dividers(); + + PCR::regs().i2s_tx_clkm_div_conf().modify(|_, w| unsafe { + w.i2s_tx_clkm_div_x().bits(clkm_div.x as u16); + w.i2s_tx_clkm_div_y().bits(clkm_div.y as u16); + w.i2s_tx_clkm_div_yn1().bit(clkm_div.yn1); + w.i2s_tx_clkm_div_z().bits(clkm_div.z as u16) + }); + + PCR::regs().i2s_tx_clkm_conf().modify(|_, w| unsafe { + w.i2s_tx_clkm_en().set_bit(); + // for now fixed at 160MHz for C6 and 96MHz for H2 + w.i2s_tx_clkm_sel() + .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC); + w.i2s_tx_clkm_div_num() + .bits(clock_settings.mclk_divider as u8) + }); + + #[cfg(not(esp32h2))] + self.regs().tx_conf1().modify(|_, w| unsafe { + w.tx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + #[cfg(esp32h2)] + self.regs().tx_conf().modify(|_, w| unsafe { + w.tx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + } + + #[cfg(any(esp32c6, esp32h2))] + fn set_rx_clock(&self, clock_settings: I2sClockDividers) { + // I2S clocks are configured via PCR + use crate::peripherals::PCR; + + let clkm_div = clock_settings.mclk_dividers(); + + PCR::regs().i2s_rx_clkm_div_conf().modify(|_, w| unsafe { + w.i2s_rx_clkm_div_x().bits(clkm_div.x as u16); + w.i2s_rx_clkm_div_y().bits(clkm_div.y as u16); + w.i2s_rx_clkm_div_yn1().bit(clkm_div.yn1); + w.i2s_rx_clkm_div_z().bits(clkm_div.z as u16) + }); + + PCR::regs().i2s_rx_clkm_conf().modify(|_, w| unsafe { + w.i2s_rx_clkm_en().set_bit(); + // for now fixed at 160MHz for C6 and 96MHz for H2 + w.i2s_rx_clkm_sel() + .bits(crate::soc::constants::I2S_DEFAULT_CLK_SRC); + w.i2s_rx_clkm_div_num() + .bits(clock_settings.mclk_divider as u8); + w.i2s_mclk_sel().bit(true) + }); + #[cfg(not(esp32h2))] + self.regs().rx_conf1().modify(|_, w| unsafe { + w.rx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + #[cfg(esp32h2)] + self.regs().rx_conf().modify(|_, w| unsafe { + w.rx_bck_div_num() + .bits((clock_settings.bclk_divider - 1) as u8) + }); + } + + fn configure(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + + self.configure_tx(&config.tx_config)?; + self.configure_rx(&config.rx_config)?; + + Ok(()) + } + + fn configure_tx(&self, config: &UnitConfig) -> Result<(), ConfigError> { + use bitfield::Bit; + + config.validate()?; + + let ws_width = config.calculate_ws_width()?; + self.set_tx_clock(config.calculate_clock()); + + self.regs().tx_conf1().modify(|_, w| unsafe { + #[cfg(not(esp32h2))] + w.tx_msb_shift().bit(config.msb_shift); + #[allow(clippy::useless_conversion)] + w.tx_tdm_ws_width().bits((ws_width - 1).try_into().unwrap()); + w.tx_bits_mod().bits(config.data_format.data_bits() - 1); + w.tx_tdm_chan_bits() + .bits(config.data_format.channel_bits() - 1); + w.tx_half_sample_bits() + .bits((config.data_format.data_bits() * config.channels.count) / 2 - 1) + }); + + self.regs().tx_conf().modify(|_, w| unsafe { + w.tx_mono().clear_bit(); + w.tx_mono_fst_vld().set_bit(); + w.tx_stop_en().set_bit(); + w.tx_chan_equal().bit(config.channels.fill.is_none()); + w.tx_tdm_en().set_bit(); + w.tx_pdm_en().clear_bit(); + w.tx_pcm_bypass().set_bit(); + #[cfg(esp32h2)] + w.tx_msb_shift().bit(config.msb_shift); + w.tx_big_endian() + .bit(config.endianness == Endianness::BigEndian); + w.tx_bit_order().bit(config.bit_order == BitOrder::LsbFirst); + w.tx_ws_idle_pol() + .bit(config.ws_polarity == Polarity::ActiveHigh); + w.tx_chan_mod().bits(0) + }); + + self.regs().tx_tdm_ctrl().modify(|_, w| unsafe { + w.tx_tdm_tot_chan_num().bits(config.channels.count - 1); + w.tx_tdm_chan0_en().bit(config.channels.mask.bit(0)); + w.tx_tdm_chan1_en().bit(config.channels.mask.bit(1)); + w.tx_tdm_chan2_en().bit(config.channels.mask.bit(2)); + w.tx_tdm_chan3_en().bit(config.channels.mask.bit(3)); + w.tx_tdm_chan4_en().bit(config.channels.mask.bit(4)); + w.tx_tdm_chan5_en().bit(config.channels.mask.bit(5)); + w.tx_tdm_chan6_en().bit(config.channels.mask.bit(6)); + w.tx_tdm_chan7_en().bit(config.channels.mask.bit(7)); + w.tx_tdm_chan8_en().bit(config.channels.mask.bit(8)); + w.tx_tdm_chan9_en().bit(config.channels.mask.bit(9)); + w.tx_tdm_chan10_en().bit(config.channels.mask.bit(10)); + w.tx_tdm_chan11_en().bit(config.channels.mask.bit(11)); + w.tx_tdm_chan12_en().bit(config.channels.mask.bit(12)); + w.tx_tdm_chan13_en().bit(config.channels.mask.bit(13)); + w.tx_tdm_chan14_en().bit(config.channels.mask.bit(14)); + w.tx_tdm_chan15_en().bit(config.channels.mask.bit(15)); + w.tx_tdm_skip_msk_en().clear_bit() + }); + + self.regs() + .conf_sigle_data() + .modify(|_, w| unsafe { w.single_data().bits(config.channels.fill.unwrap_or(0)) }); + + Ok(()) + } + + fn configure_rx(&self, config: &UnitConfig) -> Result<(), ConfigError> { + use bitfield::Bit; + + config.validate()?; + + let ws_width = config.calculate_ws_width()?; + self.set_rx_clock(config.calculate_clock()); + + self.regs().rx_conf1().modify(|_, w| unsafe { + #[cfg(not(esp32h2))] + w.rx_msb_shift().bit(config.msb_shift); + #[allow(clippy::useless_conversion)] + w.rx_tdm_ws_width().bits((ws_width - 1).try_into().unwrap()); + w.rx_bits_mod().bits(config.data_format.data_bits() - 1); + w.rx_tdm_chan_bits() + .bits(config.data_format.channel_bits() - 1); + w.rx_half_sample_bits() + .bits((config.data_format.data_bits() * config.channels.count) / 2 - 1) + }); + + self.regs().rx_conf().modify(|_, w| unsafe { + w.rx_mono().clear_bit(); + w.rx_mono_fst_vld().set_bit(); + w.rx_stop_mode().bits(2); + w.rx_tdm_en().set_bit(); + w.rx_pdm_en().clear_bit(); + w.rx_pcm_bypass().set_bit(); + #[cfg(esp32h2)] + w.rx_msb_shift().bit(config.msb_shift); + w.rx_big_endian() + .bit(config.endianness == Endianness::BigEndian); + w.rx_bit_order().bit(config.bit_order == BitOrder::LsbFirst); + w.rx_ws_idle_pol() + .bit(config.ws_polarity == Polarity::ActiveHigh) + }); + + self.regs().rx_tdm_ctrl().modify(|_, w| unsafe { + w.rx_tdm_tot_chan_num().bits(config.channels.count - 1); + w.rx_tdm_pdm_chan0_en().bit(config.channels.mask.bit(0)); + w.rx_tdm_pdm_chan1_en().bit(config.channels.mask.bit(1)); + w.rx_tdm_pdm_chan2_en().bit(config.channels.mask.bit(2)); + w.rx_tdm_pdm_chan3_en().bit(config.channels.mask.bit(3)); + w.rx_tdm_pdm_chan4_en().bit(config.channels.mask.bit(4)); + w.rx_tdm_pdm_chan5_en().bit(config.channels.mask.bit(5)); + w.rx_tdm_pdm_chan6_en().bit(config.channels.mask.bit(6)); + w.rx_tdm_pdm_chan7_en().bit(config.channels.mask.bit(7)); + w.rx_tdm_chan8_en().bit(config.channels.mask.bit(8)); + w.rx_tdm_chan9_en().bit(config.channels.mask.bit(9)); + w.rx_tdm_chan10_en().bit(config.channels.mask.bit(10)); + w.rx_tdm_chan11_en().bit(config.channels.mask.bit(11)); + w.rx_tdm_chan12_en().bit(config.channels.mask.bit(12)); + w.rx_tdm_chan13_en().bit(config.channels.mask.bit(13)); + w.rx_tdm_chan14_en().bit(config.channels.mask.bit(14)); + w.rx_tdm_chan15_en().bit(config.channels.mask.bit(15)) + }); + + Ok(()) + } + + fn set_master(&self) { + self.regs() + .tx_conf() + .modify(|_, w| w.tx_slave_mod().clear_bit()); + self.regs() + .rx_conf() + .modify(|_, w| w.rx_slave_mod().clear_bit()); + } + + fn update(&self) { + self.regs() + .tx_conf() + .modify(|_, w| w.tx_update().clear_bit()); + self.regs().tx_conf().modify(|_, w| w.tx_update().set_bit()); + + self.regs() + .rx_conf() + .modify(|_, w| w.rx_update().clear_bit()); + self.regs().rx_conf().modify(|_, w| w.rx_update().set_bit()); + } + + fn reset_tx(&self) { + self.regs().tx_conf().modify(|_, w| { + w.tx_reset().set_bit(); + w.tx_fifo_reset().set_bit() + }); + self.regs().tx_conf().modify(|_, w| { + w.tx_reset().clear_bit(); + w.tx_fifo_reset().clear_bit() + }); + + self.regs().int_clr().write(|w| { + w.tx_done().clear_bit_by_one(); + w.tx_hung().clear_bit_by_one() + }); + } + + fn tx_start(&self) { + self.regs().tx_conf().modify(|_, w| w.tx_start().set_bit()); + } + + fn tx_stop(&self) { + self.regs() + .tx_conf() + .modify(|_, w| w.tx_start().clear_bit()); + } + + fn wait_for_tx_done(&self) { + while self.regs().state().read().tx_idle().bit_is_clear() { + // wait + } + + self.regs() + .tx_conf() + .modify(|_, w| w.tx_start().clear_bit()); + } + + fn reset_rx(&self) { + self.regs() + .rx_conf() + .modify(|_, w| w.rx_start().clear_bit()); + + self.regs().rx_conf().modify(|_, w| { + w.rx_reset().set_bit(); + w.rx_fifo_reset().set_bit() + }); + self.regs().rx_conf().modify(|_, w| { + w.rx_reset().clear_bit(); + w.rx_fifo_reset().clear_bit() + }); + + self.regs().int_clr().write(|w| { + w.rx_done().clear_bit_by_one(); + w.rx_hung().clear_bit_by_one() + }); + } + + fn rx_start(&self, len: usize) { + let len = len - 1; + + self.regs() + .rxeof_num() + .write(|w| unsafe { w.rx_eof_num().bits(len as u16) }); + self.regs().rx_conf().modify(|_, w| w.rx_start().set_bit()); + } + + fn wait_for_rx_done(&self) { + while self.regs().int_raw().read().rx_done().bit_is_clear() { + // wait + } + + self.regs() + .int_clr() + .write(|w| w.rx_done().clear_bit_by_one()); + } + } + + impl RegBlock for I2S0<'_> { + fn regs(&self) -> &RegisterBlock { + unsafe { &*I2S0::PTR.cast::() } + } + + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::I2s0 + } + } + + impl RegisterAccessPrivate for I2S0<'_> {} + + impl Signals for crate::peripherals::I2S0<'_> { + #[cfg(not(esp32))] // MCLK on ESP32 requires special handling + fn mclk_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + OutputSignal::CLK_I2S + } else if #[cfg(esp32s3)] { + OutputSignal::I2S0_MCLK + } else { + OutputSignal::I2S_MCLK + } + } + } + + fn bclk_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + OutputSignal::I2S0O_BCK + } else { + OutputSignal::I2SO_BCK + } + } + } + + fn ws_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + OutputSignal::I2S0O_WS + } else { + OutputSignal::I2SO_WS + } + } + } + + fn dout_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + OutputSignal::I2S0O_DATA_23 + } else if #[cfg(esp32s2)] { + OutputSignal::I2S0O_DATA_OUT23 + } else if #[cfg(esp32s3)] { + OutputSignal::I2S0O_SD + } else { + OutputSignal::I2SO_SD + } + } + } + + fn bclk_rx_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + OutputSignal::I2S0I_BCK + } else { + OutputSignal::I2SI_BCK + } + } + } + + fn ws_rx_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + OutputSignal::I2S0I_WS + } else { + OutputSignal::I2SI_WS + } + } + } + + fn din_signal(&self) -> InputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + InputSignal::I2S0I_DATA_15 + } else if #[cfg(esp32s2)] { + InputSignal::I2S0I_DATA_IN15 + } else if #[cfg(esp32s3)] { + InputSignal::I2S0I_SD + } else { + InputSignal::I2SI_SD + } + } + } + } + + #[cfg(soc_has_i2s1)] + impl RegBlock for I2S1<'_> { + fn regs(&self) -> &RegisterBlock { + unsafe { &*I2S1::PTR.cast::() } + } + + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::I2s1 + } + } + + #[cfg(soc_has_i2s1)] + impl RegisterAccessPrivate for I2S1<'_> {} + + #[cfg(soc_has_i2s1)] + impl Signals for crate::peripherals::I2S1<'_> { + #[cfg(not(esp32))] // MCLK on ESP32 requires special handling + fn mclk_signal(&self) -> OutputSignal { + OutputSignal::I2S1_MCLK + } + + fn bclk_signal(&self) -> OutputSignal { + OutputSignal::I2S1O_BCK + } + + fn ws_signal(&self) -> OutputSignal { + OutputSignal::I2S1O_WS + } + + fn dout_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + OutputSignal::I2S1O_DATA_23 + } else { + OutputSignal::I2S1O_SD + } + } + } + + fn bclk_rx_signal(&self) -> OutputSignal { + OutputSignal::I2S1I_BCK + } + + fn ws_rx_signal(&self) -> OutputSignal { + OutputSignal::I2S1I_WS + } + + fn din_signal(&self) -> InputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + InputSignal::I2S1I_DATA_15 + } else { + InputSignal::I2S1I_SD + } + } + } + } + + impl RegBlock for super::AnyI2s<'_> { + fn regs(&self) -> &RegisterBlock { + match &self.0 { + #[cfg(soc_has_i2s0)] + AnyI2sInner::I2s0(i2s) => RegBlock::regs(i2s), + #[cfg(soc_has_i2s1)] + AnyI2sInner::I2s1(i2s) => RegBlock::regs(i2s), + } + } + + delegate::delegate! { + to match &self.0 { + #[cfg(soc_has_i2s0)] + AnyI2sInner::I2s0(i2s) => i2s, + #[cfg(soc_has_i2s1)] + AnyI2sInner::I2s1(i2s) => i2s, + } { + fn peripheral(&self) -> crate::system::Peripheral; + } + } + } + + impl RegisterAccessPrivate for super::AnyI2s<'_> {} + + impl super::AnyI2s<'_> { + delegate::delegate! { + to match &self.0 { + #[cfg(soc_has_i2s0)] + AnyI2sInner::I2s0(i2s) => i2s, + #[cfg(soc_has_i2s1)] + AnyI2sInner::I2s1(i2s) => i2s, + } { + fn bind_peri_interrupt(&self, handler: InterruptHandler); + fn disable_peri_interrupt_on_all_cores(&self); + } + } + + pub(super) fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.disable_peri_interrupt_on_all_cores(); + self.bind_peri_interrupt(handler); + } + } + + impl Signals for super::AnyI2s<'_> { + delegate::delegate! { + to match &self.0 { + #[cfg(soc_has_i2s0)] + AnyI2sInner::I2s0(i2s) => i2s, + #[cfg(soc_has_i2s1)] + AnyI2sInner::I2s1(i2s) => i2s, + } { + #[cfg(not(esp32))] + fn mclk_signal(&self) -> OutputSignal; + fn bclk_signal(&self) -> OutputSignal; + fn ws_signal(&self) -> OutputSignal; + fn dout_signal(&self) -> OutputSignal; + fn bclk_rx_signal(&self) -> OutputSignal; + fn ws_rx_signal(&self) -> OutputSignal; + fn din_signal(&self) -> InputSignal; + } + } + } + + pub struct I2sClockDividers { + mclk_divider: u32, + bclk_divider: u32, + denominator: u32, + numerator: u32, + } + + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] + pub struct I2sMclkDividers { + x: u32, + y: u32, + z: u32, + yn1: bool, + } + + impl I2sClockDividers { + pub fn new(sample_rate: Rate, channels: u8, data_bits: u8) -> I2sClockDividers { + // this loosely corresponds to `i2s_std_calculate_clock` and + // `i2s_ll_tx_set_mclk` in esp-idf + // + // main difference is we are using fixed-point arithmetic here + + // If data_bits is a power of two, use 256 as the mclk_multiple + // If data_bits is 24, use 192 (24 * 8) as the mclk_multiple + let mclk_multiple = if data_bits == 24 { 192 } else { 256 }; + let sclk = crate::soc::constants::I2S_SCLK; // for now it's fixed 160MHz and 96MHz (just H2) + + let rate = sample_rate.as_hz(); + + let bclk = rate * channels as u32 * data_bits as u32; + let mclk = rate * mclk_multiple; + let bclk_divider = mclk / bclk; + let mut mclk_divider = sclk / mclk; + + let mut ma: u32; + let mut mb: u32; + let mut denominator: u32 = 0; + let mut numerator: u32 = 0; + + let freq_diff = sclk.abs_diff(mclk * mclk_divider); + + if freq_diff != 0 { + let decimal = freq_diff as u64 * 10000 / mclk as u64; + + // Carry bit if the decimal is greater than 1.0 - 1.0 / (63.0 * 2) = 125.0 / + // 126.0 + if decimal > 1250000 / 126 { + mclk_divider += 1; + } else { + let mut min: u32 = !0; + + for a in 2..=I2S_LL_MCLK_DIVIDER_MAX { + let b = (a as u64) * (freq_diff as u64 * 10000u64 / mclk as u64) + 5000; + ma = ((freq_diff as u64 * 10000u64 * a as u64) / 10000) as u32; + mb = (mclk as u64 * (b / 10000)) as u32; + + if ma == mb { + denominator = a as u32; + numerator = (b / 10000) as u32; + break; + } + + if mb.abs_diff(ma) < min { + denominator = a as u32; + numerator = (b / 10000) as u32; + min = mb.abs_diff(ma); + } + } + } + } + + I2sClockDividers { + mclk_divider, + bclk_divider, + denominator, + numerator, + } + } + + #[cfg(any(esp32c3, esp32s3, esp32c6, esp32h2))] + fn mclk_dividers(&self) -> I2sMclkDividers { + let x; + let y; + let z; + let yn1; + + if self.denominator == 0 || self.numerator == 0 { + x = 0; + y = 0; + z = 0; + yn1 = true; + } else if self.numerator > self.denominator / 2 { + x = self + .denominator + .overflowing_div(self.denominator.overflowing_sub(self.numerator).0) + .0 + .overflowing_sub(1) + .0; + y = self.denominator % (self.denominator.overflowing_sub(self.numerator).0); + z = self.denominator.overflowing_sub(self.numerator).0; + yn1 = true; + } else { + x = self.denominator / self.numerator - 1; + y = self.denominator % self.numerator; + z = self.numerator; + yn1 = false; + } + + I2sMclkDividers { x, y, z, yn1 } + } + } +} + +/// Async functionality +pub mod asynch { + use super::{Error, I2sRx, I2sTx, RegisterAccessPrivate}; + use crate::{ + Async, + dma::{ + DmaEligible, + ReadBuffer, + RxCircularState, + TxCircularState, + WriteBuffer, + asynch::{DmaRxDoneChFuture, DmaRxFuture, DmaTxDoneChFuture, DmaTxFuture}, + }, + }; + + impl<'d> I2sTx<'d, Async> { + /// One-shot write I2S. + pub async fn write_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + let (ptr, len) = (words.as_ptr(), words.len()); + + self.i2s.reset_tx(); + + let future = DmaTxFuture::new(&mut self.tx_channel); + + unsafe { + self.tx_chain.fill_for_tx(false, ptr, len)?; + future + .tx + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) + .and_then(|_| future.tx.start_transfer())?; + } + + self.i2s.tx_start(); + future.await?; + + Ok(()) + } + + /// Continuously write to I2S. Returns [I2sWriteDmaTransferAsync] + pub fn write_dma_circular_async( + mut self, + words: TXBUF, + ) -> Result, Error> { + let (ptr, len) = unsafe { words.read_buffer() }; + + // Reset TX unit and TX FIFO + self.i2s.reset_tx(); + + // Enable corresponding interrupts if needed + + // configure DMA outlink + unsafe { + self.tx_chain.fill_for_tx(true, ptr, len)?; + self.tx_channel + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) + .and_then(|_| self.tx_channel.start_transfer())?; + } + + // set I2S_TX_STOP_EN if needed + + // start: set I2S_TX_START + self.i2s.tx_start(); + + let state = TxCircularState::new(&mut self.tx_chain); + Ok(I2sWriteDmaTransferAsync { + i2s_tx: self, + state, + _buffer: words, + }) + } + } + + /// An in-progress async circular DMA write transfer. + pub struct I2sWriteDmaTransferAsync<'d, BUFFER> { + i2s_tx: I2sTx<'d, Async>, + state: TxCircularState, + _buffer: BUFFER, + } + + impl I2sWriteDmaTransferAsync<'_, BUFFER> { + /// How many bytes can be pushed into the DMA transaction. + /// Will wait for more than 0 bytes available. + pub async fn available(&mut self) -> Result { + loop { + self.state.update(&self.i2s_tx.tx_channel)?; + let res = self.state.available; + + if res != 0 { + break Ok(res); + } + + DmaTxDoneChFuture::new(&mut self.i2s_tx.tx_channel).await? + } + } + + /// Push bytes into the DMA transaction. + pub async fn push(&mut self, data: &[u8]) -> Result { + let avail = self.available().await?; + let to_send = usize::min(avail, data.len()); + Ok(self.state.push(&data[..to_send])?) + } + + /// Push bytes into the DMA buffer via the given closure. + /// The closure *must* return the actual number of bytes written. + /// The closure *might* get called with a slice which is smaller than + /// the total available buffer. Only useful for circular DMA + /// transfers + pub async fn push_with( + &mut self, + f: impl FnOnce(&mut [u8]) -> usize, + ) -> Result { + let _avail = self.available().await; + Ok(self.state.push_with(f)?) + } + } + + impl<'d> I2sRx<'d, Async> { + /// One-shot read I2S. + pub async fn read_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + let (ptr, len) = (words.as_mut_ptr(), words.len()); + + if !len.is_multiple_of(4) { + return Err(Error::IllegalArgument); + } + + // Reset RX unit and RX FIFO + self.i2s.reset_rx(); + + let future = DmaRxFuture::new(&mut self.rx_channel); + + // configure DMA inlink + unsafe { + self.rx_chain.fill_for_rx(false, ptr, len)?; + future + .rx + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) + .and_then(|_| future.rx.start_transfer())?; + } + + // start: set I2S_RX_START + self.i2s.rx_start(len); + + future.await?; + + Ok(()) + } + + /// Continuously read from I2S. Returns [I2sReadDmaTransferAsync] + pub fn read_dma_circular_async( + mut self, + mut words: RXBUF, + ) -> Result, Error> + where + RXBUF: WriteBuffer, + { + let (ptr, len) = unsafe { words.write_buffer() }; + + if !len.is_multiple_of(4) { + return Err(Error::IllegalArgument); + } + + // Reset RX unit and RX FIFO + self.i2s.reset_rx(); + + // Enable corresponding interrupts if needed + + // configure DMA inlink + unsafe { + self.rx_chain.fill_for_rx(true, ptr, len)?; + self.rx_channel + .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) + .and_then(|_| self.rx_channel.start_transfer())?; + } + + // start: set I2S_RX_START + self.i2s.rx_start(len); + + let state = RxCircularState::new(&mut self.rx_chain); + Ok(I2sReadDmaTransferAsync { + i2s_rx: self, + state, + _buffer: words, + }) + } + } + + /// An in-progress async circular DMA read transfer. + pub struct I2sReadDmaTransferAsync<'d, BUFFER> { + i2s_rx: I2sRx<'d, Async>, + state: RxCircularState, + _buffer: BUFFER, + } + + impl I2sReadDmaTransferAsync<'_, BUFFER> { + /// How many bytes can be popped from the DMA transaction. + /// Will wait for more than 0 bytes available. + pub async fn available(&mut self) -> Result { + loop { + self.state.update()?; + + let res = self.state.available; + + if res != 0 { + break Ok(res); + } + + DmaRxDoneChFuture::new(&mut self.i2s_rx.rx_channel).await?; + } + } + + /// Pop bytes from the DMA transaction. + pub async fn pop(&mut self, data: &mut [u8]) -> Result { + let avail = self.available().await?; + let to_rcv = usize::min(avail, data.len()); + Ok(self.state.pop(&mut data[..to_rcv])?) + } + } +} diff --git a/esp-hal/src/i2s/mod.rs b/esp-hal/src/i2s/mod.rs new file mode 100644 index 00000000000..312e56e1588 --- /dev/null +++ b/esp-hal/src/i2s/mod.rs @@ -0,0 +1,34 @@ +//! # Inter-IC Sound (I2S) + +use crate::dma::DmaEligible; + +pub mod master; + +#[cfg(esp32)] +pub mod parallel; + +crate::any_peripheral! { + /// Any I2S peripheral. + pub peripheral AnyI2s<'d> { + #[cfg(soc_has_i2s0)] + I2s0(crate::peripherals::I2S0<'d>), + #[cfg(soc_has_i2s1)] + I2s1(crate::peripherals::I2S1<'d>), + } +} + +impl<'d> DmaEligible for AnyI2s<'d> { + #[cfg(dma_kind = "gdma")] + type Dma = crate::dma::AnyGdmaChannel<'d>; + #[cfg(dma_kind = "pdma")] + type Dma = crate::dma::AnyI2sDmaChannel<'d>; + + fn dma_peripheral(&self) -> crate::dma::DmaPeripheral { + match &self.0 { + #[cfg(soc_has_i2s0)] + any::Inner::I2s0(_) => crate::dma::DmaPeripheral::I2s0, + #[cfg(soc_has_i2s1)] + any::Inner::I2s1(_) => crate::dma::DmaPeripheral::I2s1, + } + } +} diff --git a/esp-hal/src/i2s/parallel.rs b/esp-hal/src/i2s/parallel.rs new file mode 100644 index 00000000000..76ea503d0a6 --- /dev/null +++ b/esp-hal/src/i2s/parallel.rs @@ -0,0 +1,791 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Parallel Interface (via I2S) +//! +//! ## Overview +//! The I2S parallel interface allows for high-speed data transfer between the +//! ESP32 and external devices. It is commonly used to external devices such as +//! LED matrix, LCD display, and Printer. Only TX is implemented. Each +//! unit can have up to 8 or 16 data signals (depending on your target hardware) +//! plus 1 clock signal. +//! +//! ## Notes +//! +//! Data output is interleaved: +//! - 8bit: [A, B, C, D] is output as [C, D, A, B] (i.e., swapped as 16bit words) +//! - 16bit: [A, B, C, D] is output as [B, A, D, C] (i.e., 16bit words are swapped) +#![cfg_attr(esp32, doc = "")] +#![cfg_attr( + esp32, + doc = "I2S0 does not support true 8bit parallel output, so if you want to do 8bit" +)] +#![cfg_attr( + esp32, + doc = "you should use I2S1. If you have to use I2S0, it will only output the even" +)] +#![cfg_attr(esp32, doc = "bytes! so [A, B, C, D] will be output as [A, C]!!!!")] +#![cfg_attr(esp32, doc = "")] +//! ## Configuration +//! +//! The driver uses DMA (Direct Memory Access) for efficient data transfer and +//! supports various configurations, such as different data formats, standards +//! (e.g., Philips) and pin configurations. It relies on other peripheral +//! modules, such as +//! - `GPIO` +//! - `DMA` +//! - `system` (to configure and enable the I2S peripheral) +//! +//! ## Examples +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::dma::DmaTxBuf; +//! # use esp_hal::dma_buffers; +//! # use esp_hal::delay::Delay; +//! # use esp_hal::i2s::parallel::{I2sParallel, TxEightBits}; +//! +//! const BUFFER_SIZE: usize = 256; +//! +//! let delay = Delay::new(); +//! let dma_channel = peripherals.DMA_I2S1; +//! let i2s = peripherals.I2S1; +//! let clock = peripherals.GPIO25; +//! +//! let pins = TxEightBits::new( +//! peripherals.GPIO16, +//! peripherals.GPIO4, +//! peripherals.GPIO17, +//! peripherals.GPIO18, +//! peripherals.GPIO5, +//! peripherals.GPIO19, +//! peripherals.GPIO12, +//! peripherals.GPIO14, +//! ); +//! +//! let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, BUFFER_SIZE); +//! let mut parallel = +//! I2sParallel::new(i2s, dma_channel, Rate::from_mhz(1), pins, clock).into_async(); +//! +//! for (i, data) in tx_buffer.chunks_mut(4).enumerate() { +//! let offset = i * 4; +//! // i2s parallel driver expects the buffer to be interleaved +//! data[0] = (offset + 2) as u8; +//! data[1] = (offset + 3) as u8; +//! data[2] = offset as u8; +//! data[3] = (offset + 1) as u8; +//! } +//! +//! let mut tx_buf: DmaTxBuf = +//! DmaTxBuf::new(tx_descriptors, tx_buffer).expect("DmaTxBuf::new failed"); +//! +//! // Sending 256 bytes. +//! loop { +//! let xfer = match parallel.send(tx_buf) { +//! Ok(xfer) => xfer, +//! Err(_) => { +//! panic!("Failed to send buffer"); +//! } +//! }; +//! (parallel, tx_buf) = xfer.wait(); +//! delay.delay_millis(10); +//! } +//! # } +//! ``` +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + Channel, + ChannelTx, + DmaChannelFor, + DmaEligible, + DmaError, + DmaTxBuffer, + PeripheralTxChannel, + asynch::DmaTxFuture, + }, + gpio::{ + OutputConfig, + OutputSignal, + interconnect::{self, PeripheralOutput}, + }, + i2s::AnyI2s, + pac::i2s0::RegisterBlock, + peripherals::{I2S0, I2S1}, + system::PeripheralGuard, + time::Rate, +}; + +#[doc(hidden)] +pub trait TxPins<'d> { + fn bus_width(&self) -> u8; + fn configure(&mut self, instance: &(impl Instance + 'd)); +} + +/// Represents a group of 16 output pins configured for 16-bit parallel data +/// transmission. +pub struct TxSixteenBits<'d> { + pins: [interconnect::OutputSignal<'d>; 16], +} + +impl<'d> TxSixteenBits<'d> { + #[expect(clippy::too_many_arguments)] + /// Creates a new `TxSixteenBits` instance with the provided output pins. + pub fn new( + pin_0: impl PeripheralOutput<'d>, + pin_1: impl PeripheralOutput<'d>, + pin_2: impl PeripheralOutput<'d>, + pin_3: impl PeripheralOutput<'d>, + pin_4: impl PeripheralOutput<'d>, + pin_5: impl PeripheralOutput<'d>, + pin_6: impl PeripheralOutput<'d>, + pin_7: impl PeripheralOutput<'d>, + pin_8: impl PeripheralOutput<'d>, + pin_9: impl PeripheralOutput<'d>, + pin_10: impl PeripheralOutput<'d>, + pin_11: impl PeripheralOutput<'d>, + pin_12: impl PeripheralOutput<'d>, + pin_13: impl PeripheralOutput<'d>, + pin_14: impl PeripheralOutput<'d>, + pin_15: impl PeripheralOutput<'d>, + ) -> Self { + Self { + pins: [ + pin_0.into(), + pin_1.into(), + pin_2.into(), + pin_3.into(), + pin_4.into(), + pin_5.into(), + pin_6.into(), + pin_7.into(), + pin_8.into(), + pin_9.into(), + pin_10.into(), + pin_11.into(), + pin_12.into(), + pin_13.into(), + pin_14.into(), + pin_15.into(), + ], + } + } +} + +impl<'d> TxPins<'d> for TxSixteenBits<'d> { + fn bus_width(&self) -> u8 { + self.pins.len() as u8 + } + + fn configure(&mut self, instance: &(impl Instance + 'd)) { + let bits = self.bus_width(); + for (i, pin) in self.pins.iter_mut().enumerate() { + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + instance.data_out_signal(i, bits).connect_to(pin); + } + } +} + +/// Represents a group of 8 output pins configured for 8-bit parallel data +/// transmission. +pub struct TxEightBits<'d> { + pins: [interconnect::OutputSignal<'d>; 8], +} + +impl<'d> TxEightBits<'d> { + #[expect(clippy::too_many_arguments)] + /// Creates a new `TxSEightBits` instance with the provided output pins. + pub fn new( + pin_0: impl PeripheralOutput<'d>, + pin_1: impl PeripheralOutput<'d>, + pin_2: impl PeripheralOutput<'d>, + pin_3: impl PeripheralOutput<'d>, + pin_4: impl PeripheralOutput<'d>, + pin_5: impl PeripheralOutput<'d>, + pin_6: impl PeripheralOutput<'d>, + pin_7: impl PeripheralOutput<'d>, + ) -> Self { + Self { + pins: [ + pin_0.into(), + pin_1.into(), + pin_2.into(), + pin_3.into(), + pin_4.into(), + pin_5.into(), + pin_6.into(), + pin_7.into(), + ], + } + } +} + +impl<'d> TxPins<'d> for TxEightBits<'d> { + fn bus_width(&self) -> u8 { + self.pins.len() as u8 + } + + fn configure(&mut self, instance: &(impl Instance + 'd)) { + let bits = self.bus_width(); + for (i, pin) in self.pins.iter_mut().enumerate() { + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + instance.data_out_signal(i, bits).connect_to(pin); + } + } +} + +/// I2S Parallel Interface +pub struct I2sParallel<'d, Dm> +where + Dm: DriverMode, +{ + instance: AnyI2s<'d>, + tx_channel: ChannelTx>>, + _guard: PeripheralGuard, +} + +impl<'d> I2sParallel<'d, Blocking> { + /// Create a new I2S Parallel Interface + pub fn new( + i2s: impl Instance + 'd, + channel: impl DmaChannelFor>, + frequency: Rate, + mut pins: impl TxPins<'d>, + clock_pin: impl PeripheralOutput<'d>, + ) -> Self { + let channel = Channel::new(channel.degrade()); + channel.runtime_ensure_compatible(&i2s); + + let i2s = i2s.degrade(); + + let guard = PeripheralGuard::new(i2s.peripheral()); + + // configure the I2S peripheral for parallel mode + i2s.setup(frequency, pins.bus_width()); + // setup the clock pin + let clock_pin = clock_pin.into(); + + clock_pin.apply_output_config(&OutputConfig::default()); + clock_pin.set_output_enable(true); + + i2s.ws_signal().connect_to(&clock_pin); + + pins.configure(&i2s); + Self { + instance: i2s, + tx_channel: channel.tx, + _guard: guard, + } + } + + /// Converts the I2S instance into async mode. + pub fn into_async(self) -> I2sParallel<'d, Async> { + I2sParallel { + instance: self.instance, + tx_channel: self.tx_channel.into_async(), + _guard: self._guard, + } + } +} + +impl<'d> I2sParallel<'d, Async> { + /// Converts the I2S instance into async mode. + pub fn into_blocking(self) -> I2sParallel<'d, Blocking> { + I2sParallel { + instance: self.instance, + tx_channel: self.tx_channel.into_blocking(), + _guard: self._guard, + } + } +} + +impl<'d, Dm> I2sParallel<'d, Dm> +where + Dm: DriverMode, +{ + /// Write data to the I2S peripheral + pub fn send( + mut self, + mut data: BUF, + ) -> Result, (DmaError, Self, BUF)> { + self.instance.tx_reset(); + self.instance.tx_fifo_reset(); + let result = unsafe { + self.tx_channel + .prepare_transfer(self.instance.dma_peripheral(), &mut data) + } + .and_then(|_| self.tx_channel.start_transfer()); + if let Err(err) = result { + return Err((err, self, data)); + } + self.instance.tx_start(); + Ok(I2sParallelTransfer { + i2s: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(data.into_view()), + }) + } +} + +/// Represents an ongoing (or potentially finished) transfer using the i2s +/// parallel interface +pub struct I2sParallelTransfer<'d, BUF, Dm> +where + BUF: DmaTxBuffer, + Dm: DriverMode, +{ + i2s: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, BUF, Dm> I2sParallelTransfer<'d, BUF, Dm> +where + BUF: DmaTxBuffer, + Dm: DriverMode, +{ + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.i2s.instance.is_tx_done() + } + + /// Wait for the transfer to finish + pub fn wait(mut self) -> (I2sParallel<'d, Dm>, BUF::Final) { + self.i2s.instance.tx_wait_done(); + let i2s = unsafe { ManuallyDrop::take(&mut self.i2s) }; + let view = unsafe { ManuallyDrop::take(&mut self.buf_view) }; + core::mem::forget(self); + (i2s, BUF::from_view(view)) + } + + fn stop_peripherals(&mut self) { + self.i2s.instance.tx_stop(); + self.i2s.tx_channel.stop_transfer(); + } +} + +impl I2sParallelTransfer<'_, BUF, Async> +where + BUF: DmaTxBuffer, +{ + /// Wait for the transfer to finish + pub async fn wait_for_done(&mut self) -> Result<(), DmaError> { + DmaTxFuture::new(&mut self.i2s.tx_channel).await + } +} + +impl Deref for I2sParallelTransfer<'_, BUF, Dm> +where + BUF: DmaTxBuffer, + Dm: DriverMode, +{ + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for I2sParallelTransfer<'_, BUF, Dm> +where + BUF: DmaTxBuffer, + Dm: DriverMode, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for I2sParallelTransfer<'_, BUF, Dm> +where + BUF: DmaTxBuffer, + Dm: DriverMode, +{ + fn drop(&mut self) { + self.stop_peripherals(); + + // SAFETY: This is Drop, we know that self.i2s and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.i2s); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct I2sClockDividers { + pub mclk_divider: u32, + pub bclk_divider: u32, + pub denominator: u32, + pub numerator: u32, +} + +fn calculate_clock(sample_rate: Rate, data_bits: u8) -> I2sClockDividers { + // this loosely corresponds to `i2s_std_calculate_clock` and + // `i2s_ll_tx_set_mclk` in esp-idf + // + // main difference is we are using fixed-point arithmetic here + // plus adjusted for parallel interface clocking + + let sclk = crate::soc::constants::I2S_SCLK; // for now it's fixed 160MHz and 96MHz (just H2) + + let rate = sample_rate.as_hz(); + + let mclk = rate * 2; + let bclk_divider: u32 = if data_bits == 8 { 2 } else { 1 }; + let mut mclk_divider = sclk / mclk; + + let mut ma: u32; + let mut mb: u32; + let mut denominator: u32 = 0; + let mut numerator: u32 = 0; + + let freq_diff = sclk.abs_diff(mclk * mclk_divider); + + if freq_diff != 0 { + let decimal = freq_diff as u64 * 10000 / mclk as u64; + // Carry bit if the decimal is greater than 1.0 - 1.0 / (63.0 * 2) = 125.0 / + // 126.0 + if decimal > 1250000 / 126 { + mclk_divider += 1; + } else { + let mut min: u32 = !0; + + for a in 2..=crate::i2s::master::I2S_LL_MCLK_DIVIDER_MAX { + let b = (a as u64) * (freq_diff as u64 * 10000u64 / mclk as u64) + 5000; + ma = ((freq_diff as u64 * 10000u64 * a as u64) / 10000) as u32; + mb = (mclk as u64 * (b / 10000)) as u32; + + if ma == mb { + denominator = a as u32; + numerator = (b / 10000) as u32; + break; + } + + if mb.abs_diff(ma) < min { + denominator = a as u32; + numerator = b as u32; + min = mb.abs_diff(ma); + } + } + } + } + + I2sClockDividers { + mclk_divider, + bclk_divider, + denominator, + numerator, + } +} +#[doc(hidden)] +#[allow(private_bounds)] +pub trait PrivateInstance: DmaEligible { + fn regs(&self) -> &RegisterBlock; + fn peripheral(&self) -> crate::system::Peripheral; + fn ws_signal(&self) -> OutputSignal; + fn data_out_signal(&self, i: usize, bits: u8) -> OutputSignal; + + fn rx_reset(&self) { + self.regs().conf().modify(|_, w| w.rx_reset().set_bit()); + self.regs().conf().modify(|_, w| w.rx_reset().clear_bit()); + } + + fn rx_dma_reset(&self) { + self.regs().lc_conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.in_rst().clear_bit()); + } + + fn rx_fifo_reset(&self) { + self.regs() + .conf() + .modify(|_, w| w.rx_fifo_reset().set_bit()); + self.regs() + .conf() + .modify(|_, w| w.rx_fifo_reset().clear_bit()); + } + + fn tx_reset(&self) { + self.regs().conf().modify(|_, w| w.tx_reset().set_bit()); + // without this delay starting a subsequent transfer will hang waiting + // for tx_idle to clear (the transfer does not start). + // While 20 clocks works for 80MHz cpu but 100 is needed for 240MHz! + xtensa_lx::timer::delay(100); + self.regs().conf().modify(|_, w| w.tx_reset().clear_bit()); + } + + fn tx_dma_reset(&self) { + self.regs().lc_conf().modify(|_, w| w.out_rst().set_bit()); + self.regs().lc_conf().modify(|_, w| w.out_rst().clear_bit()); + } + + fn tx_fifo_reset(&self) { + self.regs() + .conf() + .modify(|_, w| w.tx_fifo_reset().set_bit()); + self.regs() + .conf() + .modify(|_, w| w.tx_fifo_reset().clear_bit()); + } + + fn tx_clear_interrupts(&self) { + self.regs().int_clr().write(|w| { + w.out_done().clear_bit_by_one(); + w.out_total_eof().clear_bit_by_one() + }); + } + + fn tx_start(&self) { + // wait for data to show up in the fifo + while self.regs().int_raw().read().tx_rempty().bit_is_clear() { + // wait + } + + // without this transfers are not reliable! + xtensa_lx::timer::delay(1); + + self.regs().conf().modify(|_, w| w.tx_start().set_bit()); + + while self.regs().state().read().tx_idle().bit_is_set() { + // wait + } + } + + fn tx_stop(&self) { + self.regs().conf().modify(|_, w| w.tx_start().clear_bit()); + } + + fn is_tx_done(&self) -> bool { + self.regs().state().read().tx_idle().bit_is_set() + } + + fn tx_wait_done(&self) { + while self.regs().state().read().tx_idle().bit_is_clear() { + // wait + } + + self.regs().conf().modify(|_, w| w.tx_start().clear_bit()); + self.regs().int_clr().write(|w| { + w.out_done().clear_bit_by_one(); + w.out_total_eof().clear_bit_by_one() + }); + } + + fn set_clock(&self, clock_settings: I2sClockDividers) { + self.regs().clkm_conf().modify(|r, w| unsafe { + w.bits(r.bits() | (crate::soc::constants::I2S_DEFAULT_CLK_SRC << 21)) + // select PLL_160M + }); + + #[cfg(esp32)] + self.regs() + .clkm_conf() + .modify(|_, w| w.clka_ena().clear_bit()); + + self.regs().clkm_conf().modify(|_, w| unsafe { + w.clk_en().set_bit(); + w.clkm_div_num().bits(clock_settings.mclk_divider as u8) + }); + + self.regs().clkm_conf().modify(|_, w| unsafe { + w.clkm_div_a().bits(clock_settings.denominator as u8); + w.clkm_div_b().bits(clock_settings.numerator as u8) + }); + + self.regs().sample_rate_conf().modify(|_, w| unsafe { + w.tx_bck_div_num().bits(clock_settings.bclk_divider as u8); + w.rx_bck_div_num().bits(clock_settings.bclk_divider as u8) + }); + } + + fn setup(&self, frequency: Rate, bits: u8) { + self.set_clock(calculate_clock(frequency, bits)); + + // Initialize I2S dev + self.rx_reset(); + self.tx_reset(); + self.rx_fifo_reset(); + self.tx_fifo_reset(); + self.rx_dma_reset(); + self.tx_dma_reset(); + + // clear all bits and enable lcd mode + self.regs().conf2().write(|w| { + // 8 bit mode needs this or it updates on half clocks! + w.lcd_tx_wrx2_en().bit(bits == 8); + w.lcd_en().set_bit() + }); + + self.regs().sample_rate_conf().modify(|_, w| unsafe { + w.rx_bits_mod().bits(bits); + w.tx_bits_mod().bits(bits) + }); + + self.regs().fifo_conf().write(|w| unsafe { + w.rx_fifo_mod_force_en().set_bit(); + w.tx_fifo_mod_force_en().set_bit(); + w.rx_fifo_mod().bits(1); + w.tx_fifo_mod().bits(1); + w.rx_data_num().bits(32); + w.tx_data_num().bits(32); + w.dscr_en().set_bit() + }); + + self.regs().conf1().write(|w| { + w.tx_stop_en().set_bit(); + w.rx_pcm_bypass().set_bit(); + w.tx_pcm_bypass().set_bit() + }); + + self.regs().conf_chan().write(|w| unsafe { + w.rx_chan_mod().bits(1); + w.tx_chan_mod().bits(1) + }); + + self.regs().conf().modify(|_, w| { + w.rx_mono().set_bit(); + w.tx_mono().set_bit(); + w.rx_right_first().set_bit(); + w.tx_right_first().set_bit() + }); + self.regs().timing().reset(); + + self.regs().pd_conf().modify(|_, w| { + w.fifo_force_pu().set_bit(); + w.fifo_force_pd().clear_bit() + }); + } +} + +impl PrivateInstance for I2S0<'_> { + fn regs(&self) -> &RegisterBlock { + unsafe { &*I2S0::PTR.cast::() } + } + + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::I2s0 + } + + fn ws_signal(&self) -> OutputSignal { + OutputSignal::I2S0O_WS + } + fn data_out_signal(&self, i: usize, bits: u8) -> OutputSignal { + assert!( + bits == 8 || bits == 16, + "Number of bits must be 8 or 16, got {}", + bits + ); + + // signals for 8bit and 16bit both start at an offset of 8 for I2S0 + // https://github.com/espressif/esp-idf/blob/9106c43accd9f5e75379f62f12597677213f5023/components/esp_lcd/i80/esp_lcd_panel_io_i2s.c#L701 + match i + 8 { + 0 => OutputSignal::I2S0O_DATA_0, + 1 => OutputSignal::I2S0O_DATA_1, + 2 => OutputSignal::I2S0O_DATA_2, + 3 => OutputSignal::I2S0O_DATA_3, + 4 => OutputSignal::I2S0O_DATA_4, + 5 => OutputSignal::I2S0O_DATA_5, + 6 => OutputSignal::I2S0O_DATA_6, + 7 => OutputSignal::I2S0O_DATA_7, + 8 => OutputSignal::I2S0O_DATA_8, + 9 => OutputSignal::I2S0O_DATA_9, + 10 => OutputSignal::I2S0O_DATA_10, + 11 => OutputSignal::I2S0O_DATA_11, + 12 => OutputSignal::I2S0O_DATA_12, + 13 => OutputSignal::I2S0O_DATA_13, + 14 => OutputSignal::I2S0O_DATA_14, + 15 => OutputSignal::I2S0O_DATA_15, + 16 => OutputSignal::I2S0O_DATA_16, + 17 => OutputSignal::I2S0O_DATA_17, + 18 => OutputSignal::I2S0O_DATA_18, + 19 => OutputSignal::I2S0O_DATA_19, + 20 => OutputSignal::I2S0O_DATA_20, + 21 => OutputSignal::I2S0O_DATA_21, + 22 => OutputSignal::I2S0O_DATA_22, + 23 => OutputSignal::I2S0O_DATA_23, + other => panic!("Invalid I2S0 Dout pin {}", other), + } + } +} + +impl PrivateInstance for I2S1<'_> { + fn regs(&self) -> &RegisterBlock { + unsafe { &*I2S1::PTR.cast::() } + } + + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::I2s1 + } + + fn ws_signal(&self) -> OutputSignal { + OutputSignal::I2S1O_WS + } + fn data_out_signal(&self, i: usize, bits: u8) -> OutputSignal { + assert!( + bits == 8 || bits == 16, + "Number of bits must be 8 or 16, got {}", + bits + ); + + // signals for 8bit start at an offset of 8 for 16bit on I2S1 + let pin_offset = if bits == 16 { 8 } else { 0 }; + + match i + pin_offset { + 0 => OutputSignal::I2S1O_DATA_0, + 1 => OutputSignal::I2S1O_DATA_1, + 2 => OutputSignal::I2S1O_DATA_2, + 3 => OutputSignal::I2S1O_DATA_3, + 4 => OutputSignal::I2S1O_DATA_4, + 5 => OutputSignal::I2S1O_DATA_5, + 6 => OutputSignal::I2S1O_DATA_6, + 7 => OutputSignal::I2S1O_DATA_7, + 8 => OutputSignal::I2S1O_DATA_8, + 9 => OutputSignal::I2S1O_DATA_9, + 10 => OutputSignal::I2S1O_DATA_10, + 11 => OutputSignal::I2S1O_DATA_11, + 12 => OutputSignal::I2S1O_DATA_12, + 13 => OutputSignal::I2S1O_DATA_13, + 14 => OutputSignal::I2S1O_DATA_14, + 15 => OutputSignal::I2S1O_DATA_15, + 16 => OutputSignal::I2S1O_DATA_16, + 17 => OutputSignal::I2S1O_DATA_17, + 18 => OutputSignal::I2S1O_DATA_18, + 19 => OutputSignal::I2S1O_DATA_19, + 20 => OutputSignal::I2S1O_DATA_20, + 21 => OutputSignal::I2S1O_DATA_21, + 22 => OutputSignal::I2S1O_DATA_22, + 23 => OutputSignal::I2S1O_DATA_23, + other => panic!("Invalid I2S1 Dout pin {}", other), + } + } +} + +impl PrivateInstance for AnyI2s<'_> { + delegate::delegate! { + to match &self.0 { + super::any::Inner::I2s0(i2s) => i2s, + super::any::Inner::I2s1(i2s) => i2s, + } { + fn regs(&self) -> &RegisterBlock; + fn peripheral(&self) -> crate::system::Peripheral; + fn ws_signal(&self) -> OutputSignal; + fn data_out_signal(&self, i: usize, bits: u8) -> OutputSignal ; + } + } +} + +/// A peripheral singleton compatible with the I2S parallel driver. +pub trait Instance: PrivateInstance + super::any::Degrade {} + +impl Instance for I2S0<'_> {} +#[cfg(soc_has_i2s1)] +impl Instance for I2S1<'_> {} +impl Instance for AnyI2s<'_> {} diff --git a/esp-hal/src/i2s/tdm_slot_msb.svg b/esp-hal/src/i2s/tdm_slot_msb.svg new file mode 100644 index 00000000000..2bb64577ba5 --- /dev/null +++ b/esp-hal/src/i2s/tdm_slot_msb.svg @@ -0,0 +1,4 @@ + + + +TDM MSB FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) SlotsSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/esp-hal/src/i2s/tdm_slot_pcm_long.svg b/esp-hal/src/i2s/tdm_slot_pcm_long.svg new file mode 100644 index 00000000000..d00ad67db23 --- /dev/null +++ b/esp-hal/src/i2s/tdm_slot_pcm_long.svg @@ -0,0 +1 @@ +TDM PCM Long FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/esp-hal/src/i2s/tdm_slot_pcm_short.svg b/esp-hal/src/i2s/tdm_slot_pcm_short.svg new file mode 100644 index 00000000000..d10462f2058 --- /dev/null +++ b/esp-hal/src/i2s/tdm_slot_pcm_short.svg @@ -0,0 +1 @@ +TDM PCM Short FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/esp-hal/src/i2s/tdm_slot_philips.svg b/esp-hal/src/i2s/tdm_slot_philips.svg new file mode 100644 index 00000000000..9eb063321e3 --- /dev/null +++ b/esp-hal/src/i2s/tdm_slot_philips.svg @@ -0,0 +1,4 @@ + + + +TDM Philips FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) Slotsbit shiftSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/esp-hal/src/interrupt/mod.rs b/esp-hal/src/interrupt/mod.rs new file mode 100644 index 00000000000..88a0b3ec2f5 --- /dev/null +++ b/esp-hal/src/interrupt/mod.rs @@ -0,0 +1,532 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Interrupt support +//! +//! This module contains code to configure and handle peripheral interrupts. +//! +//! ## Overview +//! +//! Peripheral interrupt requests are not handled by the CPU directly. Instead, they are routed +//! through the interrupt matrix, which maps peripheral interrupts to CPU interrupts. There are +//! more peripheral interrupt signals than CPU interrupts, and multiple peripheral interrupts can +//! be routed to the same CPU interrupt. A set of CPU interrupts are configured to run a default +//! handler routine, which polls the interrupt controller and calls the appropriate handlers for the +//! pending peripheral interrupts. +//! +//! This default handler implements interrupt nesting - meaning a higher [`Priority`] interrupt can +//! preempt a lower priority interrupt handler. The number of priorities is a chip-specific detail. +//! +//! ## Usage +//! +//! Peripheral drivers manage interrupts for you. Where appropriate, a `set_interrupt_handler` +//! function is provided, which allows you to register a function to handle interrupts at a priority +//! level of your choosing. Interrupt handler functions need to be marked by the [`#[handler]`] +//! attribute. These drivers also provide `listen` and `unlisten` functions that control whether an +//! interrupt will be generated for the matching event or not. For more information and examples, +//! consult the documentation of the specific peripheral drivers. +//! +//! If you are writing your own peripheral driver, you will need to first register interrupt +//! handlers using the [peripheral singletons'] `bind_X_interrupt` functions. You can use the +//! matching `enable` and `disable` functions to control the peripheral interrupt in the interrupt +//! matrix, or you can, depending on the peripheral, set or clear the appropriate enable bits in the +//! `int_ena` register. +//! +//! [`#[handler]`]: crate::handler +//! [peripheral singletons']: crate::peripherals::I2C0 +//! +//! ## Software interrupts +//! +//! The [`software`] module implements software interrupts using peripheral interrupt signals. +#![cfg_attr( + multi_core, + doc = "This mechanism can be used to implement efficient cross-core communication." +)] + +#[cfg(riscv)] +pub use self::riscv::*; +#[cfg(xtensa)] +pub use self::xtensa::*; +use crate::{peripherals::Interrupt, system::Cpu}; + +cfg_if::cfg_if! { + if #[cfg(esp32)] { + use crate::peripherals::DPORT as INTERRUPT_CORE0; + use crate::peripherals::DPORT as INTERRUPT_CORE1; + } else { + use crate::peripherals::INTERRUPT_CORE0; + #[cfg(esp32s3)] + use crate::peripherals::INTERRUPT_CORE1; + } +} + +#[cfg(riscv)] +mod riscv; +#[cfg(xtensa)] +mod xtensa; + +use crate::pac; + +unstable_driver! { + pub mod software; +} + +#[cfg(feature = "rt")] +#[unsafe(no_mangle)] +extern "C" fn EspDefaultHandler() { + panic!("Unhandled interrupt on {:?}", crate::system::Cpu::current()); +} + +/// Default (unhandled) interrupt handler +#[instability::unstable] +pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new( + { + unsafe extern "C" { + fn EspDefaultHandler(); + } + + unsafe { + core::mem::transmute::(EspDefaultHandler) + } + }, + Priority::min(), +); + +/// Trait implemented by drivers which allow the user to set an +/// [InterruptHandler] +#[instability::unstable] +pub trait InterruptConfigurable: crate::private::Sealed { + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. Some peripherals offer a shared interrupt handler for + /// multiple purposes. It's the users duty to honor this. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [DEFAULT_INTERRUPT_HANDLER] + fn set_interrupt_handler(&mut self, handler: InterruptHandler); +} + +/// Represents an ISR callback function +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub struct IsrCallback { + f: extern "C" fn(), +} + +impl IsrCallback { + /// Construct a new callback from the callback function. + pub fn new(f: extern "C" fn()) -> Self { + // a valid fn pointer is non zero + Self { f } + } + + /// Returns the address of the callback. + pub fn address(self) -> usize { + self.f as usize + } + + /// The callback function. + /// + /// This is aligned and can be called. + pub fn callback(self) -> extern "C" fn() { + self.f + } +} + +impl PartialEq for IsrCallback { + fn eq(&self, other: &Self) -> bool { + core::ptr::fn_addr_eq(self.callback(), other.callback()) + } +} + +/// An interrupt handler +#[cfg_attr( + multi_core, + doc = r" + +**Note**: Interrupts are handled on the core they were setup on. If a driver is initialized +on core 0, and moved to core 1, core 0 will still handle the interrupt." +)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub struct InterruptHandler { + f: extern "C" fn(), + prio: Priority, +} + +impl InterruptHandler { + /// Creates a new [InterruptHandler] which will call the given function at + /// the given priority. + pub const fn new(f: extern "C" fn(), prio: Priority) -> Self { + Self { f, prio } + } + + /// The Isr callback. + #[inline] + pub fn handler(&self) -> IsrCallback { + IsrCallback::new(self.f) + } + + /// Priority to be used when registering the interrupt + #[inline] + pub fn priority(&self) -> Priority { + self.prio + } +} + +const STATUS_WORDS: usize = property!("interrupts.status_registers"); + +/// Representation of peripheral-interrupt status bits. +#[derive(Clone, Copy, Default, Debug)] +#[instability::unstable] +pub struct InterruptStatus { + status: [u32; STATUS_WORDS], +} + +impl InterruptStatus { + /// Get status of peripheral interrupts + #[instability::unstable] + pub fn current() -> InterruptStatus { + match Cpu::current() { + Cpu::ProCpu => InterruptStatus { + status: core::array::from_fn(|idx| { + INTERRUPT_CORE0::regs() + .core_0_intr_status(idx) + .read() + .bits() + }), + }, + #[cfg(multi_core)] + Cpu::AppCpu => InterruptStatus { + status: core::array::from_fn(|idx| { + INTERRUPT_CORE1::regs() + .core_1_intr_status(idx) + .read() + .bits() + }), + }, + } + } + + /// Is the given interrupt bit set + #[instability::unstable] + pub fn is_set(&self, interrupt: u8) -> bool { + (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 + } + + /// Set the given interrupt status bit + #[instability::unstable] + pub fn set(&mut self, interrupt: u8) { + self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); + } + + /// Return an iterator over the set interrupt status bits + #[instability::unstable] + pub fn iterator(&self) -> InterruptStatusIterator { + InterruptStatusIterator { + status: *self, + idx: 0, + } + } +} + +/// Iterator over set interrupt status bits +#[derive(Debug, Clone)] +#[instability::unstable] +pub struct InterruptStatusIterator { + status: InterruptStatus, + idx: usize, +} + +impl Iterator for InterruptStatusIterator { + type Item = u8; + + fn next(&mut self) -> Option { + for i in self.idx..STATUS_WORDS { + if self.status.status[i] != 0 { + let bit = self.status.status[i].trailing_zeros(); + self.status.status[i] ^= 1 << bit; + self.idx = i; + return Some((bit + 32 * i as u32) as u8); + } + } + self.idx = usize::MAX; + None + } +} + +// Peripheral interrupt API. + +fn vector_entry(interrupt: Interrupt) -> &'static pac::Vector { + cfg_if::cfg_if! { + if #[cfg(xtensa)] { + &pac::__INTERRUPTS[interrupt as usize] + } else { + &pac::__EXTERNAL_INTERRUPTS[interrupt as usize] + } + } +} + +/// Returns the currently bound interrupt handler. +#[instability::unstable] +pub fn bound_handler(interrupt: Interrupt) -> Option { + unsafe { + let vector = vector_entry(interrupt); + + let addr = vector._handler; + if addr as usize == 0 { + return None; + } + + Some(IsrCallback::new(core::mem::transmute::< + unsafe extern "C" fn(), + extern "C" fn(), + >(addr))) + } +} + +/// Binds the given handler to a peripheral interrupt. +/// +/// The interrupt handler will be enabled at the specified priority level. +/// +/// The interrupt handler will be called on the core where it is registered. +/// Only one interrupt handler can be bound to a peripheral interrupt. +#[instability::unstable] +pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { + unsafe { + let vector = vector_entry(interrupt); + + let ptr = (&raw const vector._handler).cast::().cast_mut(); + + // On RISC-V MCUs we may be protecting the trap section using a watchpoint. + // If we do, we need to temporarily disable this protection. + #[cfg(all(riscv, write_vec_table_monitoring))] + if crate::soc::trap_section_protected() { + crate::debugger::DEBUGGER_LOCK.lock(|| { + let wp = crate::debugger::clear_watchpoint(1); + ptr.write_volatile(handler.handler().address()); + crate::debugger::restore_watchpoint(1, wp); + }); + enable(interrupt, handler.priority()); + return; + } + + ptr.write_volatile(handler.handler().address()); + } + enable(interrupt, handler.priority()); +} + +/// Enables a peripheral interrupt at a given priority, using vectored CPU interrupts. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt +/// for the specified priority level. +#[inline] +#[instability::unstable] +pub fn enable(interrupt: Interrupt, level: Priority) { + enable_on_cpu(Cpu::current(), interrupt, level); +} + +pub(crate) fn enable_on_cpu(cpu: Cpu, interrupt: Interrupt, level: Priority) { + let cpu_interrupt = priority_to_cpu_interrupt(interrupt, level); + map_raw(cpu, interrupt, cpu_interrupt as u32); +} + +/// Disable the given peripheral interrupt. +/// +/// Internally, this function maps the interrupt to a disabled CPU interrupt. +#[inline] +#[instability::unstable] +pub fn disable(core: Cpu, interrupt: Interrupt) { + map_raw(core, interrupt, DISABLED_CPU_INTERRUPT) +} + +pub(super) fn map_raw(core: Cpu, interrupt: Interrupt, cpu_interrupt: u32) { + match core { + Cpu::ProCpu => { + INTERRUPT_CORE0::regs() + .core_0_intr_map(interrupt as usize) + .write(|w| unsafe { w.bits(cpu_interrupt) }); + } + #[cfg(multi_core)] + Cpu::AppCpu => { + INTERRUPT_CORE1::regs() + .core_1_intr_map(interrupt as usize) + .write(|w| unsafe { w.bits(cpu_interrupt) }); + } + } +} + +/// Get cpu interrupt assigned to peripheral interrupt +pub(crate) fn mapped_to(cpu: Cpu, interrupt: Interrupt) -> Option { + mapped_to_raw(cpu, interrupt as u32) +} + +pub(crate) fn mapped_to_raw(cpu: Cpu, interrupt: u32) -> Option { + let cpu_intr = match cpu { + Cpu::ProCpu => INTERRUPT_CORE0::regs() + .core_0_intr_map(interrupt as usize) + .read() + .bits(), + #[cfg(multi_core)] + Cpu::AppCpu => INTERRUPT_CORE1::regs() + .core_1_intr_map(interrupt as usize) + .read() + .bits(), + }; + CpuInterrupt::from_u32(cpu_intr) +} + +/// Represents the priority level of running code. +/// +/// Interrupts at or below this level are masked. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum RunLevel { + /// Thread mode. + /// + /// This is the lowest level. No interrupts are masked by the run level. + ThreadMode, + + /// An elevated level, usually an interrupt handler's. + Interrupt(Priority), +} + +impl RunLevel { + #[procmacros::doc_replace] + /// Returns the current run level. + /// + /// ## Examples + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::interrupt::RunLevel; + /// + /// let level = RunLevel::current(); + /// println!("Current run level: {:?}", level); + /// # {after_snippet} + /// ``` + #[inline] + pub fn current() -> Self { + current_runlevel() + } + + /// Changes the current run level to the specified level and returns the previous level. + /// + /// # Safety + /// + /// This function must only be used to raise the run level and to restore it + /// to a previous value. It must not be used to arbitrarily lower the + /// run level. + #[inline] + #[instability::unstable] + pub unsafe fn change(to: Self) -> Self { + unsafe { change_current_runlevel(to) } + } + + #[procmacros::doc_replace] + /// Checks if the run level indicates thread mode. + /// + /// This function can be used to determine if the CPU is executing an interrupt handler. + /// + /// ## Examples + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::interrupt::RunLevel; + /// + /// let level = RunLevel::current(); + /// + /// if level.is_thread() { + /// println!("Running in thread mode"); + /// } else { + /// println!("Running in an interrupt handler"); + /// } + /// # {after_snippet} + /// ``` + #[inline] + pub fn is_thread(&self) -> bool { + matches!(self, RunLevel::ThreadMode) + } + + pub(crate) fn try_from_u32(priority: u32) -> Result { + if priority == 0 { + Ok(RunLevel::ThreadMode) + } else { + Priority::try_from_u32(priority).map(RunLevel::Interrupt) + } + } +} + +impl PartialEq for RunLevel { + fn eq(&self, other: &Priority) -> bool { + *self == RunLevel::Interrupt(*other) + } +} + +impl From for u32 { + fn from(level: RunLevel) -> Self { + match level { + RunLevel::ThreadMode => 0, + RunLevel::Interrupt(priority) => priority as u32, + } + } +} + +/// Priority Level Error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum PriorityError { + /// The priority is not valid + InvalidInterruptPriority, +} + +#[instability::unstable] +impl TryFrom for RunLevel { + type Error = PriorityError; + + fn try_from(priority: u32) -> Result { + Self::try_from_u32(priority) + } +} + +#[cfg(feature = "rt")] +pub(crate) fn setup_interrupts() { + // disable all known peripheral interrupts + for peripheral_interrupt in 0..255 { + crate::peripherals::Interrupt::try_from(peripheral_interrupt) + .map(|intr| { + #[cfg(multi_core)] + disable(Cpu::AppCpu, intr); + disable(Cpu::ProCpu, intr); + }) + .ok(); + } + + unsafe { crate::interrupt::init_vectoring() }; +} + +#[inline(always)] +#[cfg(feature = "rt")] +fn should_handle(core: Cpu, interrupt_nr: u32, level: u32) -> bool { + if let Some(cpu_interrupt) = crate::interrupt::mapped_to_raw(core, interrupt_nr) + && cpu_interrupt.is_vectored() + && cpu_interrupt.level() == level + { + true + } else { + false + } +} diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs new file mode 100644 index 00000000000..13ff2897a2c --- /dev/null +++ b/esp-hal/src/interrupt/riscv.rs @@ -0,0 +1,574 @@ +//! Interrupt handling +//! +//! Peripheral interrupts go through the interrupt matrix, which routes them to the appropriate CPU +//! interrupt line. The interrupt matrix is largely the same across devices, but CPU interrupts are +//! device-specific before CLIC. +//! +//! Peripheral interrupts can be bound directly to CPU interrupts for better performance, but +//! due to the limited number of CPU interrupts, the preferred mechanism is to use the vectored +//! interrupts. The vectored interrupt handlers will call the appropriate interrupt handlers. +//! The configuration of vectored interrupt handlers cannot be changed in runtime. + +#[cfg(feature = "rt")] +#[instability::unstable] +pub use esp_riscv_rt::TrapFrame; + +#[cfg_attr(interrupt_controller = "riscv_basic", path = "riscv/basic.rs")] +#[cfg_attr(interrupt_controller = "plic", path = "riscv/plic.rs")] +#[cfg_attr(interrupt_controller = "clic", path = "riscv/clic.rs")] +mod cpu_int; + +use crate::{ + interrupt::{PriorityError, RunLevel}, + peripherals::Interrupt, + system::Cpu, +}; + +/// Interrupt kind +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum InterruptKind { + /// Level interrupt + Level, + /// Edge interrupt + Edge, +} + +for_each_interrupt!( + (all $( ([$class:ident $idx_in_class:literal] $n:literal) ),*) => { + paste::paste! { + /// Enumeration of available CPU interrupts. + #[repr(u32)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub enum CpuInterrupt { + $( + #[doc = concat!(" Interrupt number ", stringify!($n), ".")] + [] = $n, + )* + } + + impl CpuInterrupt { + #[inline] + pub(crate) fn from_u32(n: u32) -> Option { + match n { + $(n if n == $n && n != DISABLED_CPU_INTERRUPT => Some(Self:: []),)* + _ => None + } + } + } + } + }; +); + +for_each_classified_interrupt!( + (direct_bindable $( ([$class:ident $idx_in_class:literal] $n:literal) ),*) => { + paste::paste! { + /// Enumeration of CPU interrupts available for direct binding. + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum DirectBindableCpuInterrupt { + $( + #[doc = concat!(" Direct bindable CPU interrupt number ", stringify!($idx_in_class), ".")] + #[doc = " "] + #[doc = concat!(" Corresponds to CPU interrupt ", stringify!($n), ".")] + [] = $n, + )* + } + + impl From for CpuInterrupt { + fn from(bindable: DirectBindableCpuInterrupt) -> CpuInterrupt { + match bindable { + $( + DirectBindableCpuInterrupt::[] => CpuInterrupt::[], + )* + } + } + } + } + }; +); + +impl CpuInterrupt { + #[inline] + #[cfg(feature = "rt")] + pub(crate) fn is_vectored(self) -> bool { + // Assumes contiguous interrupt allocation. + const VECTORED_CPU_INTERRUPT_RANGE: core::ops::RangeInclusive = PRIORITY_TO_INTERRUPT + [0] as u32 + ..=PRIORITY_TO_INTERRUPT[PRIORITY_TO_INTERRUPT.len() - 1] as u32; + VECTORED_CPU_INTERRUPT_RANGE.contains(&(self as u32)) + } + + /// Enable the CPU interrupt + #[inline] + #[instability::unstable] + pub fn enable(self) { + cpu_int::enable_cpu_interrupt_raw(self as u32); + } + + /// Clear the CPU interrupt status bit + #[inline] + #[instability::unstable] + pub fn clear(self) { + cpu_int::clear_raw(self as u32); + } + + /// Set the interrupt kind (i.e. level or edge) of an CPU interrupt + /// + /// This is safe to call when the `vectored` feature is enabled. The + /// vectored interrupt handler will take care of clearing edge interrupt + /// bits. + #[inline] + #[instability::unstable] + pub fn set_kind(self, kind: InterruptKind) { + cpu_int::set_kind_raw(self as u32, kind); + } + + /// Set the priority level of a CPU interrupt + #[inline] + #[instability::unstable] + pub fn set_priority(self, priority: Priority) { + cpu_int::set_priority_raw(self as u32, priority); + } + + /// Get interrupt priority for the CPU + #[inline] + #[instability::unstable] + pub fn priority(self) -> Priority { + unwrap!(Priority::try_from_u32(self.level())) + } + + #[inline] + pub(crate) fn level(self) -> u32 { + cpu_int::cpu_interrupt_priority_raw(self as u32) as u32 + } +} + +for_each_interrupt_priority!( + (all $( ($idx:literal, $n:literal, $ident:ident) ),*) => { + /// Interrupt priority levels. + /// + /// A higher numeric value means higher priority. Interrupt requests at higher priority + /// levels will be able to preempt code running at a lower [`RunLevel`][super::RunLevel]. + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + pub enum Priority { + $( + #[doc = concat!(" Priority level ", stringify!($n), ".")] + $ident = $n, + )* + } + + impl Priority { + fn iter() -> impl Iterator { + [$(Priority::$ident,)*].into_iter() + } + } + }; +); + +impl Priority { + /// Maximum interrupt priority + #[allow(unused_assignments)] + #[instability::unstable] + pub const fn max() -> Priority { + const { + let mut last = Self::min(); + for_each_interrupt_priority!( + ($_idx:literal, $_n:literal, $ident:ident) => { + last = Self::$ident; + }; + ); + last + } + } + + /// Minimum interrupt priority + pub const fn min() -> Priority { + Priority::Priority1 + } + + pub(crate) fn try_from_u32(priority: u32) -> Result { + let result; + for_each_interrupt_priority!( + (all $( ($idx:literal, $n:literal, $ident:ident) ),*) => { + result = match priority { + $($n => Ok(Priority::$ident),)* + _ => Err(PriorityError::InvalidInterruptPriority), + } + }; + ); + result + } +} + +#[instability::unstable] +impl TryFrom for Priority { + type Error = PriorityError; + + fn try_from(value: u32) -> Result { + Self::try_from_u32(value) + } +} + +#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))] +pub(super) static DISABLED_CPU_INTERRUPT: u32 = property!("interrupts.disabled_interrupt"); + +/// The number of vectored interrupts / The number of priority levels. +const VECTOR_COUNT: usize = const { + let mut count = 0; + for_each_interrupt!(([vector $n:tt] $_:literal) => { count += 1; };); + + core::assert!(count == Priority::max() as usize); + + count +}; + +/// Maps priority levels to their corresponding interrupt vectors. +#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))] +pub(super) static PRIORITY_TO_INTERRUPT: [CpuInterrupt; VECTOR_COUNT] = const { + let mut counter = 0; + let mut vector = [CpuInterrupt::Interrupt0; VECTOR_COUNT]; + + for_each_interrupt!( + ([vector $_n:tt] $interrupt:literal) => { + vector[counter] = paste::paste! { CpuInterrupt::[] }; + counter += 1; + }; + ); + vector +}; + +/// Enable an interrupt by directly binding it to an available CPU interrupt +/// +/// ⚠️ This installs a *raw trap handler*, the `handler` user provides is written directly into the +/// CPU interrupt vector table. That means: +/// +/// - Provided handler will be used as an actual trap-handler +/// - It is user's responsibility to: +/// - Save and restore all registers they use. +/// - Clear the interrupt source if necessary. +/// - Return using the `mret` instruction. +/// - The handler should be declared as naked function. The compiler will not insert a function +/// prologue/epilogue for the user, normal Rust `fn` will result in an error. +/// +/// Unless you are sure that you need such low-level control to achieve the lowest possible latency, +/// you most likely want to use [`enable`][crate::interrupt::enable] instead. +#[instability::unstable] +pub fn enable_direct( + interrupt: Interrupt, + level: Priority, + cpu_interrupt: DirectBindableCpuInterrupt, + handler: unsafe extern "C" fn(), +) { + cfg_if::cfg_if! { + if #[cfg(interrupt_controller = "clic")] { + let clic = unsafe { crate::soc::pac::CLIC::steal() }; + + // Enable hardware vectoring + clic.int_attr(cpu_interrupt as usize).modify(|_, w| { + w.shv().hardware(); + w.trig().positive_level() + }); + + let mtvt_table: *mut [u32; 48]; + unsafe { core::arch::asm!("csrr {0}, 0x307", out(reg) mtvt_table) }; + + let int_slot = mtvt_table + .cast::() + .wrapping_add(cpu_interrupt as usize); + + let instr = handler as usize as u32; + } else { + use riscv::register::mtvec; + let mt = mtvec::read(); + + assert_eq!( + mt.trap_mode().into_usize(), + mtvec::TrapMode::Vectored.into_usize() + ); + + let base_addr = mt.address() as usize; + + let int_slot = base_addr.wrapping_add((cpu_interrupt as usize) * 4) as *mut u32; + + let instr = encode_jal_x0(handler as usize, int_slot as usize); + } + } + + if crate::debugger::debugger_connected() { + unsafe { core::ptr::write_volatile(int_slot, instr) }; + } else { + crate::debugger::DEBUGGER_LOCK.lock(|| unsafe { + let wp = crate::debugger::clear_watchpoint(1); + core::ptr::write_volatile(int_slot, instr); + crate::debugger::restore_watchpoint(1, wp); + }); + } + unsafe { + core::arch::asm!("fence.i"); + } + + super::map_raw(Cpu::current(), interrupt, cpu_interrupt as u32); + cpu_int::set_priority_raw(cpu_interrupt as u32, level); + cpu_int::set_kind_raw(cpu_interrupt as u32, InterruptKind::Level); + cpu_int::enable_cpu_interrupt_raw(cpu_interrupt as u32); +} + +// helper: returns correctly encoded RISC-V `jal` instruction +#[cfg(not(interrupt_controller = "clic"))] +fn encode_jal_x0(target: usize, pc: usize) -> u32 { + let offset = (target as isize) - (pc as isize); + + const MIN: isize = -(1isize << 20); + const MAX: isize = (1isize << 20) - 1; + + assert!(offset % 2 == 0 && (MIN..=MAX).contains(&offset)); + + let imm = offset as u32; + let imm20 = (imm >> 20) & 0x1; + let imm10_1 = (imm >> 1) & 0x3ff; + let imm11 = (imm >> 11) & 0x1; + let imm19_12 = (imm >> 12) & 0xff; + + (imm20 << 31) + | (imm19_12 << 12) + | (imm11 << 20) + | (imm10_1 << 21) + // https://lhtin.github.io/01world/app/riscv-isa/?xlen=32&insn_name=jal + | 0b1101111u32 +} + +// Runlevel APIs + +/// Get the current run level (the level below which interrupts are masked). +pub(crate) fn current_runlevel() -> RunLevel { + let priority = cpu_int::current_runlevel(); + unwrap!(RunLevel::try_from_u32(priority as u32)) +} + +/// Changes the current run level (the level below which interrupts are +/// masked), and returns the previous run level. +/// +/// # Safety +/// +/// This function must only be used to raise the runlevel and to restore it +/// to a previous value. It must not be used to arbitrarily lower the +/// runlevel. +pub(crate) unsafe fn change_current_runlevel(level: RunLevel) -> RunLevel { + let previous = cpu_int::change_current_runlevel(level); + unwrap!(RunLevel::try_from_u32(previous as u32)) +} + +fn cpu_wait_mode_on() -> bool { + cfg_if::cfg_if! { + if #[cfg(soc_has_pcr)] { + crate::peripherals::PCR::regs().cpu_waiti_conf().read().cpu_wait_mode_force_on().bit_is_set() + } else { + crate::peripherals::SYSTEM::regs() + .cpu_per_conf() + .read() + .cpu_wait_mode_force_on() + .bit_is_set() + } + } +} + +/// Wait for an interrupt to occur. +/// +/// This function causes the current CPU core to execute its Wait For Interrupt +/// (WFI or equivalent) instruction. After executing this function, the CPU core +/// will stop execution until an interrupt occurs. +/// +/// This function will return immediately when a debugger is attached, so it is intended to be +/// called in a loop. +#[inline(always)] +#[instability::unstable] +pub fn wait_for_interrupt() { + if crate::debugger::debugger_connected() && !cpu_wait_mode_on() { + // when SYSTEM_CPU_WAIT_MODE_FORCE_ON is disabled in WFI mode SBA access to memory does not + // work for debugger, so do not enter that mode when debugger is connected. + // https://github.com/espressif/esp-idf/blob/b9a308a47ca4128d018495662b009a7c461b6780/components/esp_hw_support/cpu.c#L57-L60 + return; + } + unsafe { core::arch::asm!("wfi") }; +} + +pub(crate) fn priority_to_cpu_interrupt(_interrupt: Interrupt, level: Priority) -> CpuInterrupt { + PRIORITY_TO_INTERRUPT[(level as usize) - 1] +} + +/// Setup interrupts ready for vectoring +/// +/// # Safety +/// +/// This function must be called only during core startup. +#[cfg(any(feature = "rt", all(feature = "unstable", multi_core)))] +pub(crate) unsafe fn init_vectoring() { + use riscv::register::mtvec; + + unsafe extern "C" { + static _vector_table: u32; + #[cfg(interrupt_controller = "clic")] + static _mtvt_table: u32; + } + + unsafe { + let vec_table = (&raw const _vector_table).addr(); + + #[cfg(not(interrupt_controller = "clic"))] + { + mtvec::write({ + let mut mtvec = mtvec::Mtvec::from_bits(0); + mtvec.set_trap_mode(mtvec::TrapMode::Vectored); + mtvec.set_address(vec_table); + mtvec + }); + } + + #[cfg(interrupt_controller = "clic")] + { + mtvec::write({ + let mut mtvec = mtvec::Mtvec::from_bits(0x03); // MODE = CLIC + mtvec.set_address(vec_table); + mtvec + }); + + // set mtvt (hardware vector base) + let mtvt_table = (&raw const _mtvt_table).addr(); + core::arch::asm!("csrw 0x307, {0}", in(reg) mtvt_table); + } + }; + + // Configure and enable vectored interrupts + for (int, prio) in PRIORITY_TO_INTERRUPT.iter().copied().zip(Priority::iter()) { + let num = int as u32; + cpu_int::set_kind_raw(num, InterruptKind::Level); + cpu_int::set_priority_raw(num, prio); + cpu_int::enable_cpu_interrupt_raw(num); + } +} + +#[cfg(feature = "rt")] +pub(crate) mod rt { + use esp_riscv_rt::TrapFrame; + use riscv::register::mcause; + + use super::*; + use crate::interrupt::InterruptStatus; + + /// The total number of interrupts. + #[cfg(not(interrupt_controller = "clic"))] + const INTERRUPT_COUNT: usize = const { + let mut count = 0; + for_each_interrupt!(([$_class:tt $n:tt] $_:literal) => { count += 1; };); + count + }; + + /// Maps interrupt numbers to their vector priority levels. + #[cfg(not(interrupt_controller = "clic"))] + #[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))] + pub(super) static INTERRUPT_TO_PRIORITY: [Option; INTERRUPT_COUNT] = const { + let mut priorities = [None; INTERRUPT_COUNT]; + + for_each_interrupt!( + ([vector $n:tt] $int:literal) => { + for_each_interrupt_priority!(($n, $__:tt, $ident:ident) => { priorities[$int] = Some(Priority::$ident); };); + }; + ); + + priorities + }; + + /// # Safety + /// + /// This function is called from an assembly trap handler. + #[doc(hidden)] + #[unsafe(link_section = ".trap.rust")] + #[unsafe(export_name = "_start_trap_rust_hal")] + unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) { + assert!( + mcause::read().is_exception(), + "Arrived into _start_trap_rust_hal but mcause is not an exception!" + ); + unsafe extern "C" { + fn ExceptionHandler(tf: *mut TrapFrame); + } + unsafe { + ExceptionHandler(trap_frame); + } + } + + #[doc(hidden)] + #[unsafe(no_mangle)] + unsafe fn _setup_interrupts() { + crate::interrupt::setup_interrupts(); + + cpu_int::init(); + + #[cfg(interrupt_controller = "plic")] + unsafe { + core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX); + } + } + + #[unsafe(no_mangle)] + #[crate::ram] + unsafe fn handle_interrupts(cpu_intr: CpuInterrupt) { + let status = InterruptStatus::current(); + + // this has no effect on level interrupts, but the interrupt may be an edge one + // so we clear it anyway + cpu_intr.clear(); + + cfg_if::cfg_if! { + if #[cfg(interrupt_controller = "clic")] { + let prio = unwrap!(Priority::try_from_u32(cpu_int::current_runlevel() as u32)); + let mcause = riscv::register::mcause::read(); + } else { + // Change the current runlevel so that interrupt handlers can access the correct runlevel. + let prio = unwrap!(INTERRUPT_TO_PRIORITY[cpu_intr as usize]); + let level = unsafe { change_current_runlevel(RunLevel::Interrupt(prio)) }; + } + } + + let handle_interrupts = || unsafe { + for interrupt_nr in status.iterator().filter(|&interrupt_nr| { + crate::interrupt::should_handle(Cpu::current(), interrupt_nr as u32, prio as u32) + }) { + let handler = + crate::soc::pac::__EXTERNAL_INTERRUPTS[interrupt_nr as usize]._handler; + + handler(); + } + }; + + // Do not enable nesting on the highest priority level. Older interrupt controllers couldn't + // properly mask the highest priority interrupt, and for CLIC we don't want to waste + // the cycles it takes to enable nesting unnecessarily. + if prio != Priority::max() { + unsafe { + riscv::interrupt::nested(handle_interrupts); + } + } else { + handle_interrupts(); + } + + cfg_if::cfg_if! { + if #[cfg(interrupt_controller = "clic")] { + // In case the target uses the CLIC, it is mandatory to restore `mcause` register + // since it contains the former CPU priority. When executing `mret`, + // the hardware will restore the former threshold, from `mcause` to + // `mintstatus` CSR + unsafe { + core::arch::asm!("csrw 0x342, {}", in(reg) mcause.bits()) + } + } else { + unsafe { change_current_runlevel(level) }; + } + } + } +} diff --git a/esp-hal/src/interrupt/riscv/basic.rs b/esp-hal/src/interrupt/riscv/basic.rs new file mode 100644 index 00000000000..4887e252173 --- /dev/null +++ b/esp-hal/src/interrupt/riscv/basic.rs @@ -0,0 +1,65 @@ +use super::{InterruptKind, Priority, RunLevel}; +use crate::peripherals::INTERRUPT_CORE0; + +#[cfg(feature = "rt")] +pub(super) fn init() {} + +pub(super) fn enable_cpu_interrupt_raw(cpu_interrupt: u32) { + INTERRUPT_CORE0::regs() + .cpu_int_enable() + .modify(|r, w| unsafe { w.bits((1 << cpu_interrupt) | r.bits()) }); +} + +pub(super) fn set_kind_raw(cpu_interrupt_number: u32, kind: InterruptKind) { + let interrupt_type = match kind { + InterruptKind::Level => 0, + InterruptKind::Edge => 1, + }; + INTERRUPT_CORE0::regs() + .cpu_int_type() + .modify(|r, w| unsafe { + w.bits( + r.bits() & !(1 << cpu_interrupt_number) | (interrupt_type << cpu_interrupt_number), + ) + }); +} + +pub(super) fn set_priority_raw(cpu_interrupt: u32, priority: Priority) { + INTERRUPT_CORE0::regs() + .cpu_int_pri(cpu_interrupt as usize) + .write(|w| unsafe { w.map().bits(priority as u8) }); +} + +pub(super) fn clear_raw(cpu_interrupt: u32) { + INTERRUPT_CORE0::regs() + .cpu_int_clear() + .write(|w| unsafe { w.bits(1 << cpu_interrupt) }); +} + +pub(super) fn cpu_interrupt_priority_raw(cpu_interrupt: u32) -> u8 { + INTERRUPT_CORE0::regs() + .cpu_int_pri(cpu_interrupt as usize) + .read() + .map() + .bits() +} + +pub(super) fn current_runlevel() -> u8 { + INTERRUPT_CORE0::regs() + .cpu_int_thresh() + .read() + .bits() + .saturating_sub(1) as u8 +} + +pub(super) fn change_current_runlevel(level: RunLevel) -> u8 { + let prev_interrupt_priority = current_runlevel(); + + // The CPU responds to interrupts `>= level`, but we want to also disable + // interrupts at `level` so we set the threshold to `level + 1`. + INTERRUPT_CORE0::regs() + .cpu_int_thresh() + .write(|w| unsafe { w.bits(u32::from(level) + 1) }); + + prev_interrupt_priority +} diff --git a/esp-hal/src/interrupt/riscv/clic.rs b/esp-hal/src/interrupt/riscv/clic.rs new file mode 100644 index 00000000000..7b4e1b8feaa --- /dev/null +++ b/esp-hal/src/interrupt/riscv/clic.rs @@ -0,0 +1,206 @@ +use super::{InterruptKind, Priority, RunLevel}; +use crate::soc::pac::CLIC; + +#[cfg(feature = "rt")] +pub(super) fn init() { + let clic = unsafe { CLIC::steal() }; + + // Set 3 level bits = 8 priority levels + clic.int_config() + .modify(|_, w| unsafe { w.mnlbits().bits(3) }); + + // Enable hardware vectoring + for int in clic.int_attr_iter() { + int.modify(|_, w| { + w.shv().hardware(); + w.trig().positive_level() + }); + } +} + +pub(super) fn enable_cpu_interrupt_raw(cpu_interrupt: u32) { + // Lower 16 interrupts are reserved for CLINT, which is currently not implemented. + let clic = unsafe { CLIC::steal() }; + clic.int_ie(cpu_interrupt as usize) + .write(|w| w.int_ie().set_bit()); +} + +pub(super) fn set_kind_raw(cpu_interrupt: u32, kind: InterruptKind) { + // Lower 16 interrupts are reserved for CLINT, which is currently not implemented. + let clic = unsafe { CLIC::steal() }; + clic.int_attr(cpu_interrupt as usize) + .modify(|_, w| match kind { + InterruptKind::Level => w.trig().positive_level(), + InterruptKind::Edge => w.trig().positive_edge(), + }); +} + +pub(super) fn set_priority_raw(cpu_interrupt: u32, priority: Priority) { + // Lower 16 interrupts are reserved for CLINT, which is currently not implemented. + let clic = unsafe { CLIC::steal() }; + let prio_bits = prio_to_bits(RunLevel::Interrupt(priority)); + // The `ctl` field would only write the 3 programmable bits, but we have the correct final + // value anyway so let's write it directly. + clic.int_ctl(cpu_interrupt as usize) + .write(|w| unsafe { w.bits(prio_bits) }); +} + +pub(super) fn clear_raw(cpu_interrupt: u32) { + // Lower 16 interrupts are reserved for CLINT, which is currently not implemented. + let clic = unsafe { CLIC::steal() }; + clic.int_ip(cpu_interrupt as usize) + .write(|w| w.int_ip().clear_bit()); +} + +pub(super) fn cpu_interrupt_priority_raw(cpu_interrupt: u32) -> u8 { + // Lower 16 interrupts are reserved for CLINT, which is currently not implemented. + let clic = unsafe { CLIC::steal() }; + let prio_level = clic.int_ctl(cpu_interrupt as usize).read().bits() as usize; + bits_to_prio(prio_level) +} + +/// Changes the current interrupt runlevel (the level below which interrupts are masked), +/// and returns the previous runlevel. +pub(super) fn change_current_runlevel(level: RunLevel) -> u8 { + let current_runlevel = current_runlevel(); + + // All machine mode pending interrupts with levels less than or equal + // to the effective threshold level are not allowed to preempt the execution. + unsafe { mintthresh::write(prio_to_bits(level) as usize) }; + + current_runlevel +} + +/// Get the current run level (the level below which interrupts are masked). +pub(crate) fn mil() -> usize { + mintstatus::read().mil() +} + +pub(super) fn current_runlevel() -> u8 { + let mintthresh = mintthresh::read(); + + let mil = mil(); + + let level = mil.max(mintthresh); + + bits_to_prio(level) +} + +fn prio_to_bits(priority: RunLevel) -> u8 { + if priority.is_thread() { + 0 + } else { + 0x1F | ((u32::from(priority) as u8 - 1) << 5) + } +} + +fn bits_to_prio(bits: usize) -> u8 { + // If mintthresh starts from 0xf, make sure we don't return Priority1 + if bits < 0x1f { + 0 + } else { + ((bits >> 5) + 1) as u8 + } +} + +mod mintthresh { + riscv::read_csr_as_usize!(0x347); + riscv::write_csr_as_usize!(0x347); +} + +mod mintstatus { + // Work around riscv using a non-qualified `assert!` in constants + macro_rules! assert { + ($($tt:tt)*) => { + ::core::assert!($($tt)*) + }; + } + riscv::read_only_csr! { + /// `mintstatus` register + Mintstatus: 0xfb1, + mask: usize::MAX, + } + riscv::read_only_csr_field! { + Mintstatus, + /// Returns the `mil` field. + mil: [24:31], + } +} + +#[cfg(feature = "rt")] +core::arch::global_asm!( + r#" + + .section .trap, "ax" + + /* Prevent the compiler from generating 2-byte instruction in the vector tables */ + .option push + .option norvc + + /** + * Vectored interrupt table. MTVT CSR points here. + * + * If an interrupt occurs and is configured as (hardware) vectored, the CPU will jump to + * MTVT[31:0] + 4 * interrupt_id + * + * In the case of the ESP32P4/ESP32C5, the interrupt matrix, between the CPU interrupt lines + * and the peripherals, offers 32 lines, and the lower 16 interrupts are used for CLINT. + */ + .balign 0x40 + .global _mtvt_table + .type _mtvt_table, @function +_mtvt_table: + .word 0 + .word _start_Trap1_trap + .word _start_Trap2_trap + .word _start_Trap3_trap + .word _start_Trap4_trap + .word _start_Trap5_trap + .word _start_Trap6_trap + .word _start_Trap7_trap + .word _start_Trap8_trap + .word _start_Trap9_trap + .word _start_Trap10_trap + .word _start_Trap11_trap + .word _start_Trap12_trap + .word _start_Trap13_trap + .word _start_Trap14_trap + .word _start_Trap15_trap + .word _start_Trap16_trap + .word _start_Trap17_trap + .word _start_Trap18_trap + .word _start_Trap19_trap + .word _start_Trap20_trap + .word _start_Trap21_trap + .word _start_Trap22_trap + .word _start_Trap23_trap + .word _start_Trap24_trap + .word _start_Trap25_trap + .word _start_Trap26_trap + .word _start_Trap27_trap + .word _start_Trap28_trap + .word _start_Trap29_trap + .word _start_Trap30_trap + .word _start_Trap31_trap + .word _start_Trap32_trap + .word _start_Trap33_trap + .word _start_Trap34_trap + .word _start_Trap35_trap + .word _start_Trap36_trap + .word _start_Trap37_trap + .word _start_Trap38_trap + .word _start_Trap39_trap + .word _start_Trap40_trap + .word _start_Trap41_trap + .word _start_Trap42_trap + .word _start_Trap43_trap + .word _start_Trap44_trap + .word _start_Trap45_trap + .word _start_Trap46_trap + .word _start_Trap47_trap + + .size _mtvt_table, .-_mtvt_table + .option pop + + "# +); diff --git a/esp-hal/src/interrupt/riscv/plic.rs b/esp-hal/src/interrupt/riscv/plic.rs new file mode 100644 index 00000000000..087b2fd71db --- /dev/null +++ b/esp-hal/src/interrupt/riscv/plic.rs @@ -0,0 +1,75 @@ +use super::{InterruptKind, Priority, RunLevel}; +use crate::peripherals::PLIC_MX; + +#[cfg(feature = "rt")] +pub(super) fn init() {} + +pub(super) fn enable_cpu_interrupt_raw(cpu_interrupt: u32) { + PLIC_MX::regs().mxint_enable().modify(|r, w| unsafe { + w.cpu_mxint_enable() + .bits(r.cpu_mxint_enable().bits() | (1 << cpu_interrupt)) + }); +} + +pub(super) fn set_kind_raw(cpu_interrupt: u32, kind: InterruptKind) { + let interrupt_type = match kind { + InterruptKind::Level => 0, + InterruptKind::Edge => 1, + }; + + PLIC_MX::regs().mxint_type().modify(|r, w| unsafe { + w.cpu_mxint_type().bits( + r.cpu_mxint_type().bits() & !(1 << cpu_interrupt) | (interrupt_type << cpu_interrupt), + ) + }); +} + +pub(super) fn set_priority_raw(cpu_interrupt: u32, priority: Priority) { + PLIC_MX::regs() + .mxint_pri(cpu_interrupt as usize) + .modify(|_, w| unsafe { w.cpu_mxint_pri().bits(priority as u8) }); +} + +pub(super) fn clear_raw(cpu_interrupt: u32) { + PLIC_MX::regs().mxint_clear().modify(|r, w| unsafe { + w.cpu_mxint_clear() + .bits(r.cpu_mxint_clear().bits() | (1 << cpu_interrupt)) + }); +} + +pub(super) fn cpu_interrupt_priority_raw(cpu_interrupt: u32) -> u8 { + PLIC_MX::regs() + .mxint_pri(cpu_interrupt as usize) + .read() + .cpu_mxint_pri() + .bits() +} + +pub(super) fn current_runlevel() -> u8 { + PLIC_MX::regs() + .mxint_thresh() + .read() + .cpu_mxint_thresh() + .bits() + .saturating_sub(1) +} + +/// Changes the current run level (the level below which interrupts are +/// masked), and returns the previous run level. +/// +/// # Safety +/// +/// This function must only be used to raise the runlevel and to restore it +/// to a previous value. It must not be used to arbitrarily lower the +/// runlevel. +pub(crate) fn change_current_runlevel(level: RunLevel) -> u8 { + let prev_interrupt_priority = current_runlevel(); + + // The CPU responds to interrupts `>= level`, but we want to also disable + // interrupts at `level` so we set the threshold to `level + 1`. + PLIC_MX::regs() + .mxint_thresh() + .write(|w| unsafe { w.cpu_mxint_thresh().bits(u32::from(level) as u8 + 1) }); + + prev_interrupt_priority +} diff --git a/esp-hal/src/interrupt/software.rs b/esp-hal/src/interrupt/software.rs new file mode 100644 index 00000000000..d91acc5e644 --- /dev/null +++ b/esp-hal/src/interrupt/software.rs @@ -0,0 +1,195 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Software Interrupts +//! +//! The [`SoftwareInterruptControl`] struct gives access to the available +//! software interrupts. +//! +//! The [`SoftwareInterrupt`] struct allows raising or resetting software +//! interrupts using the [`raise()`][SoftwareInterrupt::raise] and +//! [`reset()`][SoftwareInterrupt::reset] methods. +//! +//! ## Examples +//! +//! ```rust, no_run +//! # {before_snippet} +//! let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); +//! +//! // Take the interrupt you want to use. +//! let mut int0 = sw_ints.software_interrupt0; +//! +//! // Set up the interrupt handler. Do this in a critical section so the global +//! // contains the interrupt object before the interrupt is triggered. +//! critical_section::with(|cs| { +//! int0.set_interrupt_handler(swint0_handler); +//! SWINT0.borrow_ref_mut(cs).replace(int0); +//! }); +//! # {after_snippet} +//! +//! # use core::cell::RefCell; +//! # use critical_section::Mutex; +//! # use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl}; +//! // ... somewhere outside of your main function +//! +//! // Define a shared handle to the software interrupt. +//! static SWINT0: Mutex>>> = Mutex::new(RefCell::new(None)); +//! +//! #[esp_hal::handler] +//! fn swint0_handler() { +//! println!("SW interrupt0 handled"); +//! +//! // Clear the interrupt request. +//! critical_section::with(|cs| { +//! if let Some(swint) = SWINT0.borrow_ref(cs).as_ref() { +//! swint.reset(); +//! } +//! }); +//! } +//! ``` + +use core::marker::PhantomData; + +use crate::{ + interrupt::{self, InterruptConfigurable, InterruptHandler}, + peripherals::Interrupt, + system::Cpu, +}; + +/// A software interrupt can be triggered by software. +#[non_exhaustive] +pub struct SoftwareInterrupt<'d, const NUM: u8> { + _lifetime: PhantomData<&'d mut ()>, +} + +impl SoftwareInterrupt<'_, NUM> { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a + /// time. + #[inline] + pub unsafe fn steal() -> Self { + Self { + _lifetime: PhantomData, + } + } + + /// Creates a new peripheral reference with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripheral + /// after you dropped the driver that consumes this. + /// + /// See [Peripheral singleton] section for more information. + /// + /// [Peripheral singleton]: crate#peripheral-singletons + pub fn reborrow(&mut self) -> SoftwareInterrupt<'_, NUM> { + unsafe { SoftwareInterrupt::steal() } + } + + /// Sets the interrupt handler for this software-interrupt + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + let interrupt; + + for_each_sw_interrupt! { + (all $( ($n:literal, $interrupt_name:ident, $f:ident) ),*) => { + interrupt = match NUM { + $($n => Interrupt::$interrupt_name,)* + _ => unreachable!(), + }; + }; + } + + for core in Cpu::other() { + interrupt::disable(core, interrupt); + } + interrupt::bind_handler(interrupt, handler); + } + + /// Trigger this software-interrupt + pub fn raise(&self) { + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let regs = crate::peripherals::INTPRI::regs(); + } else { + let regs = crate::peripherals::SYSTEM::regs(); + } + } + let reg; + + for_each_sw_interrupt! { + (all $( ($n:literal, $i:ident, $f:ident) ),*) => { + reg = match NUM { + $($n => regs.cpu_intr_from_cpu($n),)* + _ => unreachable!(), + }; + }; + } + + reg.write(|w| w.cpu_intr().set_bit()); + _ = reg.read(); // Read back to ensure the write is completed. + + // Insert enough delay to ensure the interrupt is fired before returning. + sw_interrupt_delay!(); + } + + /// Resets this software-interrupt + pub fn reset(&self) { + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let regs = crate::peripherals::INTPRI::regs(); + } else { + let regs = crate::peripherals::SYSTEM::regs(); + } + } + let reg; + + for_each_sw_interrupt! { + (all $( ($n:literal, $i:ident, $f:ident) ),*) => { + reg = match NUM { + $($n => regs.cpu_intr_from_cpu($n),)* + _ => unreachable!(), + }; + }; + } + + reg.write(|w| w.cpu_intr().clear_bit()); + } +} + +impl crate::private::Sealed for SoftwareInterrupt<'_, NUM> {} + +impl InterruptConfigurable for SoftwareInterrupt<'_, NUM> { + fn set_interrupt_handler(&mut self, handler: interrupt::InterruptHandler) { + SoftwareInterrupt::set_interrupt_handler(self, handler); + } +} + +for_each_sw_interrupt! { + (all $( ($n:literal, $i:ident, $field:ident) ),*) => { + /// This gives access to the available software interrupts. + /// + /// This struct contains several instances of software interrupts that can be + /// used for signaling between different parts of a program or system. + #[non_exhaustive] + pub struct SoftwareInterruptControl<'d> { + $( + #[doc = concat!("Software interrupt ", stringify!($n), ".")] + pub $field: SoftwareInterrupt<'d, $n>, + )* + } + + impl<'d> SoftwareInterruptControl<'d> { + /// Create a new instance of the software interrupt control. + pub fn new(_peripheral: crate::peripherals::SW_INTERRUPT<'d>) -> Self { + SoftwareInterruptControl { + $( + $field: SoftwareInterrupt { + _lifetime: PhantomData, + }, + )* + } + } + } + }; +} diff --git a/esp-hal/src/interrupt/xtensa.rs b/esp-hal/src/interrupt/xtensa.rs new file mode 100644 index 00000000000..2b7b47b6bbe --- /dev/null +++ b/esp-hal/src/interrupt/xtensa.rs @@ -0,0 +1,530 @@ +//! Interrupt handling + +#[cfg(esp32)] +pub(crate) use xtensa_lx::interrupt::free; + +use crate::{ + interrupt::{PriorityError, RunLevel}, + peripherals::Interrupt, +}; + +/// Enumeration of available CPU interrupts +/// +/// It's possible to create one handler per priority level. (e.g +/// `level1_interrupt`) +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +#[instability::unstable] +pub enum CpuInterrupt { + /// Level-triggered interrupt with priority 1. + Interrupt0LevelPriority1 = 0, + /// Level-triggered interrupt with priority 1. + Interrupt1LevelPriority1 = 1, + /// Level-triggered interrupt with priority 1. + Interrupt2LevelPriority1 = 2, + /// Level-triggered interrupt with priority 1. + Interrupt3LevelPriority1 = 3, + /// Level-triggered interrupt with priority 1. + Interrupt4LevelPriority1 = 4, + /// Level-triggered interrupt with priority 1. + Interrupt5LevelPriority1 = 5, + /// Timer 0 interrupt with priority 1. + Interrupt6Timer0Priority1 = 6, + /// Software-triggered interrupt with priority 1. + Interrupt7SoftwarePriority1 = 7, + /// Level-triggered interrupt with priority 1. + Interrupt8LevelPriority1 = 8, + /// Level-triggered interrupt with priority 1. + Interrupt9LevelPriority1 = 9, + /// Edge-triggered interrupt with priority 1. + Interrupt10EdgePriority1 = 10, + /// Profiling-related interrupt with priority 3. + Interrupt11ProfilingPriority3 = 11, + /// Level-triggered interrupt with priority 1. + Interrupt12LevelPriority1 = 12, + /// Level-triggered interrupt with priority 1. + Interrupt13LevelPriority1 = 13, + /// Timer 1 interrupt with priority 3. + Interrupt15Timer1Priority3 = 15, + /// Level-triggered interrupt with priority 1. + Interrupt17LevelPriority1 = 17, + /// Level-triggered interrupt with priority 1. + Interrupt18LevelPriority1 = 18, + /// Level-triggered interrupt with priority 2. + Interrupt19LevelPriority2 = 19, + /// Level-triggered interrupt with priority 2. + Interrupt20LevelPriority2 = 20, + /// Level-triggered interrupt with priority 2. + Interrupt21LevelPriority2 = 21, + /// Edge-triggered interrupt with priority 3. + Interrupt22EdgePriority3 = 22, + /// Level-triggered interrupt with priority 3. + Interrupt23LevelPriority3 = 23, + /// Level-triggered interrupt with priority 3. + Interrupt27LevelPriority3 = 27, + /// Software-triggered interrupt with priority 3. + Interrupt29SoftwarePriority3 = 29, + // TODO: re-add higher level interrupts +} + +impl CpuInterrupt { + pub(super) fn from_u32(n: u32) -> Option { + match n { + 0 => Some(Self::Interrupt0LevelPriority1), + 1 => Some(Self::Interrupt1LevelPriority1), + 2 => Some(Self::Interrupt2LevelPriority1), + 3 => Some(Self::Interrupt3LevelPriority1), + 4 => Some(Self::Interrupt4LevelPriority1), + 5 => Some(Self::Interrupt5LevelPriority1), + 6 => Some(Self::Interrupt6Timer0Priority1), + 7 => Some(Self::Interrupt7SoftwarePriority1), + 8 => Some(Self::Interrupt8LevelPriority1), + 9 => Some(Self::Interrupt9LevelPriority1), + 10 => Some(Self::Interrupt10EdgePriority1), + 11 => Some(Self::Interrupt11ProfilingPriority3), + 12 => Some(Self::Interrupt12LevelPriority1), + 13 => Some(Self::Interrupt13LevelPriority1), + 15 => Some(Self::Interrupt15Timer1Priority3), + 17 => Some(Self::Interrupt17LevelPriority1), + 18 => Some(Self::Interrupt18LevelPriority1), + 19 => Some(Self::Interrupt19LevelPriority2), + 20 => Some(Self::Interrupt20LevelPriority2), + 21 => Some(Self::Interrupt21LevelPriority2), + 22 => Some(Self::Interrupt22EdgePriority3), + 23 => Some(Self::Interrupt23LevelPriority3), + 27 => Some(Self::Interrupt27LevelPriority3), + 29 => Some(Self::Interrupt29SoftwarePriority3), + _ => None, + } + } + + #[inline] + #[cfg(feature = "rt")] + pub(crate) fn is_vectored(self) -> bool { + // Even "direct bound" interrupts go through the vectored interrupt handler + true + } + + /// Enable the CPU interrupt + #[inline] + #[instability::unstable] + pub fn enable(self) { + enable_cpu_interrupt_raw(self as u32); + } + + /// Clear the CPU interrupt status bit + #[inline] + #[instability::unstable] + pub fn clear(self) { + unsafe { xtensa_lx::interrupt::clear(1 << self as u32) }; + } + + /// Get interrupt priority for the CPU + #[inline] + #[instability::unstable] + pub fn priority(self) -> Priority { + match self { + CpuInterrupt::Interrupt0LevelPriority1 + | CpuInterrupt::Interrupt1LevelPriority1 + | CpuInterrupt::Interrupt2LevelPriority1 + | CpuInterrupt::Interrupt3LevelPriority1 + | CpuInterrupt::Interrupt4LevelPriority1 + | CpuInterrupt::Interrupt5LevelPriority1 + | CpuInterrupt::Interrupt6Timer0Priority1 + | CpuInterrupt::Interrupt7SoftwarePriority1 + | CpuInterrupt::Interrupt8LevelPriority1 + | CpuInterrupt::Interrupt9LevelPriority1 + | CpuInterrupt::Interrupt10EdgePriority1 + | CpuInterrupt::Interrupt12LevelPriority1 + | CpuInterrupt::Interrupt13LevelPriority1 + | CpuInterrupt::Interrupt17LevelPriority1 + | CpuInterrupt::Interrupt18LevelPriority1 => Priority::Priority1, + + CpuInterrupt::Interrupt19LevelPriority2 + | CpuInterrupt::Interrupt20LevelPriority2 + | CpuInterrupt::Interrupt21LevelPriority2 => Priority::Priority2, + + CpuInterrupt::Interrupt11ProfilingPriority3 + | CpuInterrupt::Interrupt15Timer1Priority3 + | CpuInterrupt::Interrupt22EdgePriority3 + | CpuInterrupt::Interrupt27LevelPriority3 + | CpuInterrupt::Interrupt29SoftwarePriority3 + | CpuInterrupt::Interrupt23LevelPriority3 => Priority::Priority3, + } + } + + #[inline] + #[cfg(feature = "rt")] + pub(crate) fn level(self) -> u32 { + self.priority() as u32 + } +} + +/// Interrupt priority levels. +/// +/// A higher numeric value means higher priority. Interrupt requests at higher priority levels will +/// be able to preempt code running at a lower [`RunLevel`][super::RunLevel]. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[non_exhaustive] +pub enum Priority { + /// Priority level 1. + Priority1 = 1, + /// Priority level 2. + Priority2 = 2, + /// Priority level 3. + Priority3 = 3, + // TODO: Xtensa has 7 priority levels, the higher ones are only not recommended for use. + // We should add these levels, and a mechanism to bind assembly-written handlers for them. +} + +impl Priority { + /// Maximum interrupt priority + #[instability::unstable] + pub const fn max() -> Priority { + Priority::Priority3 + } + + /// Minimum interrupt priority + pub const fn min() -> Priority { + Priority::Priority1 + } + + pub(crate) fn try_from_u32(priority: u32) -> Result { + match priority { + 1 => Ok(Priority::Priority1), + 2 => Ok(Priority::Priority2), + 3 => Ok(Priority::Priority3), + _ => Err(PriorityError::InvalidInterruptPriority), + } + } +} + +#[instability::unstable] +impl TryFrom for Priority { + type Error = PriorityError; + + fn try_from(value: u32) -> Result { + Self::try_from_u32(value) + } +} + +#[instability::unstable] +impl TryFrom for Priority { + type Error = PriorityError; + + fn try_from(value: u8) -> Result { + Self::try_from(value as u32) + } +} + +pub(super) const DISABLED_CPU_INTERRUPT: u32 = 16; + +// CPU interrupt API. These don't take a core, because the control mechanisms are generally +// core-local. + +pub(crate) fn enable_cpu_interrupt_raw(cpu_interrupt: u32) { + unsafe { xtensa_lx::interrupt::enable_mask(1 << cpu_interrupt) }; +} + +// Runlevel APIs + +/// Get the current run level (the level below which interrupts are masked). +pub(crate) fn current_runlevel() -> RunLevel { + unwrap!(RunLevel::try_from_u32(xtensa_lx::interrupt::get_level())) +} + +/// Changes the current run level (the level below which interrupts are +/// masked), and returns the previous run level. +/// +/// # Safety +/// +/// This function must only be used to raise the runlevel and to restore it +/// to a previous value. It must not be used to arbitrarily lower the +/// runlevel. +pub(crate) unsafe fn change_current_runlevel(level: RunLevel) -> RunLevel { + let token: u32; + unsafe { + match level { + RunLevel::ThreadMode => core::arch::asm!("rsil {0}, 0", out(reg) token), + RunLevel::Interrupt(Priority::Priority1) => { + core::arch::asm!("rsil {0}, 1", out(reg) token) + } + RunLevel::Interrupt(Priority::Priority2) => { + core::arch::asm!("rsil {0}, 2", out(reg) token) + } + RunLevel::Interrupt(Priority::Priority3) => { + core::arch::asm!("rsil {0}, 3", out(reg) token) + } + }; + } + + unwrap!(RunLevel::try_from_u32(token & 0x0F)) +} + +/// Wait for an interrupt to occur. +/// +/// This function causes the current CPU core to execute its Wait For Interrupt +/// (WFI or equivalent) instruction. After executing this function, the CPU core +/// will stop execution until an interrupt occurs. +#[inline(always)] +#[instability::unstable] +pub fn wait_for_interrupt() { + unsafe { core::arch::asm!("waiti 0") }; +} + +pub(crate) fn priority_to_cpu_interrupt(interrupt: Interrupt, level: Priority) -> CpuInterrupt { + if EDGE_INTERRUPTS.contains(&interrupt) { + match level { + Priority::Priority1 => CpuInterrupt::Interrupt10EdgePriority1, + Priority::Priority2 => { + warn!("Priority 2 edge interrupts are not supported, using Priority 1 instead"); + CpuInterrupt::Interrupt10EdgePriority1 + } + Priority::Priority3 => CpuInterrupt::Interrupt22EdgePriority3, + } + } else { + match level { + Priority::Priority1 => CpuInterrupt::Interrupt1LevelPriority1, + Priority::Priority2 => CpuInterrupt::Interrupt19LevelPriority2, + Priority::Priority3 => CpuInterrupt::Interrupt23LevelPriority3, + } + } +} + +cfg_if::cfg_if! { + if #[cfg(esp32)] { + pub(crate) const EDGE_INTERRUPTS: [Interrupt; 8] = [ + Interrupt::TG0_T0_EDGE, + Interrupt::TG0_T1_EDGE, + Interrupt::TG0_WDT_EDGE, + Interrupt::TG0_LACT_EDGE, + Interrupt::TG1_T0_EDGE, + Interrupt::TG1_T1_EDGE, + Interrupt::TG1_WDT_EDGE, + Interrupt::TG1_LACT_EDGE, + ]; + } else if #[cfg(esp32s2)] { + pub(crate) const EDGE_INTERRUPTS: [Interrupt; 11] = [ + Interrupt::TG0_T0_EDGE, + Interrupt::TG0_T1_EDGE, + Interrupt::TG0_WDT_EDGE, + Interrupt::TG0_LACT_EDGE, + Interrupt::TG1_T0_EDGE, + Interrupt::TG1_T1_EDGE, + Interrupt::TG1_WDT_EDGE, + Interrupt::TG1_LACT_EDGE, + Interrupt::SYSTIMER_TARGET0, + Interrupt::SYSTIMER_TARGET1, + Interrupt::SYSTIMER_TARGET2, + ]; + } else if #[cfg(esp32s3)] { + pub(crate) const EDGE_INTERRUPTS: [Interrupt; 0] = []; + } else { + compile_error!("Unsupported chip"); + } +} + +/// Setup interrupts ready for vectoring +/// +/// # Safety +/// +/// This function must be called only during core startup. +#[cfg(any(feature = "rt", all(feature = "unstable", multi_core)))] +pub(crate) unsafe fn init_vectoring() { + // Enable vectored interrupts. No configuration is needed because these interrupts have + // fixed priority and trigger mode. + for cpu_int in [ + CpuInterrupt::Interrupt10EdgePriority1, + CpuInterrupt::Interrupt22EdgePriority3, + CpuInterrupt::Interrupt1LevelPriority1, + CpuInterrupt::Interrupt19LevelPriority2, + CpuInterrupt::Interrupt23LevelPriority3, + ] { + cpu_int.enable(); + } +} + +#[cfg(feature = "rt")] +pub(crate) mod rt { + use procmacros::ram; + use xtensa_lx_rt::{exception::Context, interrupt::CpuInterruptLevel}; + + use super::*; + use crate::{interrupt::InterruptStatus, system::Cpu}; + + #[cfg_attr(place_switch_tables_in_ram, ram)] + pub(crate) static CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000; + #[cfg_attr(place_switch_tables_in_ram, ram)] + pub(crate) static CPU_INTERRUPT_EDGE: u32 = 0b_0111_0000_0100_0000_0000_1100_1000_0000; + + #[cfg_attr(place_switch_tables_in_ram, ram)] + pub(crate) static CPU_INTERRUPT_LEVELS: [u32; 8] = [ + 0, // Dummy level 0 + CpuInterruptLevel::Level1.mask(), + CpuInterruptLevel::Level2.mask(), + CpuInterruptLevel::Level3.mask(), + CpuInterruptLevel::Level4.mask(), + CpuInterruptLevel::Level5.mask(), + CpuInterruptLevel::Level6.mask(), + CpuInterruptLevel::Level7.mask(), + ]; + + /// A bitmap of edge-triggered peripheral interrupts. See `handle_interrupts` why this is + /// necessary + #[cfg_attr(place_switch_tables_in_ram, ram)] + pub static INTERRUPT_EDGE: InterruptStatus = const { + let mut masks = [0; crate::interrupt::STATUS_WORDS]; + + let mut idx = 0; + while idx < EDGE_INTERRUPTS.len() { + let interrupt_idx = EDGE_INTERRUPTS[idx] as usize; + let word_idx = interrupt_idx / 32; + masks[word_idx] |= 1 << (interrupt_idx % 32); + idx += 1; + } + + InterruptStatus { status: masks } + }; + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_1_interrupt(save_frame: &mut Context) { + unsafe { + handle_interrupts::<1>(save_frame); + } + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_2_interrupt(save_frame: &mut Context) { + unsafe { + handle_interrupts::<2>(save_frame); + } + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_3_interrupt(save_frame: &mut Context) { + unsafe { + handle_interrupts::<3>(save_frame); + } + } + + #[inline(always)] + unsafe fn handle_interrupts(save_frame: &mut Context) { + let cpu_interrupt_mask = xtensa_lx::interrupt::get() + & xtensa_lx::interrupt::get_mask() + & CPU_INTERRUPT_LEVELS[LEVEL as usize]; + + if cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL != 0 { + // Let's handle CPU-internal interrupts (NMI, Timer, Software, Profiling). + // These are rarely used by the HAL. + + // Mask the relevant bits + let cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL; + + // Pick one + let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros(); + + // If the interrupt is edge triggered, we need to clear the request on the CPU's + // side. + if ((1 << cpu_interrupt_nr) & CPU_INTERRUPT_EDGE) != 0 { + unsafe { + xtensa_lx::interrupt::clear(1 << cpu_interrupt_nr); + } + } + + if let Some(handler) = cpu_interrupt_nr_to_cpu_interrupt_handler(cpu_interrupt_nr) { + unsafe { handler(save_frame) }; + } + } else { + let status = if !cfg!(esp32s3) && (cpu_interrupt_mask & CPU_INTERRUPT_EDGE) != 0 { + // Next, handle edge triggered peripheral interrupts. Note that on the S3 all + // peripheral interrupts are level-triggered. + + // If the interrupt is edge triggered, we need to clear the + // request on the CPU's side + unsafe { xtensa_lx::interrupt::clear(cpu_interrupt_mask & CPU_INTERRUPT_EDGE) }; + + // For edge interrupts we cannot rely on the peripherals' interrupt status + // registers, therefore call all registered handlers for current level. + INTERRUPT_EDGE + } else { + // Finally, check level-triggered peripheral sources. + // These interrupts are cleared by the peripheral. + InterruptStatus::current() + }; + + let core = Cpu::current(); + for interrupt_nr in status.iterator().filter(|&interrupt_nr| { + crate::interrupt::should_handle(core, interrupt_nr as u32, LEVEL) + }) { + let handler = unsafe { crate::pac::__INTERRUPTS[interrupt_nr as usize]._handler }; + let handler: fn(&mut Context) = unsafe { + core::mem::transmute::(handler) + }; + handler(save_frame); + } + } + } + + #[inline] + pub(crate) fn cpu_interrupt_nr_to_cpu_interrupt_handler( + number: u32, + ) -> Option { + use xtensa_lx_rt::*; + // we're fortunate that all esp variants use the same CPU interrupt layout + Some(match number { + 6 => Timer0, + 7 => Software0, + 11 => Profiling, + 14 => NMI, + 15 => Timer1, + 16 => Timer2, + 29 => Software1, + _ => return None, + }) + } + + // Raw handlers for CPU interrupts, assembly only. + unsafe extern "C" { + fn level4_interrupt(save_frame: &mut Context); + fn level5_interrupt(save_frame: &mut Context); + #[cfg(not(all(feature = "rt", feature = "exception-handler", stack_guard_monitoring)))] + fn level6_interrupt(save_frame: &mut Context); + fn level7_interrupt(save_frame: &mut Context); + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_4_interrupt(save_frame: &mut Context) { + unsafe { level4_interrupt(save_frame) } + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_5_interrupt(save_frame: &mut Context) { + unsafe { level5_interrupt(save_frame) } + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_6_interrupt(save_frame: &mut Context) { + cfg_if::cfg_if! { + if #[cfg(all(feature = "rt", feature = "exception-handler", stack_guard_monitoring))] { + crate::exception_handler::breakpoint_interrupt(save_frame); + } else { + unsafe { level6_interrupt(save_frame) } + } + } + } + + #[unsafe(no_mangle)] + #[ram] + unsafe fn __level_7_interrupt(save_frame: &mut Context) { + unsafe { level7_interrupt(save_frame) } + } +} diff --git a/esp-hal/src/lcd_cam/cam.rs b/esp-hal/src/lcd_cam/cam.rs new file mode 100644 index 00000000000..71f5d3fd36f --- /dev/null +++ b/esp-hal/src/lcd_cam/cam.rs @@ -0,0 +1,623 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Camera - Master or Slave Mode +//! +//! ## Overview +//! The camera module is designed to receive parallel video data signals, and +//! its bus supports DVP 8-/16-bit modes in master or slave mode. +//! +//! ## Configuration +//! In master mode, the peripheral provides the master clock to drive the +//! camera, in slave mode it does not. This is configured with the +//! `with_master_clock` method on the camera driver. The driver (due to the +//! peripheral) mandates DMA (Direct Memory Access) for efficient data transfer. +//! +//! ## Examples +//! ## Master Mode +//! Following code shows how to receive some bytes from an 8 bit DVP stream in +//! master mode. +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::lcd_cam::{cam::{Camera, Config}, LcdCam}; +//! # use esp_hal::dma_rx_stream_buffer; +//! +//! # let dma_buf = dma_rx_stream_buffer!(20 * 1000, 1000); +//! +//! let mclk_pin = peripherals.GPIO15; +//! let vsync_pin = peripherals.GPIO6; +//! let href_pin = peripherals.GPIO7; +//! let pclk_pin = peripherals.GPIO13; +//! +//! let config = Config::default().with_frequency(Rate::from_mhz(20)); +//! +//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM); +//! let mut camera = Camera::new(lcd_cam.cam, peripherals.DMA_CH0, config)? +//! .with_master_clock(mclk_pin) // Remove this for slave mode +//! .with_pixel_clock(pclk_pin) +//! .with_vsync(vsync_pin) +//! .with_h_enable(href_pin) +//! .with_data0(peripherals.GPIO11) +//! .with_data1(peripherals.GPIO9) +//! .with_data2(peripherals.GPIO8) +//! .with_data3(peripherals.GPIO10) +//! .with_data4(peripherals.GPIO12) +//! .with_data5(peripherals.GPIO18) +//! .with_data6(peripherals.GPIO17) +//! .with_data7(peripherals.GPIO16); +//! +//! let transfer = camera.receive(dma_buf).map_err(|e| e.0)?; +//! +//! # {after_snippet} +//! ``` + +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use crate::{ + Blocking, + dma::{ChannelRx, DmaError, DmaPeripheral, DmaRxBuffer, PeripheralRxChannel, RxChannelFor}, + gpio::{ + InputConfig, + InputSignal, + OutputConfig, + OutputSignal, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + lcd_cam::{BitOrder, ByteOrder, ClockError, calculate_clkm}, + pac, + peripherals::LCD_CAM, + soc::clocks::ClockTree, + system::{self, GenericPeripheralGuard}, + time::Rate, +}; + +/// Generation of GDMA SUC EOF +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EofMode { + /// Generate GDMA SUC EOF by data byte length. + /// + /// When the length of received data reaches this value + 1, GDMA in_suc_eof is triggered. + ByteLen(u16), + /// Generate GDMA SUC EOF by the vsync signal + VsyncSignal, +} + +/// Vsync Filter Threshold +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum VsyncFilterThreshold { + /// Requires 1 valid VSYNC pulse to trigger synchronization. + One, + /// Requires 2 valid VSYNC pulse to trigger synchronization. + Two, + /// Requires 3 valid VSYNC pulse to trigger synchronization. + Three, + /// Requires 4 valid VSYNC pulse to trigger synchronization. + Four, + /// Requires 5 valid VSYNC pulse to trigger synchronization. + Five, + /// Requires 6 valid VSYNC pulse to trigger synchronization. + Six, + /// Requires 7 valid VSYNC pulse to trigger synchronization. + Seven, + /// Requires 8 valid VSYNC pulse to trigger synchronization. + Eight, +} + +/// Vsync/Hsync or Data Enable Mode +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum VhdeMode { + /// VSYNC + HSYNC mode is selected, in this mode, + /// the signals of VSYNC, HSYNC and DE are used to control the data. + /// For this case, users need to wire the three signal lines. + VsyncHsync, + + /// DE mode is selected, the signals of VSYNC and + /// DE are used to control the data. For this case, wiring HSYNC signal + /// line is not a must. But in this case, the YUV-RGB conversion + /// function of camera module is not available. + De, +} + +/// Vsync Filter Threshold +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// The frequency is out of range. + Clock(ClockError), + + /// The line interrupt value is too large. Max value is 127. + LineInterrupt, +} + +/// Represents the camera interface. +pub struct Cam<'d> { + /// The LCD_CAM peripheral reference for managing the camera functionality. + pub(crate) lcd_cam: LCD_CAM<'d>, + pub(super) _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>, +} + +/// Represents the camera interface with DMA support. +pub struct Camera<'d> { + lcd_cam: LCD_CAM<'d>, + rx_channel: ChannelRx>>, + _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>, +} + +impl<'d> Camera<'d> { + /// Creates a new `Camera` instance with DMA support. + pub fn new( + cam: Cam<'d>, + channel: impl RxChannelFor>, + config: Config, + ) -> Result { + let rx_channel = ChannelRx::new(channel.degrade()); + + let mut this = Self { + lcd_cam: cam.lcd_cam, + rx_channel, + _guard: cam._guard, + }; + + this.apply_config(&config)?; + + Ok(this) + } + + fn regs(&self) -> &pac::lcd_cam::RegisterBlock { + self.lcd_cam.register_block() + } + + /// Applies the configuration to the camera interface. + /// + /// # Errors + /// + /// [`ConfigError::Clock`] will be returned if the frequency passed in + /// `Config` is too low. + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + let (i, divider) = ClockTree::with(|clocks| { + calculate_clkm( + config.frequency.as_hz() as _, + &[ + crate::soc::clocks::xtal_clk_frequency(clocks) as usize, + crate::soc::clocks::pll_d2_frequency(clocks) as usize, + crate::soc::clocks::crypto_pwm_clk_frequency(clocks) as usize, + ], + ) + }) + .map_err(ConfigError::Clock)?; + + if let Some(value) = config.line_interrupt + && value > 0b0111_1111 + { + return Err(ConfigError::LineInterrupt); + } + + self.regs().cam_ctrl().write(|w| { + // Force enable the clock for all configuration registers. + unsafe { + w.cam_clk_sel().bits((i + 1) as _); + w.cam_clkm_div_num().bits(divider.div_num as _); + w.cam_clkm_div_b().bits(divider.div_b as _); + w.cam_clkm_div_a().bits(divider.div_a as _); + if let Some(threshold) = config.vsync_filter_threshold { + w.cam_vsync_filter_thres().bits(threshold as _); + } + w.cam_byte_order() + .bit(config.byte_order != ByteOrder::default()); + w.cam_bit_order() + .bit(config.bit_order != BitOrder::default()); + w.cam_vs_eof_en() + .bit(matches!(config.eof_mode, EofMode::VsyncSignal)); + w.cam_line_int_en().bit(config.line_interrupt.is_some()); + w.cam_stop_en().clear_bit() + } + }); + self.regs().cam_ctrl1().write(|w| unsafe { + w.cam_2byte_en().bit(config.enable_2byte_mode); + w.cam_vh_de_mode_en() + .bit(matches!(config.vh_de_mode, VhdeMode::VsyncHsync)); + if let EofMode::ByteLen(value) = config.eof_mode { + w.cam_rec_data_bytelen().bits(value); + } + if let Some(value) = config.line_interrupt { + w.cam_line_int_num().bits(value); + } + w.cam_vsync_filter_en() + .bit(config.vsync_filter_threshold.is_some()); + w.cam_clk_inv().bit(config.invert_pixel_clock); + w.cam_de_inv().bit(config.invert_h_enable); + w.cam_hsync_inv().bit(config.invert_hsync); + w.cam_vsync_inv().bit(config.invert_vsync) + }); + + self.regs() + .cam_rgb_yuv() + .write(|w| w.cam_conv_bypass().clear_bit()); + + self.regs() + .cam_ctrl() + .modify(|_, w| w.cam_update().set_bit()); + + Ok(()) + } +} + +impl<'d> Camera<'d> { + /// Configures the master clock (MCLK) pin for the camera interface. + pub fn with_master_clock(self, mclk: impl PeripheralOutput<'d>) -> Self { + let mclk = mclk.into(); + + mclk.apply_output_config(&OutputConfig::default()); + mclk.set_output_enable(true); + + OutputSignal::CAM_CLK.connect_to(&mclk); + + self + } + + /// Configures the pixel clock (PCLK) pin for the camera interface. + pub fn with_pixel_clock(self, pclk: impl PeripheralInput<'d>) -> Self { + let pclk = pclk.into(); + + pclk.apply_input_config(&InputConfig::default()); + pclk.set_input_enable(true); + InputSignal::CAM_PCLK.connect_to(&pclk); + + self + } + + /// Configures the Vertical Sync (VSYNC) pin for the camera interface. + pub fn with_vsync(self, pin: impl PeripheralInput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_input_config(&InputConfig::default()); + pin.set_input_enable(true); + InputSignal::CAM_V_SYNC.connect_to(&pin); + + self + } + + /// Configures the Horizontal Sync (HSYNC) pin for the camera interface. + pub fn with_hsync(self, pin: impl PeripheralInput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_input_config(&InputConfig::default()); + pin.set_input_enable(true); + InputSignal::CAM_H_SYNC.connect_to(&pin); + + self + } + + /// Configures the Horizontal Enable (HENABLE) pin for the camera interface. + /// + /// Also known as "Data Enable". + pub fn with_h_enable(self, pin: impl PeripheralInput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_input_config(&InputConfig::default()); + pin.set_input_enable(true); + InputSignal::CAM_H_ENABLE.connect_to(&pin); + + self + } + + fn with_data_pin(self, signal: InputSignal, pin: impl PeripheralInput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_input_config(&InputConfig::default()); + pin.set_input_enable(true); + signal.connect_to(&pin); + + self + } + + /// Configures the DATA 0 pin for the camera interface. + pub fn with_data0(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_0, pin) + } + + /// Configures the DATA 1 pin for the camera interface. + pub fn with_data1(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_1, pin) + } + + /// Configures the DATA 2 pin for the camera interface. + pub fn with_data2(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_2, pin) + } + + /// Configures the DATA 3 pin for the camera interface. + pub fn with_data3(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_3, pin) + } + + /// Configures the DATA 4 pin for the camera interface. + pub fn with_data4(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_4, pin) + } + + /// Configures the DATA 5 pin for the camera interface. + pub fn with_data5(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_5, pin) + } + + /// Configures the DATA 6 pin for the camera interface. + pub fn with_data6(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_6, pin) + } + + /// Configures the DATA 7 pin for the camera interface. + pub fn with_data7(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_7, pin) + } + + /// Configures the DATA 8 pin for the camera interface. + pub fn with_data8(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_8, pin) + } + + /// Configures the DATA 9 pin for the camera interface. + pub fn with_data9(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_9, pin) + } + + /// Configures the DATA 10 pin for the camera interface. + pub fn with_data10(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_10, pin) + } + + /// Configures the DATA 11 pin for the camera interface. + pub fn with_data11(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_11, pin) + } + + /// Configures the DATA 12 pin for the camera interface. + pub fn with_data12(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_12, pin) + } + + /// Configures the DATA 13 pin for the camera interface. + pub fn with_data13(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_13, pin) + } + + /// Configures the DATA 14 pin for the camera interface. + pub fn with_data14(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_14, pin) + } + + /// Configures the DATA 15 pin for the camera interface. + pub fn with_data15(self, pin: impl PeripheralInput<'d>) -> Self { + self.with_data_pin(InputSignal::CAM_DATA_15, pin) + } + + /// Starts a DMA transfer to receive data from the camera peripheral. + pub fn receive( + mut self, + mut buf: BUF, + ) -> Result, (DmaError, Self, BUF)> { + // Reset Camera control unit and Async Rx FIFO + self.regs() + .cam_ctrl1() + .modify(|_, w| w.cam_reset().set_bit()); + self.regs() + .cam_ctrl1() + .modify(|_, w| w.cam_reset().clear_bit()); + self.regs() + .cam_ctrl1() + .modify(|_, w| w.cam_afifo_reset().set_bit()); + self.regs() + .cam_ctrl1() + .modify(|_, w| w.cam_afifo_reset().clear_bit()); + + // Start DMA to receive incoming transfer. + let result = unsafe { + self.rx_channel + .prepare_transfer(DmaPeripheral::LcdCam, &mut buf) + .and_then(|_| self.rx_channel.start_transfer()) + }; + + if let Err(e) = result { + return Err((e, self, buf)); + } + + // Start the Camera unit to listen for incoming DVP stream. + self.regs().cam_ctrl().modify(|_, w| { + // Automatically stops the camera unit once the GDMA Rx FIFO is full. + w.cam_stop_en().set_bit(); + + w.cam_update().set_bit() + }); + self.regs() + .cam_ctrl1() + .modify(|_, w| w.cam_start().set_bit()); + + Ok(CameraTransfer { + camera: ManuallyDrop::new(self), + buffer_view: ManuallyDrop::new(buf.into_view()), + }) + } +} + +/// Represents an ongoing (or potentially stopped) transfer from the Camera to a +/// DMA buffer. +pub struct CameraTransfer<'d, BUF: DmaRxBuffer> { + camera: ManuallyDrop>, + buffer_view: ManuallyDrop, +} + +impl<'d, BUF: DmaRxBuffer> CameraTransfer<'d, BUF> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + // This peripheral doesn't really "complete". As long the camera (or anything + // pretending to be :D) sends data, it will receive it and pass it to the DMA. + // This implementation of is_done is an opinionated one. When the transfer is + // started, the CAM_STOP_EN bit is set, which tells the LCD_CAM to stop + // itself when the DMA stops emptying its async RX FIFO. This will + // typically be because the DMA ran out descriptors but there could be other + // reasons as well. + + // In the future, a user of esp_hal may not want this behaviour, which would be + // a reasonable ask. At which point is_done and wait would go away, and + // the driver will stop pretending that this peripheral has some kind of + // finish line. + + // For now, most people probably want this behaviour, so it shall be kept for + // the sake of familiarity and similarity with other drivers. + + self.camera + .regs() + .cam_ctrl1() + .read() + .cam_start() + .bit_is_clear() + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn stop(mut self) -> (Camera<'d>, BUF::Final) { + self.stop_peripherals(); + let (camera, view) = self.release(); + (camera, BUF::from_view(view)) + } + + /// Waits for the transfer to stop and returns the peripheral and buffer. + /// + /// Note: The camera doesn't really "finish" its transfer, so what you're + /// really waiting for here is a DMA Error. You typically just want to + /// call [Self::stop] once you have the data you need. + pub fn wait(mut self) -> (Result<(), DmaError>, Camera<'d>, BUF::Final) { + while !self.is_done() {} + + // Stop the DMA as it doesn't know that the camera has stopped. + self.camera.rx_channel.stop_transfer(); + + // Note: There is no "done" interrupt to clear. + + let (camera, view) = self.release(); + + let result = if camera.rx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, camera, BUF::from_view(view)) + } + + fn release(mut self) -> (Camera<'d>, BUF::View) { + // SAFETY: Since forget is called on self, we know that self.camera and + // self.buffer_view won't be touched again. + let result = unsafe { + let camera = ManuallyDrop::take(&mut self.camera); + let view = ManuallyDrop::take(&mut self.buffer_view); + (camera, view) + }; + core::mem::forget(self); + result + } + + fn stop_peripherals(&mut self) { + // Stop the LCD_CAM peripheral. + self.camera + .regs() + .cam_ctrl1() + .modify(|_, w| w.cam_start().clear_bit()); + + // Stop the DMA + self.camera.rx_channel.stop_transfer(); + } +} + +impl Deref for CameraTransfer<'_, BUF> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buffer_view + } +} + +impl DerefMut for CameraTransfer<'_, BUF> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer_view + } +} + +impl Drop for CameraTransfer<'_, BUF> { + fn drop(&mut self) { + self.stop_peripherals(); + + // SAFETY: This is Drop, we know that self.camera and self.buffer_view + // won't be touched again. + unsafe { + ManuallyDrop::drop(&mut self.camera); + ManuallyDrop::drop(&mut self.buffer_view); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Configuration settings for the Camera interface. +pub struct Config { + /// The pixel clock frequency for the camera interface. + frequency: Rate, + + /// Enable 16 bit mode (instead of 8 bit). + enable_2byte_mode: bool, + + /// The byte order for the camera data. + byte_order: ByteOrder, + + /// The bit order for the camera data. + bit_order: BitOrder, + + /// Vsync/Hsync or Data Enable Mode + vh_de_mode: VhdeMode, + + /// The Vsync filter threshold. + vsync_filter_threshold: Option, + + /// Conditions under which Camera should emit a SUC_EOF to the DMA. + eof_mode: EofMode, + + /// If set, the line interrupt is enabled and will be triggered when + /// the number of received lines reaches this value + 1. + /// + /// This is a 7 bit value which means a max of 128 lines. + line_interrupt: Option, + + /// Invert VSYNC signal, valid in high level. + invert_vsync: bool, + + /// Invert HSYNC signal, valid in high level. + invert_hsync: bool, + + /// Invert H_ENABLE signal (Also known as "Data Enable"), valid in high level. + invert_h_enable: bool, + + /// Invert PCLK signal. + invert_pixel_clock: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: Rate::from_mhz(20), + enable_2byte_mode: false, + byte_order: Default::default(), + bit_order: Default::default(), + vh_de_mode: VhdeMode::De, + vsync_filter_threshold: None, + eof_mode: EofMode::VsyncSignal, + line_interrupt: None, + invert_vsync: false, + invert_hsync: false, + invert_h_enable: false, + invert_pixel_clock: false, + } + } +} diff --git a/esp-hal/src/lcd_cam/lcd/dpi.rs b/esp-hal/src/lcd_cam/lcd/dpi.rs new file mode 100644 index 00000000000..c9c619f4718 --- /dev/null +++ b/esp-hal/src/lcd_cam/lcd/dpi.rs @@ -0,0 +1,805 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # LCD - RGB/Digital Parallel Interface Mode +//! +//! ## Overview +//! +//! The LCD_CAM peripheral Dpi driver provides support for the DPI (commonly +//! know as RGB) format/timing. The driver mandates DMA (Direct Memory Access) +//! for efficient data transfer. +//! +//! ## Examples +//! +//! ### A display +//! +//! The following example shows how to setup and send a solid frame to a DPI +//! display. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::Level; +//! # use esp_hal::lcd_cam::{ +//! # LcdCam, +//! # lcd::{ +//! # ClockMode, Polarity, Phase, +//! # dpi::{Config, Dpi, Format, FrameTiming, self} +//! # } +//! # }; +//! # use esp_hal::dma_loop_buffer; +//! +//! # let channel = peripherals.DMA_CH0; +//! # let mut dma_buf = dma_loop_buffer!(32); +//! +//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM); +//! +//! let config = dpi::Config::default() +//! .with_frequency(Rate::from_mhz(1)) +//! .with_clock_mode(ClockMode { +//! polarity: Polarity::IdleLow, +//! phase: Phase::ShiftLow, +//! }) +//! .with_format(Format { +//! enable_2byte_mode: true, +//! ..Default::default() +//! }) +//! .with_timing(FrameTiming { +//! horizontal_active_width: 480, +//! horizontal_total_width: 520, +//! horizontal_blank_front_porch: 10, +//! +//! vertical_active_height: 480, +//! vertical_total_height: 510, +//! vertical_blank_front_porch: 10, +//! +//! hsync_width: 10, +//! vsync_width: 10, +//! +//! hsync_position: 0, +//! }) +//! .with_vsync_idle_level(Level::High) +//! .with_hsync_idle_level(Level::High) +//! .with_de_idle_level(Level::Low) +//! .with_disable_black_region(false); +//! +//! let mut dpi = Dpi::new(lcd_cam.lcd, channel, config)? +//! .with_vsync(peripherals.GPIO3) +//! .with_hsync(peripherals.GPIO46) +//! .with_de(peripherals.GPIO17) +//! .with_pclk(peripherals.GPIO9) +//! // Blue +//! .with_data0(peripherals.GPIO10) +//! .with_data1(peripherals.GPIO11) +//! .with_data2(peripherals.GPIO12) +//! .with_data3(peripherals.GPIO13) +//! .with_data4(peripherals.GPIO14) +//! // Green +//! .with_data5(peripherals.GPIO21) +//! .with_data6(peripherals.GPIO8) +//! .with_data7(peripherals.GPIO18) +//! .with_data8(peripherals.GPIO45) +//! .with_data9(peripherals.GPIO38) +//! .with_data10(peripherals.GPIO39) +//! // Red +//! .with_data11(peripherals.GPIO40) +//! .with_data12(peripherals.GPIO41) +//! .with_data13(peripherals.GPIO42) +//! .with_data14(peripherals.GPIO2) +//! .with_data15(peripherals.GPIO1); +//! +//! let color: u16 = 0b11111_000000_00000; // RED +//! for chunk in dma_buf.chunks_mut(2) { +//! chunk.copy_from_slice(&color.to_le_bytes()); +//! } +//! +//! let transfer = dpi.send(false, dma_buf).map_err(|e| e.0)?; +//! transfer.wait(); +//! # {after_snippet} +//! ``` + +use core::{ + marker::PhantomData, + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use crate::{ + Blocking, + DriverMode, + dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, TxChannelFor}, + gpio::{Level, OutputConfig, OutputSignal, interconnect::PeripheralOutput}, + lcd_cam::{ + BitOrder, + ByteOrder, + ClockError, + calculate_clkm, + lcd::{ClockMode, DelayMode, Lcd, Phase, Polarity}, + }, + pac, + peripherals::LCD_CAM, + soc::clocks::ClockTree, + system::{self, GenericPeripheralGuard}, + time::Rate, +}; + +/// Errors that can occur when configuring the DPI peripheral. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Clock configuration error. + Clock(ClockError), +} + +/// Represents the RGB LCD interface. +pub struct Dpi<'d, Dm: DriverMode> { + lcd_cam: LCD_CAM<'d>, + tx_channel: ChannelTx>>, + _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>, + _mode: PhantomData, +} + +impl<'d, Dm> Dpi<'d, Dm> +where + Dm: DriverMode, +{ + /// Create a new instance of the RGB/DPI driver. + pub fn new( + lcd: Lcd<'d, Dm>, + channel: impl TxChannelFor>, + config: Config, + ) -> Result { + let tx_channel = ChannelTx::new(channel.degrade()); + + let mut this = Self { + lcd_cam: lcd.lcd_cam, + tx_channel, + _guard: lcd._guard, + _mode: PhantomData, + }; + + this.apply_config(&config)?; + + Ok(this) + } + + fn regs(&self) -> &pac::lcd_cam::RegisterBlock { + self.lcd_cam.register_block() + } + + /// Applies the configuration to the peripheral. + /// + /// # Errors + /// + /// [`ConfigError::Clock`] variant will be returned if the frequency passed + /// in `Config` is too low. + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + // Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf + // the LCD_PCLK divider must be at least 2. To make up for this the user + // provided frequency is doubled to match. + let (i, divider) = ClockTree::with(|clocks| { + calculate_clkm( + (config.frequency.as_hz() * 2) as _, + &[ + crate::soc::clocks::xtal_clk_frequency(clocks) as usize, + crate::soc::clocks::pll_d2_frequency(clocks) as usize, + crate::soc::clocks::crypto_pwm_clk_frequency(clocks) as usize, + ], + ) + }) + .map_err(ConfigError::Clock)?; + + self.regs().lcd_clock().write(|w| unsafe { + // Force enable the clock for all configuration registers. + w.clk_en().set_bit(); + w.lcd_clk_sel().bits((i + 1) as _); + w.lcd_clkm_div_num().bits(divider.div_num as _); + w.lcd_clkm_div_b().bits(divider.div_b as _); + w.lcd_clkm_div_a().bits(divider.div_a as _); // LCD_PCLK = LCD_CLK / 2 + w.lcd_clk_equ_sysclk().clear_bit(); + w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0. + w.lcd_ck_idle_edge() + .bit(config.clock_mode.polarity == Polarity::IdleHigh); + w.lcd_ck_out_edge() + .bit(config.clock_mode.phase == Phase::ShiftHigh) + }); + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_reset().set_bit()); + + self.regs() + .lcd_rgb_yuv() + .write(|w| w.lcd_conv_bypass().clear_bit()); + + self.regs().lcd_user().modify(|_, w| { + if config.format.enable_2byte_mode { + w.lcd_8bits_order().bit(false); + w.lcd_byte_order() + .bit(config.format.byte_order == ByteOrder::Inverted); + } else { + w.lcd_8bits_order() + .bit(config.format.byte_order == ByteOrder::Inverted); + w.lcd_byte_order().bit(false); + } + w.lcd_bit_order() + .bit(config.format.bit_order == BitOrder::Inverted); + w.lcd_2byte_en().bit(config.format.enable_2byte_mode); + + // Only valid in Intel8080 mode. + w.lcd_cmd().clear_bit(); + w.lcd_dummy().clear_bit(); + + // This needs to be explicitly set for RGB mode. + w.lcd_dout().set_bit() + }); + + let timing = &config.timing; + self.regs().lcd_ctrl().modify(|_, w| unsafe { + // Enable RGB mode, and input VSYNC, HSYNC, and DE signals. + w.lcd_rgb_mode_en().set_bit(); + + w.lcd_hb_front() + .bits((timing.horizontal_blank_front_porch as u16).saturating_sub(1)); + w.lcd_va_height() + .bits((timing.vertical_active_height as u16).saturating_sub(1)); + w.lcd_vt_height() + .bits((timing.vertical_total_height as u16).saturating_sub(1)) + }); + self.regs().lcd_ctrl1().modify(|_, w| unsafe { + w.lcd_vb_front() + .bits((timing.vertical_blank_front_porch as u8).saturating_sub(1)); + w.lcd_ha_width() + .bits((timing.horizontal_active_width as u16).saturating_sub(1)); + w.lcd_ht_width() + .bits((timing.horizontal_total_width as u16).saturating_sub(1)) + }); + self.regs().lcd_ctrl2().modify(|_, w| unsafe { + w.lcd_vsync_width() + .bits((timing.vsync_width as u8).saturating_sub(1)); + w.lcd_vsync_idle_pol().bit(config.vsync_idle_level.into()); + w.lcd_de_idle_pol().bit(config.de_idle_level.into()); + w.lcd_hs_blank_en().bit(config.hs_blank_en); + w.lcd_hsync_width() + .bits((timing.hsync_width as u8).saturating_sub(1)); + w.lcd_hsync_idle_pol().bit(config.hsync_idle_level.into()); + w.lcd_hsync_position().bits(timing.hsync_position as u8) + }); + + self.regs().lcd_misc().modify(|_, w| unsafe { + // TODO: Find out what this field actually does. + // Set the threshold for Async Tx FIFO full event. (5 bits) + w.lcd_afifo_threshold_num().bits((1 << 5) - 1); + + // Doesn't matter for RGB mode. + w.lcd_vfk_cyclelen().bits(0); + w.lcd_vbk_cyclelen().bits(0); + + // 1: Send the next frame data when the current frame is sent out. + // 0: LCD stops when the current frame is sent out. + w.lcd_next_frame_en().clear_bit(); + + // Enable blank region when LCD sends data out. + w.lcd_bk_en().bit(!config.disable_black_region) + }); + self.regs().lcd_dly_mode().modify(|_, w| unsafe { + w.lcd_de_mode().bits(config.de_mode as u8); + w.lcd_hsync_mode().bits(config.hsync_mode as u8); + w.lcd_vsync_mode().bits(config.vsync_mode as u8); + w + }); + self.regs().lcd_data_dout_mode().modify(|_, w| unsafe { + w.dout0_mode().bits(config.output_bit_mode as u8); + w.dout1_mode().bits(config.output_bit_mode as u8); + w.dout2_mode().bits(config.output_bit_mode as u8); + w.dout3_mode().bits(config.output_bit_mode as u8); + w.dout4_mode().bits(config.output_bit_mode as u8); + w.dout5_mode().bits(config.output_bit_mode as u8); + w.dout6_mode().bits(config.output_bit_mode as u8); + w.dout7_mode().bits(config.output_bit_mode as u8); + w.dout8_mode().bits(config.output_bit_mode as u8); + w.dout9_mode().bits(config.output_bit_mode as u8); + w.dout10_mode().bits(config.output_bit_mode as u8); + w.dout11_mode().bits(config.output_bit_mode as u8); + w.dout12_mode().bits(config.output_bit_mode as u8); + w.dout13_mode().bits(config.output_bit_mode as u8); + w.dout14_mode().bits(config.output_bit_mode as u8); + w.dout15_mode().bits(config.output_bit_mode as u8) + }); + + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_update().set_bit()); + + Ok(()) + } + + /// Assign the VSYNC pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the VSYNC + /// signal. + pub fn with_vsync(self, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + OutputSignal::LCD_V_SYNC.connect_to(&pin); + + self + } + + /// Assign the HSYNC pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the HSYNC + /// signal. + pub fn with_hsync(self, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + OutputSignal::LCD_H_SYNC.connect_to(&pin); + + self + } + + /// Assign the DE pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DE + /// signal. + pub fn with_de(self, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + OutputSignal::LCD_H_ENABLE.connect_to(&pin); + + self + } + + /// Assign the PCLK pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the PCLK + /// signal. + pub fn with_pclk(self, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + OutputSignal::LCD_PCLK.connect_to(&pin); + + self + } + + fn with_data_pin(self, signal: OutputSignal, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + signal.connect_to(&pin); + + self + } + + /// Assign the DATA_0 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_0 + /// signal. + pub fn with_data0(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_0, pin) + } + + /// Assign the DATA_1 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_1 + /// signal. + pub fn with_data1(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_1, pin) + } + + /// Assign the DATA_2 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_2 + /// signal. + pub fn with_data2(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_2, pin) + } + + /// Assign the DATA_3 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_3 + /// signal. + pub fn with_data3(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_3, pin) + } + + /// Assign the DATA_4 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_4 + /// signal. + pub fn with_data4(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_4, pin) + } + + /// Assign the DATA_5 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_5 + /// signal. + pub fn with_data5(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_5, pin) + } + + /// Assign the DATA_6 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_6 + /// signal. + pub fn with_data6(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_6, pin) + } + + /// Assign the DATA_7 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_7 + /// signal. + pub fn with_data7(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_7, pin) + } + + /// Assign the DATA_8 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_8 + /// signal. + pub fn with_data8(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_8, pin) + } + + /// Assign the DATA_9 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the DATA_9 + /// signal. + pub fn with_data9(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_9, pin) + } + + /// Assign the DATA_10 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_10 signal. + pub fn with_data10(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_10, pin) + } + + /// Assign the DATA_11 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_11 signal. + pub fn with_data11(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_11, pin) + } + + /// Assign the DATA_12 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_12 signal. + pub fn with_data12(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_12, pin) + } + + /// Assign the DATA_13 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_13 signal. + pub fn with_data13(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_13, pin) + } + + /// Assign the DATA_14 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_14 signal. + pub fn with_data14(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_14, pin) + } + + /// Assign the DATA_15 pin for the LCD_CAM. + /// + /// Sets the specified pin to push-pull output and connects it to the + /// DATA_15 signal. + pub fn with_data15(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_15, pin) + } + + /// Sending out the [DmaTxBuffer] to the RGB/DPI interface. + /// + /// - `next_frame_en`: Automatically send the next frame data when the current frame is sent + /// out. + pub fn send( + mut self, + next_frame_en: bool, + mut buf: TX, + ) -> Result, (DmaError, Self, TX)> { + let result = unsafe { + self.tx_channel + .prepare_transfer(DmaPeripheral::LcdCam, &mut buf) + } + .and_then(|_| self.tx_channel.start_transfer()); + if let Err(err) = result { + return Err((err, self, buf)); + } + + // Reset LCD control unit and Async Tx FIFO + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_reset().set_bit()); + self.regs() + .lcd_misc() + .modify(|_, w| w.lcd_afifo_reset().set_bit()); + + self.regs().lcd_misc().modify(|_, w| { + // 1: Send the next frame data when the current frame is sent out. + // 0: LCD stops when the current frame is sent out. + w.lcd_next_frame_en().bit(next_frame_en) + }); + + // Start the transfer. + self.regs().lcd_user().modify(|_, w| { + w.lcd_update().set_bit(); + w.lcd_start().set_bit() + }); + + Ok(DpiTransfer { + dpi: ManuallyDrop::new(self), + buffer_view: ManuallyDrop::new(buf.into_view()), + }) + } +} + +/// Represents an ongoing (or potentially finished) transfer using the RGB LCD +/// interface +pub struct DpiTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> { + dpi: ManuallyDrop>, + buffer_view: ManuallyDrop, +} + +impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> DpiTransfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.dpi.regs().lcd_user().read().lcd_start().bit_is_clear() + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn stop(mut self) -> (Dpi<'d, Dm>, BUF::Final) { + self.stop_peripherals(); + let (dpi, view) = self.release(); + (dpi, BUF::from_view(view)) + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + /// + /// Note: If you specified `next_frame_en` as true in [Dpi::send], you're + /// just waiting for a DMA error when you call this. + pub fn wait(mut self) -> (Result<(), DmaError>, Dpi<'d, Dm>, BUF::Final) { + while !self.is_done() { + core::hint::spin_loop(); + } + + // Stop the DMA. + // + // If the user sends more data to the DMA than the LCD_CAM needs for a single + // frame, the DMA will still be running after the LCD_CAM stops. + self.dpi.tx_channel.stop_transfer(); + + // Note: There is no "done" interrupt to clear. + + let (dpi, view) = self.release(); + let result = if dpi.tx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, dpi, BUF::from_view(view)) + } + + fn release(mut self) -> (Dpi<'d, Dm>, BUF::View) { + // SAFETY: Since forget is called on self, we know that self.dpi and + // self.buffer_view won't be touched again. + let result = unsafe { + let dpi = ManuallyDrop::take(&mut self.dpi); + let view = ManuallyDrop::take(&mut self.buffer_view); + (dpi, view) + }; + core::mem::forget(self); + result + } + + fn stop_peripherals(&mut self) { + // Stop the LCD_CAM peripheral. + self.dpi + .regs() + .lcd_user() + .modify(|_, w| w.lcd_start().clear_bit()); + + // Stop the DMA + self.dpi.tx_channel.stop_transfer(); + } +} + +impl Deref for DpiTransfer<'_, BUF, Dm> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buffer_view + } +} + +impl DerefMut for DpiTransfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer_view + } +} + +impl Drop for DpiTransfer<'_, BUF, Dm> { + fn drop(&mut self) { + self.stop_peripherals(); + + // SAFETY: This is Drop, we know that self.dpi and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.dpi); + ManuallyDrop::take(&mut self.buffer_view) + }; + let _ = BUF::from_view(view); + } +} + +/// Configuration settings for the RGB/DPI interface. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Specifies the clock mode, including polarity and phase settings. + clock_mode: ClockMode, + + /// The frequency of the pixel clock. + frequency: Rate, + + /// Format of the byte data sent out. + format: Format, + + /// Timing settings for the peripheral. + timing: FrameTiming, + + /// The vsync signal level in IDLE state. + vsync_idle_level: Level, + + /// The hsync signal level in IDLE state. + hsync_idle_level: Level, + + /// The de signal level in IDLE state. + de_idle_level: Level, + + /// If enabled, the hsync pulse will be sent out in vertical blanking lines. + /// i.e. When no valid data is actually sent out. Otherwise, hysnc + /// pulses will only be sent out in active region lines. + hs_blank_en: bool, + + /// Disables blank region when LCD sends data out. + disable_black_region: bool, + + /// The output LCD_DE is delayed by module clock LCD_CLK. + de_mode: DelayMode, + /// The output LCD_HSYNC is delayed by module clock LCD_CLK. + hsync_mode: DelayMode, + /// The output LCD_VSYNC is delayed by module clock LCD_CLK. + vsync_mode: DelayMode, + /// The output data bits are delayed by module clock LCD_CLK. + output_bit_mode: DelayMode, +} + +impl Default for Config { + fn default() -> Self { + Config { + clock_mode: Default::default(), + frequency: Rate::from_mhz(1), + format: Default::default(), + timing: Default::default(), + vsync_idle_level: Level::Low, + hsync_idle_level: Level::Low, + de_idle_level: Level::Low, + hs_blank_en: true, + disable_black_region: false, + de_mode: Default::default(), + hsync_mode: Default::default(), + vsync_mode: Default::default(), + output_bit_mode: Default::default(), + } + } +} + +/// Controls how the peripheral should treat data received from the DMA. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Format { + /// Configures the bit order for data transmission. + pub bit_order: BitOrder, + + /// Configures the byte order for data transmission. + /// + /// - In 8-bit mode, [ByteOrder::Inverted] means every two bytes are swapped. + /// - In 16-bit mode, this controls the byte order (endianness). + pub byte_order: ByteOrder, + + /// If true, the width of the output is 16 bits. + /// Otherwise, the width of the output is 8 bits. + pub enable_2byte_mode: bool, +} + +/// The timing numbers for the driver to follow. +/// +/// Note: The names of the fields in this struct don't match what you +/// would typically find in an LCD's datasheet. Carefully read the doc on each +/// field to understand what to set it to. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FrameTiming { + /// The horizontal total width of a frame (in units of PCLK). + /// + /// This should be greater than `horizontal_blank_front_porch` + + /// `horizontal_active_width`. + /// + /// Max is 4096 (12 bits). + pub horizontal_total_width: usize, + + /// The horizontal blank front porch of a frame (in units of PCLK). + /// + /// This is the number of PCLKs between the start of the line and the start + /// of active data in the line. + /// + /// Note: This includes `hsync_width`. + /// + /// Max is 2048 (11 bits). + pub horizontal_blank_front_porch: usize, + + /// The horizontal active width of a frame. i.e. The number of pixels in a + /// line. This is typically the horizontal resolution of the screen. + /// + /// Max is 4096 (12 bits). + pub horizontal_active_width: usize, + + /// The vertical total height of a frame (in units of lines). + /// + /// This should be greater than `vertical_blank_front_porch` + + /// `vertical_active_height`. + /// + /// Max is 1024 (10 bits). + pub vertical_total_height: usize, + + /// The vertical blank front porch height of a frame (in units of lines). + /// + /// This is the number of (blank/invalid) lines before the start of the + /// frame. + /// + /// Note: This includes `vsync_width`. + /// + /// Max is 256 (8 bits). + pub vertical_blank_front_porch: usize, + + /// The vertical active height of a frame. i.e. The number of lines in a + /// frame. This is typically the vertical resolution of the screen. + /// + /// Max is 1024 (10 bits). + pub vertical_active_height: usize, + + /// It is the width of LCD_VSYNC active pulse in a line (in units of lines). + /// + /// Max is 128 (7 bits). + pub vsync_width: usize, + + /// The width of LCD_HSYNC active pulse in a line (in units of PCLK). + /// + /// This should be less than vertical_blank_front_porch, otherwise the hsync + /// pulse will overlap with valid pixel data. + /// + /// Max is 128 (7 bits). + pub hsync_width: usize, + + /// It is the position of LCD_HSYNC active pulse in a line (in units of + /// PCLK). + /// + /// This should be less than horizontal_total_width. + /// + /// Max is 128 (7 bits). + pub hsync_position: usize, +} diff --git a/esp-hal/src/lcd_cam/lcd/i8080.rs b/esp-hal/src/lcd_cam/lcd/i8080.rs new file mode 100644 index 00000000000..87d91ccabe4 --- /dev/null +++ b/esp-hal/src/lcd_cam/lcd/i8080.rs @@ -0,0 +1,701 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # LCD - I8080/MOTO6800 Mode. +//! +//! ## Overview +//! +//! The LCD_CAM peripheral I8080 driver provides support for the I8080 +//! format/timing. The driver mandates DMA (Direct Memory Access) for +//! efficient data transfer. +//! +//! ## Examples +//! +//! ### MIPI-DSI Display +//! +//! The following example shows how to send a command to a MIPI-DSI display over +//! the I8080 protocol. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::lcd_cam::{LcdCam, lcd::i8080::{Config, I8080}}; +//! # use esp_hal::dma_tx_buffer; +//! # use esp_hal::dma::DmaTxBuf; +//! +//! # let mut dma_buf = dma_tx_buffer!(32678)?; +//! +//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM); +//! +//! let config = Config::default().with_frequency(Rate::from_mhz(20)); +//! +//! let mut i8080 = I8080::new(lcd_cam.lcd, peripherals.DMA_CH0, config)? +//! .with_dc(peripherals.GPIO0) +//! .with_wrx(peripherals.GPIO47) +//! .with_data0(peripherals.GPIO9) +//! .with_data1(peripherals.GPIO46) +//! .with_data2(peripherals.GPIO3) +//! .with_data3(peripherals.GPIO8) +//! .with_data4(peripherals.GPIO18) +//! .with_data5(peripherals.GPIO17) +//! .with_data6(peripherals.GPIO16) +//! .with_data7(peripherals.GPIO15); +//! +//! dma_buf.fill(&[0x55]); +//! let transfer = i8080.send(0x3Au8, 0, dma_buf)?; // RGB565 +//! transfer.wait(); +//! # {after_snippet} +//! ``` + +use core::{ + fmt::Formatter, + marker::PhantomData, + mem::{ManuallyDrop, size_of}, + ops::{Deref, DerefMut}, +}; + +use crate::{ + Blocking, + DriverMode, + dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, TxChannelFor}, + gpio::{OutputConfig, OutputSignal, interconnect::PeripheralOutput}, + lcd_cam::{ + BitOrder, + ByteOrder, + ClockError, + Instance, + LCD_DONE_WAKER, + Lcd, + calculate_clkm, + lcd::{ClockMode, DelayMode, Phase, Polarity}, + }, + pac, + peripherals::LCD_CAM, + soc::clocks::ClockTree, + system::{self, GenericPeripheralGuard}, + time::Rate, +}; + +/// A configuration error. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Clock configuration error. + Clock(ClockError), +} + +/// Represents the I8080 LCD interface. +pub struct I8080<'d, Dm: DriverMode> { + lcd_cam: LCD_CAM<'d>, + tx_channel: ChannelTx>>, + _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>, + _mode: PhantomData, +} + +impl<'d, Dm> I8080<'d, Dm> +where + Dm: DriverMode, +{ + /// Creates a new instance of the I8080 LCD interface. + pub fn new( + lcd: Lcd<'d, Dm>, + channel: impl TxChannelFor>, + config: Config, + ) -> Result { + let tx_channel = ChannelTx::new(channel.degrade()); + + let mut this = Self { + lcd_cam: lcd.lcd_cam, + tx_channel, + _guard: lcd._guard, + _mode: PhantomData, + }; + + this.apply_config(&config)?; + + Ok(this) + } + + fn regs(&self) -> &pac::lcd_cam::RegisterBlock { + self.lcd_cam.register_block() + } + + /// Applies configuration. + /// + /// # Errors + /// + /// [`ConfigError::Clock`] variant will be returned if the frequency passed + /// in `Config` is too low. + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + // Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf + // the LCD_PCLK divider must be at least 2. To make up for this the user + // provided frequency is doubled to match. + let (i, divider) = ClockTree::with(|clocks| { + calculate_clkm( + (config.frequency.as_hz() * 2) as _, + &[ + crate::soc::clocks::xtal_clk_frequency(clocks) as usize, + crate::soc::clocks::pll_d2_frequency(clocks) as usize, + crate::soc::clocks::crypto_pwm_clk_frequency(clocks) as usize, + ], + ) + }) + .map_err(ConfigError::Clock)?; + + self.regs().lcd_clock().write(|w| unsafe { + // Force enable the clock for all configuration registers. + w.clk_en().set_bit(); + w.lcd_clk_sel().bits((i + 1) as _); + w.lcd_clkm_div_num().bits(divider.div_num as _); + w.lcd_clkm_div_b().bits(divider.div_b as _); + w.lcd_clkm_div_a().bits(divider.div_a as _); // LCD_PCLK = LCD_CLK / 2 + w.lcd_clk_equ_sysclk().clear_bit(); + w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0. + w.lcd_ck_idle_edge() + .bit(config.clock_mode.polarity == Polarity::IdleHigh); + w.lcd_ck_out_edge() + .bit(config.clock_mode.phase == Phase::ShiftHigh) + }); + + self.regs() + .lcd_ctrl() + .write(|w| w.lcd_rgb_mode_en().clear_bit()); + self.regs() + .lcd_rgb_yuv() + .write(|w| w.lcd_conv_bypass().clear_bit()); + + self.regs().lcd_user().modify(|_, w| { + w.lcd_8bits_order().bit(false); + w.lcd_bit_order().bit(false); + w.lcd_byte_order().bit(false); + w.lcd_2byte_en().bit(false) + }); + self.regs().lcd_misc().write(|w| unsafe { + // Set the threshold for Async Tx FIFO full event. (5 bits) + w.lcd_afifo_threshold_num().bits(0); + // Configure the setup cycles in LCD non-RGB mode. Setup cycles + // expected = this value + 1. (6 bit) + w.lcd_vfk_cyclelen() + .bits(config.setup_cycles.saturating_sub(1) as _); + // Configure the hold time cycles in LCD non-RGB mode. Hold + // cycles expected = this value + 1. + w.lcd_vbk_cyclelen() + .bits(config.hold_cycles.saturating_sub(1) as _); + // 1: Send the next frame data when the current frame is sent out. + // 0: LCD stops when the current frame is sent out. + w.lcd_next_frame_en().clear_bit(); + // Enable blank region when LCD sends data out. + w.lcd_bk_en().set_bit(); + // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DOUT phase. + // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE. + w.lcd_cd_data_set() + .bit(config.cd_data_edge != config.cd_idle_edge); + // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DUMMY phase. + // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE. + w.lcd_cd_dummy_set() + .bit(config.cd_dummy_edge != config.cd_idle_edge); + // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in CMD phase. + // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE. + w.lcd_cd_cmd_set() + .bit(config.cd_cmd_edge != config.cd_idle_edge); + // The default value of LCD_CD + w.lcd_cd_idle_edge().bit(config.cd_idle_edge) + }); + self.regs() + .lcd_dly_mode() + .write(|w| unsafe { w.lcd_cd_mode().bits(config.cd_mode as u8) }); + self.regs().lcd_data_dout_mode().write(|w| unsafe { + w.dout0_mode().bits(config.output_bit_mode as u8); + w.dout1_mode().bits(config.output_bit_mode as u8); + w.dout2_mode().bits(config.output_bit_mode as u8); + w.dout3_mode().bits(config.output_bit_mode as u8); + w.dout4_mode().bits(config.output_bit_mode as u8); + w.dout5_mode().bits(config.output_bit_mode as u8); + w.dout6_mode().bits(config.output_bit_mode as u8); + w.dout7_mode().bits(config.output_bit_mode as u8); + w.dout8_mode().bits(config.output_bit_mode as u8); + w.dout9_mode().bits(config.output_bit_mode as u8); + w.dout10_mode().bits(config.output_bit_mode as u8); + w.dout11_mode().bits(config.output_bit_mode as u8); + w.dout12_mode().bits(config.output_bit_mode as u8); + w.dout13_mode().bits(config.output_bit_mode as u8); + w.dout14_mode().bits(config.output_bit_mode as u8); + w.dout15_mode().bits(config.output_bit_mode as u8) + }); + + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_update().set_bit()); + + Ok(()) + } + + /// Configures the byte order for data transmission in 16-bit mode. + /// This must be set to [ByteOrder::default()] when transmitting in 8-bit + /// mode. + pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self { + let is_inverted = byte_order != ByteOrder::default(); + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_byte_order().bit(is_inverted)); + self + } + + /// Configures the byte order for data transmission in 8-bit mode. + /// This must be set to [ByteOrder::default()] when transmitting in 16-bit + /// mode. + pub fn set_8bits_order(&mut self, byte_order: ByteOrder) -> &mut Self { + let is_inverted = byte_order != ByteOrder::default(); + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_8bits_order().bit(is_inverted)); + self + } + + /// Configures the bit order for data transmission. + pub fn set_bit_order(&mut self, bit_order: BitOrder) -> &mut Self { + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_bit_order().bit(bit_order != BitOrder::default())); + self + } + + /// Associates a CS pin with the I8080 interface. + pub fn with_cs(self, cs: impl PeripheralOutput<'d>) -> Self { + let cs = cs.into(); + + cs.apply_output_config(&OutputConfig::default()); + cs.set_output_enable(true); + + OutputSignal::LCD_CS.connect_to(&cs); + + self + } + + /// Associates a DC pin with the I8080 interface. + pub fn with_dc(self, dc: impl PeripheralOutput<'d>) -> Self { + let dc = dc.into(); + + dc.apply_output_config(&OutputConfig::default()); + dc.set_output_enable(true); + OutputSignal::LCD_DC.connect_to(&dc); + + self + } + + /// Associates a WRX pin with the I8080 interface. + pub fn with_wrx(self, wrx: impl PeripheralOutput<'d>) -> Self { + let wrx = wrx.into(); + + wrx.apply_output_config(&OutputConfig::default()); + wrx.set_output_enable(true); + OutputSignal::LCD_PCLK.connect_to(&wrx); + + self + } + + fn with_data_pin(self, signal: OutputSignal, pin: impl PeripheralOutput<'d>) -> Self { + let pin = pin.into(); + + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + signal.connect_to(&pin); + + self + } + + /// Associate a DATA 0 pin with the I8080 interface. + pub fn with_data0(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_0, pin) + } + + /// Associate a DATA 1 pin with the I8080 interface. + pub fn with_data1(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_1, pin) + } + + /// Associate a DATA 2 pin with the I8080 interface. + pub fn with_data2(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_2, pin) + } + + /// Associate a DATA 3 pin with the I8080 interface. + pub fn with_data3(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_3, pin) + } + + /// Associate a DATA 4 pin with the I8080 interface. + pub fn with_data4(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_4, pin) + } + + /// Associate a DATA 5 pin with the I8080 interface. + pub fn with_data5(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_5, pin) + } + + /// Associate a DATA 6 pin with the I8080 interface. + pub fn with_data6(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_6, pin) + } + + /// Associate a DATA 7 pin with the I8080 interface. + pub fn with_data7(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_7, pin) + } + + /// Associate a DATA 8 pin with the I8080 interface. + pub fn with_data8(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_8, pin) + } + + /// Associate a DATA 9 pin with the I8080 interface. + pub fn with_data9(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_9, pin) + } + + /// Associate a DATA 10 pin with the I8080 interface. + pub fn with_data10(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_10, pin) + } + + /// Associate a DATA 11 pin with the I8080 interface. + pub fn with_data11(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_11, pin) + } + + /// Associate a DATA 12 pin with the I8080 interface. + pub fn with_data12(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_12, pin) + } + + /// Associate a DATA 13 pin with the I8080 interface. + pub fn with_data13(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_13, pin) + } + + /// Associate a DATA 14 pin with the I8080 interface. + pub fn with_data14(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_14, pin) + } + + /// Associate a DATA 15 pin with the I8080 interface. + pub fn with_data15(self, pin: impl PeripheralOutput<'d>) -> Self { + self.with_data_pin(OutputSignal::LCD_DATA_15, pin) + } + + /// Sends a command and data to the LCD using DMA. + /// + /// Passing a `Command` will make this an 8-bit transfer and a + /// `Command` will make this a 16-bit transfer. + /// + /// Note: A 16-bit transfer on an 8-bit bus will silently truncate the 2nd + /// byte and an 8-bit transfer on a 16-bit bus will silently pad each + /// byte to 2 bytes. + pub fn send + Copy, BUF: DmaTxBuffer>( + mut self, + cmd: impl Into>, + dummy: u8, + mut data: BUF, + ) -> Result, (DmaError, Self, BUF)> { + let cmd = cmd.into(); + + // Reset LCD control unit and Async Tx FIFO + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_reset().set_bit()); + self.regs() + .lcd_misc() + .modify(|_, w| w.lcd_afifo_reset().set_bit()); + + // Set cmd value + match cmd { + Command::None => { + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_cmd().clear_bit()); + } + Command::One(value) => { + self.regs().lcd_user().modify(|_, w| { + w.lcd_cmd().set_bit(); + w.lcd_cmd_2_cycle_en().clear_bit() + }); + self.regs() + .lcd_cmd_val() + .write(|w| unsafe { w.lcd_cmd_value().bits(value.into() as _) }); + } + Command::Two(first, second) => { + self.regs().lcd_user().modify(|_, w| { + w.lcd_cmd().set_bit(); + w.lcd_cmd_2_cycle_en().set_bit() + }); + let cmd = first.into() as u32 | ((second.into() as u32) << 16); + self.regs() + .lcd_cmd_val() + .write(|w| unsafe { w.lcd_cmd_value().bits(cmd) }); + } + } + + let is_2byte_mode = size_of::() == 2; + self.regs().lcd_user().modify(|_, w| unsafe { + // Set dummy length + if dummy > 0 { + // Enable DUMMY phase in LCD sequence when LCD starts. + w.lcd_dummy() + .set_bit() + // Configure DUMMY cycles. DUMMY cycles = this value + 1. (2 bits) + .lcd_dummy_cyclelen() + .bits((dummy - 1) as _) + } else { + w.lcd_dummy().clear_bit() + } + .lcd_2byte_en() + .bit(is_2byte_mode) + }); + + // Use continous mode for DMA. FROM the S3 TRM: + // > In a continuous output, LCD module keeps sending data till: + // > i. LCD_CAM_LCD_START is cleared; + // > ii. or LCD_CAM_LCD_RESET is set; + // > iii. or all the data in GDMA is sent out. + self.regs() + .lcd_user() + .modify(|_, w| w.lcd_always_out_en().set_bit().lcd_dout().set_bit()); + + let result = unsafe { + self.tx_channel + .prepare_transfer(DmaPeripheral::LcdCam, &mut data) + } + .and_then(|_| self.tx_channel.start_transfer()); + if let Err(err) = result { + return Err((err, self, data)); + } + + // Setup interrupts. + self.regs() + .lc_dma_int_clr() + .write(|w| w.lcd_trans_done_int_clr().set_bit()); + + self.regs().lcd_user().modify(|_, w| { + w.lcd_update().set_bit(); + w.lcd_start().set_bit() + }); + + Ok(I8080Transfer { + i8080: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(data.into_view()), + }) + } +} + +impl core::fmt::Debug for I8080<'_, Dm> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("I8080").finish() + } +} + +/// Represents an ongoing (or potentially finished) transfer using the I8080 LCD +/// interface +pub struct I8080Transfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> { + i8080: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> I8080Transfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.i8080 + .regs() + .lcd_user() + .read() + .lcd_start() + .bit_is_clear() + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn cancel(mut self) -> (I8080<'d, Dm>, BUF::Final) { + self.stop_peripherals(); + let (_, i8080, buf) = self.wait(); + (i8080, buf) + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + /// + /// Note: This also clears the transfer interrupt so it can be used in + /// interrupt handlers to "handle" the interrupt. + pub fn wait(mut self) -> (Result<(), DmaError>, I8080<'d, Dm>, BUF::Final) { + while !self.is_done() {} + + // Clear "done" interrupt. + self.i8080 + .regs() + .lc_dma_int_clr() + .write(|w| w.lcd_trans_done_int_clr().set_bit()); + + // SAFETY: Since forget is called on self, we know that self.i8080 and + // self.buf_view won't be touched again. + let (i8080, view) = unsafe { + let i8080 = ManuallyDrop::take(&mut self.i8080); + let view = ManuallyDrop::take(&mut self.buf_view); + core::mem::forget(self); + (i8080, view) + }; + + let result = if i8080.tx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, i8080, BUF::from_view(view)) + } + + fn stop_peripherals(&mut self) { + // Stop the LCD_CAM peripheral. + self.i8080 + .regs() + .lcd_user() + .modify(|_, w| w.lcd_start().clear_bit()); + + // Stop the DMA + self.i8080.tx_channel.stop_transfer(); + } +} + +impl Deref for I8080Transfer<'_, BUF, Dm> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for I8080Transfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl I8080Transfer<'_, BUF, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + + struct LcdDoneFuture {} + + impl Future for LcdDoneFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if Instance::is_lcd_done_set() { + // Interrupt bit will be cleared in Self::wait. + // This allows `wait_for_done` to be called more than once. + // + // Instance::clear_lcd_done(); + Poll::Ready(()) + } else { + LCD_DONE_WAKER.register(cx.waker()); + Instance::listen_lcd_done(); + Poll::Pending + } + } + } + + impl Drop for LcdDoneFuture { + fn drop(&mut self) { + Instance::unlisten_lcd_done(); + } + } + + LcdDoneFuture {}.await + } +} + +impl Drop for I8080Transfer<'_, BUF, Dm> { + fn drop(&mut self) { + self.stop_peripherals(); + + // SAFETY: This is Drop, we know that self.i8080 and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.i8080); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Configuration settings for the I8080 interface. +pub struct Config { + /// Specifies the clock mode, including polarity and phase settings. + clock_mode: ClockMode, + + /// The frequency of the pixel clock. + frequency: Rate, + + /// Setup cycles expected, must be at least 1. (6 bits) + setup_cycles: usize, + + /// Hold cycles expected, must be at least 1. (13 bits) + hold_cycles: usize, + + /// The default value of LCD_CD. + cd_idle_edge: bool, + /// The value of LCD_CD during CMD phase. + cd_cmd_edge: bool, + /// The value of LCD_CD during dummy phase. + cd_dummy_edge: bool, + /// The value of LCD_CD during data phase. + cd_data_edge: bool, + + /// The output LCD_CD is delayed by module clock LCD_CLK. + cd_mode: DelayMode, + /// The output data bits are delayed by module clock LCD_CLK. + output_bit_mode: DelayMode, +} + +impl Default for Config { + fn default() -> Self { + Self { + clock_mode: Default::default(), + frequency: Rate::from_mhz(20), + setup_cycles: 1, + hold_cycles: 1, + cd_idle_edge: false, + cd_cmd_edge: false, + cd_dummy_edge: false, + cd_data_edge: true, + cd_mode: Default::default(), + output_bit_mode: Default::default(), + } + } +} + +/// LCD_CAM I8080 command. +/// +/// Can be [Command::None] if command phase should be suppressed. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// Suppresses the command phase. No command is sent. + None, + /// Sends a single-word command. + One(T), + /// Sends a two-word command. + Two(T, T), +} + +impl From for Command { + fn from(value: u8) -> Self { + Command::One(value) + } +} + +impl From for Command { + fn from(value: u16) -> Self { + Command::One(value) + } +} diff --git a/esp-hal/src/lcd_cam/lcd/mod.rs b/esp-hal/src/lcd_cam/lcd/mod.rs new file mode 100644 index 00000000000..343a03dfc12 --- /dev/null +++ b/esp-hal/src/lcd_cam/lcd/mod.rs @@ -0,0 +1,73 @@ +//! LCD +//! +//! ## Overview +//! The LCD module is designed to send parallel video data signals, and its bus +//! supports RGB, MOTO6800, and I8080 interface timing. +//! +//! For more information on these modes, please refer to the documentation in +//! their respective modules. + +use super::GenericPeripheralGuard; +use crate::{peripherals::LCD_CAM, system}; + +pub mod dpi; +pub mod i8080; + +/// Represents an LCD interface. +pub struct Lcd<'d, Dm: crate::DriverMode> { + /// The `LCD_CAM` peripheral reference for managing the LCD functionality. + pub(crate) lcd_cam: LCD_CAM<'d>, + + /// A marker for the mode of operation (blocking or asynchronous). + pub(crate) _mode: core::marker::PhantomData, + + pub(super) _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the clock mode configuration for the LCD interface. +pub struct ClockMode { + /// The polarity of the clock signal (idle high or low). + pub polarity: Polarity, + + /// The phase of the clock signal (shift on the rising or falling edge). + pub phase: Phase, +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the polarity of the clock signal for the LCD interface. +pub enum Polarity { + /// The clock signal is low when idle. + #[default] + IdleLow, + + /// The clock signal is high when idle. + IdleHigh, +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the phase of the clock signal for the LCD interface. +pub enum Phase { + /// Data is shifted on the low (falling) edge of the clock signal. + #[default] + ShiftLow, + + /// Data is shifted on the high (rising) edge of the clock signal. + ShiftHigh, +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the delay mode for the LCD signal output. +pub enum DelayMode { + /// Output without delay. + #[default] + None = 0, + /// Delayed by the rising edge of LCD_CLK. + RaisingEdge = 1, + /// Delayed by the falling edge of LCD_CLK. + FallingEdge = 2, +} diff --git a/esp-hal/src/lcd_cam/mod.rs b/esp-hal/src/lcd_cam/mod.rs new file mode 100644 index 00000000000..d69952b1946 --- /dev/null +++ b/esp-hal/src/lcd_cam/mod.rs @@ -0,0 +1,316 @@ +//! # LCD and Camera +//! +//! ## Overview +//! This peripheral consists of an LCD module and a Camera module, which can be +//! used simultaneously. For more information on these modules, please refer to +//! the documentation in their respective modules. + +pub mod cam; +pub mod lcd; + +use core::marker::PhantomData; + +use crate::{ + Async, + Blocking, + asynch::AtomicWaker, + handler, + interrupt::InterruptHandler, + lcd_cam::{cam::Cam, lcd::Lcd}, + peripherals::{Interrupt, LCD_CAM}, + system::{Cpu, GenericPeripheralGuard}, +}; + +/// Represents a combined LCD and Camera interface. +pub struct LcdCam<'d, Dm: crate::DriverMode> { + /// The LCD interface. + pub lcd: Lcd<'d, Dm>, + /// The Camera interface. + pub cam: Cam<'d>, +} + +impl<'d> LcdCam<'d, Blocking> { + /// Creates a new `LcdCam` instance. + pub fn new(lcd_cam: LCD_CAM<'d>) -> Self { + let lcd_guard = GenericPeripheralGuard::new(); + let cam_guard = GenericPeripheralGuard::new(); + + Self { + lcd: Lcd { + lcd_cam: unsafe { lcd_cam.clone_unchecked() }, + _mode: PhantomData, + _guard: lcd_guard, + }, + cam: Cam { + lcd_cam, + _guard: cam_guard, + }, + } + } + + /// Reconfigures the peripheral for asynchronous operation. + pub fn into_async(mut self) -> LcdCam<'d, Async> { + self.set_interrupt_handler(interrupt_handler); + LcdCam { + lcd: Lcd { + lcd_cam: self.lcd.lcd_cam, + _mode: PhantomData, + _guard: self.lcd._guard, + }, + cam: self.cam, + } + } + + /// Registers an interrupt handler for the LCD_CAM peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::LCD_CAM); + } + crate::interrupt::bind_handler(Interrupt::LCD_CAM, handler); + } +} + +impl crate::private::Sealed for LcdCam<'_, Blocking> {} +// TODO: This interrupt is shared with the Camera module, we should handle this +// in a similar way to the gpio::IO +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for LcdCam<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +impl<'d> LcdCam<'d, Async> { + /// Reconfigures the peripheral for blocking operation. + pub fn into_blocking(self) -> LcdCam<'d, Blocking> { + crate::interrupt::disable(Cpu::current(), Interrupt::LCD_CAM); + LcdCam { + lcd: Lcd { + lcd_cam: self.lcd.lcd_cam, + _mode: PhantomData, + _guard: self.lcd._guard, + }, + cam: self.cam, + } + } +} + +/// LCD_CAM bit order +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BitOrder { + /// Do not change bit order. + #[default] + Native = 0, + /// Invert bit order. + Inverted = 1, +} + +/// LCD_CAM byte order +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ByteOrder { + /// Do not change byte order. + #[default] + Native = 0, + /// Invert byte order. + Inverted = 1, +} + +pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new(); + +#[handler] +fn interrupt_handler() { + // TODO: this is a shared interrupt with Camera and here we ignore that! + if Instance::is_lcd_done_set() { + Instance::unlisten_lcd_done(); + LCD_DONE_WAKER.wake() + } +} + +pub(crate) struct Instance; + +// NOTE: the LCD_CAM interrupt registers are shared between LCD and Camera and +// this is only implemented for the LCD side, when the Camera is implemented a +// CriticalSection will be needed to protect these shared registers. +impl Instance { + fn enable_listenlcd_done(en: bool) { + LCD_CAM::regs() + .lc_dma_int_ena() + .modify(|_, w| w.lcd_trans_done_int_ena().bit(en)); + } + + pub(crate) fn listen_lcd_done() { + Self::enable_listenlcd_done(true); + } + + pub(crate) fn unlisten_lcd_done() { + Self::enable_listenlcd_done(false); + } + + pub(crate) fn is_lcd_done_set() -> bool { + LCD_CAM::regs() + .lc_dma_int_raw() + .read() + .lcd_trans_done_int_raw() + .bit() + } +} +pub(crate) struct ClockDivider { + // Integral LCD clock divider value. (8 bits) + // Value 0 is treated as 256 + // Value 1 is treated as 2 + // Value N is treated as N + pub div_num: usize, + + // Fractional clock divider numerator value. (6 bits) + pub div_b: usize, + + // Fractional clock divider denominator value. (6 bits) + pub div_a: usize, +} + +/// Clock configuration errors. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// Desired frequency was too low for the dividers to divide to + FrequencyTooLow, +} + +pub(crate) fn calculate_clkm( + desired_frequency: usize, + source_frequencies: &[usize], +) -> Result<(usize, ClockDivider), ClockError> { + let mut result_freq = 0; + let mut result = None; + + for (i, &source_frequency) in source_frequencies.iter().enumerate() { + let div = calculate_closest_divider(source_frequency, desired_frequency); + if let Some(div) = div { + let freq = calculate_output_frequency(source_frequency, &div); + if result.is_none() || freq > result_freq { + result = Some((i, div)); + result_freq = freq; + } + } + } + + result.ok_or(ClockError::FrequencyTooLow) +} + +fn calculate_output_frequency(source_frequency: usize, divider: &ClockDivider) -> usize { + let n = match divider.div_num { + 0 => 256, + 1 => 2, + _ => divider.div_num.min(256), + }; + + if divider.div_b != 0 && divider.div_a != 0 { + // OUTPUT = SOURCE / (N + B/A) + // OUTPUT = SOURCE / ((NA + B)/A) + // OUTPUT = (SOURCE * A) / (NA + B) + + // u64 is required to fit the numbers from this arithmetic. + + let source = source_frequency as u64; + let n = n as u64; + let a = divider.div_b as u64; + let b = divider.div_a as u64; + + ((source * a) / (n * a + b)) as _ + } else { + source_frequency / n + } +} + +fn calculate_closest_divider( + source_frequency: usize, + desired_frequency: usize, +) -> Option { + let div_num = source_frequency / desired_frequency; + if div_num < 2 { + // Source clock isn't fast enough to reach the desired frequency. + // Return max output. + return Some(ClockDivider { + div_num: 1, + div_b: 0, + div_a: 0, + }); + } + if div_num > 256 { + // Source is too fast to divide to the desired frequency. Return None. + return None; + } + + let div_num = if div_num == 256 { 0 } else { div_num }; + + let div_fraction = { + let div_remainder = source_frequency % desired_frequency; + let gcd = hcf(div_remainder, desired_frequency); + Fraction { + numerator: div_remainder / gcd, + denominator: desired_frequency / gcd, + } + }; + + let divider = if div_fraction.numerator == 0 { + ClockDivider { + div_num, + div_b: 0, + div_a: 0, + } + } else { + let target = div_fraction; + let closest = farey_sequence(63).find(|curr| { + // https://en.wikipedia.org/wiki/Fraction#Adding_unlike_quantities + + let new_curr_num = curr.numerator * target.denominator; + let new_target_num = target.numerator * curr.denominator; + new_curr_num >= new_target_num + }); + + let closest = unwrap!(closest, "The fraction must be between 0 and 1"); + + ClockDivider { + div_num, + div_b: closest.numerator, + div_a: closest.denominator, + } + }; + Some(divider) +} + +// https://en.wikipedia.org/wiki/Euclidean_algorithm +const fn hcf(a: usize, b: usize) -> usize { + if b != 0 { hcf(b, a % b) } else { a } +} + +struct Fraction { + pub numerator: usize, + pub denominator: usize, +} + +// https://en.wikipedia.org/wiki/Farey_sequence#Next_term +fn farey_sequence(denominator: usize) -> impl Iterator { + let mut a = 0; + let mut b = 1; + let mut c = 1; + let mut d = denominator; + core::iter::from_fn(move || { + if a > denominator { + return None; + } + let next = Fraction { + numerator: a, + denominator: b, + }; + let k = (denominator + b) / d; + (a, b, c, d) = (c, d, k * c - a, k * d - b); + Some(next) + }) +} diff --git a/esp-hal/src/ledc/channel.rs b/esp-hal/src/ledc/channel.rs new file mode 100644 index 00000000000..9e94140f21d --- /dev/null +++ b/esp-hal/src/ledc/channel.rs @@ -0,0 +1,713 @@ +//! # LEDC channel +//! +//! ## Overview +//! The LEDC Channel module provides a high-level interface to +//! configure and control individual PWM channels of the LEDC peripheral. +//! +//! ## Configuration +//! The module allows precise and flexible control over LED lighting and other +//! `Pulse-Width Modulation (PWM)` applications by offering configurable duty +//! cycles and frequencies. + +use super::timer::{TimerIFace, TimerSpeed}; +use crate::{ + gpio::{ + DriveMode, + OutputConfig, + OutputSignal, + interconnect::{self, PeripheralOutput}, + }, + pac::ledc::RegisterBlock, + peripherals::LEDC, +}; + +/// Fade parameter sub-errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FadeError { + /// Start duty % out of range + StartDuty, + /// End duty % out of range + EndDuty, + /// Duty % change from start to end is out of range + DutyRange, + /// Duration too long for timer frequency and duty resolution + Duration, +} + +/// Channel errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid duty % value + Duty, + /// Timer not configured + Timer, + /// Channel not configured + Channel, + /// Fade parameters invalid + Fade(FadeError), +} + +/// Channel number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Number { + /// Channel 0 + Channel0 = 0, + /// Channel 1 + Channel1 = 1, + /// Channel 2 + Channel2 = 2, + /// Channel 3 + Channel3 = 3, + /// Channel 4 + Channel4 = 4, + /// Channel 5 + Channel5 = 5, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + /// Channel 6 + Channel6 = 6, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + /// Channel 7 + Channel7 = 7, +} + +/// Channel configuration +pub mod config { + use crate::{ + gpio::DriveMode, + ledc::timer::{TimerIFace, TimerSpeed}, + }; + + /// Channel configuration + #[derive(Copy, Clone)] + pub struct Config<'a, S: TimerSpeed> { + /// A reference to the timer associated with this channel. + pub timer: &'a dyn TimerIFace, + /// The duty cycle percentage (0-100). + pub duty_pct: u8, + /// The pin configuration (PushPull or OpenDrain). + pub drive_mode: DriveMode, + } +} + +/// Channel interface +pub trait ChannelIFace<'a, S: TimerSpeed + 'a> +where + Channel<'a, S>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>; + + /// Set channel duty HW + fn set_duty(&self, duty_pct: u8) -> Result<(), Error>; + + /// Start a duty-cycle fade + fn start_duty_fade( + &self, + start_duty_pct: u8, + end_duty_pct: u8, + duration_ms: u16, + ) -> Result<(), Error>; + + /// Check whether a duty-cycle fade is running + fn is_duty_fade_running(&self) -> bool; +} + +/// Channel HW interface +pub trait ChannelHW { + /// Configure Channel HW except for the duty which is set via + /// [`Self::set_duty_hw`]. + fn configure_hw(&mut self) -> Result<(), Error>; + /// Configure the hardware for the channel with a specific pin + /// configuration. + fn configure_hw_with_drive_mode(&mut self, cfg: DriveMode) -> Result<(), Error>; + + /// Set channel duty HW + fn set_duty_hw(&self, duty: u32); + + /// Start a duty-cycle fade HW + fn start_duty_fade_hw( + &self, + start_duty: u32, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ); + + /// Check whether a duty-cycle fade is running HW + fn is_duty_fade_running_hw(&self) -> bool; +} + +/// Channel struct +pub struct Channel<'a, S: TimerSpeed> { + ledc: &'a RegisterBlock, + timer: Option<&'a dyn TimerIFace>, + number: Number, + output_pin: interconnect::OutputSignal<'a>, +} + +impl<'a, S: TimerSpeed> Channel<'a, S> { + /// Return a new channel + pub fn new(number: Number, output_pin: impl PeripheralOutput<'a>) -> Self { + let ledc = LEDC::regs(); + Channel { + ledc, + timer: None, + number, + output_pin: output_pin.into(), + } + } +} + +impl<'a, S: TimerSpeed> ChannelIFace<'a, S> for Channel<'a, S> +where + Channel<'a, S>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> { + self.timer = Some(config.timer); + + self.set_duty(config.duty_pct)?; + self.configure_hw_with_drive_mode(config.drive_mode)?; + + Ok(()) + } + + /// Set duty % of channel + fn set_duty(&self, duty_pct: u8) -> Result<(), Error> { + let duty_exp; + if let Some(timer) = self.timer { + if let Some(timer_duty) = timer.duty() { + duty_exp = timer_duty as u32; + } else { + return Err(Error::Timer); + } + } else { + return Err(Error::Channel); + } + + let duty_range = 2u32.pow(duty_exp); + let duty_value = (duty_range * duty_pct as u32) / 100; + + if duty_pct > 100u8 { + // duty_pct greater than 100% + return Err(Error::Duty); + } + + self.set_duty_hw(duty_value); + + Ok(()) + } + + /// Start a duty fade from one % to another. + /// + /// There's a constraint on the combination of timer frequency, timer PWM + /// duty resolution (the bit count), the fade "range" (abs(start-end)), and + /// the duration: + /// + /// frequency * duration / ((1< Result<(), Error> { + let duty_exp; + let frequency; + if start_duty_pct > 100u8 { + return Err(Error::Fade(FadeError::StartDuty)); + } + if end_duty_pct > 100u8 { + return Err(Error::Fade(FadeError::EndDuty)); + } + if let Some(timer) = self.timer { + if let Some(timer_duty) = timer.duty() { + if timer.frequency() > 0 { + duty_exp = timer_duty as u32; + frequency = timer.frequency(); + } else { + return Err(Error::Timer); + } + } else { + return Err(Error::Timer); + } + } else { + return Err(Error::Channel); + } + + let duty_range = (1u32 << duty_exp) - 1; + let start_duty_value = (duty_range * start_duty_pct as u32) / 100; + let end_duty_value = (duty_range * end_duty_pct as u32) / 100; + + // NB: since we do the multiplication first here, there's no loss of + // precision from using milliseconds instead of (e.g.) nanoseconds. + let pwm_cycles = (duration_ms as u32) * frequency / 1000; + + let abs_duty_diff = end_duty_value.abs_diff(start_duty_value); + let duty_steps: u32 = u16::try_from(abs_duty_diff).unwrap_or(65535).into(); + // This conversion may fail if duration_ms is too big, and if either + // duty_steps gets truncated, or the fade is over a short range of duty + // percentages, so it's too small. Returning an Err in either case is + // fine: shortening the duration_ms will sort things out. + let cycles_per_step: u16 = (pwm_cycles / duty_steps) + .try_into() + .map_err(|_| Error::Fade(FadeError::Duration)) + .and_then(|res| { + if res > 1023 { + Err(Error::Fade(FadeError::Duration)) + } else { + Ok(res) + } + })?; + // This can't fail unless abs_duty_diff is bigger than 65536*65535-1, + // and so duty_steps gets truncated. But that requires duty_exp to be + // at least 32, and the hardware only supports up to 20. Still, handle + // it in case something changes in the future. + let duty_per_cycle: u16 = (abs_duty_diff / duty_steps) + .try_into() + .map_err(|_| Error::Fade(FadeError::DutyRange))?; + + self.start_duty_fade_hw( + start_duty_value, + end_duty_value > start_duty_value, + duty_steps.try_into().unwrap(), + cycles_per_step, + duty_per_cycle, + ); + + Ok(()) + } + + fn is_duty_fade_running(&self) -> bool { + self.is_duty_fade_running_hw() + } +} + +mod ehal1 { + use embedded_hal::pwm::{self, ErrorKind, ErrorType, SetDutyCycle}; + + use super::{Channel, ChannelHW, Error}; + use crate::ledc::timer::TimerSpeed; + + impl pwm::Error for Error { + fn kind(&self) -> pwm::ErrorKind { + ErrorKind::Other + } + } + + impl ErrorType for Channel<'_, S> { + type Error = Error; + } + + impl<'a, S: TimerSpeed> SetDutyCycle for Channel<'a, S> + where + Channel<'a, S>: ChannelHW, + { + fn max_duty_cycle(&self) -> u16 { + let duty_exp; + + if let Some(timer_duty) = self.timer.and_then(|timer| timer.duty()) { + duty_exp = timer_duty as u32; + } else { + return 0; + } + + let duty_range = 2u32.pow(duty_exp); + + duty_range as u16 + } + + fn set_duty_cycle(&mut self, mut duty: u16) -> Result<(), Self::Error> { + let max = self.max_duty_cycle(); + duty = if duty > max { max } else { duty }; + self.set_duty_hw(duty.into()); + Ok(()) + } + } +} + +impl Channel<'_, S> { + #[cfg(esp32)] + fn set_channel(&mut self, timer_number: u8) { + if S::IS_HS { + let ch = self.ledc.hsch(self.number as usize); + ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) }); + ch.conf0() + .modify(|_, w| unsafe { w.sig_out_en().set_bit().timer_sel().bits(timer_number) }); + } else { + let ch = self.ledc.lsch(self.number as usize); + ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) }); + ch.conf0() + .modify(|_, w| unsafe { w.sig_out_en().set_bit().timer_sel().bits(timer_number) }); + } + self.start_duty_without_fading(); + } + #[cfg(not(esp32))] + fn set_channel(&mut self, timer_number: u8) { + { + let ch = self.ledc.ch(self.number as usize); + ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) }); + ch.conf0().modify(|_, w| { + w.sig_out_en().set_bit(); + unsafe { w.timer_sel().bits(timer_number) } + }); + } + + // this is needed to make low duty-resolutions / high frequencies work + #[cfg(any(esp32h2, esp32c6))] + self.ledc + .ch_gamma_wr_addr(self.number as usize) + .write(|w| unsafe { w.bits(0) }); + + self.start_duty_without_fading(); + } + + #[cfg(esp32)] + fn start_duty_without_fading(&self) { + if S::IS_HS { + self.ledc + .hsch(self.number as usize) + .conf1() + .write(|w| unsafe { + w.duty_start().set_bit(); + w.duty_inc().set_bit(); + w.duty_num().bits(0x1); + w.duty_cycle().bits(0x1); + w.duty_scale().bits(0x0) + }); + } else { + self.ledc + .lsch(self.number as usize) + .conf1() + .write(|w| unsafe { + w.duty_start().set_bit(); + w.duty_inc().set_bit(); + w.duty_num().bits(0x1); + w.duty_cycle().bits(0x1); + w.duty_scale().bits(0x0) + }); + } + } + #[cfg(any(esp32c6, esp32h2))] + fn start_duty_without_fading(&self) { + let cnum = self.number as usize; + self.ledc + .ch(cnum) + .conf1() + .write(|w| w.duty_start().set_bit()); + self.ledc.ch_gamma_wr(cnum).write(|w| { + w.ch_gamma_duty_inc().set_bit(); + unsafe { + w.ch_gamma_duty_num().bits(0x1); + w.ch_gamma_duty_cycle().bits(0x1); + w.ch_gamma_scale().bits(0x0) + } + }); + } + #[cfg(not(any(esp32, esp32c6, esp32h2)))] + fn start_duty_without_fading(&self) { + self.ledc.ch(self.number as usize).conf1().write(|w| { + w.duty_start().set_bit(); + w.duty_inc().set_bit(); + unsafe { + w.duty_num().bits(0x1); + w.duty_cycle().bits(0x1); + w.duty_scale().bits(0x0) + } + }); + } + + #[cfg(esp32)] + fn start_duty_fade_inner( + &self, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + if S::IS_HS { + self.ledc + .hsch(self.number as usize) + .conf1() + .write(|w| unsafe { + w.duty_start() + .set_bit() + .duty_inc() + .variant(duty_inc) + .duty_num() // count of incs before stopping + .bits(duty_steps) + .duty_cycle() // overflows between incs + .bits(cycles_per_step) + .duty_scale() + .bits(duty_per_cycle) + }); + } else { + self.ledc + .lsch(self.number as usize) + .conf1() + .write(|w| unsafe { + w.duty_start() + .set_bit() + .duty_inc() + .variant(duty_inc) + .duty_num() // count of incs before stopping + .bits(duty_steps) + .duty_cycle() // overflows between incs + .bits(cycles_per_step) + .duty_scale() + .bits(duty_per_cycle) + }); + } + } + + #[cfg(any(esp32c6, esp32h2))] + fn start_duty_fade_inner( + &self, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + let cnum = self.number as usize; + self.ledc + .ch(cnum) + .conf1() + .write(|w| w.duty_start().set_bit()); + self.ledc.ch_gamma_wr(cnum).write(|w| unsafe { + w.ch_gamma_duty_inc() + .variant(duty_inc) + .ch_gamma_duty_num() // count of incs before stopping + .bits(duty_steps) + .ch_gamma_duty_cycle() // overflows between incs + .bits(cycles_per_step) + .ch_gamma_scale() + .bits(duty_per_cycle) + }); + self.ledc + .ch_gamma_wr_addr(cnum) + .write(|w| unsafe { w.ch_gamma_wr_addr().bits(0) }); + self.ledc + .ch_gamma_conf(cnum) + .write(|w| unsafe { w.ch_gamma_entry_num().bits(0x1) }); + } + + #[cfg(not(any(esp32, esp32c6, esp32h2)))] + fn start_duty_fade_inner( + &self, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + self.ledc + .ch(self.number as usize) + .conf1() + .write(|w| unsafe { + w.duty_start().set_bit(); + w.duty_inc().variant(duty_inc); + // count of incs before stopping + w.duty_num().bits(duty_steps); + // overflows between incs + w.duty_cycle().bits(cycles_per_step); + w.duty_scale().bits(duty_per_cycle) + }); + } + + #[cfg(esp32)] + fn update_channel(&self) { + if !S::IS_HS { + self.ledc + .lsch(self.number as usize) + .conf0() + .modify(|_, w| w.para_up().set_bit()); + } + } + #[cfg(not(esp32))] + fn update_channel(&self) { + self.ledc + .ch(self.number as usize) + .conf0() + .modify(|_, w| w.para_up().set_bit()); + } +} + +impl ChannelHW for Channel<'_, S> +where + S: crate::ledc::timer::TimerSpeed, +{ + /// Configure Channel HW + fn configure_hw(&mut self) -> Result<(), Error> { + self.configure_hw_with_drive_mode(DriveMode::PushPull) + } + fn configure_hw_with_drive_mode(&mut self, cfg: DriveMode) -> Result<(), Error> { + if let Some(timer) = self.timer { + if !timer.is_configured() { + return Err(Error::Timer); + } + + self.output_pin + .apply_output_config(&OutputConfig::default().with_drive_mode(cfg)); + self.output_pin.set_output_enable(true); + + let timer_number = timer.number() as u8; + + self.set_channel(timer_number); + self.update_channel(); + + #[cfg(esp32)] + let signal = if S::IS_HS { + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + match self.number { + Number::Channel0 => OutputSignal::LEDC_HS_SIG0, + Number::Channel1 => OutputSignal::LEDC_HS_SIG1, + Number::Channel2 => OutputSignal::LEDC_HS_SIG2, + Number::Channel3 => OutputSignal::LEDC_HS_SIG3, + Number::Channel4 => OutputSignal::LEDC_HS_SIG4, + Number::Channel5 => OutputSignal::LEDC_HS_SIG5, + Number::Channel6 => OutputSignal::LEDC_HS_SIG6, + Number::Channel7 => OutputSignal::LEDC_HS_SIG7, + } + } else { + match self.number { + Number::Channel0 => OutputSignal::LEDC_LS_SIG0, + Number::Channel1 => OutputSignal::LEDC_LS_SIG1, + Number::Channel2 => OutputSignal::LEDC_LS_SIG2, + Number::Channel3 => OutputSignal::LEDC_LS_SIG3, + Number::Channel4 => OutputSignal::LEDC_LS_SIG4, + Number::Channel5 => OutputSignal::LEDC_LS_SIG5, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + Number::Channel6 => OutputSignal::LEDC_LS_SIG6, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + Number::Channel7 => OutputSignal::LEDC_LS_SIG7, + } + }; + #[cfg(not(esp32))] + let signal = match self.number { + Number::Channel0 => OutputSignal::LEDC_LS_SIG0, + Number::Channel1 => OutputSignal::LEDC_LS_SIG1, + Number::Channel2 => OutputSignal::LEDC_LS_SIG2, + Number::Channel3 => OutputSignal::LEDC_LS_SIG3, + Number::Channel4 => OutputSignal::LEDC_LS_SIG4, + Number::Channel5 => OutputSignal::LEDC_LS_SIG5, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + Number::Channel6 => OutputSignal::LEDC_LS_SIG6, + #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + Number::Channel7 => OutputSignal::LEDC_LS_SIG7, + }; + + signal.connect_to(&self.output_pin); + } else { + return Err(Error::Timer); + } + + Ok(()) + } + + /// Set duty in channel HW + #[cfg(esp32)] + fn set_duty_hw(&self, duty: u32) { + if S::IS_HS { + self.ledc + .hsch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(duty << 4) }); + } else { + self.ledc + .lsch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(duty << 4) }); + } + self.start_duty_without_fading(); + self.update_channel(); + } + + /// Set duty in channel HW + #[cfg(not(esp32))] + fn set_duty_hw(&self, duty: u32) { + self.ledc + .ch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(duty << 4) }); + self.start_duty_without_fading(); + self.update_channel(); + } + + /// Start a duty-cycle fade HW + #[cfg(esp32)] + fn start_duty_fade_hw( + &self, + start_duty: u32, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + if S::IS_HS { + self.ledc + .hsch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(start_duty << 4) }); + self.ledc + .int_clr() + .write(|w| w.duty_chng_end_hsch(self.number as u8).clear_bit_by_one()); + } else { + self.ledc + .lsch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(start_duty << 4) }); + self.ledc + .int_clr() + .write(|w| w.duty_chng_end_lsch(self.number as u8).clear_bit_by_one()); + } + self.start_duty_fade_inner(duty_inc, duty_steps, cycles_per_step, duty_per_cycle); + self.update_channel(); + } + + /// Start a duty-cycle fade HW + #[cfg(not(esp32))] + fn start_duty_fade_hw( + &self, + start_duty: u32, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + self.ledc + .ch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(start_duty << 4) }); + self.ledc + .int_clr() + .write(|w| w.duty_chng_end_ch(self.number as u8).clear_bit_by_one()); + self.start_duty_fade_inner(duty_inc, duty_steps, cycles_per_step, duty_per_cycle); + self.update_channel(); + } + + #[cfg(esp32)] + fn is_duty_fade_running_hw(&self) -> bool { + let reg = self.ledc.int_raw().read(); + if S::IS_HS { + reg.duty_chng_end_hsch(self.number as u8).bit_is_clear() + } else { + reg.duty_chng_end_lsch(self.number as u8).bit_is_clear() + } + } + + #[cfg(not(esp32))] + fn is_duty_fade_running_hw(&self) -> bool { + self.ledc + .int_raw() + .read() + .duty_chng_end_ch(self.number as u8) + .bit_is_clear() + } +} diff --git a/esp-hal/src/ledc/mod.rs b/esp-hal/src/ledc/mod.rs new file mode 100644 index 00000000000..7d06834b0ba --- /dev/null +++ b/esp-hal/src/ledc/mod.rs @@ -0,0 +1,184 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # LED Controller (LEDC) +//! +//! ## Overview +//! +//! The LEDC peripheral is primarily designed to control the intensity of LEDs, +//! although it can also be used to generate PWM signals for other purposes. It +//! has multiple channels which can generate independent waveforms that can be +//! used, for example, to drive RGB LED devices. +//! +//! The PWM controller can automatically increase or decrease the duty cycle +//! gradually, allowing for fades without any processor interference. +//! +//! ## Configuration +//! Currently only supports fixed-frequency output. High Speed channels are +//! available for the ESP32 only, while Low Speed channels are available for all +//! supported chips. +//! +//! ## Examples +//! +//! ### Low Speed Channel +//! +//! The following example will configure the Low Speed Channel0 to 24kHz output +//! with 10% duty using the ABPClock and turn on LED with the option to change +//! LED intensity depending on `duty` value. Possible values (`u32`) are in +//! range 0..100. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::ledc::Ledc; +//! # use esp_hal::ledc::LSGlobalClkSource; +//! # use esp_hal::ledc::timer::{self, TimerIFace}; +//! # use esp_hal::ledc::LowSpeed; +//! # use esp_hal::ledc::channel::{self, ChannelIFace}; +//! # use esp_hal::gpio::DriveMode; +//! # let led = peripherals.GPIO0; +//! +//! let mut ledc = Ledc::new(peripherals.LEDC); +//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); +//! +//! let mut lstimer0 = ledc.timer::(timer::Number::Timer0); +//! lstimer0.configure(timer::config::Config { +//! duty: timer::config::Duty::Duty5Bit, +//! clock_source: timer::LSClockSource::APBClk, +//! frequency: Rate::from_khz(24), +//! })?; +//! +//! let mut channel0 = ledc.channel(channel::Number::Channel0, led); +//! channel0.configure(channel::config::Config { +//! timer: &lstimer0, +//! duty_pct: 10, +//! drive_mode: DriveMode::PushPull, +//! })?; +//! +//! loop { +//! // Set up a breathing LED: fade from off to on over a second, then +//! // from on back off over the next second. Then loop. +//! channel0.start_duty_fade(0, 100, 1000)?; +//! while channel0.is_duty_fade_running() {} +//! channel0.start_duty_fade(100, 0, 1000)?; +//! while channel0.is_duty_fade_running() {} +//! } +//! # } +//! ``` +//! +//! ## Implementation State +//! - Source clock selection is not supported +//! - Interrupts are not supported + +use self::{ + channel::Channel, + timer::{Timer, TimerSpeed}, +}; +use crate::{ + gpio::interconnect::PeripheralOutput, + pac, + peripherals::LEDC, + system::{Peripheral as PeripheralEnable, PeripheralClockControl}, +}; + +pub mod channel; +pub mod timer; + +/// Global slow clock source +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LSGlobalClkSource { + /// APB clock. + APBClk, +} + +/// LEDC (LED PWM Controller) +pub struct Ledc<'d> { + _instance: LEDC<'d>, + ledc: &'d pac::ledc::RegisterBlock, +} + +#[cfg(esp32)] +#[derive(Clone, Copy)] +/// Used to specify HighSpeed Timer/Channel +pub struct HighSpeed {} + +#[derive(Clone, Copy)] +/// Used to specify LowSpeed Timer/Channel +pub struct LowSpeed {} + +/// Trait representing the speed mode of a clock or peripheral. +pub trait Speed { + /// Boolean constant indicating whether the speed is high-speed. + const IS_HS: bool; +} + +#[cfg(esp32)] +impl Speed for HighSpeed { + const IS_HS: bool = true; +} + +impl Speed for LowSpeed { + const IS_HS: bool = false; +} + +impl<'d> Ledc<'d> { + /// Return a new LEDC + pub fn new(_instance: LEDC<'d>) -> Self { + if PeripheralClockControl::enable(PeripheralEnable::Ledc) { + PeripheralClockControl::reset(PeripheralEnable::Ledc); + } + + let ledc = LEDC::regs(); + Ledc { _instance, ledc } + } + + /// Set global slow clock source + #[cfg(esp32)] + pub fn set_global_slow_clock(&mut self, _clock_source: LSGlobalClkSource) { + self.ledc.conf().write(|w| w.apb_clk_sel().set_bit()); + self.ledc + .lstimer(0) + .conf() + .modify(|_, w| w.para_up().set_bit()); + } + + #[cfg(not(esp32))] + /// Set global slow clock source + pub fn set_global_slow_clock(&mut self, clock_source: LSGlobalClkSource) { + #[cfg(any(esp32c6, esp32h2))] + let pcr = unsafe { &*crate::peripherals::PCR::ptr() }; + + #[cfg(any(esp32c6, esp32h2))] + pcr.ledc_sclk_conf().write(|w| w.ledc_sclk_en().set_bit()); + + match clock_source { + LSGlobalClkSource::APBClk => { + #[cfg(not(any(esp32c6, esp32h2)))] + self.ledc + .conf() + .write(|w| unsafe { w.apb_clk_sel().bits(1) }); + #[cfg(esp32c6)] + pcr.ledc_sclk_conf() + .write(|w| unsafe { w.ledc_sclk_sel().bits(1) }); + #[cfg(esp32h2)] + pcr.ledc_sclk_conf() + .write(|w| unsafe { w.ledc_sclk_sel().bits(0) }); + } + } + self.ledc + .timer(0) + .conf() + .modify(|_, w| w.para_up().set_bit()); + } + + /// Return a new timer + pub fn timer(&self, number: timer::Number) -> Timer<'d, S> { + Timer::new(self.ledc, number) + } + + /// Return a new channel + pub fn channel( + &self, + number: channel::Number, + output_pin: impl PeripheralOutput<'d>, + ) -> Channel<'d, S> { + Channel::new(number, output_pin) + } +} diff --git a/esp-hal/src/ledc/timer.rs b/esp-hal/src/ledc/timer.rs new file mode 100644 index 00000000000..f8f11065911 --- /dev/null +++ b/esp-hal/src/ledc/timer.rs @@ -0,0 +1,408 @@ +//! # LEDC timer +//! +//! ## Overview +//! The LEDC Timer provides a high-level interface to configure and control +//! individual timers of the `LEDC` peripheral. +//! +//! ## Configuration +//! The module allows precise and flexible control over timer configurations, +//! duty cycles and frequencies, making it ideal for Pulse-Width Modulation +//! (PWM) applications and LED lighting control. +//! +//! LEDC uses APB as clock source. + +#[cfg(esp32)] +use super::HighSpeed; +use super::{LowSpeed, Speed}; +use crate::{clock::Clocks, pac, time::Rate}; + +const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF; + +/// Timer errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid Divisor + Divisor, + /// Frequency unset + FrequencyUnset, +} + +#[cfg(esp32)] +/// Clock source for HS Timers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HSClockSource { + /// APB clock. + APBClk, + // TODO RefTick, +} + +/// Clock source for LS Timers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LSClockSource { + /// APB clock. + APBClk, + // TODO SLOWClk +} + +/// Timer number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Number { + /// Timer 0. + Timer0 = 0, + /// Timer 1. + Timer1 = 1, + /// Timer 2. + Timer2 = 2, + /// Timer 3. + Timer3 = 3, +} + +/// Timer configuration +pub mod config { + use crate::time::Rate; + + /// Number of bits reserved for duty cycle adjustment + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[allow(clippy::enum_variant_names)] // FIXME: resolve before stabilizing this driver + pub enum Duty { + /// 1-bit resolution for duty cycle adjustment. + Duty1Bit = 1, + /// 2-bit resolution for duty cycle adjustment. + Duty2Bit, + /// 3-bit resolution for duty cycle adjustment. + Duty3Bit, + /// 4-bit resolution for duty cycle adjustment. + Duty4Bit, + /// 5-bit resolution for duty cycle adjustment. + Duty5Bit, + /// 6-bit resolution for duty cycle adjustment. + Duty6Bit, + /// 7-bit resolution for duty cycle adjustment. + Duty7Bit, + /// 8-bit resolution for duty cycle adjustment. + Duty8Bit, + /// 9-bit resolution for duty cycle adjustment. + Duty9Bit, + /// 10-bit resolution for duty cycle adjustment. + Duty10Bit, + /// 11-bit resolution for duty cycle adjustment. + Duty11Bit, + /// 12-bit resolution for duty cycle adjustment. + Duty12Bit, + /// 13-bit resolution for duty cycle adjustment. + Duty13Bit, + /// 14-bit resolution for duty cycle adjustment. + Duty14Bit, + #[cfg(esp32)] + /// 15-bit resolution for duty cycle adjustment. + Duty15Bit, + #[cfg(esp32)] + /// 16-bit resolution for duty cycle adjustment. + Duty16Bit, + #[cfg(esp32)] + /// 17-bit resolution for duty cycle adjustment. + Duty17Bit, + #[cfg(esp32)] + /// 18-bit resolution for duty cycle adjustment. + Duty18Bit, + #[cfg(esp32)] + /// 19-bit resolution for duty cycle adjustment. + Duty19Bit, + #[cfg(esp32)] + /// 20-bit resolution for duty cycle adjustment. + Duty20Bit, + } + + impl TryFrom for Duty { + type Error = (); + + fn try_from(value: u32) -> Result { + Ok(match value { + 1 => Self::Duty1Bit, + 2 => Self::Duty2Bit, + 3 => Self::Duty3Bit, + 4 => Self::Duty4Bit, + 5 => Self::Duty5Bit, + 6 => Self::Duty6Bit, + 7 => Self::Duty7Bit, + 8 => Self::Duty8Bit, + 9 => Self::Duty9Bit, + 10 => Self::Duty10Bit, + 11 => Self::Duty11Bit, + 12 => Self::Duty12Bit, + 13 => Self::Duty13Bit, + 14 => Self::Duty14Bit, + #[cfg(esp32)] + 15 => Self::Duty15Bit, + #[cfg(esp32)] + 16 => Self::Duty16Bit, + #[cfg(esp32)] + 17 => Self::Duty17Bit, + #[cfg(esp32)] + 18 => Self::Duty18Bit, + #[cfg(esp32)] + 19 => Self::Duty19Bit, + #[cfg(esp32)] + 20 => Self::Duty20Bit, + _ => Err(())?, + }) + } + } + + /// Timer configuration + #[derive(Copy, Clone)] + pub struct Config { + /// The duty cycle resolution. + pub duty: Duty, + /// The clock source for the timer. + pub clock_source: CS, + /// The frequency of the PWM signal in Hertz. + pub frequency: Rate, + } +} + +/// Trait defining the type of timer source +pub trait TimerSpeed: Speed { + /// The type of clock source used by the timer in this speed mode. + type ClockSourceType; +} + +/// Timer source type for LowSpeed timers +impl TimerSpeed for LowSpeed { + /// The clock source type for low-speed timers. + type ClockSourceType = LSClockSource; +} + +#[cfg(esp32)] +/// Timer source type for HighSpeed timers +impl TimerSpeed for HighSpeed { + /// The clock source type for high-speed timers. + type ClockSourceType = HSClockSource; +} + +/// Interface for Timers +pub trait TimerIFace { + /// Return the frequency of the timer + fn freq(&self) -> Option; + + /// Configure the timer + fn configure(&mut self, config: config::Config) -> Result<(), Error>; + + /// Check if the timer has been configured + fn is_configured(&self) -> bool; + + /// Return the duty resolution of the timer + fn duty(&self) -> Option; + + /// Return the timer number + fn number(&self) -> Number; + + /// Return the timer frequency, or 0 if not configured + fn frequency(&self) -> u32; +} + +/// Interface for HW configuration of timer +pub trait TimerHW { + /// Get the current source timer frequency from the HW + fn freq_hw(&self) -> Option; + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32); + + /// Update the timer in HW + fn update_hw(&self); +} + +/// Timer struct +pub struct Timer<'a, S: TimerSpeed> { + ledc: &'a pac::ledc::RegisterBlock, + number: Number, + duty: Option, + frequency: u32, + configured: bool, + #[cfg(soc_has_clock_node_ref_tick)] + use_ref_tick: bool, + clock_source: Option, +} + +impl<'a, S: TimerSpeed> TimerIFace for Timer<'a, S> +where + Timer<'a, S>: TimerHW, +{ + /// Return the frequency of the timer + fn freq(&self) -> Option { + self.freq_hw() + } + + /// Configure the timer + fn configure(&mut self, config: config::Config) -> Result<(), Error> { + self.duty = Some(config.duty); + self.clock_source = Some(config.clock_source); + + let src_freq: u32 = self.freq().ok_or(Error::FrequencyUnset)?.as_hz(); + let precision = 1 << config.duty as u32; + let frequency: u32 = config.frequency.as_hz(); + self.frequency = frequency; + + #[cfg_attr(not(soc_has_clock_node_ref_tick), expect(unused_mut))] + let mut divisor = ((src_freq as u64) << 8) / frequency as u64 / precision as u64; + + #[cfg(soc_has_clock_node_ref_tick)] + if divisor > LEDC_TIMER_DIV_NUM_MAX { + // APB_CLK results in divisor which too high. Try using REF_TICK as clock + // source. + self.use_ref_tick = true; + divisor = (1_000_000u64 << 8) / frequency as u64 / precision as u64; + } + + if !(256..LEDC_TIMER_DIV_NUM_MAX).contains(&divisor) { + return Err(Error::Divisor); + } + + self.configure_hw(divisor as u32); + self.update_hw(); + + self.configured = true; + + Ok(()) + } + + /// Check if the timer has been configured + fn is_configured(&self) -> bool { + self.configured + } + + /// Return the duty resolution of the timer + fn duty(&self) -> Option { + self.duty + } + + /// Return the timer number + fn number(&self) -> Number { + self.number + } + + /// Return the timer frequency + fn frequency(&self) -> u32 { + self.frequency + } +} + +impl<'a, S: TimerSpeed> Timer<'a, S> { + /// Create a new instance of a timer + pub fn new(ledc: &'a pac::ledc::RegisterBlock, number: Number) -> Self { + Timer { + ledc, + number, + duty: None, + frequency: 0u32, + configured: false, + #[cfg(soc_has_clock_node_ref_tick)] + use_ref_tick: false, + clock_source: None, + } + } +} + +/// Timer HW implementation for LowSpeed timers +impl TimerHW for Timer<'_, LowSpeed> { + /// Get the current source timer frequency from the HW + fn freq_hw(&self) -> Option { + self.clock_source.map(|source| match source { + LSClockSource::APBClk => { + let clocks = Clocks::get(); + clocks.apb_clock + } + }) + } + + #[cfg(esp32)] + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = unwrap!(self.duty) as u8; + let use_apb = !self.use_ref_tick; + + self.ledc + .lstimer(self.number as usize) + .conf() + .modify(|_, w| unsafe { + w.tick_sel().bit(use_apb); + w.rst().clear_bit(); + w.pause().clear_bit(); + w.div_num().bits(divisor); + w.duty_res().bits(duty) + }); + } + + #[cfg(not(esp32))] + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = unwrap!(self.duty) as u8; + + self.ledc + .timer(self.number as usize) + .conf() + .modify(|_, w| unsafe { + #[cfg(soc_has_clock_node_ref_tick)] + w.tick_sel().bit(self.use_ref_tick); + w.rst().clear_bit(); + w.pause().clear_bit(); + w.clk_div().bits(divisor); + w.duty_res().bits(duty) + }); + } + + /// Update the timer in HW + fn update_hw(&self) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let tmr = self.ledc.lstimer(self.number as usize); + } else { + let tmr = self.ledc.timer(self.number as usize); + } + } + + tmr.conf().modify(|_, w| w.para_up().set_bit()); + } +} + +#[cfg(esp32)] +/// Timer HW implementation for HighSpeed timers +impl TimerHW for Timer<'_, HighSpeed> { + /// Get the current source timer frequency from the HW + fn freq_hw(&self) -> Option { + self.clock_source.map(|source| match source { + HSClockSource::APBClk => { + let clocks = Clocks::get(); + clocks.apb_clock + } + }) + } + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = unwrap!(self.duty) as u8; + let sel_hstimer = self.clock_source == Some(HSClockSource::APBClk); + + self.ledc + .hstimer(self.number as usize) + .conf() + .modify(|_, w| unsafe { + w.tick_sel().bit(sel_hstimer); + w.rst().clear_bit(); + w.pause().clear_bit(); + w.div_num().bits(divisor); + w.duty_res().bits(duty) + }); + } + + /// Update the timer in HW + fn update_hw(&self) { + // Nothing to do for HS timers + } +} diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs new file mode 100644 index 00000000000..d8d9b62681a --- /dev/null +++ b/esp-hal/src/lib.rs @@ -0,0 +1,804 @@ +#![cfg_attr( + all(docsrs, not(not_really_docsrs)), + doc = "

    You might want to browse the esp-hal documentation on the esp-rs website instead.

    The documentation here on docs.rs is built for a single chip only (ESP32-C6, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs change depending on the chip.

    \n\n
    \n\n" +)] +//! # Bare-metal (`no_std`) HAL for all Espressif ESP32 devices. +//! +//! This documentation is built for the +#![doc = concat!("**", chip_pretty!(), "**")] +//! . Please ensure you are reading the correct [documentation] for your target +//! device. +//! +//! ## Overview +//! +//! esp-hal is a Hardware Abstraction Layer (HAL) for Espressif's ESP32 lineup of +//! microcontrollers offering safe, idiomatic APIs to control hardware peripherals. +//! +//! ### Peripheral drivers +//! +//! The HAL implements both [`Blocking`] _and_ [`Async`] APIs for all applicable peripherals. +//! Where applicable, driver implement the [embedded-hal] and +//! [embedded-hal-async] traits. Drivers that don't currently have a stable API +//! are marked as `unstable` in the documentation. +//! +//! ### Peripheral singletons +//! +//! Each peripheral driver needs a peripheral singleton that tells the driver +//! which hardware block to use. The peripheral singletons are created by the +//! HAL initialization, and are returned from [`init`] as fields of the +//! [`Peripherals`] struct. +//! +//! These singletons, by default, represent peripherals for the entire lifetime +//! of the program. To allow for reusing peripherals, the HAL provides a +//! `reborrow` method on each peripheral singleton. This method creates a new +//! handle to the peripheral with a shorter lifetime. This allows you to pass +//! the handle to a driver, while still keeping the original handle alive. Once +//! you drop the driver, you will be able to reborrow the peripheral again. +#![cfg_attr( + // Feature-gated so that this doesn't prevent gradual device bringup. Any + // stable driver would serve the purpose here, so this block will be part + // of the released documentation. + i2c_master_driver_supported, + doc = r#" +For example, if you want to use the [`I2c`](i2c::master::I2c) driver and you +don't intend to drop the driver, you can pass the peripheral singleton to +the driver by value: + +```rust, ignore +// Peripheral singletons are returned from the `init` function. +let peripherals = esp_hal::init(esp_hal::Config::default()); + +let mut i2c = I2c::new(peripherals.I2C0, /* ... */); +``` +"# +)] +//! If you want to use the peripheral in multiple places (for example, you want +//! to drop the driver for some period of time to minimize power consumption), +//! you can reborrow the peripheral singleton and pass it to the driver by +//! reference: +//! +//! ```rust, ignore +//! // Note that in this case, `peripherals` needs to be mutable. +//! let mut peripherals = esp_hal::init(esp_hal::Config::default()); +//! +//! let i2c = I2C::new(peripherals.I2C0.reborrow(), /* ... */); +//! +//! // Do something with the I2C driver... +//! +//! core::mem::drop(i2c); // Drop the driver to minimize power consumption. +//! +//! // Do something else... +//! +//! // You can then take or reborrow the peripheral singleton again. +//! let i2c = I2C::new(peripherals.I2C0.reborrow(), /* ... */); +//! ``` +//! +//! ## Examples +//! +//! We have a plethora of [examples] in the esp-hal repository. We use +//! an [xtask] to automate the building, running, and testing of code and +//! examples within esp-hal. +//! +//! Invoke the following command in the root of the esp-hal repository to get +//! started: +//! +//! ```bash +//! cargo xtask help +//! ``` +//! +//! ## Creating a Project +//! +//! We have a [book] that explains the full esp-hal ecosystem +//! and how to get started, it's advisable to give that a read +//! before proceeding. We also have a [training] that covers some common +//! scenarios with examples. +//! +//! We have developed a project generation tool, [esp-generate], which we +//! recommend when starting new projects. It can be installed and run, e.g. +//! for the ESP32-C6, as follows: +//! +//! ```bash +//! cargo install esp-generate +//! esp-generate --chip=esp32c6 your-project +//! ``` +//! +//! ## Blinky +//! +//! Some minimal code to blink an LED looks like this: +//! +//! ```rust, no_run +//! #![no_std] +//! #![no_main] +//! +//! use esp_hal::{ +//! clock::CpuClock, +//! gpio::{Io, Level, Output, OutputConfig}, +//! main, +//! time::{Duration, Instant}, +//! }; +//! +//! // You need a panic handler. Usually, you would use esp_backtrace, panic-probe, or +//! // something similar, but you can also bring your own like this: +//! #[panic_handler] +//! fn panic(_: &core::panic::PanicInfo) -> ! { +//! esp_hal::system::software_reset() +//! } +//! +//! #[main] +//! fn main() -> ! { +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! +//! // Set GPIO0 as an output, and set its state high initially. +//! let mut led = Output::new(peripherals.GPIO0, Level::High, OutputConfig::default()); +//! +//! loop { +//! led.toggle(); +//! // Wait for half a second +//! let delay_start = Instant::now(); +//! while delay_start.elapsed() < Duration::from_millis(500) {} +//! } +//! } +//! ``` +//! +//! ## Additional configuration +//! +//! We've exposed some configuration options that don't fit into cargo +//! features. These can be set via environment variables, or via cargo's `[env]` +//! section inside `.cargo/config.toml`. Note that unstable options can only be +//! enabled when the `unstable` feature is enabled for the crate. Below is a +//! table of tunable parameters for this crate: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_config_table.md"))] +#![doc = ""] +//! ## Don't use `core::mem::forget` +//! +//! You should never use `core::mem::forget` on any type defined in [esp crates]. +//! Many types heavily rely on their `Drop` implementation to not leave the +//! hardware in undefined state which can cause undefined behaviour in your program. +//! +//! You might want to consider using [`#[deny(clippy::mem_forget)`](https://rust-lang.github.io/rust-clippy/v0.0.212/index.html#mem_forget) in your project. +//! +//! ## Library usage +//! +//! If you intend to write a library that uses esp-hal, you should import it as follows: +//! +//! ```toml +//! [dependencies] +//! esp-hal = { version = "1", default-features = false } } +//! ``` +//! +//! This ensures that the `rt` feature is not enabled, nor any chip features. The application that +//! uses your library will then be able to choose the chip feature it needs and enable `rt` such +//! that only the final user application calls [`init`]. +//! +//! If your library depends on `unstable` features, you *must* use the `requires-unstable` feature, +//! and *not* the unstable feature itself. Doing so, improves the quality of the error messages if a +//! user hasn't enabled the unstable feature of esp-hal. +//! +//! [documentation]: https://docs.espressif.com/projects/rust/esp-hal/latest/ +//! [examples]: https://github.com/esp-rs/esp-hal/tree/main/examples +//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ +//! [embedded-hal-async]: https://docs.rs/embedded-hal-async/latest/embedded_hal_async/ +//! [xtask]: https://github.com/matklad/cargo-xtask +//! [esp-generate]: https://github.com/esp-rs/esp-generate +//! [book]: https://docs.espressif.com/projects/rust/book/ +//! [training]: https://docs.espressif.com/projects/rust/no_std-training/ +//! [esp crates]: https://docs.espressif.com/projects/rust/book/introduction/ancillary-crates.html#esp-hal-ecosystem +//! +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![allow(asm_sub_register, async_fn_in_trait, stable_features)] +#![cfg_attr(xtensa, feature(asm_experimental_arch))] +#![deny(missing_docs, rust_2018_idioms, rustdoc::all)] +#![allow(rustdoc::private_doc_tests)] // compile tests are done via rustdoc +#![cfg_attr(docsrs, feature(doc_cfg, custom_inner_attributes, proc_macro_hygiene))] +// Don't trip up on broken/private links when running semver-checks +#![cfg_attr( + semver_checks, + allow(rustdoc::private_intra_doc_links, rustdoc::broken_intra_doc_links) +)] +// Do not document `cfg` gates by default. +#![cfg_attr(docsrs, allow(invalid_doc_attributes))] // doc(auto_cfg = false) requires a new nightly (~2025-10-09+) +#![cfg_attr(docsrs, doc(auto_cfg = false))] +#![no_std] + +// MUST be the first module +mod fmt; + +#[macro_use] +extern crate esp_metadata_generated; + +// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727 +#[doc(hidden)] +macro_rules! unstable_module { + ($( + $(#[$meta:meta])* + pub mod $module:ident; + )*) => { + $( + $(#[$meta])* + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + pub mod $module; + + $(#[$meta])* + #[cfg(not(feature = "unstable"))] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[allow(unused)] + pub(crate) mod $module; + )* + }; +} + +// we can't use instability because it mucks up the short description in rustdoc +#[doc(hidden)] +macro_rules! unstable_reexport { + ($( + $(#[$meta:meta])* + pub use $path:path; + )*) => { + $( + $(#[$meta])* + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + pub use $path; + + $(#[$meta])* + #[cfg(not(feature = "unstable"))] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[allow(unused)] + pub(crate) use $path; + )* + }; +} + +// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727 +// we don't want unstable drivers to be compiled even, unless enabled +#[doc(hidden)] +macro_rules! unstable_driver { + ($( + $(#[$meta:meta])* + pub mod $module:ident; + )*) => { + $( + $(#[$meta])* + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + pub mod $module; + )* + }; +} + +use core::marker::PhantomData; + +pub use esp_metadata_generated::chip; +use esp_rom_sys as _; +pub(crate) use unstable_driver; +pub(crate) use unstable_module; + +metadata!("build_info", CHIP_NAME, chip!()); +metadata!( + "build_info", + MIN_CHIP_REVISION, + esp_config::esp_config_str!("ESP_HAL_CONFIG_MIN_CHIP_REVISION") +); + +#[cfg(all(riscv, feature = "rt"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", feature = "rt"))))] +#[cfg_attr(not(feature = "unstable"), doc(hidden))] +pub use esp_riscv_rt::{self, riscv}; +use esp_sync::RawMutex; +pub(crate) use peripherals::pac; +#[cfg(xtensa)] +#[cfg(all(xtensa, feature = "rt"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", feature = "rt"))))] +#[cfg_attr(not(feature = "unstable"), doc(hidden))] +pub use xtensa_lx_rt::{self, xtensa_lx}; + +#[cfg(any(soc_has_dport, soc_has_hp_sys, soc_has_pcr, soc_has_system))] +pub mod clock; +#[cfg(gpio_driver_supported)] +pub mod gpio; +#[cfg(i2c_master_driver_supported)] +pub mod i2c; +pub mod peripherals; +#[cfg(all( + feature = "unstable", + any(ecc_driver_supported, hmac_driver_supported, sha_driver_supported) +))] +mod reg_access; +#[cfg(any(spi_master_driver_supported, spi_slave_driver_supported))] +pub mod spi; +#[cfg_attr(esp32c5, allow(dead_code))] +pub mod system; +pub mod time; +#[cfg(uart_driver_supported)] +pub mod uart; + +mod macros; + +#[cfg(feature = "rt")] +pub use procmacros::blocking_main as main; +pub use procmacros::ram; + +unstable_reexport! { + pub use procmacros::handler; + + #[cfg(any(lp_core, ulp_riscv_core))] + pub use procmacros::load_lp_code; + + #[cfg(lp_core)] + pub use self::soc::lp_core; + + #[cfg(ulp_riscv_core)] + pub use self::soc::ulp_core; +} + +#[cfg(all(feature = "rt", feature = "exception-handler"))] +mod exception_handler; + +pub mod efuse; +pub mod interrupt; + +unstable_module! { + pub mod asynch; + pub mod debugger; + pub mod rom; + #[doc(hidden)] + pub mod sync; + // Drivers needed for initialization or they are tightly coupled to something else. + #[cfg(any(adc_driver_supported, dac_driver_supported))] + pub mod analog; + #[cfg(any(systimer_driver_supported, timergroup_driver_supported))] + pub mod timer; + #[cfg(soc_has_lpwr)] + pub mod rtc_cntl; + #[cfg(dma_driver_supported)] + pub mod dma; + #[cfg(etm_driver_supported)] + pub mod etm; + #[cfg(usb_otg_driver_supported)] + pub mod otg_fs; + #[cfg(psram)] // DMA needs some things from here + pub mod psram; +} + +#[cfg(any( + sha_driver_supported, + rsa_driver_supported, + aes_driver_supported, + ecc_driver_supported +))] +mod work_queue; + +unstable_driver! { + #[cfg(aes_driver_supported)] + pub mod aes; + #[cfg(assist_debug_driver_supported)] + pub mod assist_debug; + pub mod delay; + #[cfg(ecc_driver_supported)] + pub mod ecc; + #[cfg(hmac_driver_supported)] + pub mod hmac; + #[cfg(i2s_driver_supported)] + pub mod i2s; + #[cfg(soc_has_lcd_cam)] + pub mod lcd_cam; + #[cfg(ledc_driver_supported)] + pub mod ledc; + #[cfg(mcpwm_driver_supported)] + pub mod mcpwm; + #[cfg(parl_io_driver_supported)] + pub mod parl_io; + #[cfg(pcnt_driver_supported)] + pub mod pcnt; + #[cfg(rmt_driver_supported)] + pub mod rmt; + #[cfg(rng_driver_supported)] + pub mod rng; + #[cfg(rsa_driver_supported)] + pub mod rsa; + #[cfg(sha_driver_supported)] + pub mod sha; + #[cfg(touch)] + pub mod touch; + #[cfg(soc_has_trace0)] + pub mod trace; + #[cfg(soc_has_tsens)] + pub mod tsens; + #[cfg(twai_driver_supported)] + pub mod twai; + #[cfg(usb_serial_jtag_driver_supported)] + pub mod usb_serial_jtag; +} + +/// State of the CPU saved when entering exception or interrupt +#[instability::unstable] +#[cfg(feature = "rt")] +#[allow(unused_imports)] +pub mod trapframe { + #[cfg(riscv)] + pub use esp_riscv_rt::TrapFrame; + #[cfg(xtensa)] + pub use xtensa_lx_rt::exception::Context as TrapFrame; +} + +// The `soc` module contains chip-specific implementation details and should not +// be directly exposed. +mod soc; + +#[cfg(is_debug_build)] +procmacros::warning! {" +WARNING: use --release + We *strongly* recommend using release profile when building esp-hal. + The dev profile can potentially be one or more orders of magnitude + slower than release, and may cause issues with timing-sensitive + peripherals and/or devices. +"} + +/// A marker trait for driver modes. +/// +/// Different driver modes offer different features and different API. Using +/// this trait as a generic parameter ensures that the driver is initialized in +/// the correct mode. +pub trait DriverMode: crate::private::Sealed {} + +#[procmacros::doc_replace] +/// Marker type signalling that a driver is initialized in blocking mode. +/// +/// Drivers are constructed in blocking mode by default. To learn about the +/// differences between blocking and async drivers, see the [`Async`] mode +/// documentation. +/// +/// [`Async`] drivers can be converted to a [`Blocking`] driver using the +/// `into_blocking` method, for example: +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use esp_hal::uart::{Config, Uart}; +/// let uart = Uart::new(peripherals.UART0, Config::default())? +/// .with_rx(peripherals.GPIO1) +/// .with_tx(peripherals.GPIO2) +/// .into_async(); +/// +/// let blocking_uart = uart.into_blocking(); +/// +/// # {after_snippet} +/// ``` +#[derive(Debug)] +#[non_exhaustive] +pub struct Blocking; + +#[procmacros::doc_replace] +/// Marker type signalling that a driver is initialized in async mode. +/// +/// Drivers are constructed in blocking mode by default. To set up an async +/// driver, a [`Blocking`] driver must be converted to an `Async` driver using +/// the `into_async` method, for example: +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use esp_hal::uart::{Config, Uart}; +/// let uart = Uart::new(peripherals.UART0, Config::default())? +/// .with_rx(peripherals.GPIO1) +/// .with_tx(peripherals.GPIO2) +/// .into_async(); +/// +/// # {after_snippet} +/// ``` +/// +/// Drivers can be converted back to blocking mode using the `into_blocking` +/// method, see [`Blocking`] documentation for more details. +/// +/// Async mode drivers offer most of the same features as blocking drivers, but +/// with the addition of async APIs. Interrupt-related functions are not +/// available in async mode, as they are handled by the driver's interrupt +/// handlers. +/// +/// Note that async functions usually take up more space than their blocking +/// counterparts, and they are generally slower. This is because async functions +/// are implemented using a state machine that is driven by interrupts and is +/// polled by a runtime. For short operations, the overhead of the state machine +/// can be significant. Consider using the blocking functions on the async +/// driver for small transfers. +/// +/// When initializing an async driver, the driver disables user-specified +/// interrupt handlers, and sets up internal interrupt handlers that drive the +/// driver's async API. The driver's interrupt handlers run on the same core as +/// the driver was initialized on. This means that the driver can not be sent +/// across threads, to prevent incorrect concurrent access to the peripheral. +/// +/// Switching back to blocking mode will disable the interrupt handlers and +/// return the driver to a state where it can be sent across threads. +#[derive(Debug)] +#[non_exhaustive] +pub struct Async(PhantomData<*const ()>); + +unsafe impl Sync for Async {} + +impl crate::DriverMode for Blocking {} +impl crate::DriverMode for Async {} +impl crate::private::Sealed for Blocking {} +impl crate::private::Sealed for Async {} + +pub(crate) mod private { + use core::mem::ManuallyDrop; + + pub trait Sealed {} + + #[non_exhaustive] + #[doc(hidden)] + /// Magical incantation to gain access to internal APIs. + pub struct Internal; + + impl Internal { + /// Obtain magical powers to access internal APIs. + /// + /// # Safety + /// + /// By calling this function, you accept that you are using an internal + /// API that is not guaranteed to be documented, stable, working + /// and may change at any time. + /// + /// You declare that you have tried to look for other solutions, that + /// you have opened a feature request or an issue to discuss the + /// need for this function. + pub unsafe fn conjure() -> Self { + Self + } + } + + pub(crate) struct OnDrop(ManuallyDrop); + impl OnDrop { + pub fn new(cb: F) -> Self { + Self(ManuallyDrop::new(cb)) + } + + pub fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { (ManuallyDrop::take(&mut self.0))() } + } + } +} + +#[doc(hidden)] +pub use private::Internal; + +/// Marker trait for types that can be safely used in `#[ram(unstable(persistent))]`. +/// +/// # Safety +/// +/// - The type must be inhabited +/// - The type must be valid for any bit pattern of its backing memory in case a reset occurs during +/// a write or a reset interrupts the zero initialization on first boot. +/// - Structs must contain only `Persistable` fields and padding +#[instability::unstable] +pub unsafe trait Persistable: Sized {} + +/// Marker trait for types that can be safely used in `#[ram(reclaimed)]`. +/// +/// # Safety +/// +/// - The type must be some form of `MaybeUninit` +#[doc(hidden)] +pub unsafe trait Uninit: Sized {} + +macro_rules! impl_persistable { + ($($t:ty),+) => {$( + unsafe impl Persistable for $t {} + )+}; + (atomic $($t:ident),+) => {$( + unsafe impl Persistable for portable_atomic::$t {} + )+}; +} + +impl_persistable!( + u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64 +); +impl_persistable!(atomic AtomicU8, AtomicI8, AtomicU16, AtomicI16, AtomicU32, AtomicI32, AtomicUsize, AtomicIsize); + +unsafe impl Persistable for [T; N] {} + +unsafe impl Uninit for core::mem::MaybeUninit {} +unsafe impl Uninit for [core::mem::MaybeUninit; N] {} + +#[doc(hidden)] +pub mod __macro_implementation { + //! Private implementation details of esp-hal-procmacros. + + #[instability::unstable] + pub const fn assert_is_zeroable() {} + + #[instability::unstable] + pub const fn assert_is_persistable() {} + + pub const fn assert_is_uninit() {} + + #[cfg(feature = "rt")] + #[cfg(riscv)] + pub use esp_riscv_rt::entry as __entry; + #[cfg(feature = "rt")] + #[cfg(xtensa)] + pub use xtensa_lx_rt::entry as __entry; +} + +use crate::clock::{ClockConfig, CpuClock}; +#[cfg(feature = "rt")] +use crate::{clock::Clocks, peripherals::Peripherals}; + +/// A spinlock for seldom called stuff. Users assume that lock contention is not an issue. +pub(crate) static ESP_HAL_LOCK: RawMutex = RawMutex::new(); + +#[procmacros::doc_replace] +/// System configuration. +/// +/// This `struct` is marked with `#[non_exhaustive]` and can't be instantiated +/// directly. This is done to prevent breaking changes when new fields are added +/// to the `struct`. Instead, use the [`Config::default()`] method to create a +/// new instance. +/// +/// ## Examples +/// +/// ### Default initialization +/// +/// ```rust, no_run +/// # {before_snippet} +/// let peripherals = esp_hal::init(esp_hal::Config::default()); +/// # {after_snippet} +/// ``` +/// +/// ### Custom initialization +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::{clock::CpuClock, time::Duration}; +/// let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +/// let peripherals = esp_hal::init(config); +/// # {after_snippet} +/// ``` +#[non_exhaustive] +#[derive(Default, Clone, Copy, procmacros::BuilderLite)] +pub struct Config { + /// The CPU clock configuration. + #[builder_lite(skip)] + cpu_clock: ClockConfig, + + /// PSRAM configuration. + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[cfg(feature = "psram")] + #[builder_lite(unstable)] + psram: psram::PsramConfig, +} + +impl Config { + /// Apply a clock configuration. + #[cfg_attr( + feature = "unstable", + doc = r" + +With the `unstable` feature enabled, this function accepts both [`ClockConfig`] and [`CpuClock`]. +" + )] + #[cfg(feature = "unstable")] + pub fn with_cpu_clock(self, cpu_clock: impl Into) -> Self { + Self { + cpu_clock: cpu_clock.into(), + ..self + } + } + + /// Apply a clock configuration. + #[cfg(not(feature = "unstable"))] + pub fn with_cpu_clock(self, cpu_clock: CpuClock) -> Self { + Self { + cpu_clock: cpu_clock.into(), + ..self + } + } + + /// The CPU clock configuration preset. + /// + /// # Panics + /// + /// This function will panic if the CPU clock configuration is not **exactly** one of the + /// [`CpuClock`] presets. + #[cfg_attr(feature = "unstable", deprecated(note = "Use `clock_config` instead."))] // TODO: mention ClockTree APIs once they are exposed to the user. + pub fn cpu_clock(&self) -> CpuClock { + unwrap!( + self.cpu_clock.try_get_preset(), + "CPU clock configuration is not a preset" + ) + } + + /// The CPU clock configuration. + #[instability::unstable] + pub fn clock_config(&self) -> ClockConfig { + self.cpu_clock + } +} + +#[procmacros::doc_replace] +/// Initialize the system. +/// +/// This function sets up the CPU clock and watchdog, then, returns the +/// peripherals and clocks. +/// +/// # Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::{Config, init}; +/// let peripherals = init(Config::default()); +/// # {after_snippet} +/// ``` +#[cfg_attr(docsrs, doc(cfg(feature = "rt")))] +#[cfg(feature = "rt")] +pub fn init(config: Config) -> Peripherals { + crate::soc::pre_init(); + + #[cfg(soc_cpu_has_branch_predictor)] + { + // Enable branch predictor + // Note that the branch predictor will start cache requests and needs to be disabled when + // the cache is disabled. + // MHCR: CSR 0x7c1 + const MHCR_RS: u32 = 1 << 4; // R/W, address return stack set bit + const MHCR_BFE: u32 = 1 << 5; // R/W, allow predictive jump set bit + const MHCR_BTB: u32 = 1 << 12; // R/W, branch target prediction enable bit + unsafe { + core::arch::asm!("csrrs x0, 0x7c1, {0}", in(reg) MHCR_RS | MHCR_BFE | MHCR_BTB); + } + } + + #[cfg(stack_guard_monitoring)] + crate::soc::enable_main_stack_guard_monitoring(); + + system::disable_peripherals(); + + let mut peripherals = Peripherals::take(); + + Clocks::init(config.clock_config()); + + // RTC domain must be enabled before we try to disable + let mut rtc = crate::rtc_cntl::Rtc::new(peripherals.LPWR.reborrow()); + + #[cfg(sleep_driver_supported)] + crate::rtc_cntl::sleep::RtcSleepConfig::base_settings(&rtc); + + // Disable watchdog timers + #[cfg(swd)] + rtc.swd.disable(); + + rtc.rwdt.disable(); + + #[cfg(timergroup_timg0)] + crate::timer::timg::Wdt::>::new().disable(); + + #[cfg(timergroup_timg1)] + crate::timer::timg::Wdt::>::new().disable(); + + crate::time::implem::time_init(); + + #[cfg(gpio_driver_supported)] + crate::gpio::interrupt::bind_default_interrupt_handler(); + + #[cfg(feature = "psram")] + crate::psram::init_psram(config.psram); + + unsafe { + esp_rom_sys::init_syscall_table(); + } + + #[cfg(all(riscv, write_vec_table_monitoring))] + crate::soc::setup_trap_section_protection(); + + peripherals +} diff --git a/esp-hal/src/macros.rs b/esp-hal/src/macros.rs new file mode 100644 index 00000000000..88df00ec370 --- /dev/null +++ b/esp-hal/src/macros.rs @@ -0,0 +1,468 @@ +//! Macros used by the HAL. +//! +//! Most of the macros in this module are hidden and intended for internal use +//! only. For the list of public macros, see the [procmacros](https://docs.rs/esp-hal-procmacros/latest/esp_hal_procmacros/) +//! documentation. + +#[doc(hidden)] +/// Helper macro for checking doctest code snippets +#[macro_export] +macro_rules! before_snippet { + () => { + r#" +# #![no_std] +# use esp_hal::{interrupt::{self, InterruptConfigurable}, time::{Duration, Instant, Rate}}; +# macro_rules! println { +# ($($tt:tt)*) => { }; +# } +# macro_rules! print { +# ($($tt:tt)*) => { }; +# } +# #[panic_handler] +# fn panic(_ : &core::panic::PanicInfo) -> ! { +# loop {} +# } +# fn main() { +# let _ = example(); +# } +# struct ExampleError {} +# impl From for ExampleError where T: core::fmt::Debug { +# fn from(_value: T) -> Self { +# Self{} +# } +# } +# async fn example() -> Result<(), ExampleError> { +# let mut peripherals = esp_hal::init(esp_hal::Config::default()); +"# + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! after_snippet { + () => { + r#" +# Ok(()) +# } +"# + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! trm_markdown_link { + () => { + concat!("[Technical Reference Manual](", property!("trm"), ")") + }; + ($anchor:literal) => { + concat!( + "[Technical Reference Manual](", + property!("trm"), + "#", + $anchor, + ")" + ) + }; +} + +#[doc(hidden)] +/// Shorthand to define AnyPeripheral instances. +/// +/// This macro generates the following: +/// +/// - An `AnyPeripheral` struct, name provided by the macro call. +/// - An `any::Degrade` trait which is supposed to be used as a supertrait of a relevant Instance. +/// - An `any::Inner` enum, with the same variants as the original peripheral. +/// - A `From` implementation for each peripheral variant. +/// - A `degrade` method for each peripheral variant using the `any::Degrade` trait. +#[macro_export] +macro_rules! any_peripheral { + ($(#[$meta:meta])* $vis:vis peripheral $name:ident<'d> { + $( + $(#[cfg($variant_meta:meta)])* + $variant:ident($inner:ty) + ),* $(,)? + }) => { + #[doc = concat!("Utilities related to [`", stringify!($name), "`]")] + #[doc(hidden)] + #[instability::unstable] + pub mod any { + #[allow(unused_imports)] + use super::*; + + macro_rules! delegate { + ($any:ident, $inner_ident:ident => $code:tt) => { + match &$any.0 { + $( + $(#[cfg($variant_meta)])* + any::Inner::$variant($inner_ident) => $code, + )* + } + } + } + + pub(crate) use delegate; + + $(#[$meta])* + #[derive(Debug)] + pub(crate) enum Inner<'d> { + $( + $(#[cfg($variant_meta)])* + $variant($inner), + )* + } + + #[cfg(feature = "defmt")] + impl defmt::Format for Inner<'_> { + fn format(&self, fmt: defmt::Formatter<'_>) { + match self { + $( + $(#[cfg($variant_meta)])* + Self::$variant(inner) => inner.format(fmt), + )* + } + } + } + + // Trick to make peripherals implement something Into-like, without + // requiring Instance traits to have lifetimes. Rustdoc will list + // this trait as a supertrait, but will not give its definition. + // Users are encouraged to use From to convert a singleton into its + // relevant AnyPeripheral counterpart. + #[allow(unused)] + pub trait Degrade: Sized + $crate::private::Sealed { + fn degrade<'a>(self) -> super::$name<'a> + where + Self: 'a; + } + } + + $(#[$meta])* + /// + /// This struct is a type-erased version of a peripheral singleton. It is useful + /// for creating arrays of peripherals, or avoiding generics. Peripheral singletons + /// can be type erased by using their `From` implementation. + /// + /// ```rust,ignore + #[doc = concat!("let any_peripheral = ", stringify!($name), "::from(peripheral);")] + /// ``` + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + $vis struct $name<'d>(any::Inner<'d>); + + impl $name<'_> { + /// Unsafely clone this peripheral reference. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn clone_unchecked(&self) -> Self { unsafe { + any::delegate!(self, inner => { Self::from(inner.clone_unchecked()) }) + }} + + /// Creates a new peripheral reference with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripheral after + /// you dropped the driver that consumes this. + /// + /// See [Peripheral singleton] section for more information. + /// + /// [Peripheral singleton]: crate#peripheral-singletons + #[inline] + pub fn reborrow(&mut self) -> $name<'_> { + unsafe { self.clone_unchecked() } + } + + #[procmacros::doc_replace] + /// Attempts to downcast the pin into the underlying peripheral instance. + /// + /// ## Example + /// + /// ```rust,no_run + /// # {before_snippet} + /// # + /// # use esp_hal::{ + /// # uart::AnyUart as AnyPeripheral, + /// # peripherals::{UART0 as PERI0, UART1 as PERI1}, + /// # }; + /// # + /// # let peri0 = peripherals.UART0; + /// # let peri1 = peripherals.UART1; + /// // let peri0 = peripherals.PERI0; + /// // let peri1 = peripherals.PERI1; + /// let any_peri0 = AnyPeripheral::from(peri0); + /// let any_peri1 = AnyPeripheral::from(peri1); + /// + /// let uart0 = any_peri0 + /// .downcast::() + /// .expect("This downcast succeeds because AnyPeripheral was created from Peri0"); + /// let uart0 = any_peri1 + /// .downcast::() + /// .expect_err("This AnyPeripheral was created from Peri1, it cannot be downcast to Peri0"); + /// # + /// # {after_snippet} + /// ``` + #[inline] + pub fn downcast

    (self) -> Result + where + Self: TryInto + { + self.try_into() + } + } + + impl $crate::private::Sealed for $name<'_> {} + + // AnyPeripheral converts into itself + impl<'d> any::Degrade for $name<'d> { + #[inline] + fn degrade<'a>(self) -> $name<'a> + where + Self: 'a, + { + self + } + } + + $( + // Variants convert into AnyPeripheral + $(#[cfg($variant_meta)])* + impl<'d> any::Degrade for $inner { + #[inline] + fn degrade<'a>(self) -> $name<'a> + where + Self: 'a, + { + $name::from(self) + } + } + + $(#[cfg($variant_meta)])* + impl<'d> From<$inner> for $name<'d> { + #[inline] + fn from(inner: $inner) -> Self { + Self(any::Inner::$variant(inner)) + } + } + + $(#[cfg($variant_meta)])* + impl<'d> TryFrom<$name<'d>> for $inner { + type Error = $name<'d>; + + #[inline] + fn try_from(any: $name<'d>) -> Result> { + #[allow(irrefutable_let_patterns)] + if let $name(any::Inner::$variant(inner)) = any { + Ok(inner) + } else { + Err(any) + } + } + } + )* + }; +} + +/// Macro to choose between two expressions. Useful for implementing "else" for +/// `$()?` macro syntax. +#[macro_export] +#[doc(hidden)] +macro_rules! if_set { + (, $not_set:expr) => { + $not_set + }; + ($set:expr, $not_set:expr) => { + $set + }; +} + +/// Macro to ignore tokens. +/// +/// This is useful when we need existence of a metavariable (to expand a +/// repetition), but we don't need to use it. +#[macro_export] +#[doc(hidden)] +macro_rules! ignore { + ($($item:tt)*) => {}; +} + +/// Define a piece of (Espressif-specific) metadata that external tools may +/// parse. +/// +/// The symbol name be formatted as `_ESP_METADATA__`. +/// +/// This metadata is zero cost, i.e. the value will not be flashed to the +/// device. +#[macro_export] +#[doc(hidden)] +macro_rules! metadata { + ($category:literal, $key:ident, $value:expr) => { + #[cfg(feature = "rt")] + #[unsafe(link_section = concat!(".espressif.metadata"))] + #[used] + #[unsafe(export_name = concat!($category, ".", stringify!($key)))] + static $key: [u8; $value.len()] = const { + let val_bytes = $value.as_bytes(); + let mut val_bytes_array = [0; $value.len()]; + let mut i = 0; + while i < val_bytes.len() { + val_bytes_array[i] = val_bytes[i]; + i += 1; + } + val_bytes_array + }; + }; +} + +#[procmacros::doc_replace] +/// Extract fields from [`Peripherals`][crate::peripherals::Peripherals] into named groups. +/// +/// ## Example +/// +/// ```rust,no_run +/// # {before_snippet} +/// # +/// use esp_hal::assign_resources; +/// +/// assign_resources! { +/// Resources<'d> { +/// display: DisplayResources<'d> { +/// spi: SPI2, +/// sda: GPIO5, +/// sclk: GPIO4, +/// cs: GPIO3, +/// dc: GPIO2, +/// }, +/// axl: AccelerometerResources<'d> { +/// i2c: I2C0, +/// sda: GPIO0, +/// scl: GPIO1, +/// }, +/// } +/// } +/// +/// # struct Display<'d>(core::marker::PhantomData<&'d ()>); +/// fn init_display<'d>(r: DisplayResources<'d>) -> Display<'d> { +/// // use `r.spi`, `r.sda`, `r.sclk`, `r.cs`, `r.dc` +/// todo!() +/// } +/// +/// # struct Accelerometer<'d>(core::marker::PhantomData<&'d ()>); +/// fn init_accelerometer<'d>(r: AccelerometerResources<'d>) -> Accelerometer<'d> { +/// // use `r.i2c`, `r.sda`, `r.scl` +/// todo!() +/// } +/// +/// // let peripherals = esp_hal::init(...); +/// let resources = split_resources!(peripherals); +/// +/// let display = init_display(resources.display); +/// let axl = init_accelerometer(resources.axl); +/// +/// // Other fields (`peripherals.UART0`, ...) of the `peripherals` struct can still be accessed. +/// # {after_snippet} +/// ``` +// Based on https://crates.io/crates/assign-resources +#[macro_export] +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +macro_rules! assign_resources { + { + $(#[$struct_meta:meta])* + $vis:vis $struct_name:ident<$struct_lt:lifetime> { + $( + $(#[$group_meta:meta])* + $group_name:ident : $group_struct:ident<$group_lt:lifetime> { + $( + $(#[$resource_meta:meta])* + $resource_name:ident : $resource_field:ident + ),* + $(,)? + } + ),+ + $(,)? + } + } => { + // Group structs + $( + $(#[$group_meta])* + #[allow(missing_docs)] + $vis struct $group_struct<$group_lt> { + $( + $(#[$resource_meta])* + pub $resource_name: $crate::peripherals::$resource_field<$group_lt>, + )+ + } + + impl<$group_lt> $group_struct<$group_lt> { + /// Unsafely create an instance of the assigned peripherals out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of the contained peripherals at a time. + pub unsafe fn steal() -> Self { + unsafe { + Self { + $($resource_name: $crate::peripherals::$resource_field::steal()),* + } + } + } + + /// Creates a new reference to the peripheral group with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripherals after + /// you dropped the drivers that consume this. + pub fn reborrow(&mut self) -> $group_struct<'_> { + $group_struct { + $($resource_name: self.$resource_name.reborrow()),* + } + } + } + )+ + + // Outer struct + $(#[$struct_meta])* + /// Assigned resources. + $vis struct $struct_name<$struct_lt> { + $( pub $group_name: $group_struct<$struct_lt>, )+ + } + + impl<$struct_lt> $struct_name<$struct_lt> { + /// Unsafely create an instance of the assigned peripherals out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of the contained peripherals at a time. + pub unsafe fn steal() -> Self { + unsafe { + Self { + $($group_name: $group_struct::steal()),* + } + } + } + + /// Creates a new reference to the assigned peripherals with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripherals after + /// you dropped the drivers that consume this. + pub fn reborrow(&mut self) -> $struct_name<'_> { + $struct_name { + $($group_name: self.$group_name.reborrow()),* + } + } + } + + /// Extracts resources from the `Peripherals` struct. + #[macro_export] + macro_rules! split_resources { + ($peris:ident) => { + $struct_name { + $($group_name: $group_struct { + $($resource_name: $peris.$resource_field),* + }),* + } + } + } + }; +} diff --git a/esp-hal/src/mcpwm/mod.rs b/esp-hal/src/mcpwm/mod.rs new file mode 100644 index 00000000000..2343abcae54 --- /dev/null +++ b/esp-hal/src/mcpwm/mod.rs @@ -0,0 +1,338 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "mcpwm_freq" => { + cfg(not(esp32h2)) => "40", + cfg(esp32h2) => "32" + }, + "clock_src" => { + cfg(esp32) => "PLL_F160M (160 MHz)", + cfg(esp32s3) => "CRYPTO_PWM_CLK (160 MHz)", + cfg(esp32c6) => "PLL_F160M (160 MHz)", + cfg(esp32h2) => "PLL_F96M_CLK (96 MHz)", + } +))] +//! # Motor Control Pulse Width Modulator (MCPWM) +//! +//! ## Overview +//! +//! The MCPWM peripheral is a versatile PWM generator, which contains various +//! submodules to make it a key element in power electronic applications like +//! motor control, digital power, and so on. Typically, the MCPWM peripheral can +//! be used in the following scenarios: +//! - Digital motor control, e.g., brushed/brushless DC motor, RC servo motor +//! - Switch mode-based digital power conversion +//! - Power DAC, where the duty cycle is equivalent to a DAC analog value +//! - Calculate external pulse width, and convert it into other analog values like speed, distance +//! - Generate Space Vector PWM (SVPWM) signals for Field Oriented Control (FOC) +//! +//! ## Configuration +//! +//! * PWM Timers 0, 1 and 2 +//! * Every PWM timer has a dedicated 8-bit clock prescaler. +//! * The 16-bit counter in the PWM timer can work in count-up mode, count-down mode or +//! count-up-down mode. +//! * A hardware sync or software sync can trigger a reload on the PWM timer with a phase +//! register (Not yet implemented) +//! * PWM Operators 0, 1 and 2 +//! * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work independently, in +//! symmetric and asymmetric configuration. +//! * Software, asynchronously override control of PWM signals. +//! * Configurable dead-time on rising and falling edges; each set up independently. (Not yet +//! implemented) +//! * All events can trigger CPU interrupts. (Not yet implemented) +//! * Modulating of PWM output by high-frequency carrier signals, useful when gate drivers are +//! insulated with a transformer. (Not yet implemented) +//! * Period, time stamps and important control registers have shadow registers with flexible +//! updating methods. +//! * Fault Detection Module (Not yet implemented) +//! * Capture Module (Not yet implemented) +//! +//! Clock source is __clock_src__ by default. +//! +//! ## Examples +//! +//! ### Output a 20 kHz signal +//! +//! This example uses timer0 and operator0 of the MCPWM0 peripheral to output a +//! 50% duty signal at 20 kHz. The signal will be output to the pin assigned to +//! `pin`. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::mcpwm::{operator::{DeadTimeCfg, PWMStream, PwmPinConfig}, timer::PwmWorkingMode, McPwm, PeripheralClockConfig}; +//! # let pin = peripherals.GPIO0; +//! +//! // initialize peripheral +//! let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_freq__))?; +//! let mut mcpwm = McPwm::new(peripherals.MCPWM0, clock_cfg); +//! +//! // connect operator0 to timer0 +//! mcpwm.operator0.set_timer(&mcpwm.timer0); +//! +//! // connect operator0 to pin +//! let mut pwm_pin = mcpwm +//! .operator0 +//! .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH); +//! +//! // start timer with timestamp values in the range of 0..=99 and a frequency +//! // of 20 kHz +//! let timer_clock_cfg = clock_cfg +//! .timer_clock_with_frequency(99, PwmWorkingMode::Increase, +//! Rate::from_khz(20))?; mcpwm.timer0.start(timer_clock_cfg); +//! +//! // pin will be high 50% of the time +//! pwm_pin.set_timestamp(50); +//! # {after_snippet} +//! ``` + +use operator::Operator; +use timer::Timer; + +use crate::{ + gpio::OutputSignal, + pac, + private::OnDrop, + soc::clocks::{self, ClockTree}, + system::{self, Peripheral, PeripheralGuard}, + time::Rate, +}; + +/// MCPWM operators +pub mod operator; +/// MCPWM timers +pub mod timer; + +type RegisterBlock = pac::mcpwm0::RegisterBlock; + +#[allow(dead_code)] // Field is seemingly unused but we rely on its Drop impl +struct PwmClockGuard(OnDrop); + +impl PwmClockGuard { + pub fn new() -> Self { + if PWM::peripheral() == Peripheral::Mcpwm0 { + ClockTree::with(clocks::request_mcpwm0_function_clock); + } else { + #[cfg(soc_has_mcpwm1)] + ClockTree::with(clocks::request_mcpwm1_function_clock); + } + + Self(OnDrop::new(|| { + if PWM::peripheral() == Peripheral::Mcpwm0 { + ClockTree::with(clocks::release_mcpwm0_function_clock); + } else { + #[cfg(soc_has_mcpwm1)] + ClockTree::with(clocks::release_mcpwm1_function_clock); + } + })) + } +} + +/// The MCPWM peripheral +#[non_exhaustive] +pub struct McPwm<'d, PWM> { + _inner: PWM, + /// Timer0 + pub timer0: Timer<0, PWM>, + /// Timer1 + pub timer1: Timer<1, PWM>, + /// Timer2 + pub timer2: Timer<2, PWM>, + /// Operator0 + pub operator0: Operator<'d, 0, PWM>, + /// Operator1 + pub operator1: Operator<'d, 1, PWM>, + /// Operator2 + pub operator2: Operator<'d, 2, PWM>, +} + +impl<'d, PWM: PwmPeripheral + 'd> McPwm<'d, PWM> { + /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)` + pub fn new(peripheral: PWM, peripheral_clock: PeripheralClockConfig) -> Self { + let guard = PeripheralGuard::new(PWM::peripheral()); + + let register_block = unsafe { &*PWM::block() }; + + // set prescaler + register_block + .clk_cfg() + .write(|w| unsafe { w.clk_prescale().bits(peripheral_clock.prescaler) }); + + // enable clock + register_block.clk().write(|w| w.en().set_bit()); + + Self { + _inner: peripheral, + timer0: Timer::new(guard.clone()), + timer1: Timer::new(guard.clone()), + timer2: Timer::new(guard.clone()), + operator0: Operator::new(guard.clone()), + operator1: Operator::new(guard.clone()), + operator2: Operator::new(guard), + } + } +} + +/// Clock configuration of the MCPWM peripheral +#[derive(Copy, Clone)] +pub struct PeripheralClockConfig { + frequency: Rate, + prescaler: u8, +} + +impl PeripheralClockConfig { + fn source_clock() -> Rate { + // FIXME: this works right now because we configure the default clock source during startup. + // Needs to be revisited when refactoring the MCPWM driver before stabilization. + ClockTree::with(|clocks| Rate::from_hz(clocks::mcpwm0_function_clock_frequency(clocks))) + } + + /// Get a clock configuration with the given prescaler. + /// + /// With standard system clock configurations the input clock to the MCPWM + /// peripheral is `160 MHz`. + /// + /// The peripheral clock frequency is calculated as: + /// `peripheral_clock = input_clock / (prescaler + 1)` + pub fn with_prescaler(prescaler: u8) -> Self { + let source_clock = Self::source_clock(); + + Self { + frequency: source_clock / (prescaler as u32 + 1), + prescaler, + } + } + + /// Get a clock configuration with the given frequency. + /// + /// ### Note: + /// This will try to select an appropriate prescaler for the + /// [`PeripheralClockConfig::with_prescaler`] method. + /// If the calculated prescaler is not in the range `0..u8::MAX` + /// [`FrequencyError`] will be returned. + /// + /// With standard system clock configurations the input clock to the MCPWM + /// peripheral is `160 MHz`. + /// + /// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ..., + /// `160 Mhz / 256`) are representable exactly. Other target frequencies + /// will be rounded up to the next divisor. + pub fn with_frequency(target_freq: Rate) -> Result { + let source_clock = Self::source_clock(); + + if target_freq.as_hz() == 0 || target_freq > source_clock { + return Err(FrequencyError); + } + + let prescaler = source_clock / target_freq - 1; + if prescaler > u8::MAX as u32 { + return Err(FrequencyError); + } + + Ok(Self::with_prescaler(prescaler as u8)) + } + + /// Get the peripheral clock frequency. + /// + /// ### Note: + /// The actual value is rounded down to the nearest `u32` value + pub fn frequency(&self) -> Rate { + self.frequency + } + + /// Get a timer clock configuration with the given prescaler. + /// + /// The resulting timer frequency depends on the chosen + /// [`timer::PwmWorkingMode`]. + /// + /// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease` + /// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)` + /// #### `PwmWorkingMode::UpDown` + /// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)` + pub fn timer_clock_with_prescaler( + &self, + period: u16, + mode: timer::PwmWorkingMode, + prescaler: u8, + ) -> timer::TimerClockConfig { + timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler) + } + + /// Get a timer clock configuration with the given frequency. + /// + /// ### Note: + /// This will try to select an appropriate prescaler for the timer. + /// If the calculated prescaler is not in the range `0..u8::MAX` + /// [`FrequencyError`] will be returned. + /// + /// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the + /// frequency is calculated. + pub fn timer_clock_with_frequency( + &self, + period: u16, + mode: timer::PwmWorkingMode, + target_freq: Rate, + ) -> Result { + timer::TimerClockConfig::with_frequency(self, period, mode, target_freq) + } +} + +/// Target frequency could not be set. +/// Check how the frequency is calculated in the corresponding method docs. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FrequencyError; + +/// A MCPWM peripheral +pub trait PwmPeripheral: crate::private::Sealed { + /// Get a pointer to the peripheral RegisterBlock + fn block() -> *const RegisterBlock; + /// Get operator GPIO mux output signal + fn output_signal() -> OutputSignal; + /// Peripheral + fn peripheral() -> system::Peripheral; +} + +#[cfg(soc_has_mcpwm0)] +impl PwmPeripheral for crate::peripherals::MCPWM0<'_> { + fn block() -> *const RegisterBlock { + Self::regs() + } + + fn output_signal() -> OutputSignal { + match (OP, IS_A) { + (0, true) => OutputSignal::PWM0_0A, + (1, true) => OutputSignal::PWM0_1A, + (2, true) => OutputSignal::PWM0_2A, + (0, false) => OutputSignal::PWM0_0B, + (1, false) => OutputSignal::PWM0_1B, + (2, false) => OutputSignal::PWM0_2B, + _ => unreachable!(), + } + } + + fn peripheral() -> system::Peripheral { + system::Peripheral::Mcpwm0 + } +} + +#[cfg(soc_has_mcpwm1)] +impl PwmPeripheral for crate::peripherals::MCPWM1<'_> { + fn block() -> *const RegisterBlock { + Self::regs() + } + + fn output_signal() -> OutputSignal { + match (OP, IS_A) { + (0, true) => OutputSignal::PWM1_0A, + (1, true) => OutputSignal::PWM1_1A, + (2, true) => OutputSignal::PWM1_2A, + (0, false) => OutputSignal::PWM1_0B, + (1, false) => OutputSignal::PWM1_1B, + (2, false) => OutputSignal::PWM1_2B, + _ => unreachable!(), + } + } + + fn peripheral() -> system::Peripheral { + system::Peripheral::Mcpwm1 + } +} diff --git a/esp-hal/src/mcpwm/operator.rs b/esp-hal/src/mcpwm/operator.rs new file mode 100644 index 00000000000..2cd22874077 --- /dev/null +++ b/esp-hal/src/mcpwm/operator.rs @@ -0,0 +1,718 @@ +//! # MCPWM Operator Module +//! +//! ## Overview +//! The `operator` is responsible for generating `PWM (Pulse Width Modulation)` +//! signals and handling various aspects related to `PWM` signal generation. +//! +//! ## Configuration +//! This module provides flexibility in configuring the PWM outputs. Its +//! implementation allows for motor control and other applications that demand +//! accurate pulse timing and sophisticated modulation techniques. + +use core::marker::PhantomData; + +use super::PeripheralGuard; +use crate::{ + gpio::interconnect::{OutputSignal, PeripheralOutput}, + mcpwm::{PwmClockGuard, PwmPeripheral, timer::Timer}, + pac, +}; + +/// Input/Output Stream descriptor for each channel +#[derive(Copy, Clone)] +#[allow(clippy::upper_case_acronyms, reason = "peripheral is unstable")] +pub enum PWMStream { + /// PWM Stream A + PWMA, + /// PWM Stream B + PWMB, +} + +/// Configuration for MCPWM Operator DeadTime +/// It's recommended to reference the technical manual for configuration +#[derive(Copy, Clone)] +pub struct DeadTimeCfg { + cfg_reg: u32, +} + +#[allow(clippy::unusual_byte_groupings)] +impl DeadTimeCfg { + // NOTE: it's a bit difficult to make this typestate + // due to the different interconnections (FED/RED vs PWMxA/PWMxB) and + // the many modes of operation + + /// B_OUTBYPASS + const S0: u32 = 0b01_0000_0000_0000_0000; + /// A_OUTBYPASS + const S1: u32 = 0b00_1000_0000_0000_0000; + /// RED_OUTINVERT + const S2: u32 = 0b00_0010_0000_0000_0000; + /// FED_OUTINVERT + const S3: u32 = 0b00_0100_0000_0000_0000; + /// RED_INSEL + const S4: u32 = 0b00_0000_1000_0000_0000; + /// FED_INSEL + const S5: u32 = 0b00_0001_0000_0000_0000; + /// A_OUTSWAP + const S6: u32 = 0b00_0000_0010_0000_0000; + /// B_OUTSWAP + const S7: u32 = 0b00_0000_0100_0000_0000; + /// DEB_MODE + const _S8: u32 = 0b00_0000_0001_0000_0000; + /// Use PT_clk instead of PWM_clk + const CLK_SEL: u32 = 0b10_0000_0000_0000_0000; + + /// Uses the following configuration: + /// * Clock: PWM_clk + /// * Bypass: A & B + /// * Inputs: A->A, B->B (InSel) + /// * Outputs: A->A, B->B (OutSwap) + /// * No Dual-edge B + /// * No Invert + /// * FED/RED update mode = immediate + pub const fn new_bypass() -> DeadTimeCfg { + DeadTimeCfg { + cfg_reg: Self::S0 | Self::S1, + } + } + + /// Active High Complementary (AHC) from Technical Reference manual + /// + /// Will generate a PWM from input PWMA, such that output PWMA & PWMB are + /// each others complement except during a transition in which they will + /// be both off (as deadtime) such that they should never overlap, useful + /// for H-Bridge type scenarios + pub const fn new_ahc() -> DeadTimeCfg { + DeadTimeCfg { cfg_reg: Self::S3 } + } + // TODO: Add some common configurations ~AHC~,ALC,AH,AC + + #[must_use] + const fn set_flag(mut self, flag: u32, val: bool) -> Self { + if val { + self.cfg_reg |= flag; + } else { + self.cfg_reg &= !flag; + } + self + } + + /// Sets FED/RED output inverter + /// Inverts the output of the FED/RED module (excl DEB mode feedback) + #[must_use] + pub const fn invert_output(self, fed: bool, red: bool) -> Self { + self.set_flag(Self::S3, fed).set_flag(Self::S2, red) + } + + /// Swaps the output of a PWM Stream + /// i.e. If both streams have output_swap enabled, the output of the module + /// is swapped, while if only one is enabled that one 'copies' from the + /// other stream + #[must_use] + pub const fn set_output_swap(self, stream: PWMStream, swap: bool) -> Self { + self.set_flag( + match stream { + PWMStream::PWMA => Self::S6, + PWMStream::PWMB => Self::S7, + }, + swap, + ) + } + + /// Set PWMA/PWMB stream to bypass everything except output_swap + /// This means no deadtime is applied when enabled + #[must_use] + pub const fn set_bypass(self, stream: PWMStream, enable: bool) -> Self { + self.set_flag( + match stream { + PWMStream::PWMA => Self::S1, + PWMStream::PWMB => Self::S0, + }, + enable, + ) + } + + /// Select Between PWMClk & PT_Clk + #[must_use] + pub const fn select_clock(self, pwm_clock: bool) -> Self { + self.set_flag(Self::CLK_SEL, pwm_clock) + } + + /// Select which stream is used for the input of FED/RED + #[must_use] + pub const fn select_input(self, fed: PWMStream, red: PWMStream) -> Self { + self.set_flag( + Self::S5, + match fed { + PWMStream::PWMA => false, + PWMStream::PWMB => true, + }, + ) + .set_flag( + Self::S4, + match red { + PWMStream::PWMA => false, + PWMStream::PWMB => true, + }, + ) + } +} + +/// A MCPWM operator +/// +/// The PWM Operator submodule has the following functions: +/// * Generates a PWM signal pair, based on timing references obtained from the corresponding PWM +/// timer. +/// * Each signal out of the PWM signal pair includes a specific pattern of dead time. (Not yet +/// implemented) +/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet implemented) +/// * Handles response under fault conditions. (Not yet implemented) +pub struct Operator<'d, const OP: u8, PWM> { + phantom: PhantomData<&'d PWM>, + _guard: PeripheralGuard, + _pwm_clock_guard: PwmClockGuard, +} + +impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { + pub(super) fn new(guard: PeripheralGuard) -> Self { + // Side note: + // It would have been nice to deselect any timer reference on peripheral + // initialization. + // However experimentation (ESP32-S3) showed that writing `3` to timersel + // will not disable the timer reference but instead act as though `2` was + // written. + Operator { + phantom: PhantomData, + _guard: guard, + _pwm_clock_guard: PwmClockGuard::new::(), + } + } + + /// Select a [`Timer`] to be the timing reference for this operator + /// + /// ### Note: + /// By default TIMER0 is used + pub fn set_timer(&mut self, timer: &Timer) { + let _ = timer; + // SAFETY: + // We only write to our OPERATORx_TIMERSEL register + let block = unsafe { &*PWM::block() }; + block.operator_timersel().modify(|_, w| match OP { + 0 => unsafe { w.operator0_timersel().bits(TIM) }, + 1 => unsafe { w.operator1_timersel().bits(TIM) }, + 2 => unsafe { w.operator2_timersel().bits(TIM) }, + _ => { + unreachable!() + } + }); + } + + /// Use the A output with the given pin and configuration + pub fn with_pin_a( + self, + pin: impl PeripheralOutput<'d>, + config: PwmPinConfig, + ) -> PwmPin<'d, PWM, OP, true> { + PwmPin::new(pin, config) + } + + /// Use the B output with the given pin and configuration + pub fn with_pin_b( + self, + pin: impl PeripheralOutput<'d>, + config: PwmPinConfig, + ) -> PwmPin<'d, PWM, OP, false> { + PwmPin::new(pin, config) + } + + /// Use both the A and the B output with the given pins and configurations + pub fn with_pins( + self, + pin_a: impl PeripheralOutput<'d>, + config_a: PwmPinConfig, + pin_b: impl PeripheralOutput<'d>, + config_b: PwmPinConfig, + ) -> (PwmPin<'d, PWM, OP, true>, PwmPin<'d, PWM, OP, false>) { + (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b)) + } + + /// Link two pins using the deadtime generator + /// + /// This is useful for complementary or mirrored signals with or without + /// configured deadtime + pub fn with_linked_pins( + self, + pin_a: impl PeripheralOutput<'d>, + config_a: PwmPinConfig, + pin_b: impl PeripheralOutput<'d>, + config_b: PwmPinConfig, + config_dt: DeadTimeCfg, + ) -> LinkedPins<'d, PWM, OP> { + LinkedPins::new(pin_a, config_a, pin_b, config_b, config_dt) + } +} + +/// Configuration describing how the operator generates a signal on a connected +/// pin +pub struct PwmPinConfig { + actions: PwmActions, + update_method: PwmUpdateMethod, +} + +impl PwmPinConfig { + /// A configuration using [`PwmActions::UP_ACTIVE_HIGH`] and + /// [`PwmUpdateMethod::SYNC_ON_ZERO`] + pub const UP_ACTIVE_HIGH: Self = + Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO); + /// A configuration using [`PwmActions::UP_DOWN_ACTIVE_HIGH`] and + /// [`PwmUpdateMethod::SYNC_ON_ZERO`] + pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new( + PwmActions::UP_DOWN_ACTIVE_HIGH, + PwmUpdateMethod::SYNC_ON_ZERO, + ); + /// A configuration using [`PwmActions::empty`] and + /// [`PwmUpdateMethod::empty`] + pub const EMPTY: Self = Self::new(PwmActions::empty(), PwmUpdateMethod::empty()); + + /// Get a configuration using the given `PwmActions` and `PwmUpdateMethod` + pub const fn new(actions: PwmActions, update_method: PwmUpdateMethod) -> Self { + PwmPinConfig { + actions, + update_method, + } + } +} + +/// A pin driven by an MCPWM operator +pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> { + pin: OutputSignal<'d>, + phantom: PhantomData, + _guard: PeripheralGuard, +} + +impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> { + fn new(pin: impl PeripheralOutput<'d>, config: PwmPinConfig) -> Self { + let pin = pin.into(); + + let guard = PeripheralGuard::new(PWM::peripheral()); + + let mut pin = PwmPin { + pin, + phantom: PhantomData, + _guard: guard, + }; + pin.set_actions(config.actions); + pin.set_update_method(config.update_method); + + PWM::output_signal::().connect_to(&pin.pin); + pin.pin.set_output_enable(true); + + pin + } + + /// Configure what actions should be taken on timing events + pub fn set_actions(&mut self, value: PwmActions) { + // SAFETY: + // We only write to our GENx_x register + let ch = unsafe { Self::ch() }; + let bits = value.0; + + // SAFETY: + // `bits` is a valid bit pattern + ch.gen_((!IS_A) as usize).write(|w| unsafe { w.bits(bits) }); + } + + /// Set how a new timestamp syncs with the timer + pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { + // SAFETY: + // We only write to our GENx_x_UPMETHOD register + let ch = unsafe { Self::ch() }; + let bits = update_method.0; + + #[cfg(esp32s3)] + let cfg = ch.cmpr_cfg(); + #[cfg(any(esp32, esp32c6, esp32h2))] + let cfg = ch.gen_stmp_cfg(); + + cfg.modify(|_, w| unsafe { + if IS_A { + w.a_upmethod().bits(bits) + } else { + w.b_upmethod().bits(bits) + } + }); + } + + /// Write a new timestamp. + /// The written value will take effect according to the set + /// [`PwmUpdateMethod`]. + pub fn set_timestamp(&mut self, value: u16) { + // SAFETY: + // We only write to our GENx_TSTMP_x register + let ch = unsafe { Self::ch() }; + + #[cfg(esp32s3)] + if IS_A { + ch.cmpr_value0().write(|w| unsafe { w.a().bits(value) }); + } else { + ch.cmpr_value1().write(|w| unsafe { w.b().bits(value) }); + } + + #[cfg(any(esp32, esp32c6, esp32h2))] + if IS_A { + ch.gen_tstmp_a().write(|w| unsafe { w.a().bits(value) }); + } else { + ch.gen_tstmp_b().write(|w| unsafe { w.b().bits(value) }); + } + } + + /// Get the old timestamp. + /// The value of the timestamp will take effect according to the set + /// [`PwmUpdateMethod`]. + pub fn timestamp(&self) -> u16 { + // SAFETY: + // We only read to our GENx_TSTMP_x register + let ch = unsafe { Self::ch() }; + + #[cfg(esp32s3)] + if IS_A { + ch.cmpr_value0().read().a().bits() + } else { + ch.cmpr_value1().read().b().bits() + } + + #[cfg(any(esp32, esp32c6, esp32h2))] + if IS_A { + ch.gen_tstmp_a().read().a().bits() + } else { + ch.gen_tstmp_b().read().b().bits() + } + } + + /// Get the period of the timer. + pub fn period(&self) -> u16 { + // SAFETY: + // We only grant access to our CFG0 register with the lifetime of &mut self + let block = unsafe { &*PWM::block() }; + + let tim_select = block.operator_timersel().read(); + let tim = match OP { + 0 => tim_select.operator0_timersel().bits(), + 1 => tim_select.operator1_timersel().bits(), + 2 => tim_select.operator2_timersel().bits(), + _ => { + unreachable!() + } + }; + + // SAFETY: + // The CFG0 registers are identical for all timers so we can pretend they're + // TIMER0_CFG0 + block.timer(tim as usize).cfg0().read().period().bits() + } + + unsafe fn ch() -> &'static pac::mcpwm0::CH { + let block = unsafe { &*PWM::block() }; + block.ch(OP as usize) + } +} + +/// Implement no error type for the PwmPin because the method are infallible +impl embedded_hal::pwm::ErrorType + for PwmPin<'_, PWM, OP, IS_A> +{ + type Error = core::convert::Infallible; +} + +/// Implement the trait SetDutyCycle for PwmPin +impl embedded_hal::pwm::SetDutyCycle + for PwmPin<'_, PWM, OP, IS_A> +{ + /// Get the max duty of the PwmPin + fn max_duty_cycle(&self) -> u16 { + self.period() + } + + /// Set the max duty of the PwmPin + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), core::convert::Infallible> { + self.set_timestamp(duty); + Ok(()) + } +} + +#[procmacros::doc_replace( + "mcpwm_clk" => { + cfg(not(esp32h2)) => "40", + cfg(esp32h2) => "32" + } +)] +/// Two pins driven by the same timer and operator +/// +/// Useful for complementary or mirrored signals with or without +/// configured deadtime. +/// +/// # H-Bridge example +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; +/// # use esp_hal::mcpwm::operator::{DeadTimeCfg, PwmPinConfig, PWMStream}; +/// // active high complementary using PWMA input +/// let bridge_active = DeadTimeCfg::new_ahc(); +/// +/// // use PWMB as input for both outputs +/// let bridge_off = DeadTimeCfg::new_bypass().set_output_swap(PWMStream::PWMA, true); +/// +/// let mut mcpwm = McPwm::new( +/// peripherals.MCPWM0, +/// PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_clk__))?, +/// ); +/// +/// let mut pins = mcpwm.operator0.with_linked_pins( +/// peripherals.GPIO0, +/// PwmPinConfig::UP_DOWN_ACTIVE_HIGH, // use PWMA as our main input +/// peripherals.GPIO1, +/// PwmPinConfig::EMPTY, // keep PWMB "low" +/// bridge_off, +/// ); +/// +/// pins.set_falling_edge_deadtime(5); +/// pins.set_rising_edge_deadtime(5); +/// // pin_a: ________________________________________ +/// // pin_b: ________________________________________ +/// pins.set_timestamp_a(40); // 40% duty cycle if period configured to 100 +/// pins.set_deadtime_cfg(bridge_active); +/// // pin_a: _______-------_____________-------______ +/// // pin_b: ------_________-----------_________----- +/// # {after_snippet} +/// ``` +pub struct LinkedPins<'d, PWM, const OP: u8> { + pin_a: PwmPin<'d, PWM, OP, true>, + pin_b: PwmPin<'d, PWM, OP, false>, +} + +impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> { + fn new( + pin_a: impl PeripheralOutput<'d>, + config_a: PwmPinConfig, + pin_b: impl PeripheralOutput<'d>, + config_b: PwmPinConfig, + config_dt: DeadTimeCfg, + ) -> Self { + // setup deadtime config before enabling the pins + #[cfg(esp32s3)] + let dt_cfg = unsafe { Self::ch() }.db_cfg(); + #[cfg(not(esp32s3))] + let dt_cfg = unsafe { Self::ch() }.dt_cfg(); + dt_cfg.write(|w| unsafe { w.bits(config_dt.cfg_reg) }); + + let pin_a = PwmPin::new(pin_a, config_a); + let pin_b = PwmPin::new(pin_b, config_b); + + LinkedPins { pin_a, pin_b } + } + + /// Configure what actions should be taken on timing events + pub fn set_actions_a(&mut self, value: PwmActions) { + self.pin_a.set_actions(value) + } + /// Configure what actions should be taken on timing events + pub fn set_actions_b(&mut self, value: PwmActions) { + self.pin_b.set_actions(value) + } + + /// Set how a new timestamp syncs with the timer + pub fn set_update_method_a(&mut self, update_method: PwmUpdateMethod) { + self.pin_a.set_update_method(update_method) + } + /// Set how a new timestamp syncs with the timer + pub fn set_update_method_b(&mut self, update_method: PwmUpdateMethod) { + self.pin_b.set_update_method(update_method) + } + + /// Write a new timestamp. + /// The written value will take effect according to the set + /// [`PwmUpdateMethod`]. + pub fn set_timestamp_a(&mut self, value: u16) { + self.pin_a.set_timestamp(value) + } + /// Write a new timestamp. + /// The written value will take effect according to the set + /// [`PwmUpdateMethod`]. + pub fn set_timestamp_b(&mut self, value: u16) { + self.pin_a.set_timestamp(value) + } + + /// Configure the deadtime generator + pub fn set_deadtime_cfg(&mut self, config: DeadTimeCfg) { + #[cfg(esp32s3)] + let dt_cfg = unsafe { Self::ch() }.db_cfg(); + #[cfg(not(esp32s3))] + let dt_cfg = unsafe { Self::ch() }.dt_cfg(); + dt_cfg.write(|w| unsafe { w.bits(config.cfg_reg) }); + } + + /// Set the deadtime generator rising edge delay + pub fn set_rising_edge_deadtime(&mut self, dead_time: u16) { + #[cfg(esp32s3)] + let dt_red = unsafe { Self::ch() }.db_red_cfg(); + #[cfg(not(esp32s3))] + let dt_red = unsafe { Self::ch() }.dt_red_cfg(); + dt_red.write(|w| unsafe { w.red().bits(dead_time) }); + } + /// Set the deadtime generator falling edge delay + pub fn set_falling_edge_deadtime(&mut self, dead_time: u16) { + #[cfg(esp32s3)] + let dt_fed = unsafe { Self::ch() }.db_fed_cfg(); + #[cfg(not(esp32s3))] + let dt_fed = unsafe { Self::ch() }.dt_fed_cfg(); + dt_fed.write(|w| unsafe { w.fed().bits(dead_time) }); + } + + unsafe fn ch() -> &'static pac::mcpwm0::CH { + let block = unsafe { &*PWM::block() }; + block.ch(OP as usize) + } +} + +/// An action the operator applies to an output +#[non_exhaustive] +#[repr(u32)] +pub enum UpdateAction { + /// Clear the output by setting it to a low level. + SetLow = 1, + /// Set the output to a high level. + SetHigh = 2, + /// Change the current output level to the opposite value. + /// If it is currently pulled high, pull it low, or vice versa. + Toggle = 3, +} + +/// Settings for what actions should be taken on timing events +/// +/// ### Note: +/// The hardware supports using a timestamp A event to trigger an action on +/// output B or vice versa. For clearer ownership semantics this HAL does not +/// support such configurations. +pub struct PwmActions(u32); + +impl PwmActions { + /// Using this setting together with a timer configured with + /// [`PwmWorkingMode::Increase`](super::timer::PwmWorkingMode::Increase) + /// will set the output high for a duration proportional to the set + /// timestamp. + pub const UP_ACTIVE_HIGH: Self = Self::empty() + .on_up_counting_timer_equals_zero(UpdateAction::SetHigh) + .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow); + + /// Using this setting together with a timer configured with + /// [`PwmWorkingMode::UpDown`](super::timer::PwmWorkingMode::UpDown) will + /// set the output high for a duration proportional to the set + /// timestamp. + pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty() + .on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh) + .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow); + + /// `PwmActions` with no `UpdateAction`s set + pub const fn empty() -> Self { + PwmActions(0) + } + + /// Choose an `UpdateAction` for an `UTEZ` event + pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 0) + } + + /// Choose an `UpdateAction` for an `UTEP` event + pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 2) + } + + /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event + pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self { + match IS_A { + true => self.with_value_at_offset(action as u32, 4), + false => self.with_value_at_offset(action as u32, 6), + } + } + + /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event where you can + /// specify which of the A/B to use + pub const fn on_up_counting_timer_equals_ch_timestamp( + self, + action: UpdateAction, + ) -> Self { + match CH_A { + true => self.with_value_at_offset(action as u32, 4), + false => self.with_value_at_offset(action as u32, 6), + } + } + + /// Choose an `UpdateAction` for an `DTEZ` event + pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 12) + } + + /// Choose an `UpdateAction` for an `DTEP` event + pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 14) + } + + /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event + pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self { + match IS_A { + true => self.with_value_at_offset(action as u32, 16), + false => self.with_value_at_offset(action as u32, 18), + } + } + + /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event where you can + /// specify which of the A/B to use + pub const fn on_down_counting_timer_equals_ch_timestamp( + self, + action: UpdateAction, + ) -> Self { + match CH_A { + true => self.with_value_at_offset(action as u32, 16), + false => self.with_value_at_offset(action as u32, 18), + } + } + + const fn with_value_at_offset(self, value: u32, offset: u32) -> Self { + let mask = !(0b11 << offset); + let value = (self.0 & mask) | (value << offset); + PwmActions(value) + } +} + +/// Settings for when [`PwmPin::set_timestamp`] takes effect +/// +/// Multiple syncing triggers can be set. +pub struct PwmUpdateMethod(u8); + +impl PwmUpdateMethod { + /// New timestamp will be applied immediately + pub const SYNC_IMMEDIATLY: Self = Self::empty(); + /// New timestamp will be applied when timer is equal to zero + pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero(); + /// New timestamp will be applied when timer is equal to period + pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period(); + + /// `PwmUpdateMethod` with no sync triggers. + /// Corresponds to syncing immediately + pub const fn empty() -> Self { + PwmUpdateMethod(0) + } + + /// Enable syncing new timestamp values when timer is equal to zero + pub const fn sync_on_timer_equals_zero(mut self) -> Self { + self.0 |= 0b0001; + self + } + + /// Enable syncing new timestamp values when timer is equal to period + pub const fn sync_on_timer_equals_period(mut self) -> Self { + self.0 |= 0b0010; + self + } +} diff --git a/esp-hal/src/mcpwm/timer.rs b/esp-hal/src/mcpwm/timer.rs new file mode 100644 index 00000000000..98475e71b85 --- /dev/null +++ b/esp-hal/src/mcpwm/timer.rs @@ -0,0 +1,256 @@ +//! # MCPWM Timer Module +//! +//! ## Overview +//! The `timer` module provides an interface to configure and use timers for +//! generating `PWM` signals used in motor control and other applications. + +use core::marker::PhantomData; + +use super::PeripheralGuard; +use crate::{ + mcpwm::{FrequencyError, PeripheralClockConfig, PwmClockGuard, PwmPeripheral}, + pac, + time::Rate, +}; + +/// A MCPWM timer +/// +/// Every timer of a particular [`MCPWM`](super::McPwm) peripheral can be used +/// as a timing reference for every +/// [`Operator`](super::operator::Operator) of that peripheral +pub struct Timer { + pub(super) phantom: PhantomData, + _guard: PeripheralGuard, + _pwm_clock_guard: PwmClockGuard, +} + +impl Timer { + pub(super) fn new(guard: PeripheralGuard) -> Self { + Timer { + phantom: PhantomData, + _guard: guard, + _pwm_clock_guard: PwmClockGuard::new::(), + } + } + + /// Apply the given timer configuration. + /// + /// ### Note: + /// The prescaler and period configuration will be applied immediately by + /// default and before setting the [`PwmWorkingMode`]. + /// If the timer is already running you might want to call [`Timer::stop`] + /// and/or [`Timer::set_counter`] first + /// (if the new period is larger than the current counter value this will + /// cause weird behavior). + /// + /// If configured via [`TimerClockConfig::with_period_updating_method`], + /// another behavior can be applied. Currently, only + /// [`PeriodUpdatingMethod::Immediately`] + /// and [`PeriodUpdatingMethod::TimerEqualsZero`] are useful as the sync + /// method is not yet implemented. + /// + /// The hardware supports writing these settings in sync with certain timer + /// events but this HAL does not expose these for now. + pub fn start(&mut self, timer_config: TimerClockConfig) { + // write prescaler and period with immediate update method + self.cfg0().write(|w| unsafe { + w.prescale().bits(timer_config.prescaler); + w.period().bits(timer_config.period); + w.period_upmethod() + .bits(timer_config.period_updating_method as u8) + }); + + // set timer to continuously run and set the timer working mode + self.cfg1().write(|w| unsafe { + w.start().bits(2); + w.mod_().bits(timer_config.mode as u8) + }); + } + + /// Stop the timer in its current state + pub fn stop(&mut self) { + // freeze the timer + self.cfg1().write(|w| unsafe { w.mod_().bits(0) }); + } + + /// Set the timer counter to the provided value + pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) { + // SAFETY: + // We only write to our TIMERx_SYNC register + let tmr = unsafe { Self::tmr() }; + let sw = tmr.sync().read().sw().bit_is_set(); + tmr.sync().write(|w| { + w.phase_direction().bit(direction as u8 != 0); + unsafe { + w.phase().bits(phase); + } + w.sw().bit(!sw) + }); + } + + /// Read the counter value and counter direction of the timer + pub fn status(&self) -> (u16, CounterDirection) { + // SAFETY: + // We only read from our TIMERx_STATUS register + let reg = unsafe { Self::tmr() }.status().read(); + (reg.value().bits(), reg.direction().bit_is_set().into()) + } + + fn cfg0(&mut self) -> &pac::mcpwm0::timer::CFG0 { + // SAFETY: + // We only grant access to our CFG0 register with the lifetime of &mut self + unsafe { Self::tmr() }.cfg0() + } + + fn cfg1(&mut self) -> &pac::mcpwm0::timer::CFG1 { + // SAFETY: + // We only grant access to our CFG0 register with the lifetime of &mut self + unsafe { Self::tmr() }.cfg1() + } + + unsafe fn tmr() -> &'static pac::mcpwm0::TIMER { + let block = unsafe { &*PWM::block() }; + block.timer(TIM as usize) + } +} + +/// Clock configuration of a MCPWM timer +/// +/// Use [`PeripheralClockConfig::timer_clock_with_prescaler`](super::PeripheralClockConfig::timer_clock_with_prescaler) or +/// [`PeripheralClockConfig::timer_clock_with_frequency`](super::PeripheralClockConfig::timer_clock_with_frequency) to it. +#[derive(Copy, Clone)] +pub struct TimerClockConfig { + frequency: Rate, + period: u16, + period_updating_method: PeriodUpdatingMethod, + prescaler: u8, + mode: PwmWorkingMode, +} + +impl TimerClockConfig { + pub(super) fn with_prescaler( + clock: &PeripheralClockConfig, + period: u16, + mode: PwmWorkingMode, + prescaler: u8, + ) -> Self { + let cycle_period = match mode { + PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1, + // The reference manual seems to provide an incorrect formula for UpDown + PwmWorkingMode::UpDown => period as u32 * 2, + }; + let frequency = clock.frequency / (prescaler as u32 + 1) / cycle_period; + + TimerClockConfig { + frequency, + prescaler, + period, + period_updating_method: PeriodUpdatingMethod::Immediately, + mode, + } + } + + pub(super) fn with_frequency( + clock: &PeripheralClockConfig, + period: u16, + mode: PwmWorkingMode, + target_freq: Rate, + ) -> Result { + let cycle_period = match mode { + PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1, + // The reference manual seems to provide an incorrect formula for UpDown + PwmWorkingMode::UpDown => period as u32 * 2, + }; + let target_timer_frequency = target_freq + .as_hz() + .checked_mul(cycle_period) + .ok_or(FrequencyError)?; + if target_timer_frequency == 0 || target_freq > clock.frequency { + return Err(FrequencyError); + } + let prescaler = clock.frequency.as_hz() / target_timer_frequency - 1; + if prescaler > u8::MAX as u32 { + return Err(FrequencyError); + } + let frequency = clock.frequency / (prescaler + 1) / cycle_period; + + Ok(TimerClockConfig { + frequency, + prescaler: prescaler as u8, + period, + period_updating_method: PeriodUpdatingMethod::Immediately, + mode, + }) + } + + /// Set the method for updating the PWM period + pub fn with_period_updating_method(self, method: PeriodUpdatingMethod) -> Self { + Self { + period_updating_method: method, + ..self + } + } + + /// Get the timer clock frequency. + /// + /// ### Note: + /// The actual value is rounded down to the nearest `u32` value + pub fn frequency(&self) -> Rate { + self.frequency + } +} + +/// Method for updating the PWM period +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum PeriodUpdatingMethod { + /// The period is updated immediately. + Immediately = 0, + /// The period is updated when the timer equals zero. + TimerEqualsZero = 1, + /// The period is updated on a synchronization event. + Sync = 2, + /// The period is updated either when the timer equals zero or on a + /// synchronization event. + TimerEqualsZeroOrSync = 3, +} + +/// PWM working mode +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum PwmWorkingMode { + /// In this mode, the PWM timer increments from zero until reaching the + /// value configured in the period field. Once done, the PWM timer + /// returns to zero and starts increasing again. PWM period is equal to the + /// value of the period field + 1. + Increase = 1, + /// The PWM timer decrements to zero, starting from the value configured in + /// the period field. After reaching zero, it is set back to the period + /// value. Then it starts to decrement again. In this case, the PWM period + /// is also equal to the value of period field + 1. + Decrease = 2, + /// This is a combination of the two modes mentioned above. The PWM timer + /// starts increasing from zero until the period value is reached. Then, + /// the timer decreases back to zero. This pattern is then repeated. The + /// PWM period is the result of the value of the period field × 2. + UpDown = 3, +} + +/// The direction the timer counter is changing +#[derive(Debug)] +#[repr(u8)] +pub enum CounterDirection { + /// The timer counter is increasing + Increasing = 0, + /// The timer counter is decreasing + Decreasing = 1, +} + +impl From for CounterDirection { + fn from(bit: bool) -> Self { + match bit { + false => CounterDirection::Increasing, + true => CounterDirection::Decreasing, + } + } +} diff --git a/esp-hal/src/otg_fs.rs b/esp-hal/src/otg_fs.rs new file mode 100644 index 00000000000..91e10da9cc1 --- /dev/null +++ b/esp-hal/src/otg_fs.rs @@ -0,0 +1,335 @@ +//! # USB On-The-Go (USB OTG) +//! +//! ## Overview +//! The USB OTG Full-speed (FS) peripheral allows communication +//! with USB devices using either blocking (usb-device) or asynchronous +//! (embassy-usb) APIs. +//! +//! It can operate as either a USB Host or Device, and supports full-speed (FS) +//! and low-speed (LS) data rates of the USB 2.0 specification. +//! +//! The blocking driver uses the `esp_synopsys_usb_otg` crate, which provides +//! the `USB bus` implementation and `USB peripheral traits`. +//! +//! The asynchronous driver uses the `embassy_usb_synopsys_otg` crate, which +//! provides the `USB bus` and `USB device` implementations. +//! +//! The module also relies on other peripheral modules, such as `GPIO`, +//! `system`, and `clock control`, to configure and enable the `USB` peripheral. +//! +//! ## Configuration +//! To use the USB OTG Full-speed peripheral driver, you need to initialize the +//! peripheral and configure its settings. The [`Usb`] struct represents the USB +//! peripheral and requires the GPIO pins that implement [`UsbDp`], and +//! [`UsbDm`], which define the specific types used for USB pin selection. +//! +//! The returned `Usb` instance can be used with the `usb-device` crate, or it +//! can be further configured with [`asynch::Driver`] to be used with the +//! `embassy-usb` crate. +//! +//! ## Examples +//! Visit the [USB Serial] example for an example of using the USB OTG +//! peripheral. +//! +//! [USB Serial]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/usb_serial/src/main.rs +//! +//! ## Implementation State +//! - Low-speed (LS) is not supported. + +pub use esp_synopsys_usb_otg::UsbBus; +use esp_synopsys_usb_otg::UsbPeripheral; + +use crate::{ + gpio::InputSignal, + peripherals, + system::{GenericPeripheralGuard, Peripheral as PeripheralEnable}, +}; + +#[doc(hidden)] +/// Trait representing the USB D+ (data plus) pin. +pub trait UsbDp: crate::private::Sealed {} + +#[doc(hidden)] +/// Trait representing the USB D- (data minus) pin. +pub trait UsbDm: crate::private::Sealed {} + +for_each_analog_function! { + (USB_DM, $gpio:ident) => { + impl UsbDm for crate::peripherals::$gpio<'_> {} + }; + (USB_DP, $gpio:ident) => { + impl UsbDp for crate::peripherals::$gpio<'_> {} + }; +} + +/// USB peripheral. +pub struct Usb<'d> { + _usb0: peripherals::USB0<'d>, + _guard: GenericPeripheralGuard<{ PeripheralEnable::Usb as u8 }>, +} + +impl<'d> Usb<'d> { + /// Creates a new `Usb` instance. + pub fn new( + usb0: peripherals::USB0<'d>, + _usb_dp: impl UsbDp + 'd, + _usb_dm: impl UsbDm + 'd, + ) -> Self { + let guard = GenericPeripheralGuard::new(); + + Self { + _usb0: usb0, + _guard: guard, + } + } + + fn _enable() { + peripherals::USB_WRAP::regs().otg_conf().modify(|_, w| { + w.usb_pad_enable().set_bit(); + w.phy_sel().clear_bit(); + w.clk_en().set_bit(); + w.ahb_clk_force_on().set_bit(); + w.phy_clk_force_on().set_bit() + }); + + #[cfg(esp32s3)] + peripherals::LPWR::regs().usb_conf().modify(|_, w| { + w.sw_hw_usb_phy_sel().set_bit(); + w.sw_usb_phy_sel().set_bit() + }); + + use crate::gpio::Level; + + InputSignal::USB_OTG_IDDIG.connect_to(&Level::High); // connected connector is mini-B side + InputSignal::USB_SRP_BVALID.connect_to(&Level::High); // HIGH to force USB device mode + InputSignal::USB_OTG_VBUSVALID.connect_to(&Level::High); // receiving a valid Vbus from device + InputSignal::USB_OTG_AVALID.connect_to(&Level::Low); + } + + fn _disable() { + // TODO + } +} + +unsafe impl Sync for Usb<'_> {} + +unsafe impl UsbPeripheral for Usb<'_> { + const REGISTERS: *const () = peripherals::USB0::PTR.cast(); + + const HIGH_SPEED: bool = false; + const FIFO_DEPTH_WORDS: usize = 256; + const ENDPOINT_COUNT: usize = 5; + + fn enable() { + Self::_enable(); + } + + fn ahb_frequency_hz(&self) -> u32 { + // unused + 80_000_000 + } +} +/// Async functionality +pub mod asynch { + use embassy_usb_driver::{ + EndpointAddress, + EndpointAllocError, + EndpointType, + Event, + Unsupported, + }; + pub use embassy_usb_synopsys_otg::Config; + use embassy_usb_synopsys_otg::{ + Bus as OtgBus, + ControlPipe, + Driver as OtgDriver, + Endpoint, + In, + OtgInstance, + Out, + PhyType, + State, + on_interrupt, + otg_v1::Otg, + }; + use procmacros::handler; + + use super::*; + use crate::system::Cpu; + + // From ESP32-S3 TRM: + // Six additional endpoints (endpoint numbers 1 to 6), configurable as IN or OUT + const MAX_EP_COUNT: usize = 7; + + static STATE: State = State::new(); + + /// Asynchronous USB driver. + pub struct Driver<'d> { + inner: OtgDriver<'d, MAX_EP_COUNT>, + _usb: Usb<'d>, + } + + impl<'d> Driver<'d> { + const REGISTERS: Otg = unsafe { Otg::from_ptr(Usb::REGISTERS.cast_mut()) }; + + /// Initializes USB OTG peripheral with internal Full-Speed PHY, for + /// asynchronous operation. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new(peri: Usb<'d>, ep_out_buffer: &'d mut [u8], config: Config) -> Self { + // From `synopsys-usb-otg` crate: + // This calculation doesn't correspond to one in a Reference Manual. + // In fact, the required number of words is higher than indicated in RM. + // The following numbers are pessimistic and were figured out empirically. + const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; + + let instance = OtgInstance { + regs: Self::REGISTERS, + state: &STATE, + fifo_depth_words: Usb::FIFO_DEPTH_WORDS as u16, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: Usb::ENDPOINT_COUNT, + phy_type: PhyType::InternalFullSpeed, + calculate_trdt_fn: |_| 5, + }; + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + _usb: peri, + } + } + } + + impl<'d> embassy_usb_driver::Driver<'d> for Driver<'d> { + type EndpointOut = Endpoint<'d, Out>; + type EndpointIn = Endpoint<'d, In>; + type ControlPipe = ControlPipe<'d>; + type Bus = Bus<'d>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + ep_addr: Option, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.inner + .alloc_endpoint_in(ep_type, ep_addr, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + ep_addr: Option, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.inner + .alloc_endpoint_out(ep_type, ep_addr, max_packet_size, interval_ms) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let (bus, cp) = self.inner.start(control_max_packet_size); + + let mut bus = Bus { + inner: bus, + _usb: self._usb, + }; + + bus.init(); + + (bus, cp) + } + } + + /// Asynchronous USB bus mainly used internally by `embassy-usb`. + // We need a custom wrapper implementation to handle custom initialization. + pub struct Bus<'d> { + inner: OtgBus<'d, MAX_EP_COUNT>, + _usb: Usb<'d>, + } + + impl Bus<'_> { + fn init(&mut self) { + Usb::_enable(); + + let r = Driver::REGISTERS; + + // Wait for AHB ready. + while !r.grstctl().read().ahbidl() {} + + // Configure as device. + r.gusbcfg().modify(|w| { + // Force device mode + w.set_fdmod(true); + w.set_srpcap(false); + }); + + // Perform core soft-reset + while !r.grstctl().read().ahbidl() {} + r.grstctl().modify(|w| w.set_csrst(true)); + while r.grstctl().read().csrst() {} + + self.inner.config_v1(); + + // Enable PHY clock + r.pcgcctl().write(|w| w.0 = 0); + + crate::interrupt::bind_handler(crate::peripherals::Interrupt::USB, interrupt_handler); + } + + fn disable(&mut self) { + crate::interrupt::disable(Cpu::ProCpu, peripherals::Interrupt::USB); + + #[cfg(multi_core)] + crate::interrupt::disable(Cpu::AppCpu, peripherals::Interrupt::USB); + + Usb::_disable(); + } + } + + impl embassy_usb_driver::Bus for Bus<'_> { + async fn poll(&mut self) -> Event { + self.inner.poll().await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + self.inner.endpoint_set_stalled(ep_addr, stalled) + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + self.inner.endpoint_is_stalled(ep_addr) + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + self.inner.endpoint_set_enabled(ep_addr, enabled) + } + + async fn enable(&mut self) { + self.inner.enable().await + } + + async fn disable(&mut self) { + self.inner.disable().await + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + self.inner.remote_wakeup().await + } + } + + impl Drop for Bus<'_> { + fn drop(&mut self) { + Bus::disable(self); + } + } + + #[handler(priority = crate::interrupt::Priority::max())] + fn interrupt_handler() { + unsafe { on_interrupt(Driver::REGISTERS, &STATE, Usb::ENDPOINT_COUNT) } + } +} diff --git a/esp-hal/src/parl_io.rs b/esp-hal/src/parl_io.rs new file mode 100644 index 00000000000..d565912a4d1 --- /dev/null +++ b/esp-hal/src/parl_io.rs @@ -0,0 +1,2120 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Parallel IO (PARL_IO) +//! +//! ## Overview +//! The Parallel IO peripheral is a general purpose parallel interface that can +//! be used to connect to external devices such as LED matrix, LCD display, +//! Printer and Camera. The peripheral has independent TX and RX units. Each +//! unit can have up to 8 or 16 data signals (depending on your target hardware) +//! plus 1 or 2 clock signals. +//! +//! ## Configuration +//! The driver uses DMA (Direct Memory Access) for efficient data transfer. +//! +//! ## Examples +//! ### Initialization for RX +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::delay::Delay; +//! # use esp_hal::dma_buffers; +//! # use esp_hal::dma::DmaRxBuf; +//! # use esp_hal::gpio::NoPin; +//! # use esp_hal::parl_io::{BitPackOrder, ParlIo, RxConfig, RxFourBits}; +//! +//! // Initialize DMA buffer and descriptors for data reception +//! let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0); +//! let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; +//! let dma_channel = peripherals.DMA_CH0; +//! +//! // Configure the 4-bit input pins and clock pin +//! let mut rx_pins = RxFourBits::new( +//! peripherals.GPIO1, +//! peripherals.GPIO2, +//! peripherals.GPIO3, +//! peripherals.GPIO4, +//! ); +//! let mut rx_clk_pin = NoPin; +//! +//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing +//! // configuration +//! let parl_io = ParlIo::new(peripherals.PARL_IO, dma_channel)?; +//! +//! let config = RxConfig::default() +//! .with_frequency(Rate::from_mhz(1)) +//! .with_bit_order(BitPackOrder::Msb) +//! .with_timeout_ticks(0xfff); +//! +//! let mut parl_io_rx = parl_io.rx.with_config(rx_pins, rx_clk_pin, config)?; +//! +//! // Initialize the buffer and delay +//! dma_rx_buf.as_mut_slice().fill(0u8); +//! let delay = Delay::new(); +//! +//! loop { +//! // Read data via DMA and print received values +//! let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf)?; +//! (_, parl_io_rx, dma_rx_buf) = transfer.wait(); +//! +//! delay.delay_millis(500); +//! } +//! # } +//! ``` +//! +//! ### Initialization for TX +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::delay::Delay; +//! # use esp_hal::dma_tx_buffer; +//! # use esp_hal::parl_io::{BitPackOrder, ParlIo, TxFourBits, ClkOutPin, TxConfig, TxPinConfigWithValidPin}; +//! +//! // Initialize DMA buffer and descriptors for data reception +//! let mut dma_tx_buf = dma_tx_buffer!(32000).unwrap(); +//! let dma_channel = peripherals.DMA_CH0; +//! +//! // Configure the 4-bit input pins and clock pin +//! let tx_pins = TxFourBits::new( +//! peripherals.GPIO1, +//! peripherals.GPIO2, +//! peripherals.GPIO3, +//! peripherals.GPIO4, +//! ); +//! +//! let mut pin_conf = TxPinConfigWithValidPin::new(tx_pins, peripherals.GPIO5); +//! +//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing +//! // configuration +//! let parl_io = ParlIo::new( +//! peripherals.PARL_IO, +//! dma_channel, +//! )?; +//! +//! let mut clock_pin = ClkOutPin::new(peripherals.GPIO8); +//! let config = TxConfig::default() +//! .with_frequency(Rate::from_mhz(1)) +//! .with_bit_order(BitPackOrder::Msb); +//! +//! let mut parl_io_tx = parl_io +//! .tx +//! .with_config( +//! pin_conf, +//! clock_pin, +//! config, +//! )?; +//! +//! for i in 0..dma_tx_buf.len() { +//! dma_tx_buf.as_mut_slice()[i] = (i % 255) as u8; +//! } +//! +//! let delay = Delay::new(); +//! loop { +//! let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf)?; +//! (_, parl_io_tx, dma_tx_buf) = transfer.wait(); +//! delay.delay_millis(500); +//! } +//! # } +//! ``` + +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use enumset::{EnumSet, EnumSetType}; +use private::*; + +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + Channel, + ChannelRx, + ChannelTx, + DmaChannelFor, + DmaError, + DmaPeripheral, + DmaRxBuffer, + DmaTxBuffer, + PeripheralRxChannel, + PeripheralTxChannel, + }, + gpio::{ + self, + InputSignal, + NoPin, + OutputSignal, + interconnect::{self, PeripheralInput, PeripheralOutput}, + }, + interrupt::InterruptHandler, + parl_io::asynch::interrupt_handler, + peripherals::{PARL_IO, PCR}, + soc::clocks::ClockTree, + system::{GenericPeripheralGuard, Peripheral}, + time::Rate, +}; + +// TODO: C5 has the option for unlimited DMA transfers +const MAX_DMA_SIZE: usize = 65535; + +/// Interrupts generated by the peripheral +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ParlIoInterrupt { + /// Triggered when TX FIFO is empty. This interrupt indicates that there + /// might be error in the data sent by TX. + TxFifoReEmpty, + /// Triggered when RX FIFO is full. This interrupt indicates that there + /// might be error in the data received by RX. + RxFifoWOvf, + /// Triggered when TX finishes sending a complete frame of data. + TxEof, +} + +/// Parallel IO errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")] +pub enum Error { + /// General DMA error + DmaError(DmaError), + /// Maximum transfer size (32736) exceeded + MaxDmaTransferSizeExceeded, +} + +impl From for Error { + fn from(value: DmaError) -> Self { + Error::DmaError(value) + } +} + +/// Parallel IO sample edge +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SampleEdge { + /// Positive edge + Normal = 0, + /// Negative edge + Invert = 1, +} + +/// Parallel IO bit packing order +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BitPackOrder { + /// Bit pack order: MSB + Msb = 0, + /// Bit pack order: LSB + Lsb = 1, +} + +#[cfg(parl_io_version = "1")] +/// Enable Mode +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EnableMode { + /// Enable at high level + HighLevel, + /// Enable at low level + LowLevel, + /// Positive pulse start (data bit included) & Positive pulse end (data bit + /// included) + PulseMode1, + /// Positive pulse start (data bit included) & Positive pulse end (data bit + /// excluded) + PulseMode2, + /// Positive pulse start (data bit excluded) & Positive pulse end (data bit + /// included) + PulseMode3, + /// Positive pulse start (data bit excluded) & Positive pulse end (data bit + /// excluded) + PulseMode4, + /// Positive pulse start (data bit included) & Length end + PulseMode5, + /// Positive pulse start (data bit excluded) & Length end + PulseMode6, + /// Negative pulse start (data bit included) & Negative pulse end(data bit + /// included) + PulseMode7, + /// Negative pulse start (data bit included) & Negative pulse end (data bit + /// excluded) + PulseMode8, + /// Negative pulse start (data bit excluded) & Negative pulse end (data bit + /// included) + PulseMode9, + /// Negative pulse start (data bit excluded) & Negative pulse end (data bit + /// excluded) + PulseMode10, + /// Negative pulse start (data bit included) & Length end + PulseMode11, + /// Negative pulse start (data bit excluded) & Length end + PulseMode12, +} + +#[cfg(parl_io_version = "1")] +impl EnableMode { + fn pulse_submode_sel(&self) -> Option { + match self { + EnableMode::PulseMode1 => Some(0), + EnableMode::PulseMode2 => Some(1), + EnableMode::PulseMode3 => Some(2), + EnableMode::PulseMode4 => Some(3), + EnableMode::PulseMode5 => Some(4), + EnableMode::PulseMode6 => Some(5), + EnableMode::PulseMode7 => Some(6), + EnableMode::PulseMode8 => Some(7), + EnableMode::PulseMode9 => Some(8), + EnableMode::PulseMode10 => Some(9), + EnableMode::PulseMode11 => Some(10), + EnableMode::PulseMode12 => Some(11), + _ => None, + } + } + + fn level_submode_sel(&self) -> Option { + match self { + EnableMode::HighLevel => Some(0), + EnableMode::LowLevel => Some(1), + _ => None, + } + } + + fn smp_model_sel(&self) -> Option { + match self { + EnableMode::HighLevel => Some(self::private::SampleMode::ExternalLevel), + EnableMode::LowLevel => Some(self::private::SampleMode::ExternalLevel), + _ => Some(self::private::SampleMode::ExternalPulse), + } + } +} + +#[cfg(parl_io_version = "2")] +/// Enable Mode +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EnableMode { + /// Enable at high level + HighLevel, + /// Positive pulse start (data bit included) & Positive pulse end (data bit + /// included) + PulseMode1, + /// Positive pulse start (data bit included) & Positive pulse end (data bit + /// excluded) + PulseMode2, + /// Positive pulse start (data bit excluded) & Positive pulse end (data bit + /// included) + PulseMode3, + /// Positive pulse start (data bit excluded) & Positive pulse end (data bit + /// excluded) + PulseMode4, + /// Positive pulse start (data bit included) & Length end + PulseMode5, + /// Positive pulse start (data bit excluded) & Length end + PulseMode6, +} + +#[cfg(parl_io_version = "2")] +impl EnableMode { + fn pulse_submode_sel(&self) -> Option { + match self { + EnableMode::PulseMode1 => Some(0), + EnableMode::PulseMode2 => Some(1), + EnableMode::PulseMode3 => Some(2), + EnableMode::PulseMode4 => Some(3), + EnableMode::PulseMode5 => Some(4), + EnableMode::PulseMode6 => Some(5), + _ => None, + } + } + + fn level_submode_sel(&self) -> Option { + match self { + EnableMode::HighLevel => Some(0), + _ => None, + } + } + + fn smp_model_sel(&self) -> Option { + match self { + EnableMode::HighLevel => Some(self::private::SampleMode::ExternalLevel), + _ => Some(self::private::SampleMode::ExternalPulse), + } + } +} + +/// PARL_IO RX configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct RxConfig { + /// The target frequency. + frequency: Rate, + + /// Configures the packing order to pack bits into 1 byte when data + /// bus width is 4/2/1 bit. + bit_order: BitPackOrder, + + /// RX threshold of a timeout counter. + /// When the timeout is triggered, a GDMA ERR EOF signal will be + /// generated and sent to the GDMA interface to indicate the end of the + /// receiving + /// + /// In units of AHB clock cycles. + timeout_ticks: Option, +} + +impl Default for RxConfig { + fn default() -> Self { + Self { + frequency: Rate::from_khz(100), + bit_order: BitPackOrder::Msb, + timeout_ticks: None, + } + } +} + +/// PARL_IO TX configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TxConfig { + /// The target frequency. + frequency: Rate, + + /// Configures the data value on TX bus when in idle state. + idle_value: u16, + + /// Configures whether to invert the TX output clock. + sample_edge: SampleEdge, + + /// Configures the unpacking order to unpack bits from 1 byte when + /// data bus width is 4/2/1 bit. + bit_order: BitPackOrder, +} + +impl Default for TxConfig { + fn default() -> Self { + Self { + frequency: Rate::from_khz(100), + idle_value: 0, + sample_edge: SampleEdge::Normal, + bit_order: BitPackOrder::Msb, + } + } +} + +/// Configuration errors. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Trying to use an impossible clock rate + UnreachableClockRate, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::UnreachableClockRate => { + write!(f, "The requested frequency is not supported") + } + } + } +} + +/// Used to configure no pin as clock output +impl TxClkPin for NoPin { + fn configure(&mut self) { + OutputSignal::PARL_TX_CLK.connect_to(self); + } +} +impl RxClkPin for NoPin { + fn configure(&mut self) { + InputSignal::PARL_RX_CLK.connect_to(self); + } +} + +/// Wraps a GPIO pin which will be used as the clock output signal +pub struct ClkOutPin<'d> { + pin: interconnect::OutputSignal<'d>, +} +impl<'d> ClkOutPin<'d> { + /// Create a ClkOutPin + pub fn new(pin: impl PeripheralOutput<'d>) -> Self { + Self { pin: pin.into() } + } +} +impl TxClkPin for ClkOutPin<'_> { + fn configure(&mut self) { + self.pin.apply_output_config(&gpio::OutputConfig::default()); + self.pin.set_output_enable(true); + + OutputSignal::PARL_TX_CLK.connect_to(&self.pin); + } +} + +/// Wraps a GPIO pin which will be used as the TX clock input signal +pub struct ClkInPin<'d> { + pin: interconnect::InputSignal<'d>, +} +impl<'d> ClkInPin<'d> { + /// Create a new ClkInPin + pub fn new(pin: impl PeripheralInput<'d>) -> Self { + Self { pin: pin.into() } + } +} +impl TxClkPin for ClkInPin<'_> { + fn configure(&mut self) { + let pcr = PCR::regs(); + pcr.parl_clk_tx_conf() + .modify(|_, w| unsafe { w.parl_clk_tx_sel().bits(3).parl_clk_tx_div_num().bits(0) }); // PAD_CLK_TX, no divider + + self.pin.apply_input_config(&gpio::InputConfig::default()); + self.pin.set_input_enable(true); + InputSignal::PARL_TX_CLK.connect_to(&self.pin); + } +} + +/// Wraps a GPIO pin which will be used as the RX clock input signal +pub struct RxClkInPin<'d> { + pin: interconnect::InputSignal<'d>, + sample_edge: SampleEdge, +} +impl<'d> RxClkInPin<'d> { + /// Create a new RxClkInPin + pub fn new(pin: impl PeripheralInput<'d>, sample_edge: SampleEdge) -> Self { + Self { + pin: pin.into(), + sample_edge, + } + } +} +impl RxClkPin for RxClkInPin<'_> { + fn configure(&mut self) { + let pcr = PCR::regs(); + pcr.parl_clk_rx_conf() + .modify(|_, w| unsafe { w.parl_clk_rx_sel().bits(3).parl_clk_rx_div_num().bits(0) }); // PAD_CLK_TX, no divider + + self.pin.apply_input_config(&gpio::InputConfig::default()); + self.pin.set_input_enable(true); + + InputSignal::PARL_RX_CLK.connect_to(&self.pin); + + Instance::set_rx_clk_edge_sel(self.sample_edge); + } +} + +/// Pin configuration with an additional pin for the valid signal. +pub struct TxPinConfigWithValidPin<'d, P> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd, +{ + tx_pins: P, + valid_pin: interconnect::OutputSignal<'d>, +} + +impl<'d, P> TxPinConfigWithValidPin<'d, P> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd, +{ + /// Create a [TxPinConfigWithValidPin] + pub fn new(tx_pins: P, valid_pin: impl PeripheralOutput<'d>) -> Self { + Self { + tx_pins, + valid_pin: valid_pin.into(), + } + } +} + +impl<'d, P> TxPins for TxPinConfigWithValidPin<'d, P> where + P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd +{ +} + +impl<'d, P> ConfigurePins for TxPinConfigWithValidPin<'d, P> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd, +{ + fn configure(&mut self) { + self.tx_pins.configure(); + + self.valid_pin + .apply_output_config(&gpio::OutputConfig::default()); + self.valid_pin.set_output_enable(true); + + #[cfg(esp32c5)] + { + // C5 has a dedicated chip select signal. This signal is active low, and we need to + // invert it to end up with the same behaviour as older chips. + // TODO: this needs to be turned into metadata. + use crate::gpio::interconnect::OutputSignal; + + let valid_pin = core::mem::replace( + &mut self.valid_pin, + OutputSignal::new_level(crate::gpio::Level::Low), + ); + let was_inverted = valid_pin.is_output_inverted(); + self.valid_pin = valid_pin.with_output_inverter(!was_inverted); + } + + Instance::tx_valid_pin_signal().connect_to(&self.valid_pin); + Instance::set_tx_hw_valid_en(true); + } +} + +/// Pin configuration where the pin for the valid signal is the MSB pin. +pub struct TxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + tx_pins: P, +} + +impl

    TxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + /// Create a new [TxPinConfigIncludingValidPin] + pub fn new(tx_pins: P) -> Self { + Self { tx_pins } + } +} + +impl

    TxPins for TxPinConfigIncludingValidPin

    where + P: ContainsValidSignalPin + TxPins + ConfigurePins +{ +} + +impl

    ConfigurePins for TxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + fn configure(&mut self) { + self.tx_pins.configure(); + Instance::set_tx_hw_valid_en(true); + } +} + +macro_rules! tx_pins { + ($name:ident, $width:literal, $($pin:ident = $signal:ident),+ ) => { + paste::paste! { + #[doc = "Data pin configuration for "] + #[doc = stringify!($width)] + #[doc = "bit output mode"] + pub struct $name<'d> { + $( + [< pin_ $pin:lower >] : interconnect::OutputSignal<'d>, + )+ + } + + impl<'d> $name<'d> + { + /// Create a new TX pin + #[allow(clippy::too_many_arguments)] + pub fn new( + $( + [< pin_ $pin:lower >] : impl PeripheralOutput<'d>, + )+ + ) -> Self { + Self { $( [< pin_ $pin:lower >]: [< pin_ $pin:lower >].into() ),+ } + } + } + + impl ConfigurePins for $name<'_> + { + fn configure(&mut self) { + $( + self.[< pin_ $pin:lower >].apply_output_config(&gpio::OutputConfig::default()); + self.[< pin_ $pin:lower >].set_output_enable(true); + + OutputSignal::$signal.connect_to(&mut self.[< pin_ $pin:lower >]); + )+ + + private::Instance::set_tx_bit_width( private::WidSel::[< Bits $width >]); + } + } + + impl TxPins for $name<'_> {} + } + }; +} + +tx_pins!(TxOneBit, 1, P0 = PARL_TX_DATA0); +tx_pins!(TxTwoBits, 2, P0 = PARL_TX_DATA0, P1 = PARL_TX_DATA1); +tx_pins!( + TxFourBits, + 4, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3 +); +tx_pins!( + TxEightBits, + 8, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3, + P4 = PARL_TX_DATA4, + P5 = PARL_TX_DATA5, + P6 = PARL_TX_DATA6, + P7 = PARL_TX_DATA7 +); +#[cfg(parl_io_version = "1")] +tx_pins!( + TxSixteenBits, + 16, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3, + P4 = PARL_TX_DATA4, + P5 = PARL_TX_DATA5, + P6 = PARL_TX_DATA6, + P7 = PARL_TX_DATA7, + P8 = PARL_TX_DATA8, + P9 = PARL_TX_DATA9, + P10 = PARL_TX_DATA10, + P11 = PARL_TX_DATA11, + P12 = PARL_TX_DATA12, + P13 = PARL_TX_DATA13, + P14 = PARL_TX_DATA14, + P15 = PARL_TX_DATA15 +); + +impl NotContainsValidSignalPin for TxOneBit<'_> {} +impl NotContainsValidSignalPin for TxTwoBits<'_> {} +impl NotContainsValidSignalPin for TxFourBits<'_> {} + +#[cfg(any(esp32c5, esp32c6))] +impl NotContainsValidSignalPin for TxEightBits<'_> {} + +#[cfg(esp32h2)] +impl ContainsValidSignalPin for TxEightBits<'_> {} + +#[cfg(parl_io_version = "1")] +impl ContainsValidSignalPin for TxSixteenBits<'_> {} + +/// Pin configuration with an additional pin for the valid signal. +pub struct RxPinConfigWithValidPin<'d, P> +where + P: NotContainsValidSignalPin + RxPins + ConfigurePins, +{ + rx_pins: P, + valid_pin: interconnect::InputSignal<'d>, + enable_mode: EnableMode, +} + +impl<'d, P> RxPinConfigWithValidPin<'d, P> +where + P: NotContainsValidSignalPin + RxPins + ConfigurePins, +{ + /// Create a new [RxPinConfigWithValidPin] + pub fn new(rx_pins: P, valid_pin: impl PeripheralInput<'d>, enable_mode: EnableMode) -> Self { + Self { + rx_pins, + valid_pin: valid_pin.into(), + enable_mode, + } + } +} + +impl

    RxPins for RxPinConfigWithValidPin<'_, P> where + P: NotContainsValidSignalPin + RxPins + ConfigurePins +{ +} + +impl

    ConfigurePins for RxPinConfigWithValidPin<'_, P> +where + P: NotContainsValidSignalPin + RxPins + ConfigurePins, +{ + fn configure(&mut self) { + self.rx_pins.configure(); + + self.valid_pin + .apply_input_config(&gpio::InputConfig::default()); + self.valid_pin.set_input_enable(true); + + Instance::rx_valid_pin_signal().connect_to(&self.valid_pin); + Instance::set_rx_sw_en(false); + if let Some(sel) = self.enable_mode.pulse_submode_sel() { + Instance::set_rx_pulse_submode_sel(sel); + } + if let Some(sel) = self.enable_mode.level_submode_sel() { + Instance::set_rx_level_submode_sel(sel); + } + if let Some(sel) = self.enable_mode.smp_model_sel() { + Instance::set_rx_sample_mode(sel); + } + } +} + +/// Pin configuration where the pin for the valid signal is the MSB pin. +pub struct RxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + RxPins + ConfigurePins, +{ + rx_pins: P, + enable_mode: EnableMode, +} + +impl

    RxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + RxPins + ConfigurePins, +{ + /// Create a new [RxPinConfigIncludingValidPin] + pub fn new(rx_pins: P, enable_mode: EnableMode) -> Self { + Self { + rx_pins, + enable_mode, + } + } +} + +impl

    RxPins for RxPinConfigIncludingValidPin

    where + P: ContainsValidSignalPin + RxPins + ConfigurePins +{ +} + +impl

    ConfigurePins for RxPinConfigIncludingValidPin

    +where + P: ContainsValidSignalPin + RxPins + ConfigurePins, +{ + fn configure(&mut self) { + self.rx_pins.configure(); + Instance::set_rx_sw_en(false); + if let Some(sel) = self.enable_mode.pulse_submode_sel() { + Instance::set_rx_pulse_submode_sel(sel); + } + if let Some(sel) = self.enable_mode.level_submode_sel() { + Instance::set_rx_level_submode_sel(sel); + } + if let Some(sel) = self.enable_mode.smp_model_sel() { + Instance::set_rx_sample_mode(sel); + } + } +} + +macro_rules! rx_pins { + ($name:ident, $width:literal, $($pin:ident = $signal:ident),+ ) => { + paste::paste! { + #[doc = "Data pin configuration for "] + #[doc = stringify!($width)] + #[doc = "bit input mode"] + pub struct $name<'d> { + $( + [< pin_ $pin:lower >] : interconnect::InputSignal<'d>, + )+ + } + + impl<'d> $name<'d> + { + /// Create a new RX pin + #[allow(clippy::too_many_arguments)] + pub fn new( + $( + [< pin_ $pin:lower >] : impl PeripheralInput<'d>, + )+ + ) -> Self { + Self { $( [< pin_ $pin:lower >]: [< pin_ $pin:lower >].into() ),+ } + } + } + + impl ConfigurePins for $name<'_> + { + fn configure(&mut self) { + $( + self.[< pin_ $pin:lower >].apply_input_config(&gpio::InputConfig::default()); + self.[< pin_ $pin:lower >].set_input_enable(true); + InputSignal::$signal.connect_to(&self.[< pin_ $pin:lower >]); + )+ + + private::Instance::set_rx_bit_width( private::WidSel::[< Bits $width >]); + } + } + + impl RxPins for $name<'_> {} + } + }; +} + +rx_pins!(RxOneBit, 1, P0 = PARL_RX_DATA0); +rx_pins!(RxTwoBits, 2, P0 = PARL_RX_DATA0, P1 = PARL_RX_DATA1); +rx_pins!( + RxFourBits, + 4, + P0 = PARL_RX_DATA0, + P1 = PARL_RX_DATA1, + P2 = PARL_RX_DATA2, + P3 = PARL_RX_DATA3 +); +rx_pins!( + RxEightBits, + 8, + P0 = PARL_RX_DATA0, + P1 = PARL_RX_DATA1, + P2 = PARL_RX_DATA2, + P3 = PARL_RX_DATA3, + P4 = PARL_RX_DATA4, + P5 = PARL_RX_DATA5, + P6 = PARL_RX_DATA6, + P7 = PARL_RX_DATA7 +); +#[cfg(parl_io_version = "1")] +rx_pins!( + RxSixteenBits, + 16, + P0 = PARL_RX_DATA0, + P1 = PARL_RX_DATA1, + P2 = PARL_RX_DATA2, + P3 = PARL_RX_DATA3, + P4 = PARL_RX_DATA4, + P5 = PARL_RX_DATA5, + P6 = PARL_RX_DATA6, + P7 = PARL_RX_DATA7, + P8 = PARL_RX_DATA8, + P9 = PARL_RX_DATA9, + P10 = PARL_RX_DATA10, + P11 = PARL_RX_DATA11, + P12 = PARL_RX_DATA12, + P13 = PARL_RX_DATA13, + P14 = PARL_RX_DATA14, + P15 = PARL_RX_DATA15 +); + +impl NotContainsValidSignalPin for RxOneBit<'_> {} +impl NotContainsValidSignalPin for RxTwoBits<'_> {} +impl NotContainsValidSignalPin for RxFourBits<'_> {} + +#[cfg(any(esp32c5, esp32c6))] +impl NotContainsValidSignalPin for RxEightBits<'_> {} + +#[cfg(esp32h2)] +impl ContainsValidSignalPin for RxEightBits<'_> {} + +#[cfg(parl_io_version = "1")] +impl ContainsValidSignalPin for RxSixteenBits<'_> {} + +impl<'d, Dm> TxCreator<'d, Dm> +where + Dm: DriverMode, +{ + /// Configure TX to use the given pins and settings + pub fn with_config( + self, + mut tx_pins: P, + mut clk_pin: CP, + config: TxConfig, + ) -> Result, ConfigError> + where + P: TxPins + ConfigurePins + 'd, + CP: TxClkPin + 'd, + { + tx_pins.configure(); + clk_pin.configure(); + + let mut this = ParlIoTx { + tx_channel: self.tx_channel, + _guard: ParlIoTxGuard::new(self._guard), + }; + this.apply_config(&config)?; + + Ok(this) + } +} + +/// Parallel IO TX channel +#[instability::unstable] +pub struct ParlIoTx<'d, Dm> +where + Dm: DriverMode, +{ + tx_channel: ChannelTx>>, + _guard: ParlIoTxGuard, +} + +impl core::fmt::Debug for ParlIoTx<'_, Dm> +where + Dm: DriverMode, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ParlIoTx").finish() + } +} + +impl<'d, Dm> RxCreator<'d, Dm> +where + Dm: DriverMode, +{ + /// Configure RX to use the given pins and settings + pub fn with_config( + self, + mut rx_pins: P, + mut clk_pin: CP, + config: RxConfig, + ) -> Result, ConfigError> + where + P: RxPins + ConfigurePins + 'd, + CP: RxClkPin + 'd, + { + Instance::set_rx_sw_en(true); + Instance::set_rx_sample_mode(SampleMode::InternalSoftwareEnable); + + rx_pins.configure(); + clk_pin.configure(); + + let mut this = ParlIoRx { + rx_channel: self.rx_channel, + _guard: ParlIoRxGuard::new(self._guard), + }; + this.apply_config(&config)?; + + Ok(this) + } +} + +/// Parallel IO RX channel +#[instability::unstable] +pub struct ParlIoRx<'d, Dm> +where + Dm: DriverMode, +{ + rx_channel: ChannelRx>>, + _guard: ParlIoRxGuard, +} + +impl core::fmt::Debug for ParlIoRx<'_, Dm> +where + Dm: DriverMode, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ParlIoTx").finish() + } +} + +fn internal_set_interrupt_handler(handler: InterruptHandler) { + let peri = unsafe { PARL_IO::steal() }; + #[cfg(parl_io_version = "1")] + { + peri.disable_peri_interrupt_on_all_cores(); + internal_listen(EnumSet::all(), false); + internal_clear_interrupts(EnumSet::all()); + peri.bind_peri_interrupt(handler); + } + #[cfg(parl_io_version = "2")] + { + peri.disable_rx_interrupt_on_all_cores(); + peri.disable_tx_interrupt_on_all_cores(); + internal_listen(EnumSet::all(), false); + internal_clear_interrupts(EnumSet::all()); + peri.bind_rx_interrupt(handler); + peri.bind_tx_interrupt(handler); + } +} + +fn internal_listen(interrupts: EnumSet, enable: bool) { + PARL_IO::regs().int_ena().write(|w| { + for interrupt in interrupts { + match interrupt { + ParlIoInterrupt::TxFifoReEmpty => w.tx_fifo_rempty().bit(enable), + ParlIoInterrupt::RxFifoWOvf => w.rx_fifo_wovf().bit(enable), + ParlIoInterrupt::TxEof => w.tx_eof().bit(enable), + }; + } + w + }); +} + +fn internal_interrupts() -> EnumSet { + let mut res = EnumSet::new(); + let parl_io = PARL_IO::regs(); + let ints = parl_io.int_st().read(); + if ints.tx_fifo_rempty().bit() { + res.insert(ParlIoInterrupt::TxFifoReEmpty); + } + if ints.rx_fifo_wovf().bit() { + res.insert(ParlIoInterrupt::RxFifoWOvf); + } + if ints.tx_eof().bit() { + res.insert(ParlIoInterrupt::TxEof); + } + + res +} + +fn internal_clear_interrupts(interrupts: EnumSet) { + let parl_io = PARL_IO::regs(); + parl_io.int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + ParlIoInterrupt::TxFifoReEmpty => w.tx_fifo_rempty().clear_bit_by_one(), + ParlIoInterrupt::RxFifoWOvf => w.rx_fifo_wovf().clear_bit_by_one(), + ParlIoInterrupt::TxEof => w.tx_eof().clear_bit_by_one(), + }; + } + w + }); +} + +/// Parallel IO +pub struct ParlIo<'d, Dm> +where + Dm: DriverMode, +{ + /// The transmitter (TX) channel responsible for handling DMA transfers in + /// the parallel I/O operation. + pub tx: TxCreator<'d, Dm>, + /// The receiver (RX) channel responsible for handling DMA transfers in the + /// parallel I/O operation. + pub rx: RxCreator<'d, Dm>, +} + +impl<'d> ParlIo<'d, Blocking> { + /// Create a new instance of [ParlIo] + pub fn new( + _parl_io: PARL_IO<'d>, + dma_channel: impl DmaChannelFor>, + ) -> Result { + let tx_guard = GenericPeripheralGuard::new(); + let rx_guard = GenericPeripheralGuard::new(); + let dma_channel = Channel::new(dma_channel.degrade()); + + Ok(Self { + tx: TxCreator { + tx_channel: dma_channel.tx, + _guard: tx_guard, + }, + rx: RxCreator { + rx_channel: dma_channel.rx, + _guard: rx_guard, + }, + }) + } + + /// Convert to an async version. + pub fn into_async(self) -> ParlIo<'d, Async> { + internal_set_interrupt_handler(interrupt_handler); + + ParlIo { + tx: TxCreator { + tx_channel: self.tx.tx_channel.into_async(), + _guard: self.tx._guard, + }, + rx: RxCreator { + rx_channel: self.rx.rx_channel.into_async(), + _guard: self.rx._guard, + }, + } + } + + /// Sets the interrupt handler, enables it with + /// [crate::interrupt::Priority::min()] + /// + /// Interrupts are not enabled at the peripheral level here. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + internal_set_interrupt_handler(handler); + } + + /// Listen for the given interrupts + pub fn listen(&mut self, interrupts: impl Into>) { + internal_listen(interrupts.into(), true); + } + + /// Unlisten the given interrupts + pub fn unlisten(&mut self, interrupts: impl Into>) { + internal_listen(interrupts.into(), false); + } + + /// Gets asserted interrupts + pub fn interrupts(&mut self) -> EnumSet { + internal_interrupts() + } + + /// Resets asserted interrupts + pub fn clear_interrupts(&mut self, interrupts: EnumSet) { + internal_clear_interrupts(interrupts); + } +} + +impl crate::private::Sealed for ParlIo<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for ParlIo<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + ParlIo::set_interrupt_handler(self, handler); + } +} + +impl<'d> ParlIo<'d, Async> { + /// Convert to a blocking version. + pub fn into_blocking(self) -> ParlIo<'d, Blocking> { + ParlIo { + tx: TxCreator { + tx_channel: self.tx.tx_channel.into_blocking(), + _guard: self.tx._guard, + }, + rx: RxCreator { + rx_channel: self.rx.rx_channel.into_blocking(), + _guard: self.rx._guard, + }, + } + } +} + +impl<'d, Dm> ParlIoTx<'d, Dm> +where + Dm: DriverMode, +{ + /// Perform a DMA write. + /// + /// This will return a [ParlIoTxTransfer] + /// + /// The maximum amount of data to be sent is 32736 bytes. + pub fn write( + mut self, + number_of_bytes: usize, + mut buffer: BUF, + ) -> Result, (Error, Self, BUF)> + where + BUF: DmaTxBuffer, + { + if number_of_bytes > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); + } + + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| w.parl_tx_rst_en().set_bit()); + + Instance::clear_tx_interrupts(); + Instance::set_tx_bytes(number_of_bytes as u16); + + let result = unsafe { + self.tx_channel + .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer) + .and_then(|_| self.tx_channel.start_transfer()) + }; + if let Err(err) = result { + return Err((Error::DmaError(err), self, buffer)); + } + + while !Instance::is_tx_ready() {} + + Instance::set_tx_start(true); + + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| w.parl_tx_rst_en().clear_bit()); + + Ok(ParlIoTxTransfer { + parl_io: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buffer.into_view()), + }) + } + + /// Change the bus configuration. + pub fn apply_config(&mut self, config: &TxConfig) -> Result<(), ConfigError> { + if config.frequency.as_hz() > 40_000_000 { + return Err(ConfigError::UnreachableClockRate); + } + + let frequency = ClockTree::with(crate::soc::clocks::parlio_tx_clock_frequency); + let divider = frequency / config.frequency.as_hz(); + if divider > 0xFFFF { + return Err(ConfigError::UnreachableClockRate); + } + let divider = divider as u16; + + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| unsafe { w.parl_clk_tx_div_num().bits(divider) }); + + Instance::set_tx_idle_value(config.idle_value); + Instance::set_tx_sample_edge(config.sample_edge); + Instance::set_tx_bit_order(config.bit_order); + + Ok(()) + } +} + +/// Represents an ongoing (or potentially finished) transfer using the PARL_IO +/// TX. +pub struct ParlIoTxTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> { + parl_io: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> ParlIoTxTransfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + Instance::is_tx_eof() + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoTx<'d, Dm>, BUF::Final) { + while !self.is_done() {} + + Instance::set_tx_start(false); + Instance::clear_is_tx_done(); + + // Stop the DMA as it doesn't know that the parl io has stopped. + self.parl_io.tx_channel.stop_transfer(); + + let (parl_io, view) = self.release(); + + let result = if parl_io.tx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, parl_io, BUF::from_view(view)) + } + + fn release(mut self) -> (ParlIoTx<'d, Dm>, BUF::View) { + let (parl_io, view) = unsafe { + ( + ManuallyDrop::take(&mut self.parl_io), + ManuallyDrop::take(&mut self.buf_view), + ) + }; + core::mem::forget(self); + (parl_io, view) + } +} + +impl Deref for ParlIoTxTransfer<'_, BUF, Dm> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for ParlIoTxTransfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for ParlIoTxTransfer<'_, BUF, Dm> { + fn drop(&mut self) { + // There's no documented way to cancel the PARL IO transfer, so we'll just stop + // the DMA to stop the memory access. + self.parl_io.tx_channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.parl_io and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.parl_io); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +impl<'d, Dm> ParlIoRx<'d, Dm> +where + Dm: DriverMode, +{ + /// Perform a DMA read. + /// + /// This will return a [ParlIoRxTransfer] + /// + /// When the number of bytes is specified, the maximum amount of data is + /// 32736 bytes and the transfer ends when the number of specified bytes + /// is received. + /// + /// When the number of bytes is unspecified, there's no limit the amount of + /// data transferred and the transfer ends when the enable signal + /// signals the end or the DMA buffer runs out of space. + pub fn read( + mut self, + number_of_bytes: Option, + mut buffer: BUF, + ) -> Result, (Error, Self, BUF)> + where + BUF: DmaRxBuffer, + { + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| w.parl_rx_rst_en().set_bit()); + + Instance::clear_rx_interrupts(); + if let Some(number_of_bytes) = number_of_bytes { + if number_of_bytes > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); + } + Instance::set_rx_bytes(number_of_bytes as u16); + Instance::set_eof_gen_sel(EofMode::ByteLen); + } else { + Instance::set_eof_gen_sel(EofMode::EnableSignal); + } + + let result = unsafe { + self.rx_channel + .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer) + .and_then(|_| self.rx_channel.start_transfer()) + }; + if let Err(err) = result { + return Err((Error::DmaError(err), self, buffer)); + } + + Instance::set_rx_reg_update(); + + Instance::set_rx_start(true); + + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| w.parl_rx_rst_en().clear_bit()); + + Ok(ParlIoRxTransfer { + parl_io: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buffer.into_view()), + dma_result: None, + }) + } + + /// Change the bus configuration. + pub fn apply_config(&mut self, config: &RxConfig) -> Result<(), ConfigError> { + if config.frequency.as_hz() > 40_000_000 { + return Err(ConfigError::UnreachableClockRate); + } + + let frequency = ClockTree::with(crate::soc::clocks::parlio_rx_clock_frequency); + let divider = frequency / config.frequency.as_hz(); + if divider > 0xffff { + return Err(ConfigError::UnreachableClockRate); + } + let divider = divider as u16; + + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| unsafe { w.parl_clk_rx_div_num().bits(divider) }); + + Instance::set_rx_bit_order(config.bit_order); + Instance::set_rx_timeout_ticks(config.timeout_ticks); + + Ok(()) + } +} + +/// Represents an ongoing (or potentially finished) transfer using the PARL_IO +/// TX. +pub struct ParlIoRxTransfer<'d, BUF: DmaRxBuffer, Dm: DriverMode> { + parl_io: ManuallyDrop>, + buf_view: ManuallyDrop, + // Needed to use DmaRxFuture, which clear the bits we check in is_done() + dma_result: Option>, +} + +impl<'d, BUF: DmaRxBuffer, Dm: DriverMode> ParlIoRxTransfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + if self.dma_result.is_some() { + return true; + } + let ch = &self.parl_io.rx_channel; + ch.is_done() || ch.has_eof_error() || ch.has_dscr_empty_error() + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoRx<'d, Dm>, BUF::Final) { + while !self.is_done() {} + + Instance::set_rx_start(false); + + // Stop the DMA as it doesn't know that the parl io has stopped. + self.parl_io.rx_channel.stop_transfer(); + + let dma_result = self.dma_result.take(); + let (parl_io, view) = self.release(); + + let result = if parl_io.rx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + dma_result.unwrap_or(Ok(())) + }; + + (result, parl_io, BUF::from_view(view)) + } + + fn release(mut self) -> (ParlIoRx<'d, Dm>, BUF::View) { + let (parl_io, view) = unsafe { + ( + ManuallyDrop::take(&mut self.parl_io), + ManuallyDrop::take(&mut self.buf_view), + ) + }; + core::mem::forget(self); + (parl_io, view) + } +} + +impl Deref for ParlIoRxTransfer<'_, BUF, Dm> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buf_view + } +} + +impl DerefMut for ParlIoRxTransfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for ParlIoRxTransfer<'_, BUF, Dm> { + fn drop(&mut self) { + // There's no documented way to cancel the PARL IO transfer, so we'll just stop + // the DMA to stop the memory access. + self.parl_io.rx_channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.parl_io and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.parl_io); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); + } +} + +/// Creates a TX channel +pub struct TxCreator<'d, Dm> +where + Dm: DriverMode, +{ + tx_channel: ChannelTx>>, + _guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>, +} + +/// Creates a RX channel +pub struct RxCreator<'d, Dm> +where + Dm: DriverMode, +{ + rx_channel: ChannelRx>>, + _guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>, +} + +// These three traits need to be public to allow the user to create functions +// that can take either 8 or 16 bit pins and call parl_io.tx.with_config() or +// parl_io.rx.with_config(). +#[doc(hidden)] +pub trait TxPins {} +#[doc(hidden)] +pub trait RxPins {} +#[doc(hidden)] +pub trait ConfigurePins { + fn configure(&mut self); +} + +#[doc(hidden)] +pub mod asynch { + use core::task::Poll; + + use procmacros::handler; + + use super::{ParlIoRxTransfer, ParlIoTxTransfer, private::Instance}; + use crate::{ + asynch::AtomicWaker, + dma::{DmaRxBuffer, DmaTxBuffer, asynch::DmaRxFuture}, + }; + + static TX_WAKER: AtomicWaker = AtomicWaker::new(); + + #[must_use = "futures do nothing unless you `.await` or poll them"] + struct TxDoneFuture {} + + impl TxDoneFuture { + pub fn new() -> Self { + Self {} + } + } + + impl core::future::Future for TxDoneFuture { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + if Instance::is_tx_done_set() { + Poll::Ready(()) + } else { + TX_WAKER.register(cx.waker()); + Instance::listen_tx_done(); + Poll::Pending + } + } + } + + impl Drop for TxDoneFuture { + fn drop(&mut self) { + Instance::unlisten_tx_done(); + } + } + + #[handler] + pub(super) fn interrupt_handler() { + if Instance::is_tx_done_set() { + Instance::unlisten_tx_done(); + TX_WAKER.wake() + } + } + + impl ParlIoTxTransfer<'_, BUF, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { + let future = TxDoneFuture::new(); + future.await; + } + } + + impl ParlIoRxTransfer<'_, BUF, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { + if self.dma_result.is_some() { + return; + } + let future = DmaRxFuture::new(&mut self.parl_io.rx_channel); + self.dma_result = Some(future.await); + } + } +} + +mod private { + use super::{BitPackOrder, SampleEdge}; + use crate::{ + gpio::{InputSignal, OutputSignal}, + peripherals::PARL_IO, + }; + + pub trait NotContainsValidSignalPin {} + + pub trait ContainsValidSignalPin {} + + pub trait TxClkPin { + fn configure(&mut self); + } + + pub trait RxClkPin { + fn configure(&mut self); + } + + // TODO: these need to be metadata + #[cfg(parl_io_version = "1")] + pub(super) enum WidSel { + Bits16 = 0, + Bits8 = 1, + Bits4 = 2, + Bits2 = 3, + Bits1 = 4, + } + + #[cfg(parl_io_version = "2")] + pub(super) enum WidSel { + Bits8 = 3, + Bits4 = 2, + Bits2 = 1, + Bits1 = 0, + } + + pub(super) enum SampleMode { + ExternalLevel = 0, + ExternalPulse = 1, + InternalSoftwareEnable = 2, + } + + /// Generation of GDMA SUC EOF + pub(super) enum EofMode { + /// Generate GDMA SUC EOF by data byte length + ByteLen, + /// Generate GDMA SUC EOF by the external enable signal + EnableSignal, + } + + pub(super) struct Instance; + + #[cfg(parl_io_version = "1")] + impl Instance { + pub fn set_tx_bit_width(width: WidSel) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_cfg0() + .modify(|_, w| unsafe { w.tx_bus_wid_sel().bits(width as u8) }); + } + + pub fn set_tx_idle_value(value: u16) { + let reg_block = PARL_IO::regs(); + reg_block + .tx_cfg1() + .modify(|_, w| unsafe { w.tx_idle_value().bits(value) }); + } + + pub fn set_tx_sample_edge(value: SampleEdge) { + let reg_block = PARL_IO::regs(); + reg_block + .tx_cfg0() + .modify(|_, w| w.tx_smp_edge_sel().bit(value as u8 == 1)); + } + + pub fn set_tx_bit_order(value: BitPackOrder) { + let reg_block = PARL_IO::regs(); + reg_block + .tx_cfg0() + .modify(|_, w| w.tx_bit_unpack_order().bit(value as u8 == 1)); + } + + pub fn clear_tx_interrupts() { + let reg_block = PARL_IO::regs(); + + reg_block.int_clr().write(|w| { + w.tx_fifo_rempty() + .clear_bit_by_one() + .tx_eof() + .clear_bit_by_one() + }); + } + + pub fn set_tx_bytes(len: u16) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_cfg0() + .modify(|_, w| unsafe { w.tx_bytelen().bits(len) }); + } + + pub fn is_tx_ready() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.st().read().tx_ready().bit_is_set() + } + + pub fn set_tx_start(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block.tx_cfg0().modify(|_, w| w.tx_start().bit(value)); + } + + pub fn is_tx_eof() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.int_raw().read().tx_eof().bit_is_set() + } + + pub fn tx_valid_pin_signal() -> OutputSignal { + OutputSignal::PARL_TX_DATA15 + } + + pub fn set_tx_hw_valid_en(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_cfg0() + .modify(|_, w| w.tx_hw_valid_en().bit(value)); + } + + pub fn set_rx_bit_width(width: WidSel) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| unsafe { w.rx_bus_wid_sel().bits(width as u8) }); + } + + pub fn rx_valid_pin_signal() -> InputSignal { + InputSignal::PARL_RX_DATA15 + } + + pub fn set_rx_sw_en(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block.rx_cfg0().modify(|_, w| w.rx_sw_en().bit(value)); + } + + pub fn clear_rx_interrupts() { + let reg_block = PARL_IO::regs(); + + reg_block + .int_clr() + .write(|w| w.rx_fifo_wovf().clear_bit_by_one()); + } + + pub fn set_rx_bytes(len: u16) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| unsafe { w.rx_data_bytelen().bits(len) }); + } + + pub fn set_rx_sample_mode(sample_mode: SampleMode) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| unsafe { w.rx_smp_mode_sel().bits(sample_mode as u8) }); + } + + pub fn set_eof_gen_sel(mode: EofMode) { + let reg_block = PARL_IO::regs(); + + reg_block.rx_cfg0().modify(|_, w| { + w.rx_eof_gen_sel() + .bit(matches!(mode, EofMode::EnableSignal)) + }); + } + + pub fn set_rx_pulse_submode_sel(sel: u8) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| unsafe { w.rx_pulse_submode_sel().bits(sel) }); + } + + pub fn set_rx_level_submode_sel(sel: u8) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| w.rx_level_submode_sel().bit(sel == 1)); + } + + pub fn set_rx_clk_edge_sel(edge: SampleEdge) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg0() + .modify(|_, w| w.rx_clk_edge_sel().bit(edge as u8 == 1)); + } + + pub fn set_rx_start(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block.rx_cfg0().modify(|_, w| w.rx_start().bit(value)); + } + + pub fn set_rx_reg_update() { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_cfg1() + .modify(|_, w| w.rx_reg_update().bit(true)); + } + + pub fn set_rx_bit_order(value: BitPackOrder) { + let reg_block = PARL_IO::regs(); + reg_block + .rx_cfg0() + .modify(|_, w| w.rx_bit_pack_order().bit(value as u8 == 1)); + } + + pub fn set_rx_timeout_ticks(value: Option) { + let reg_block = PARL_IO::regs(); + reg_block.rx_cfg1().modify(|_, w| unsafe { + w.rx_timeout_en() + .bit(value.is_some()) + .rx_timeout_threshold() + .bits(value.unwrap_or(0xfff)) + }); + } + + pub fn listen_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_ena().modify(|_, w| w.tx_eof().set_bit()); + } + + pub fn unlisten_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit()); + } + + pub fn is_tx_done_set() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.int_raw().read().tx_eof().bit() + } + + pub fn clear_is_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_clr().write(|w| w.tx_eof().clear_bit_by_one()); + } + } + + #[cfg(parl_io_version = "2")] + impl Instance { + pub fn set_tx_bit_width(width: WidSel) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_data_cfg() + .modify(|_, w| unsafe { w.tx_bus_wid_sel().bits(width as u8) }); + } + + pub fn set_tx_idle_value(value: u16) { + let reg_block = PARL_IO::regs(); + reg_block + .tx_genrl_cfg() + .modify(|_, w| unsafe { w.tx_idle_value().bits(value) }); + } + + pub fn set_tx_sample_edge(value: SampleEdge) { + let reg_block = PARL_IO::regs(); + reg_block.tx_clk_cfg().modify(|_, w| { + w.tx_clk_i_inv() + .bit(value == SampleEdge::Invert) + .tx_clk_o_inv() + .bit(value == SampleEdge::Invert) + }); + } + + pub fn set_tx_bit_order(value: BitPackOrder) { + let reg_block = PARL_IO::regs(); + reg_block + .tx_data_cfg() + .modify(|_, w| w.tx_data_order_inv().bit(value as u8 == 1)); + } + + pub fn clear_tx_interrupts() { + let reg_block = PARL_IO::regs(); + + reg_block.int_clr().write(|w| { + w.tx_fifo_rempty() + .clear_bit_by_one() + .tx_eof() + .clear_bit_by_one() + }); + } + + pub fn set_tx_bytes(len: u16) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_data_cfg() + .modify(|_, w| unsafe { w.tx_bitlen().bits((len as u32) * 8) }); + } + + pub fn is_tx_ready() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.st().read().tx_ready().bit_is_set() + } + + pub fn set_tx_start(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_start_cfg() + .modify(|_, w| w.tx_start().bit(value)); + } + + pub fn is_tx_eof() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.int_raw().read().tx_eof().bit_is_set() + } + + pub fn tx_valid_pin_signal() -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(esp32c5)] { + OutputSignal::PARL_TX_CS + } else { + OutputSignal::PARL_TX_DATA7 + } + } + } + + pub fn set_tx_hw_valid_en(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block + .tx_genrl_cfg() + .modify(|_, w| w.tx_valid_output_en().bit(value)); + } + + pub fn set_rx_bit_width(width: WidSel) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_data_cfg() + .modify(|_, w| unsafe { w.rx_bus_wid_sel().bits(width as u8) }); + } + + pub fn rx_valid_pin_signal() -> InputSignal { + InputSignal::PARL_RX_DATA7 + } + + pub fn set_rx_sw_en(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_mode_cfg() + .modify(|_, w| w.rx_sw_en().bit(value)); + } + + pub fn clear_rx_interrupts() { + let reg_block = PARL_IO::regs(); + + reg_block + .int_clr() + .write(|w| w.rx_fifo_wovf().clear_bit_by_one()); + } + + pub fn set_rx_bytes(len: u16) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_data_cfg() + .modify(|_, w| unsafe { w.rx_bitlen().bits((len as u32) * 8) }); + } + + pub fn set_rx_sample_mode(sample_mode: SampleMode) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_mode_cfg() + .modify(|_, w| unsafe { w.rx_smp_mode_sel().bits(sample_mode as u8) }); + } + + pub fn set_eof_gen_sel(mode: EofMode) { + let reg_block = PARL_IO::regs(); + + reg_block.rx_genrl_cfg().modify(|_, w| { + w.rx_eof_gen_sel() + .bit(matches!(mode, EofMode::EnableSignal)) + }); + } + + pub fn set_rx_pulse_submode_sel(sel: u8) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_mode_cfg() + .modify(|_, w| unsafe { w.rx_pulse_submode_sel().bits(sel) }); + } + + pub fn set_rx_level_submode_sel(_sel: u8) { + // unsupported, always high + } + + pub fn set_rx_clk_edge_sel(value: SampleEdge) { + let reg_block = PARL_IO::regs(); + + reg_block.rx_clk_cfg().modify(|_, w| { + w.rx_clk_i_inv() + .bit(value == SampleEdge::Invert) + .rx_clk_o_inv() + .bit(value == SampleEdge::Invert) + }); + } + + pub fn set_rx_start(value: bool) { + let reg_block = PARL_IO::regs(); + + reg_block + .rx_start_cfg() + .modify(|_, w| w.rx_start().bit(value)); + } + + pub fn set_rx_reg_update() { + let reg_block = PARL_IO::regs(); + + reg_block + .reg_update() + .write(|w| w.rx_reg_update().bit(true)); + } + + pub fn set_rx_bit_order(value: BitPackOrder) { + let reg_block = PARL_IO::regs(); + reg_block + .rx_data_cfg() + .modify(|_, w| w.rx_data_order_inv().bit(value as u8 == 1)); + } + + pub fn set_rx_timeout_ticks(value: Option) { + let reg_block = PARL_IO::regs(); + reg_block.rx_genrl_cfg().modify(|_, w| unsafe { + w.rx_timeout_en() + .bit(value.is_some()) + .rx_timeout_thres() + .bits(value.unwrap_or(0xfff)) + }); + } + + pub fn listen_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_ena().modify(|_, w| w.tx_eof().set_bit()); + } + + pub fn unlisten_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit()); + } + + pub fn is_tx_done_set() -> bool { + let reg_block = PARL_IO::regs(); + + reg_block.int_raw().read().tx_eof().bit() + } + + pub fn clear_is_tx_done() { + let reg_block = PARL_IO::regs(); + + reg_block.int_clr().write(|w| w.tx_eof().clear_bit_by_one()); + } + } +} + +struct ParlIoTxGuard { + _guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>, +} + +impl ParlIoTxGuard { + pub fn new(_guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>) -> Self { + ClockTree::with(|clocks| { + crate::soc::clocks::configure_parlio_tx_clock(clocks, Default::default()); + crate::soc::clocks::request_parlio_tx_clock(clocks); + }); + Self { _guard } + } +} + +impl Drop for ParlIoTxGuard { + fn drop(&mut self) { + ClockTree::with(crate::soc::clocks::release_parlio_tx_clock); + } +} + +struct ParlIoRxGuard { + _guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>, +} + +impl ParlIoRxGuard { + pub fn new(_guard: GenericPeripheralGuard<{ Peripheral::ParlIo as u8 }>) -> Self { + ClockTree::with(|clocks| { + crate::soc::clocks::configure_parlio_rx_clock(clocks, Default::default()); + crate::soc::clocks::request_parlio_rx_clock(clocks); + }); + Self { _guard } + } +} + +impl Drop for ParlIoRxGuard { + fn drop(&mut self) { + ClockTree::with(crate::soc::clocks::release_parlio_rx_clock); + } +} diff --git a/esp-hal/src/pcnt/channel.rs b/esp-hal/src/pcnt/channel.rs new file mode 100644 index 00000000000..f86edc455a9 --- /dev/null +++ b/esp-hal/src/pcnt/channel.rs @@ -0,0 +1,188 @@ +//! # PCNT - Channel Configuration +//! +//! ## Overview +//! The `channel` module allows users to configure and manage individual +//! channels of the `PCNT` peripheral. It provides methods to set various +//! parameters for each channel, such as control modes for signal edges, action +//! on control level, and configurations for positive and negative edge count +//! modes. + +use core::marker::PhantomData; + +pub use crate::pac::pcnt::unit::conf0::{CTRL_MODE as CtrlMode, EDGE_MODE as EdgeMode}; +use crate::{ + gpio::{InputSignal, interconnect::PeripheralInput}, + peripherals::PCNT, + system::GenericPeripheralGuard, +}; + +/// Represents a channel within a pulse counter unit. +pub struct Channel<'d, const UNIT: usize, const NUM: usize> { + _phantom: PhantomData<&'d ()>, + // Individual channels are not Send, since they share registers. + _not_send: PhantomData<*const ()>, + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Pcnt as u8 }>, +} + +impl Channel<'_, UNIT, NUM> { + /// return a new Channel + pub(super) fn new() -> Self { + let guard = GenericPeripheralGuard::new(); + + Self { + _phantom: PhantomData, + _not_send: PhantomData, + _guard: guard, + } + } + + /// Configures how the channel behaves based on the level of the control + /// signal. + /// + /// * `low` - The behaviour of the channel when the control signal is low. + /// * `high` - The behaviour of the channel when the control signal is high. + pub fn set_ctrl_mode(&self, low: CtrlMode, high: CtrlMode) { + let pcnt = PCNT::regs(); + let conf0 = pcnt.unit(UNIT).conf0(); + + conf0.modify(|_, w| { + w.ch_hctrl_mode(NUM as u8).variant(high); + w.ch_lctrl_mode(NUM as u8).variant(low) + }); + } + + /// Configures how the channel affects the counter based on the transition + /// made by the input signal. + /// + /// * `neg_edge` - The effect on the counter when the input signal goes 1 -> 0. + /// * `pos_edge` - The effect on the counter when the input signal goes 0 -> 1. + pub fn set_input_mode(&self, neg_edge: EdgeMode, pos_edge: EdgeMode) { + let pcnt = PCNT::regs(); + let conf0 = pcnt.unit(UNIT).conf0(); + + conf0.modify(|_, w| { + w.ch_neg_mode(NUM as u8).variant(neg_edge); + w.ch_pos_mode(NUM as u8).variant(pos_edge) + }); + } + + /// Set the control signal (pin/high/low) for this channel + pub fn set_ctrl_signal<'d>(&self, source: impl PeripheralInput<'d>) -> &Self { + let signal = match UNIT { + 0 => match NUM { + 0 => InputSignal::PCNT0_CTRL_CH0, + 1 => InputSignal::PCNT0_CTRL_CH1, + _ => unreachable!(), + }, + 1 => match NUM { + 0 => InputSignal::PCNT1_CTRL_CH0, + 1 => InputSignal::PCNT1_CTRL_CH1, + _ => unreachable!(), + }, + 2 => match NUM { + 0 => InputSignal::PCNT2_CTRL_CH0, + 1 => InputSignal::PCNT2_CTRL_CH1, + _ => unreachable!(), + }, + 3 => match NUM { + 0 => InputSignal::PCNT3_CTRL_CH0, + 1 => InputSignal::PCNT3_CTRL_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 4 => match NUM { + 0 => InputSignal::PCNT4_CTRL_CH0, + 1 => InputSignal::PCNT4_CTRL_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 5 => match NUM { + 0 => InputSignal::PCNT5_CTRL_CH0, + 1 => InputSignal::PCNT5_CTRL_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 6 => match NUM { + 0 => InputSignal::PCNT6_CTRL_CH0, + 1 => InputSignal::PCNT6_CTRL_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 7 => match NUM { + 0 => InputSignal::PCNT7_CTRL_CH0, + 1 => InputSignal::PCNT7_CTRL_CH1, + _ => unreachable!(), + }, + _ => unreachable!(), + }; + + if signal as usize <= property!("gpio.input_signal_max") { + let source = source.into(); + source.set_input_enable(true); + signal.connect_to(&source); + } else { + warn!("Signal {:?} out of range", signal); + } + self + } + + /// Set the edge signal (pin/high/low) for this channel + pub fn set_edge_signal<'d>(&self, source: impl PeripheralInput<'d>) -> &Self { + let signal = match UNIT { + 0 => match NUM { + 0 => InputSignal::PCNT0_SIG_CH0, + 1 => InputSignal::PCNT0_SIG_CH1, + _ => unreachable!(), + }, + 1 => match NUM { + 0 => InputSignal::PCNT1_SIG_CH0, + 1 => InputSignal::PCNT1_SIG_CH1, + _ => unreachable!(), + }, + 2 => match NUM { + 0 => InputSignal::PCNT2_SIG_CH0, + 1 => InputSignal::PCNT2_SIG_CH1, + _ => unreachable!(), + }, + 3 => match NUM { + 0 => InputSignal::PCNT3_SIG_CH0, + 1 => InputSignal::PCNT3_SIG_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 4 => match NUM { + 0 => InputSignal::PCNT4_SIG_CH0, + 1 => InputSignal::PCNT4_SIG_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 5 => match NUM { + 0 => InputSignal::PCNT5_SIG_CH0, + 1 => InputSignal::PCNT5_SIG_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 6 => match NUM { + 0 => InputSignal::PCNT6_SIG_CH0, + 1 => InputSignal::PCNT6_SIG_CH1, + _ => unreachable!(), + }, + #[cfg(esp32)] + 7 => match NUM { + 0 => InputSignal::PCNT7_SIG_CH0, + 1 => InputSignal::PCNT7_SIG_CH1, + _ => unreachable!(), + }, + _ => unreachable!(), + }; + + if signal as usize <= property!("gpio.input_signal_max") { + let source = source.into(); + source.set_input_enable(true); + signal.connect_to(&source); + } else { + warn!("Signal {:?} out of range", signal); + } + self + } +} diff --git a/esp-hal/src/pcnt/mod.rs b/esp-hal/src/pcnt/mod.rs new file mode 100644 index 00000000000..e202c084963 --- /dev/null +++ b/esp-hal/src/pcnt/mod.rs @@ -0,0 +1,198 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Pulse Counter (PCNT) +//! +//! ## Overview +//! The PCNT module is designed to count the number of rising +//! and/or falling edges of input signals. They may contain multiple pulse +//! counter units in the module. Each unit is in effect an independent counter +//! with multiple channels, where each channel can increment/decrement the +//! counter on a rising/falling edge. Furthermore, each channel can be +//! configured separately. +//! +//! It consists of two main modules: +//! * [channel] +//! * [unit] +//! +//! ## Examples +//! ### Decoding a quadrature encoder +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::{Input, InputConfig, Pull}; +//! # use esp_hal::interrupt::Priority; +//! # use esp_hal::pcnt::{channel, unit, Pcnt}; +//! # use core::{sync::atomic::Ordering, cell::RefCell, cmp::min}; +//! # use critical_section::Mutex; +//! # use portable_atomic::AtomicI32; +//! +//! static UNIT0: Mutex>>> = Mutex::new(RefCell::new(None)); +//! static VALUE: AtomicI32 = AtomicI32::new(0); +//! +//! // Initialize Pulse Counter (PCNT) unit with limits and filter settings +//! let mut pcnt = Pcnt::new(peripherals.PCNT); +//! pcnt.set_interrupt_handler(interrupt_handler); +//! let u0 = pcnt.unit1; +//! u0.set_low_limit(Some(-100))?; +//! u0.set_high_limit(Some(100))?; +//! u0.set_filter(Some(min(10u16 * 80, 1023u16)))?; +//! u0.clear(); +//! +//! // Set up channels with control and edge signals +//! let ch0 = &u0.channel0; +//! let config = InputConfig::default().with_pull(Pull::Up); +//! let pin_a = Input::new(peripherals.GPIO4, config); +//! let pin_b = Input::new(peripherals.GPIO5, config); +//! let input_a = pin_a.peripheral_input(); +//! let input_b = pin_b.peripheral_input(); +//! ch0.set_ctrl_signal(input_a.clone()); +//! ch0.set_edge_signal(input_b.clone()); +//! ch0.set_ctrl_mode(channel::CtrlMode::Reverse, channel::CtrlMode::Keep); +//! ch0.set_input_mode(channel::EdgeMode::Increment, channel::EdgeMode::Decrement); +//! +//! let ch1 = &u0.channel1; +//! ch1.set_ctrl_signal(input_b); +//! ch1.set_edge_signal(input_a); +//! ch1.set_ctrl_mode(channel::CtrlMode::Reverse, channel::CtrlMode::Keep); +//! ch1.set_input_mode(channel::EdgeMode::Decrement, channel::EdgeMode::Increment); +//! +//! // Enable interrupts and resume pulse counter unit +//! u0.listen(); +//! u0.resume(); +//! let counter = u0.counter.clone(); +//! +//! critical_section::with(|cs| UNIT0.borrow_ref_mut(cs).replace(u0)); +//! +//! // Monitor counter value and print updates +//! let mut last_value: i32 = 0; +//! loop { +//! let value: i32 = counter.get() as i32 + VALUE.load(Ordering::SeqCst); +//! if value != last_value { +//! last_value = value; +//! } +//! } +//! +//! #[esp_hal::handler] +//! fn interrupt_handler() { +//! critical_section::with(|cs| { +//! let mut u0 = UNIT0.borrow_ref_mut(cs); +//! if let Some(u0) = u0.as_mut() { +//! if u0.interrupt_is_set() { +//! let events = u0.events(); +//! if events.high_limit { +//! VALUE.fetch_add(100, Ordering::SeqCst); +//! } else if events.low_limit { +//! VALUE.fetch_add(-100, Ordering::SeqCst); +//! } +//! u0.reset_interrupt(); +//! } +//! } +//! }); +//! } +//! # } +//! ``` +//! +//! [channel]: channel/index.html +//! [unit]: unit/index.html + +use self::unit::Unit; +use crate::{ + interrupt::{self, InterruptHandler}, + peripherals::{Interrupt, PCNT}, + system::GenericPeripheralGuard, +}; + +pub mod channel; +pub mod unit; + +/// Pulse Counter (PCNT) peripheral driver. +pub struct Pcnt<'d> { + _instance: PCNT<'d>, + + /// Unit 0 + pub unit0: Unit<'d, 0>, + /// Unit 1 + pub unit1: Unit<'d, 1>, + /// Unit 2 + pub unit2: Unit<'d, 2>, + /// Unit 3 + pub unit3: Unit<'d, 3>, + #[cfg(esp32)] + /// Unit 4 + pub unit4: Unit<'d, 4>, + #[cfg(esp32)] + /// Unit 5 + pub unit5: Unit<'d, 5>, + #[cfg(esp32)] + /// Unit 6 + pub unit6: Unit<'d, 6>, + #[cfg(esp32)] + /// Unit 7 + pub unit7: Unit<'d, 7>, + + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Pcnt as u8 }>, +} + +impl<'d> Pcnt<'d> { + /// Return a new PCNT + pub fn new(_instance: PCNT<'d>) -> Self { + let guard = GenericPeripheralGuard::new(); + let pcnt = PCNT::regs(); + + let unit_count = pcnt.unit_iter().count() as u8; + + // disable filter, all events, and channel settings + for unit in pcnt.unit_iter() { + unit.conf0().write(|w| unsafe { + // All bits are accounted for in the TRM. + w.bits(0) + }); + } + + // Remove reset bit from units. + pcnt.ctrl().modify(|_, w| { + for i in 0..unit_count { + w.cnt_rst_u(i).clear_bit(); + } + + w.clk_en().set_bit() + }); + + Pcnt { + _instance, + unit0: Unit::new(), + unit1: Unit::new(), + unit2: Unit::new(), + unit3: Unit::new(), + #[cfg(esp32)] + unit4: Unit::new(), + #[cfg(esp32)] + unit5: Unit::new(), + #[cfg(esp32)] + unit6: Unit::new(), + #[cfg(esp32)] + unit7: Unit::new(), + _guard: guard, + } + } + + /// Set the interrupt handler for the PCNT peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::PCNT); + } + interrupt::bind_handler(Interrupt::PCNT, handler); + } +} + +impl crate::private::Sealed for Pcnt<'_> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Pcnt<'_> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} diff --git a/esp-hal/src/pcnt/unit.rs b/esp-hal/src/pcnt/unit.rs new file mode 100644 index 00000000000..d495dd8be39 --- /dev/null +++ b/esp-hal/src/pcnt/unit.rs @@ -0,0 +1,342 @@ +//! # PCNT - Unit Module +//! +//! ## Overview +//! The `unit` module is responsible for configuring and handling individual +//! units of the `PCNT` peripheral. Each unit represents a separate instance of +//! the `PCNT` module, identified by unit numbers like `Unit0`, `Unit1`, and so +//! on. Users can interact with these units to configure settings such as low +//! and high limits, thresholds, and optional filtering. The unit module also +//! enables users to pause, resume, and clear the counter, as well as enable or +//! disable interrupts for specific events associated with the unit. + +use core::marker::PhantomData; + +use esp_sync::RawMutex; + +use crate::{pcnt::channel::Channel, peripherals::PCNT, system::GenericPeripheralGuard}; + +/// Invalid filter threshold value +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidFilterThreshold; + +/// Invalid low limit - must be < 0 +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidLowLimit; + +/// Invalid high limit - must be > 0 +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidHighLimit; + +/// the current status of the counter. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ZeroMode { + /// pulse counter decreases from positive to 0. + #[default] + PosZero = 0, + /// pulse counter increases from negative to 0 + NegZero = 1, + /// pulse counter is negative (not implemented?) + Negative = 2, + /// pulse counter is positive (not implemented?) + Positive = 3, +} + +impl From for ZeroMode { + fn from(value: u8) -> Self { + match value { + 0 => Self::PosZero, + 1 => Self::NegZero, + 2 => Self::Negative, + 3 => Self::Positive, + _ => unreachable!(), // TODO: is this good enough? should we use some default? + } + } +} + +/// Events that can occur in a pulse counter unit. +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Events { + /// Set when the pulse counter reaches the low limit. + pub low_limit: bool, + /// Set when the pulse counter reaches the high limit. + pub high_limit: bool, + /// Set when the pulse counter crosses threshold 0. + pub threshold0: bool, + /// Set when the pulse counter crosses threshold 1. + pub threshold1: bool, + /// Set when the pulse counter reaches zero. + pub zero: bool, +} + +/// Represents a pulse counter unit. +#[non_exhaustive] +pub struct Unit<'d, const NUM: usize> { + /// The counter for PCNT unit. + pub counter: Counter<'d, NUM>, + /// The first channel in PCNT unit. + pub channel0: Channel<'d, NUM, 0>, + /// The second channel in PCNT unit. + pub channel1: Channel<'d, NUM, 1>, +} + +static MUTEX: RawMutex = RawMutex::new(); + +impl Unit<'_, NUM> { + /// return a new Unit + pub(super) fn new() -> Self { + Self { + counter: Counter::new(), + channel0: Channel::new(), + channel1: Channel::new(), + } + } + + /// Configures a lower limit to the count value. + /// + /// When the count drops to this value: + /// - A low limit interrupt is triggered. + /// - The count is reset to 0. + /// + /// If None is specified, then no interrupt is triggered and + /// the count wraps around after [i16::MIN]. + /// + /// Note: The specified value must be negative. + pub fn set_low_limit(&self, value: Option) -> Result<(), InvalidLowLimit> { + let pcnt = PCNT::regs(); + let unit = pcnt.unit(NUM); + + if let Some(value) = value { + // low limit must be >= or the limit is -32768 and when that's + // hit the event status claims it was the high limit. + // tested on an esp32s3 + if !value.is_negative() { + return Err(InvalidLowLimit); + } else { + unit.conf2() + .modify(|_, w| unsafe { w.cnt_l_lim().bits(value as u16) }); + unit.conf0().modify(|_, w| w.thr_l_lim_en().set_bit()); + } + } else { + unit.conf0().modify(|_, w| w.thr_l_lim_en().clear_bit()); + } + Ok(()) + } + + /// Configures a high limit to the count value. + /// + /// When the count rises to this value: + /// - A high limit interrupt is triggered. + /// - The count is reset to 0. + /// + /// If None is specified, then no interrupt is triggered and + /// the count wraps around after [i16::MAX]. + /// + /// Note: The specified value must be positive. + pub fn set_high_limit(&self, value: Option) -> Result<(), InvalidHighLimit> { + let pcnt = PCNT::regs(); + let unit = pcnt.unit(NUM); + + if let Some(value) = value { + if !value.is_positive() { + return Err(InvalidHighLimit); + } else { + unit.conf2() + .modify(|_, w| unsafe { w.cnt_h_lim().bits(value as u16) }); + unit.conf0().modify(|_, w| w.thr_h_lim_en().set_bit()); + } + } else { + unit.conf0().modify(|_, w| w.thr_h_lim_en().clear_bit()); + } + Ok(()) + } + + /// Configures a threshold value to trigger an interrupt. + /// + /// When the count equals this value a threshold0 interrupt is triggered. + /// If None is specified, then no interrupt is triggered. + pub fn set_threshold0(&self, value: Option) { + let pcnt = PCNT::regs(); + let unit = pcnt.unit(NUM); + + if let Some(value) = value { + unit.conf1() + .modify(|_, w| unsafe { w.cnt_thres0().bits(value as u16) }); + unit.conf0().modify(|_, w| w.thr_thres0_en().set_bit()); + } else { + unit.conf0().modify(|_, w| w.thr_thres0_en().clear_bit()); + } + } + + /// Configures a threshold value to trigger an interrupt. + /// + /// When the count equals this value a threshold1 interrupt is triggered. + /// If None is specified, then no interrupt is triggered. + pub fn set_threshold1(&self, value: Option) { + let pcnt = PCNT::regs(); + let unit = pcnt.unit(NUM); + + if let Some(value) = value { + unit.conf1() + .modify(|_, w| unsafe { w.cnt_thres1().bits(value as u16) }); + unit.conf0().modify(|_, w| w.thr_thres1_en().set_bit()); + } else { + unit.conf0().modify(|_, w| w.thr_thres1_en().clear_bit()); + } + } + + /// Configures the glitch filter hardware of the unit. + /// + /// `threshold` is the minimum number of APB_CLK cycles for a pulse to be + /// considered valid. If it is None, the filter is disabled. + /// + /// Note: This maximum possible threshold is 1023. + pub fn set_filter(&self, threshold: Option) -> Result<(), InvalidFilterThreshold> { + let pcnt = PCNT::regs(); + let unit = pcnt.unit(NUM); + + match threshold { + None => { + unit.conf0().modify(|_, w| w.filter_en().clear_bit()); + } + Some(threshold) => { + if threshold > 1023 { + return Err(InvalidFilterThreshold); + } + unit.conf0().modify(|_, w| unsafe { + w.filter_thres().bits(threshold); + w.filter_en().set_bit() + }); + } + } + Ok(()) + } + + /// Resets the counter value to zero. + pub fn clear(&self) { + MUTEX.lock(|| { + let bits = PCNT::regs().ctrl().read().bits(); + PCNT::regs().ctrl().write(|w| { + unsafe { w.bits(bits) }; + w.cnt_rst_u(NUM as u8).set_bit() + }); + PCNT::regs().ctrl().write(|w| { + unsafe { w.bits(bits) }; + w.cnt_rst_u(NUM as u8).clear_bit() + }); + }); + } + + /// Pause the counter + pub fn pause(&self) { + MUTEX.lock(|| { + PCNT::regs() + .ctrl() + .modify(|_, w| w.cnt_pause_u(NUM as u8).set_bit()); + }); + } + + /// Resume the counter + pub fn resume(&self) { + MUTEX.lock(|| { + PCNT::regs() + .ctrl() + .modify(|_, w| w.cnt_pause_u(NUM as u8).clear_bit()); + }); + } + + /// Get the latest events for this unit. + pub fn events(&self) -> Events { + let status = PCNT::regs().u_status(NUM).read(); + + Events { + low_limit: status.l_lim().bit(), + high_limit: status.h_lim().bit(), + threshold0: status.thres0().bit(), + threshold1: status.thres1().bit(), + zero: status.zero().bit(), + } + } + + /// Get the mode of the last zero crossing + pub fn zero_mode(&self) -> ZeroMode { + PCNT::regs().u_status(NUM).read().zero_mode().bits().into() + } + + /// Enable interrupts for this unit. + pub fn listen(&self) { + MUTEX.lock(|| { + PCNT::regs() + .int_ena() + .modify(|_, w| w.cnt_thr_event_u(NUM as u8).set_bit()); + }); + } + + /// Disable interrupts for this unit. + pub fn unlisten(&self) { + MUTEX.lock(|| { + PCNT::regs() + .int_ena() + .modify(|_, w| w.cnt_thr_event_u(NUM as u8).clear_bit()); + }); + } + + /// Returns true if an interrupt is active for this unit. + pub fn interrupt_is_set(&self) -> bool { + PCNT::regs() + .int_raw() + .read() + .cnt_thr_event_u(NUM as u8) + .bit() + } + + /// Clear the interrupt bit for this unit. + pub fn reset_interrupt(&self) { + PCNT::regs() + .int_clr() + .write(|w| w.cnt_thr_event_u(NUM as u8).set_bit()); + } + + /// Get the current counter value. + pub fn value(&self) -> i16 { + self.counter.get() + } +} + +impl Drop for Unit<'_, NUM> { + fn drop(&mut self) { + // This is here to prevent the destructuring of Unit. + } +} + +// The entire Unit is Send but the individual channels are not. +unsafe impl Send for Unit<'_, NUM> {} + +/// Represents the counter within a pulse counter unit. +#[derive(Clone)] +pub struct Counter<'d, const NUM: usize> { + _phantom: PhantomData<&'d ()>, + + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Pcnt as u8 }>, +} + +impl Counter<'_, NUM> { + fn new() -> Self { + let guard = GenericPeripheralGuard::new(); + + Self { + _phantom: PhantomData, + _guard: guard, + } + } + + /// Get the current counter value. + pub fn get(&self) -> i16 { + let pcnt = PCNT::regs(); + pcnt.u_cnt(NUM).read().cnt().bits() as i16 + } +} diff --git a/esp-hal/src/peripherals/mod.rs b/esp-hal/src/peripherals/mod.rs new file mode 100644 index 00000000000..43cc807446c --- /dev/null +++ b/esp-hal/src/peripherals/mod.rs @@ -0,0 +1,278 @@ +//! # Peripheral Instances +//! +//! This module creates singleton instances for each of the various peripherals, +//! and re-exports them to allow users to access and use them in their +//! applications. +//! +//! Should be noted that that the module also re-exports the [Interrupt] enum +//! from the PAC, allowing users to handle interrupts associated with these +//! peripherals. + +// We need to export this for users to use +#[doc(hidden)] +pub use pac::Interrupt; + +pub(crate) use crate::soc::pac; + +#[cfg(esp32h2)] +#[path = "overlay_h2.rs"] +mod overlay; + +/// Macro to create a peripheral structure. +macro_rules! create_peripheral { + ($(#[$attr:meta])* $name:ident <= virtual ($($interrupt:ident: { $bind:ident, $enable:ident, $disable:ident }),*)) => { + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[non_exhaustive] + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + $(#[$attr])* + pub struct $name<'a> { + _marker: core::marker::PhantomData<&'a mut ()>, + } + + impl $name<'_> { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + Self { + _marker: core::marker::PhantomData, + } + } + + /// Unsafely clone this peripheral reference. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + #[allow(dead_code)] + pub unsafe fn clone_unchecked(&self) -> Self { + unsafe { Self::steal() } + } + + /// Creates a new peripheral reference with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripheral after + /// you dropped the driver that consumes this. + #[inline] + #[allow(dead_code)] + pub fn reborrow(&mut self) -> $name<'_> { + unsafe { self.clone_unchecked() } + } + + $( + /// Binds an interrupt handler to the corresponding interrupt for this peripheral, and enables the interrupt. + /// + ///

    + /// This function is a very low-level way to work with interrupts. Unless you're writing drivers, this is probably not the interrupt API you want to use. + ///
    + /// + #[instability::unstable] + pub fn $bind(&self, handler: $crate::interrupt::InterruptHandler) { + $crate::interrupt::bind_handler($crate::peripherals::Interrupt::$interrupt, handler); + } + + #[procmacros::doc_replace] + #[doc = concat!("Enables the ", stringify!($interrupt), " peripheral interrupt on the given priority level.")] + /// + ///
    + /// This function is a very low-level way to work with interrupts. Unless you're writing drivers, this is probably not the interrupt API you want to use. + ///
    + #[cfg_attr(multi_core, doc = "The interrupt handler will be enabled on the core that calls this function.")] + /// + /// Note that a suitable interrupt handler needs to be set up before the first interrupt + /// is triggered, otherwise the default handler will panic. + #[cfg_attr(not(feature = "unstable"), doc = "To set up an interrupt handler, create a function that has the same (non-mangled) name as the interrupt you want to handle.")] + #[cfg_attr(feature = "unstable", doc = concat!("To set up an interrupt handler, use [`Self::", stringify!($bind), "`] or create a function that has the same (non-mangled) name as the interrupt you want to handle."))] + /// + /// ## Examples + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::interrupt::Priority; + /// + /// #[unsafe(no_mangle)] + #[doc = concat!(r#"unsafe extern "C" fn "#, stringify!($interrupt), "() {")] + /// // do something + /// } + /// + #[doc = concat!("peripherals.", stringify!($name), ".", stringify!($enable), "(Priority::Priority1);")] + #[doc = concat!("peripherals.", stringify!($name), ".", stringify!($disable), "_on_all_cores();")] + /// # {after_snippet} + /// ``` + #[allow(dead_code, reason = "Peripheral may be unstable")] + pub fn $enable(&self, priority: $crate::interrupt::Priority) { + $crate::interrupt::enable($crate::peripherals::Interrupt::$interrupt, priority); + } + + paste::paste! { + #[procmacros::doc_replace] + #[doc = concat!("Disables the ", stringify!($interrupt), " peripheral interrupt handler on the current CPU core.")] + /// + ///
    + /// This function is a very low-level way to work with interrupts. Unless you're writing drivers, this is probably not the interrupt API you want to use. + ///
    + #[instability::unstable] + pub fn $disable(&self) { + $crate::interrupt::disable($crate::system::Cpu::current(), $crate::peripherals::Interrupt::$interrupt); + } + + #[procmacros::doc_replace] + #[doc = concat!("Disables the ", stringify!($interrupt), " peripheral interrupt handler on all cores.")] + /// + ///
    + /// This function is a very low-level way to work with interrupts. Unless you're writing drivers, this is probably not the interrupt API you want to use. + ///
    + #[allow(dead_code, reason = "Peripheral may be unstable")] + pub fn [<$disable _on_all_cores>](&self) { + for core in $crate::system::Cpu::all() { + $crate::interrupt::disable(core, $crate::peripherals::Interrupt::$interrupt); + } + } + } + )* + } + + impl $crate::private::Sealed for $name<'_> {} + }; + + ($(#[$attr:meta])* $name:ident <= $base:ident $interrupts:tt) => { + create_peripheral!($(#[$attr])* $name <= virtual $interrupts); + + impl $name<'_> { + #[doc = r"Pointer to the register block"] + #[instability::unstable] + pub const PTR: *const ::Target = pac::$base::PTR; + + #[doc = r"Return the pointer to the register block"] + #[inline(always)] + #[instability::unstable] + pub const fn ptr() -> *const ::Target { + pac::$base::PTR + } + + #[doc = r"Return a reference to the register block"] + #[inline(always)] + #[instability::unstable] + pub const fn regs<'a>() -> &'a ::Target { + unsafe { &*Self::PTR } + } + + #[doc = r"Return a reference to the register block"] + #[inline(always)] + #[instability::unstable] + pub fn register_block(&self) -> &::Target { + unsafe { &*Self::PTR } + } + } + }; +} + +for_each_peripheral! { + // Define stable peripheral singletons + (@peri_type $(#[$meta:meta])* $name:ident <= $from_pac:tt $interrupts:tt) => { + create_peripheral!( $(#[$meta])* $name <= $from_pac $interrupts); + }; + + // Define unstable peripheral singletons + (@peri_type $(#[$meta:meta])* $name:ident <= $from_pac:tt $interrupts:tt (unstable)) => { + create_peripheral!(#[instability::unstable] $(#[$meta])* $name <= $from_pac $interrupts); + }; + + // Define the Peripherals struct + (singletons $( ($name:ident $(($unstable:ident))?) ),*) => { + // We need a way to ignore the "unstable" marker, but macros can't generate attributes or struct fields. + // The solution is printing an empty doc comment. + macro_rules! ignore { ($any:tt) => {""} } + + /// The `Peripherals` struct provides access to all of the hardware peripherals on the chip. + #[allow(non_snake_case)] + #[non_exhaustive] + pub struct Peripherals { + $( + // This is a bit hairy, but non-macro attributes are not allowed on struct fields. We work + // around this by excluding code with the `$()?` optional macro syntax and an "unstable" marker + // in the source data. The marker itself is passed through the `ignore` macro so that it doesn't + // appear in the generated code. + // + // The code can end up looking two ways: + // + // - Without `unstable` we just generate the field: + // ``` + // #[attributes] + // pub PERI: PERI<'static>, + // ``` + // + // - With `unstable` we're basically emulating what `instability::unstable` would do: + // ``` + // #[attributes] + // #[cfg(feature = "unstable")] + // pub PERI: PERI<'static>, + // + // #[attributes] + // #[cfg(not(feature = "unstable"))] + // pub(crate) PERI: PERI<'static>, + // ``` + #[doc = concat!("The ", stringify!($name), " peripheral.")] + $( + #[doc = "**This API is marked as unstable** and is only available when the `unstable` + crate feature is enabled. This comes with no stability guarantees, and could be changed + or removed at any time."] + #[doc = ignore!($unstable)] + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + )? + pub $name: $name<'static>, + + $( + #[doc = concat!("The ", stringify!($unstable_name), " peripheral.")] + #[doc = "**This API is marked as unstable** and is only available when the `unstable` + crate feature is enabled. This comes with no stability guarantees, and could be changed + or removed at any time."] + #[doc = ignore!($unstable)] + #[cfg(not(feature = "unstable"))] + #[allow(unused)] + pub(crate) $name: $name<'static>, + )? + )* + } + + impl Peripherals { + /// Returns all the peripherals *once*. + #[inline] + #[cfg(feature = "rt")] + pub(crate) fn take() -> Self { + #[unsafe(no_mangle)] + static mut _ESP_HAL_DEVICE_PERIPHERALS: bool = false; + + crate::ESP_HAL_LOCK.lock(|| unsafe { + if _ESP_HAL_DEVICE_PERIPHERALS { + panic!("init called more than once!") + } + _ESP_HAL_DEVICE_PERIPHERALS = true; + Self::steal() + }) + } + + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + unsafe { + Self { + $( + $name: $name::steal(), + )* + } + } + } + } + }; +} diff --git a/esp-hal/src/peripherals/overlay_h2.rs b/esp-hal/src/peripherals/overlay_h2.rs new file mode 100644 index 00000000000..78ca18df191 --- /dev/null +++ b/esp-hal/src/peripherals/overlay_h2.rs @@ -0,0 +1,49 @@ +use core::ops::Deref; + +use super::*; + +// RNG must be marked `virtual` for this to work. +impl RNG<'_> { + /// Return a reference to the register block + #[inline(always)] + #[instability::unstable] + pub const fn regs<'a>() -> &'a RngRegisterBlock { + &RngRegisterBlock + } + + /// Return a reference to the register block + #[inline(always)] + #[instability::unstable] + pub fn register_block(&self) -> &RngRegisterBlock { + &RngRegisterBlock + } +} + +/// Register block overlay for the RNG peripheral +#[instability::unstable] +pub struct RngRegisterBlock; + +// This Deref makes it possible to use the rest of the registers. +impl Deref for RngRegisterBlock { + type Target = pac::rng::RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*pac::RNG::ptr() } + } +} + +impl RngRegisterBlock { + /// Random number data + #[instability::unstable] + pub fn data(&self) -> &pac::rng::DATA { + let ptr = unsafe { pac::RNG::steal().data() as *const pac::rng::DATA }; + if crate::soc::chip_revision_above(102) { + // On H2-ECO5+ the LPPERI peripherals contains an additional register inserted before + // the `rng_data` register. + // https://github.com/espressif/esp-idf/commit/4c5e1a03414a6d55be4b42ba071b30ad228414f6#diff-bc8f2eca37e32ee4ba21ac812e4934998e764132a400479c4d091eb6f7e2e444 + unsafe { &*ptr.add(1) } + } else { + unsafe { &*ptr } + } + } +} diff --git a/esp-hal/src/psram/esp32.rs b/esp-hal/src/psram/esp32.rs new file mode 100644 index 00000000000..aeec2eab962 --- /dev/null +++ b/esp-hal/src/psram/esp32.rs @@ -0,0 +1,1205 @@ +use super::PsramSize; + +const EXTMEM_ORIGIN: usize = 0x3F800000; + +/// Cache Speed +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +pub enum PsramCacheSpeed { + #[default] + PsramCacheF80mS40m = 0, + PsramCacheF40mS40m, + PsramCacheF80mS80m, +} + +/// PSRAM virtual address mode. +/// +/// Specifies how PSRAM is mapped for the CPU cores. +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PsramVaddrMode { + /// App and pro CPU use their own flash cache for external RAM access. + /// + /// In this mode both cores access the same physical PSRAM. + #[default] + Normal = 0, + /// App and pro CPU share external RAM caches: pro CPU has low * 2M, app + /// CPU has high 2M. + /// + /// In this mode the two cores will access different parts of PSRAM. + LowHigh, + /// App and pro CPU share external RAM caches: pro CPU does even 32 byte + /// ranges, app does odd ones. + /// + /// In this mode the two cores will access different parts of PSRAM. + Evenodd, +} + +/// PSRAM configuration +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PsramConfig { + /// PSRAM size + pub size: PsramSize, + /// Cache speed + pub cache_speed: PsramCacheSpeed, + /// PSRAM virtual address mode + pub psram_vaddr_mode: PsramVaddrMode, +} + +/// Initializes the PSRAM memory on supported devices. +/// +/// Returns the start of the mapped memory and the size +pub(crate) fn init_psram(config: PsramConfig) { + let mut config = config; + + utils::psram_init(&config); + + if config.size.is_auto() { + const MAX_MEM_SIZE: usize = 4 * 1024 * 1024; + + // Reading the device-id turned out to not work as expected (some bits flipped + // for unknown reason) + // + // As a workaround we just map 4m (maximum we can do) and + // probe how much memory we can write/read + utils::s_mapping(EXTMEM_ORIGIN as u32, MAX_MEM_SIZE as u32); + + let guessed_size = unsafe { + let ptr = EXTMEM_ORIGIN as *mut u8; + for i in (1023..MAX_MEM_SIZE).step_by(1024) { + ptr.add(i).write_volatile(0x7f); + } + + let mut last_correctly_read = 0; + for i in (1023..MAX_MEM_SIZE).step_by(1024) { + if ptr.add(i).read_volatile() == 0x7f { + last_correctly_read = i; + } else { + break; + } + } + + last_correctly_read + 1 + }; + + info!("Assuming {} bytes of PSRAM", guessed_size); + config.size = PsramSize::Size(guessed_size); + } else { + utils::s_mapping(EXTMEM_ORIGIN as u32, config.size.get() as u32); + } + + unsafe { + super::MAPPED_PSRAM.memory_range = EXTMEM_ORIGIN..EXTMEM_ORIGIN + config.size.get(); + } +} + +pub(crate) mod utils { + use core::ptr::addr_of_mut; + + use procmacros::ram; + + use super::*; + use crate::peripherals::{DPORT, SPI0, SPI1}; + + #[ram] + pub(crate) fn s_mapping(v_start: u32, size: u32) { + // Enable external RAM in MMU + cache_sram_mmu_set(0, 0, v_start, 0, 32, size / 1024 / 32); + // Flush and enable icache for APP CPU + DPORT::regs() + .app_cache_ctrl1() + .modify(|_, w| w.app_cache_mask_dram1().clear_bit()); + + cache_sram_mmu_set(1, 0, v_start, 0, 32, size / 1024 / 32); + } + + // we can use the ROM version of this: it works well enough and keeps the size + // of the binary down. + #[ram] + fn cache_sram_mmu_set( + cpu_no: u32, + pid: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + ) -> i32 { + unsafe { cache_sram_mmu_set_rom(cpu_no, pid, vaddr, paddr, psize, num) } + } + + // PSRAM clock and cs IO should be configured based on hardware design. + // For ESP32-WROVER or ESP32-WROVER-B module, the clock IO is IO17, the cs IO is + // IO16, they are the default value for these two configs. + const D0WD_PSRAM_CLK_IO: u8 = 17; + const D0WD_PSRAM_CS_IO: u8 = 16; + + const D2WD_PSRAM_CLK_IO: u8 = 9; // Default value is 9 + const D2WD_PSRAM_CS_IO: u8 = 10; // Default value is 10 + + // For ESP32-PICO chip, the psram share clock with flash. The flash clock pin is + // fixed, which is IO6. + const PICO_PSRAM_CLK_IO: u8 = 6; + const PICO_PSRAM_CS_IO: u8 = 10; // Default value is 10 + + const PICO_V3_02_PSRAM_CLK_IO: u8 = 10; + const PICO_V3_02_PSRAM_CS_IO: u8 = 9; + const PICO_V3_02_PSRAM_SPIWP_SD3_IO: u8 = 18; + + const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; + const ESP_ROM_EFUSE_FLASH_DEFAULT_HSPI: u32 = 1; + + const SPI_IOMUX_PIN_NUM_CLK: u8 = 6; + const SPI_IOMUX_PIN_NUM_CS: u8 = 11; + + // IO-pins for PSRAM. + // WARNING: PSRAM shares all but the CS and CLK pins with the flash, so these + // defines hardcode the flash pins as well, making this code incompatible + // with either a setup that has the flash on non-standard pins or ESP32s + // with built-in flash. + const PSRAM_SPIQ_SD0_IO: u8 = 7; + const PSRAM_SPID_SD1_IO: u8 = 8; + const PSRAM_SPIWP_SD3_IO: u8 = 10; + const PSRAM_SPIHD_SD2_IO: u8 = 9; + + const FLASH_HSPI_CLK_IO: u8 = 14; + const FLASH_HSPI_CS_IO: u8 = 15; + + const PSRAM_HSPI_SPIQ_SD0_IO: u8 = 12; + const PSRAM_HSPI_SPID_SD1_IO: u8 = 13; + const PSRAM_HSPI_SPIWP_SD3_IO: u8 = 2; + const PSRAM_HSPI_SPIHD_SD2_IO: u8 = 4; + + const DR_REG_SPI1_BASE: u32 = 0x3ff42000; + const SPI1_USER_REG: u32 = DR_REG_SPI1_BASE + 0x1C; + const SPI1_W0_REG: u32 = DR_REG_SPI1_BASE + 0x80; + + const fn psram_cs_hold_time_from_psram_speed(speed: PsramCacheSpeed) -> u32 { + match speed { + PsramCacheSpeed::PsramCacheF80mS40m => 0, + PsramCacheSpeed::PsramCacheF40mS40m => 0, + PsramCacheSpeed::PsramCacheF80mS80m => 1, + } + } + + const PSRAM_INTERNAL_IO_28: u32 = 28; + const PSRAM_INTERNAL_IO_29: u32 = 29; + const SIG_GPIO_OUT_IDX: u32 = 256; + const SPICLK_OUT_IDX: u32 = 0; + const SIG_IN_FUNC224_IDX: u32 = 224; + const SIG_IN_FUNC225_IDX: u32 = 225; + const SPICS0_OUT_IDX: u32 = 5; + const SPICS1_OUT_IDX: u32 = 6; + const SPIQ_OUT_IDX: u32 = 1; + const SPIQ_IN_IDX: u32 = 1; + const SPID_OUT_IDX: u32 = 2; + const SPID_IN_IDX: u32 = 2; + const SPIWP_OUT_IDX: u32 = 4; + const SPIWP_IN_IDX: u32 = 4; + const SPIHD_OUT_IDX: u32 = 3; + const SPIHD_IN_IDX: u32 = 3; + const FUNC_SD_CLK_SPICLK: u32 = 1; + const PIN_FUNC_GPIO: u32 = 2; + + const PSRAM_QUAD_WRITE: u32 = 0x38; + const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5; + const PSRAM_FAST_READ_QUAD: u32 = 0xEB; + + const SPI_FWRITE_DUAL_S: u32 = 12; + const SPI_FWRITE_DUAL_M: u32 = 1 << 12; + + const SPI_FREAD_QIO_M: u32 = 1 << 24; + const SPI0_R_QIO_DUMMY_CYCLELEN: u32 = 3; + const SPI_FREAD_DIO_M: u32 = 1 << 23; + const SPI0_R_DIO_DUMMY_CYCLELEN: u32 = 1; + const SPI0_R_DIO_ADDR_BITSLEN: u32 = 27; + const SPI_FREAD_QUAD_M: u32 = 1 << 20; + const SPI_FREAD_DUAL_M: u32 = 1 << 14; + const SPI0_R_FAST_DUMMY_CYCLELEN: u32 = 7; + const PSRAM_IO_MATRIX_DUMMY_40M: u8 = 1; + const PSRAM_IO_MATRIX_DUMMY_80M: u8 = 2; + + const _SPI_CACHE_PORT: u8 = 0; + const _SPI_FLASH_PORT: u8 = 1; + const _SPI_80M_CLK_DIV: u8 = 1; + const _SPI_40M_CLK_DIV: u8 = 2; + + const FLASH_ID_GD25LQ32C: u32 = 0xC86016; + + const EFUSE_SPICONFIG_RET_SPICLK_MASK: u32 = 0x3f; + const EFUSE_SPICONFIG_RET_SPICLK_SHIFT: u8 = 0; + const EFUSE_SPICONFIG_RET_SPIQ_MASK: u32 = 0x3f; + const EFUSE_SPICONFIG_RET_SPIQ_SHIFT: u8 = 6; + const EFUSE_SPICONFIG_RET_SPID_MASK: u32 = 0x3f; + const EFUSE_SPICONFIG_RET_SPID_SHIFT: u8 = 12; + const EFUSE_SPICONFIG_RET_SPICS0_MASK: u32 = 0x3f; + const EFUSE_SPICONFIG_RET_SPICS0_SHIFT: u8 = 18; + const EFUSE_SPICONFIG_RET_SPIHD_MASK: u32 = 0x3f; + const EFUSE_SPICONFIG_RET_SPIHD_SHIFT: u8 = 24; + + fn efuse_spiconfig_ret(spi_config: u32, mask: u32, shift: u8) -> u8 { + (((spi_config) >> shift) & mask) as u8 + } + + #[derive(PartialEq, Eq, Debug, Default)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct PsramIo { + flash_clk_io: u8, + flash_cs_io: u8, + psram_clk_io: u8, + psram_cs_io: u8, + psram_spiq_sd0_io: u8, + psram_spid_sd1_io: u8, + psram_spiwp_sd3_io: u8, + psram_spihd_sd2_io: u8, + } + + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + enum PsramClkMode { + PsramClkModeNorm = 0, // Normal SPI mode + PsramClkModeDclk = 1, // Two extra clock cycles after CS is set high level + } + + #[repr(C)] + pub(super) struct EspRomSpiflashChip { + pub device_id: u32, + pub chip_size: u32, // chip size in bytes + pub block_size: u32, + pub sector_size: u32, + pub page_size: u32, + pub status_mask: u32, + } + + unsafe extern "C" { + fn esp_rom_efuse_get_flash_gpio_info() -> u32; + + fn esp_rom_gpio_connect_out_signal( + gpio_num: u32, + signal_idx: u32, + out_inv: bool, + oen_inv: bool, + ); + + fn esp_rom_gpio_connect_in_signal(gpio_num: u32, signal_idx: u32, inv: bool); + + fn esp_rom_spiflash_config_clk(freqdiv: u8, spi: u8) -> i32; + + static mut g_rom_spiflash_dummy_len_plus: u8; + + pub(super) static g_rom_flashchip: EspRomSpiflashChip; + + fn cache_sram_mmu_set_rom( + cpu_no: u32, + pid: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + ) -> i32; + } + + #[ram] + pub(crate) fn psram_init(config: &PsramConfig) { + let chip = crate::efuse::chip_type(); + + let mode = config.cache_speed; + let mut psram_io = PsramIo::default(); + let clk_mode; + + match chip { + crate::efuse::ChipType::Esp32D0wdq6 | crate::efuse::ChipType::Esp32D0wdq5 => { + clk_mode = PsramClkMode::PsramClkModeNorm; + psram_io.psram_clk_io = D0WD_PSRAM_CLK_IO; + psram_io.psram_cs_io = D0WD_PSRAM_CS_IO; + } + crate::efuse::ChipType::Esp32D2wdq5 => { + clk_mode = PsramClkMode::PsramClkModeDclk; + psram_io.psram_clk_io = D2WD_PSRAM_CLK_IO; + psram_io.psram_cs_io = D2WD_PSRAM_CS_IO; + } + crate::efuse::ChipType::Esp32Picod2 => { + clk_mode = PsramClkMode::PsramClkModeNorm; + psram_io.psram_clk_io = PICO_PSRAM_CLK_IO; + psram_io.psram_cs_io = PICO_PSRAM_CS_IO; + } + crate::efuse::ChipType::Esp32Picod4 => { + panic!("PSRAM is unsupported on this chip"); + } + crate::efuse::ChipType::Esp32Picov302 => { + clk_mode = PsramClkMode::PsramClkModeNorm; + psram_io.psram_clk_io = PICO_V3_02_PSRAM_CLK_IO; + psram_io.psram_cs_io = PICO_V3_02_PSRAM_CS_IO; + } + crate::efuse::ChipType::Unknown => { + panic!("Unknown chip type. PSRAM is not supported"); + } + } + + let spiconfig = unsafe { esp_rom_efuse_get_flash_gpio_info() }; + if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI { + psram_io.flash_clk_io = SPI_IOMUX_PIN_NUM_CLK; + psram_io.flash_cs_io = SPI_IOMUX_PIN_NUM_CS; + psram_io.psram_spiq_sd0_io = PSRAM_SPIQ_SD0_IO; + psram_io.psram_spid_sd1_io = PSRAM_SPID_SD1_IO; + psram_io.psram_spiwp_sd3_io = PSRAM_SPIWP_SD3_IO; + psram_io.psram_spihd_sd2_io = PSRAM_SPIHD_SD2_IO; + } else if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_HSPI { + psram_io.flash_clk_io = FLASH_HSPI_CLK_IO; + psram_io.flash_cs_io = FLASH_HSPI_CS_IO; + psram_io.psram_spiq_sd0_io = PSRAM_HSPI_SPIQ_SD0_IO; + psram_io.psram_spid_sd1_io = PSRAM_HSPI_SPID_SD1_IO; + psram_io.psram_spiwp_sd3_io = PSRAM_HSPI_SPIWP_SD3_IO; + psram_io.psram_spihd_sd2_io = PSRAM_HSPI_SPIHD_SD2_IO; + } else if chip == crate::efuse::ChipType::Esp32Picov302 { + psram_io.flash_clk_io = efuse_spiconfig_ret( + spiconfig, + EFUSE_SPICONFIG_RET_SPICLK_MASK, + EFUSE_SPICONFIG_RET_SPICLK_SHIFT, + ); + psram_io.flash_cs_io = efuse_spiconfig_ret( + spiconfig, + EFUSE_SPICONFIG_RET_SPICS0_MASK, + EFUSE_SPICONFIG_RET_SPICS0_SHIFT, + ); + psram_io.psram_spiq_sd0_io = efuse_spiconfig_ret( + spiconfig, + EFUSE_SPICONFIG_RET_SPIQ_MASK, + EFUSE_SPICONFIG_RET_SPIQ_SHIFT, + ); + psram_io.psram_spid_sd1_io = efuse_spiconfig_ret( + spiconfig, + EFUSE_SPICONFIG_RET_SPID_MASK, + EFUSE_SPICONFIG_RET_SPID_SHIFT, + ); + psram_io.psram_spihd_sd2_io = efuse_spiconfig_ret( + spiconfig, + EFUSE_SPICONFIG_RET_SPIHD_MASK, + EFUSE_SPICONFIG_RET_SPIHD_SHIFT, + ); + psram_io.psram_spiwp_sd3_io = PICO_V3_02_PSRAM_SPIWP_SD3_IO; + } else { + panic!("Getting Flash/PSRAM pins from efuse is not supported"); + // psram_io.flash_clk_io = EFUSE_SPICONFIG_RET_SPICLK(spiconfig); + // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig); + // psram_io.psram_spiq_sd0_io = EFUSE_SPICONFIG_RET_SPIQ(spiconfig); + // psram_io.psram_spid_sd1_io = EFUSE_SPICONFIG_RET_SPID(spiconfig); + // psram_io.psram_spihd_sd2_io = + // EFUSE_SPICONFIG_RET_SPIHD(spiconfig); + // psram_io.psram_spiwp_sd3_io = bootloader_flash_get_wp_pin(); + } + info!("PS-RAM pins {:?}", &psram_io); + + unsafe { + SPI0::regs().ext3().modify(|_, w| w.bits(0x1)); + SPI1::regs() + .user() + .modify(|_, w| w.usr_prep_hold().clear_bit()); + } + + psram_spi_init(mode, clk_mode); + + match mode { + PsramCacheSpeed::PsramCacheF80mS80m => unsafe { + esp_rom_gpio_connect_out_signal( + psram_io.psram_clk_io as u32, + SPICLK_OUT_IDX, + false, + false, + ); + }, + _ => unsafe { + if clk_mode == PsramClkMode::PsramClkModeDclk { + // We need to delay CLK to the PSRAM with respect to the clock signal as output + // by the SPI peripheral. We do this by routing it signal to + // signal 224/225, which are used as a loopback; the extra run through + // the GPIO matrix causes the delay. We use GPIO20 (which is not in any package + // but has pad logic in silicon) as a temporary pad for + // this. So the signal path is: SPI CLK --> GPIO28 --> + // signal224(in then out) --> internal GPIO29 --> signal225(in then out) --> + // GPIO17(PSRAM CLK) + esp_rom_gpio_connect_out_signal( + PSRAM_INTERNAL_IO_28, + SPICLK_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(PSRAM_INTERNAL_IO_28, SIG_IN_FUNC224_IDX, false); + esp_rom_gpio_connect_out_signal( + PSRAM_INTERNAL_IO_29, + SIG_IN_FUNC224_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(PSRAM_INTERNAL_IO_29, SIG_IN_FUNC225_IDX, false); + esp_rom_gpio_connect_out_signal( + psram_io.psram_clk_io as u32, + SIG_IN_FUNC225_IDX, + false, + false, + ); + } else { + esp_rom_gpio_connect_out_signal( + psram_io.psram_clk_io as u32, + SPICLK_OUT_IDX, + false, + false, + ); + } + }, + } + + let extra_dummy = psram_gpio_config(&psram_io, mode); + info!("extra dummy = {}", extra_dummy); + + // psram_is_32mbit_ver0 would need special handling here + + unsafe { + esp_rom_gpio_connect_out_signal(PSRAM_INTERNAL_IO_28, SIG_GPIO_OUT_IDX, false, false); + esp_rom_gpio_connect_out_signal(PSRAM_INTERNAL_IO_29, SIG_GPIO_OUT_IDX, false, false); + esp_rom_gpio_connect_out_signal( + psram_io.psram_clk_io as u32, + SPICLK_OUT_IDX, + false, + false, + ); + } + + // Update cs timing according to psram driving method. + psram_set_cs_timing_spi1(mode, clk_mode); + psram_set_cs_timing_spi0(mode, clk_mode); // SPI_CACHE_PORT + psram_enable_qio_mode_spi1(clk_mode, mode); + + let psram_vaddr_mode = config.psram_vaddr_mode; + info!("PS-RAM vaddrmode = {:?}", psram_vaddr_mode); + + psram_cache_init(mode, psram_vaddr_mode, clk_mode, extra_dummy); + } + + // register initialization for sram cache params and r/w commands + fn psram_cache_init( + psram_cache_mode: PsramCacheSpeed, + vaddrmode: PsramVaddrMode, + clk_mode: PsramClkMode, + extra_dummy: u32, + ) { + unsafe { + let spi = SPI0::regs(); + info!( + "PS-RAM cache_init, psram_cache_mode={:?}, extra_dummy={}, clk_mode={:?}", + psram_cache_mode, extra_dummy, clk_mode + ); + match psram_cache_mode { + PsramCacheSpeed::PsramCacheF80mS80m => { + // flash 1 div clk,80+40; + // There's no register on bit 31. No information about it in IDF, nor TRM, + // so just doing it in this way. + spi.date().modify(|r, w| { + let current_bits = r.bits(); + let new_bits = current_bits & !((1 << 31) | (1 << 30)); + w.bits(new_bits) + }); + // pre clk div , ONLY IF SPI/ SRAM@ DIFFERENT SPEED,JUST + // FOR SPI0. FLASH DIV + // 2+SRAM DIV4 + } + PsramCacheSpeed::PsramCacheF80mS40m => { + spi.clock().modify(|_, w| w.clk_equ_sysclk().clear_bit()); + spi.clock().modify(|_, w| w.clkdiv_pre().bits(0)); + spi.clock().modify(|_, w| w.clkcnt_n().bits(1)); + spi.clock().modify(|_, w| w.clkcnt_h().bits(0)); + spi.clock().modify(|_, w| w.clkcnt_l().bits(1)); + + spi.date().modify(|r, w| { + let current_bits = r.bits(); + let new_bits = (current_bits | (1 << 31)) & !(1 << 30); + w.bits(new_bits) + }); + } + _ => { + spi.date().modify(|r, w| { + let current_bits = r.bits(); + let new_bits = current_bits & !((1 << 31) | (1 << 30)); + w.bits(new_bits) + }); + } + } + + spi.cache_sctrl() + .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command + spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command + spi.cache_sctrl() + .modify(|_, w| w.sram_addr_bitlen().bits(23)); // write address for cache command. + spi.cache_sctrl() + .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy + + // config sram cache r/w command + spi.sram_drd_cmd() + .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_value() + .bits(PSRAM_FAST_READ_QUAD as u16) + }); + spi.sram_dwr_cmd() + .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); + spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_value() + .bits(PSRAM_QUAD_WRITE as u16) + }); + + // dummy, psram cache : 40m--+1dummy; 80m--+2dummy + spi.cache_sctrl().modify(|_, w| { + w.sram_dummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) + }); + + match psram_cache_mode { + PsramCacheSpeed::PsramCacheF80mS80m => (), // in this mode , no delay is needed + _ => { + if clk_mode == PsramClkMode::PsramClkModeDclk { + spi.sram_drd_cmd() + .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(15)); // read command length, 2 bytes(1byte for delay),sending in qio mode in + // cache + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_value() + .bits((PSRAM_FAST_READ_QUAD << 8) as u16) + }); // read command value,(0x00 for delay,0xeb for cmd) + + spi.sram_dwr_cmd() + .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(15)); // write command length,2 bytes(1byte for delay,send in qio mode in cache) + spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_value() + .bits((PSRAM_QUAD_WRITE << 8) as u16) + }); // write command value,(0x00 for delay) + spi.cache_sctrl().modify(|_, w| { + w.sram_dummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) + }); // dummy, psram cache : 40m--+1dummy; 80m--+2dummy + } + } + } + + let dport = DPORT::regs(); + + dport + .pro_cache_ctrl() + .modify(|_, w| w.pro_dram_hl().clear_bit().pro_dram_split().clear_bit()); + dport + .app_cache_ctrl() + .modify(|_, w| w.app_dram_hl().clear_bit().app_dram_split().clear_bit()); + if vaddrmode == PsramVaddrMode::LowHigh { + dport + .pro_cache_ctrl() + .modify(|_, w| w.pro_dram_hl().set_bit()); + dport + .app_cache_ctrl() + .modify(|_, w| w.app_dram_hl().set_bit()); + } else if vaddrmode == PsramVaddrMode::Evenodd { + dport + .pro_cache_ctrl() + .modify(|_, w| w.pro_dram_split().set_bit()); + dport + .app_cache_ctrl() + .modify(|_, w| w.app_dram_split().set_bit()); + } + + // use Dram1 to visit ext sram. cache page mode : 1 -->16k 4 -->2k + // 0-->32k,(accord with the settings in cache_sram_mmu_set) + dport.pro_cache_ctrl1().modify(|_, w| { + w.pro_cache_mask_dram1() + .clear_bit() + .pro_cache_mask_opsdram() + .clear_bit() + }); + dport + .pro_cache_ctrl1() + .modify(|_, w| w.pro_cmmu_sram_page_mode().bits(0)); + + // use Dram1 to visit ext sram. cache page mode : 1 -->16k 4 -->2k + // 0-->32k,(accord with the settings in cache_sram_mmu_set) + dport.app_cache_ctrl1().modify(|_, w| { + w.app_cache_mask_dram1() + .clear_bit() + .app_cache_mask_opsdram() + .clear_bit() + }); + dport + .app_cache_ctrl1() + .modify(|_, w| w.app_cmmu_sram_page_mode().bits(0)); + + // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) + spi.pin().modify(|_, w| w.cs1_dis().clear_bit()); + } + } + + // spi param init for psram + #[ram] + fn psram_spi_init( + // psram_spi_num_t spi_num = PSRAM_SPI_1, + mode: PsramCacheSpeed, + clk_mode: PsramClkMode, + ) { + unsafe { + let spi = SPI1::regs(); + // We need to clear last bit of INT_EN field here. + spi.slave().modify(|_, w| w.trans_inten().clear_bit()); + // SPI_CPOL & SPI_CPHA + spi.pin().modify(|_, w| w.ck_idle_edge().clear_bit()); + spi.user().modify(|_, w| w.ck_out_edge().clear_bit()); + // SPI bit order + spi.ctrl().modify(|_, w| w.wr_bit_order().clear_bit()); + spi.ctrl().modify(|_, w| w.rd_bit_order().clear_bit()); + // SPI bit order + spi.user().modify(|_, w| w.doutdin().clear_bit()); + // May be not must to do. + spi.user1().modify(|_, w| w.bits(0)); + // SPI mode type + spi.slave().modify(|_, w| w.mode().clear_bit()); + + let ptr = SPI1_W0_REG as *mut u32; + for i in 0..16 { + ptr.offset(i).write_volatile(0); + } + + psram_set_cs_timing_spi1(mode, clk_mode); + } + } + + fn psram_set_cs_timing_spi1(psram_cache_mode: PsramCacheSpeed, clk_mode: PsramClkMode) { + unsafe { + let spi = SPI1::regs(); + if clk_mode == PsramClkMode::PsramClkModeNorm { + spi.user().modify(|_, w| w.cs_hold().set_bit()); + spi.user().modify(|_, w| w.cs_setup().set_bit()); + + spi.ctrl2().modify(|_, w| { + w.hold_time() + .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8) + }); + + // Set cs time. + spi.ctrl2().modify(|_, w| w.setup_time().bits(0)); + } else { + spi.user().modify(|_, w| w.cs_hold().clear_bit()); + spi.user().modify(|_, w| w.cs_setup().clear_bit()); + } + } + } + + fn psram_set_cs_timing_spi0(psram_cache_mode: PsramCacheSpeed, clk_mode: PsramClkMode) { + unsafe { + let spi = SPI0::regs(); + if clk_mode == PsramClkMode::PsramClkModeNorm { + spi.user().modify(|_, w| w.cs_hold().set_bit()); + spi.user().modify(|_, w| w.cs_setup().set_bit()); + + spi.ctrl2().modify(|_, w| { + w.hold_time() + .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8) + }); + + // Set cs time. + spi.ctrl2().modify(|_, w| w.setup_time().bits(0)); + } else { + spi.user().modify(|_, w| w.cs_hold().clear_bit()); + spi.user().modify(|_, w| w.cs_setup().clear_bit()); + } + } + } + + #[derive(Default, Debug, Copy, Clone, PartialEq)] + struct PsramCmd { + cmd: u16, // Command value + cmd_bit_len: u16, // Command byte length + addr: u32, // Address value + addr_bit_len: u16, // Address byte length + tx_data: *const u32, // Point to send data buffer + tx_data_bit_len: u16, // Send data byte length. + rx_data: *mut u32, // Point to recevie data buffer + rx_data_bit_len: u16, // Recevie Data byte length. + dummy_bit_len: u32, + } + + const PSRAM_ENTER_QMODE: u32 = 0x35; + + // enter QPI mode + #[ram] + fn psram_enable_qio_mode_spi1(clk_mode: PsramClkMode, psram_mode: PsramCacheSpeed) { + let mut ps_cmd: PsramCmd = PsramCmd::default(); + let addr: u32 = PSRAM_ENTER_QMODE << 24; + + ps_cmd.cmd_bit_len = 0; + if clk_mode == PsramClkMode::PsramClkModeDclk { + match psram_mode { + PsramCacheSpeed::PsramCacheF80mS80m => (), + _ => { + ps_cmd.cmd_bit_len = 2; + } + } + } + ps_cmd.cmd = 0; + ps_cmd.addr = addr; + ps_cmd.addr_bit_len = 8; + ps_cmd.tx_data = core::ptr::null(); + ps_cmd.tx_data_bit_len = 0; + ps_cmd.rx_data = core::ptr::null_mut(); + ps_cmd.rx_data_bit_len = 0; + ps_cmd.dummy_bit_len = 0; + let (backup_usr, backup_usr1, backup_usr2) = psram_cmd_config_spi1(&ps_cmd); + psram_cmd_recv_start_spi1(core::ptr::null_mut(), 0, PsramCmdMode::PsramCmdQpi); + psram_cmd_end_spi1(backup_usr, backup_usr1, backup_usr2); + } + + #[ram] + fn psram_cmd_end_spi1(backup_usr: u32, backup_usr1: u32, backup_usr2: u32) { + unsafe { + let spi = SPI1::regs(); + while spi.cmd().read().usr().bit_is_set() {} + + spi.user().write(|w| w.bits(backup_usr)); + spi.user1().write(|w| w.bits(backup_usr1)); + spi.user2().write(|w| w.bits(backup_usr2)); + } + } + + // setup spi command/addr/data/dummy in user mode + #[ram] + fn psram_cmd_config_spi1(p_in_data: &PsramCmd) -> (u32, u32, u32) { + unsafe { + let spi = SPI1::regs(); + while spi.cmd().read().usr().bit_is_set() {} + + let backup_usr = spi.user().read().bits(); + let backup_usr1 = spi.user1().read().bits(); + let backup_usr2 = spi.user2().read().bits(); + + // Set command by user. + if p_in_data.cmd_bit_len != 0 { + // Max command length 16 bits. + spi.user2().modify(|_, w| { + w.usr_command_bitlen() + .bits((p_in_data.cmd_bit_len - 1) as u8) + }); + // Enable command + spi.user().modify(|_, w| w.usr_command().set_bit()); + // Load command,bit15-0 is cmd value. + spi.user2() + .modify(|_, w| w.usr_command_value().bits(p_in_data.cmd)); + } else { + spi.user().modify(|_, w| w.usr_command().clear_bit()); + spi.user2().modify(|_, w| w.usr_command_bitlen().bits(0)); + } + // Set Address by user. + if p_in_data.addr_bit_len != 0 { + spi.user1() + .modify(|_, w| w.usr_addr_bitlen().bits((p_in_data.addr_bit_len - 1) as u8)); + // Enable address + spi.user().modify(|_, w| w.usr_addr().set_bit()); + // Set address + spi.addr().modify(|_, w| w.bits(p_in_data.addr)); + } else { + spi.user().modify(|_, w| w.usr_addr().clear_bit()); + spi.user1().modify(|_, w| w.usr_addr_bitlen().bits(0)); + } + // Set data by user. + let p_tx_val = p_in_data.tx_data; + if p_in_data.tx_data_bit_len != 0 { + // Enable MOSI + spi.user().modify(|_, w| w.usr_mosi().set_bit()); + // Load send buffer + let len = p_in_data.tx_data_bit_len.div_ceil(32); + if !p_tx_val.is_null() { + for i in 0..len { + spi.w(i as usize) + .write(|w| w.bits(p_tx_val.offset(i as isize).read_volatile())); + } + } + // Set data send buffer length.Max data length 64 bytes. + spi.mosi_dlen().modify(|_, w| { + w.usr_mosi_dbitlen() + .bits((p_in_data.tx_data_bit_len - 1) as u32) + }); + } else { + spi.user().modify(|_, w| w.usr_mosi().clear_bit()); + spi.mosi_dlen().modify(|_, w| w.usr_mosi_dbitlen().bits(0)); + } + // Set rx data by user. + if p_in_data.rx_data_bit_len != 0 { + // Enable MISO + spi.user().modify(|_, w| w.usr_miso().set_bit()); + // Set data send buffer length.Max data length 64 bytes. + spi.miso_dlen().modify(|_, w| { + w.usr_miso_dbitlen() + .bits((p_in_data.rx_data_bit_len - 1) as u32) + }); + } else { + spi.user().modify(|_, w| w.usr_miso().clear_bit()); + spi.miso_dlen().modify(|_, w| w.usr_miso_dbitlen().bits(0)); + } + if p_in_data.dummy_bit_len != 0 { + spi.user().modify(|_, w| w.usr_dummy().set_bit()); // dummy en + spi.user1().modify(|_, w| { + w.usr_dummy_cyclelen() + .bits((p_in_data.dummy_bit_len - 1) as u8) + }); // DUMMY + } else { + spi.user().modify(|_, w| w.usr_dummy().clear_bit()); // dummy dis + spi.user1().modify(|_, w| w.usr_dummy_cyclelen().bits(0)); // DUMMY + } + + (backup_usr, backup_usr1, backup_usr2) + } + } + + #[derive(Debug, Clone, Copy, PartialEq)] + enum PsramCmdMode { + PsramCmdQpi, + PsramCmdSpi, + } + + // start sending cmd/addr and optionally, receiving data + #[ram] + fn psram_cmd_recv_start_spi1( + p_rx_data: *mut u32, + rx_data_len_words: usize, + cmd_mode: PsramCmdMode, + ) { + unsafe { + let spi = SPI1::regs(); + // get cs1 + spi.pin().modify(|_, w| w.cs1_dis().clear_bit()); + spi.pin().modify(|_, w| w.cs0_dis().set_bit()); + + let mode_backup: u32 = (spi.user().read().bits() >> SPI_FWRITE_DUAL_S) & 0xf; + let rd_mode_backup: u32 = spi.ctrl().read().bits() + & (SPI_FREAD_DIO_M | SPI_FREAD_DUAL_M | SPI_FREAD_QUAD_M | SPI_FREAD_QIO_M); + + if cmd_mode == PsramCmdMode::PsramCmdSpi { + psram_set_basic_write_mode_spi1(); + psram_set_basic_read_mode_spi1(); + } else if cmd_mode == PsramCmdMode::PsramCmdQpi { + psram_set_qio_write_mode_spi1(); + psram_set_qio_read_mode_spi1(); + } + + // Wait for SPI0 to idle + while SPI1::regs().ext2().read().bits() != 0 {} + + // DPORT_SET_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); + DPORT::regs() + .host_inf_sel() + .modify(|r, w| w.bits(r.bits() | (1 << 14))); + + // Start send data + spi.cmd().modify(|_, w| w.usr().set_bit()); + while spi.cmd().read().usr().bit_is_set() {} + + // DPORT_CLEAR_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); + DPORT::regs() + .host_inf_sel() + .modify(|r, w| w.bits(r.bits() & !(1 << 14))); + + // recover spi mode + // TODO: get back to this, why writing on `0xf` address? + set_peri_reg_bits( + SPI1_USER_REG, + if !p_rx_data.is_null() { + SPI_FWRITE_DUAL_M + } else { + 0xf + }, + mode_backup, + SPI_FWRITE_DUAL_S, + ); + + spi.ctrl().modify(|_, w| { + w.fread_dio().clear_bit(); + w.fread_dual().clear_bit(); + w.fread_quad().clear_bit(); + w.fread_qio().clear_bit() + }); + spi.ctrl().modify(|r, w| w.bits(r.bits() | rd_mode_backup)); + + // return cs to cs0 + spi.pin().modify(|_, w| w.cs1_dis().set_bit()); + spi.pin().modify(|_, w| w.cs0_dis().clear_bit()); + + if !p_rx_data.is_null() { + // Read data out + for i in 0..rx_data_len_words { + p_rx_data.add(i).write_volatile(spi.w(i).read().bits()); + } + } + } + } + + // set basic SPI write mode + fn psram_set_basic_write_mode_spi1() { + SPI1::regs().user().modify(|_, w| { + w.fwrite_qio().clear_bit(); + w.fwrite_dio().clear_bit(); + w.fwrite_quad().clear_bit(); + w.fwrite_dual().clear_bit() + }); + } + + // set QPI write mode + fn psram_set_qio_write_mode_spi1() { + SPI1::regs().user().modify(|_, w| { + w.fwrite_qio().set_bit(); + w.fwrite_dio().clear_bit(); + w.fwrite_quad().clear_bit(); + w.fwrite_dual().clear_bit() + }); + } + + // set QPI read mode + fn psram_set_qio_read_mode_spi1() { + SPI1::regs().ctrl().modify(|_, w| { + w.fread_qio().set_bit(); + w.fread_quad().clear_bit(); + w.fread_dual().clear_bit(); + w.fread_dio().clear_bit() + }); + } + + // set SPI read mode + fn psram_set_basic_read_mode_spi1() { + SPI1::regs().ctrl().modify(|_, w| { + w.fread_qio().clear_bit(); + w.fread_quad().clear_bit(); + w.fread_dual().clear_bit(); + w.fread_dio().clear_bit() + }); + } + + // psram gpio init , different working frequency we have different solutions + fn psram_gpio_config(psram_io: &PsramIo, mode: PsramCacheSpeed) -> u32 { + unsafe { + let spi = SPI0::regs(); + let g_rom_spiflash_dummy_len_plus_ptr = addr_of_mut!(g_rom_spiflash_dummy_len_plus); + + #[derive(Debug, Clone, Copy)] + enum Field { + McuSel, + FunDrv, + } + + macro_rules! apply_to_field { + ($w:ident, $field:expr, $bits:expr) => { + match $field { + Field::McuSel => $w.mcu_sel().bits($bits), + Field::FunDrv => $w.fun_drv().bits($bits), + } + }; + } + + fn configure_gpio(gpio: u8, field: Field, bits: u8) { + unsafe { + let ptr = crate::gpio::io_mux_reg(gpio); + + ptr.modify(|_, w| apply_to_field!(w, field, bits)); + } + } + + let spi_cache_dummy; + let rd_mode_reg = spi.ctrl().read().bits(); + if (rd_mode_reg & SPI_FREAD_QIO_M) != 0 { + spi_cache_dummy = SPI0_R_QIO_DUMMY_CYCLELEN; + } else if (rd_mode_reg & SPI_FREAD_DIO_M) != 0 { + spi_cache_dummy = SPI0_R_DIO_DUMMY_CYCLELEN; + spi.user1() + .modify(|_, w| w.usr_addr_bitlen().bits(SPI0_R_DIO_ADDR_BITSLEN as u8)); + } else { + spi_cache_dummy = SPI0_R_FAST_DUMMY_CYCLELEN; + } + + let extra_dummy; + + match mode { + PsramCacheSpeed::PsramCacheF80mS40m => { + extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M; + + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_CACHE_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_80M); + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_FLASH_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_40M); + + spi.user1().modify(|_, w| { + w.usr_dummy_cyclelen() + .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_80M) + }); // DUMMY + + esp_rom_spiflash_config_clk(_SPI_80M_CLK_DIV, _SPI_CACHE_PORT); + esp_rom_spiflash_config_clk(_SPI_40M_CLK_DIV, _SPI_FLASH_PORT); + + // set drive ability for clock + + configure_gpio(psram_io.flash_clk_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_clk_io, Field::FunDrv, 2); + } + PsramCacheSpeed::PsramCacheF80mS80m => { + extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M; + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_CACHE_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_80M); + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_FLASH_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_80M); + + spi.user1().modify(|_, w| { + w.usr_dummy_cyclelen() + .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_80M) + }); // DUMMY + + esp_rom_spiflash_config_clk(_SPI_80M_CLK_DIV, _SPI_CACHE_PORT); + esp_rom_spiflash_config_clk(_SPI_80M_CLK_DIV, _SPI_FLASH_PORT); + + // set drive ability for clock + configure_gpio(psram_io.flash_clk_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_clk_io, Field::FunDrv, 3); + } + PsramCacheSpeed::PsramCacheF40mS40m => { + extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M; + + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_CACHE_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_40M); + g_rom_spiflash_dummy_len_plus_ptr + .offset(_SPI_FLASH_PORT as isize) + .write_volatile(PSRAM_IO_MATRIX_DUMMY_40M); + + spi.user1().modify(|_, w| { + w.usr_dummy_cyclelen() + .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_40M) + }); // DUMMY + + esp_rom_spiflash_config_clk(_SPI_40M_CLK_DIV, _SPI_CACHE_PORT); + esp_rom_spiflash_config_clk(_SPI_40M_CLK_DIV, _SPI_FLASH_PORT); + + // set drive ability for clock + configure_gpio(psram_io.flash_clk_io, Field::FunDrv, 2); + configure_gpio(psram_io.psram_clk_io, Field::FunDrv, 2); + } + } + + spi.user().modify(|_, w| w.usr_dummy().set_bit()); // dummy enable + + // In bootloader, all the signals are already configured, + // We keep the following code in case the bootloader is some older version. + + esp_rom_gpio_connect_out_signal( + psram_io.flash_cs_io as u32, + SPICS0_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_out_signal( + psram_io.psram_cs_io as u32, + SPICS1_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_out_signal( + psram_io.psram_spiq_sd0_io as u32, + SPIQ_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(psram_io.psram_spiq_sd0_io as u32, SPIQ_IN_IDX, false); + esp_rom_gpio_connect_out_signal( + psram_io.psram_spid_sd1_io as u32, + SPID_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(psram_io.psram_spid_sd1_io as u32, SPID_IN_IDX, false); + esp_rom_gpio_connect_out_signal( + psram_io.psram_spiwp_sd3_io as u32, + SPIWP_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(psram_io.psram_spiwp_sd3_io as u32, SPIWP_IN_IDX, false); + esp_rom_gpio_connect_out_signal( + psram_io.psram_spihd_sd2_io as u32, + SPIHD_OUT_IDX, + false, + false, + ); + esp_rom_gpio_connect_in_signal(psram_io.psram_spihd_sd2_io as u32, SPIHD_IN_IDX, false); + + // select pin function gpio + if (psram_io.flash_clk_io == SPI_IOMUX_PIN_NUM_CLK) + && (psram_io.flash_clk_io != psram_io.psram_clk_io) + { + // flash clock signal should come from IO MUX. + configure_gpio( + psram_io.flash_clk_io, + Field::McuSel, + FUNC_SD_CLK_SPICLK as u8, + ); + } else { + // flash clock signal should come from GPIO matrix. + configure_gpio(psram_io.flash_clk_io, Field::McuSel, PIN_FUNC_GPIO as u8); + } + configure_gpio(psram_io.flash_cs_io, Field::McuSel, PIN_FUNC_GPIO as u8); + configure_gpio(psram_io.psram_cs_io, Field::McuSel, PIN_FUNC_GPIO as u8); + configure_gpio(psram_io.psram_clk_io, Field::McuSel, PIN_FUNC_GPIO as u8); + configure_gpio( + psram_io.psram_spiq_sd0_io, + Field::McuSel, + PIN_FUNC_GPIO as u8, + ); + configure_gpio( + psram_io.psram_spid_sd1_io, + Field::McuSel, + PIN_FUNC_GPIO as u8, + ); + configure_gpio( + psram_io.psram_spihd_sd2_io, + Field::McuSel, + PIN_FUNC_GPIO as u8, + ); + configure_gpio( + psram_io.psram_spiwp_sd3_io, + Field::McuSel, + PIN_FUNC_GPIO as u8, + ); + + let flash_id: u32 = g_rom_flashchip.device_id; + info!("Flash-ID = {}", flash_id); + info!("Flash size = {}", g_rom_flashchip.chip_size); + + if flash_id == FLASH_ID_GD25LQ32C { + // Set drive ability for 1.8v flash in 80Mhz. + configure_gpio(psram_io.flash_cs_io, Field::FunDrv, 3); + configure_gpio(psram_io.flash_clk_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_cs_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_clk_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_spiq_sd0_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_spid_sd1_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_spihd_sd2_io, Field::FunDrv, 3); + configure_gpio(psram_io.psram_spiwp_sd3_io, Field::FunDrv, 3); + } + + extra_dummy as u32 + } + } + + fn set_peri_reg_bits(reg: u32, bitmap: u32, value: u32, shift: u32) { + unsafe { + (reg as *mut u32).write_volatile( + ((reg as *mut u32).read_volatile() & !(bitmap << shift)) + | ((value & bitmap) << shift), + ); + } + } +} diff --git a/esp-hal/src/psram/esp32s2.rs b/esp-hal/src/psram/esp32s2.rs new file mode 100644 index 00000000000..423eae757b7 --- /dev/null +++ b/esp-hal/src/psram/esp32s2.rs @@ -0,0 +1,616 @@ +use super::PsramSize; +use crate::peripherals::{EXTMEM, SPI0, SPI1}; + +const EXTMEM_ORIGIN: usize = 0x3f500000; + +// Cache Speed +#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +pub enum PsramCacheSpeed { + PsramCacheS80m = 1, + PsramCacheS40m, + PsramCacheS26m, + PsramCacheS20m, + #[default] + PsramCacheMax, +} + +/// PSRAM configuration +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PsramConfig { + /// PSRAM size + pub size: PsramSize, + /// Cache Speed + pub speed: PsramCacheSpeed, +} + +/// Initialize PSRAM to be used for data. +/// +/// Returns the start of the mapped memory and the size +#[procmacros::ram] +pub(crate) fn init_psram(config: PsramConfig) { + let mut config = config; + utils::psram_init(&mut config); + + #[allow(unused)] + enum CacheLayout { + Invalid = 0, + ICacheLow = 1 << 0, + ICacheHigh = 1 << 1, + DCacheLow = 1 << 2, + DCacheHigh = 1 << 3, + } + + const MMU_ACCESS_SPIRAM: u32 = 1 << 16; + + const CACHE_SIZE_8KB: u32 = 0; + const CACHE_4WAYS_ASSOC: u32 = 0; + const CACHE_LINE_SIZE_16B: u32 = 0; + + unsafe extern "C" { + /// Allocate memory to used by ICache and DCache. + /// + /// [`sram0_layout`]: u32 the usage of first 8KB internal memory block, + /// can be CACHE_MEMORY_INVALID, + /// CACHE_MEMORY_ICACHE_LOW, + /// CACHE_MEMORY_ICACHE_HIGH, CACHE_MEMORY_DCACHE_LOW and + /// CACHE_MEMORY_DCACHE_HIGH + /// [`sram1_layout`]: the usage of second 8KB internal memory block, + /// [`sram2_layout`]: the usage of third 8KB internal memory block + /// [`sram3_layout`]: the usage of forth 8KB internal memory block + fn Cache_Allocate_SRAM( + sram0_layout: u32, + sram1_layout: u32, + sram2_layout: u32, + sram3_layout: u32, + ); + + /// Set DCache mmu mapping. + /// + /// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid. + /// [`vaddr`]: u32 Virtual address in CPU address space. + /// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize. + /// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here. + /// [`num`]: u32 Pages to be set. + /// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page. + fn cache_dbus_mmu_set( + ext_ram: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + fixed: u32, + ) -> i32; + + /// Set DCache modes: cache size, associate ways and cache line size. + /// + /// [`cache_size`]: u32 the cache size, can be CACHE_SIZE_HALF and CACHE_SIZE_FULL + /// [`ways`]: u32 the associate ways of cache, can only be CACHE_4WAYS_ASSOC + /// [`cache_line_size`]: u32 the cache line size, can be CACHE_LINE_SIZE_16B, CACHE_LINE_SIZE_32B + fn Cache_Set_DCache_Mode(cache_size: u32, ways: u32, cache_line_size: u32); + + /// Invalidate all cache items in DCache. + fn Cache_Invalidate_DCache_All(); + } + + unsafe { + Cache_Allocate_SRAM( + CacheLayout::ICacheLow as u32, + CacheLayout::DCacheLow as u32, + CacheLayout::Invalid as u32, + CacheLayout::Invalid as u32, + ); + Cache_Set_DCache_Mode(CACHE_SIZE_8KB, CACHE_4WAYS_ASSOC, CACHE_LINE_SIZE_16B); + Cache_Invalidate_DCache_All(); + + const START_PAGE: u32 = 0; + + if cache_dbus_mmu_set( + MMU_ACCESS_SPIRAM, + EXTMEM_ORIGIN as u32, + START_PAGE << 16, + 64, + config.size.get() as u32 / 1024 / 64, // number of pages to map + 0, + ) != 0 + { + panic!("cache_dbus_mmu_set failed"); + } + + EXTMEM::regs().pro_dcache_ctrl1().modify(|_, w| { + w.pro_dcache_mask_bus0().clear_bit(); + w.pro_dcache_mask_bus1().clear_bit(); + w.pro_dcache_mask_bus2().clear_bit() + }); + } + + unsafe { + super::MAPPED_PSRAM.memory_range = EXTMEM_ORIGIN..EXTMEM_ORIGIN + config.size.get(); + } +} + +pub(crate) mod utils { + use super::*; + + const PSRAM_RESET_EN: u16 = 0x66; + const PSRAM_RESET: u16 = 0x99; + const PSRAM_DEVICE_ID: u16 = 0x9F; + const CS_PSRAM_SEL: u8 = 1 << 1; + + /// PS-RAM addressing mode + #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[allow(unused)] + pub enum PsramVaddrMode { + /// App and pro CPU use their own flash cache for external RAM access + #[default] + Normal = 0, + /// App and pro CPU share external RAM caches: pro CPU has low2M, app + /// CPU has high 2M + Lowhigh, + /// App and pro CPU share external RAM caches: pro CPU does even 32yte + /// ranges, app does odd ones. + Evenodd, + } + + // Function initializes the PSRAM by configuring GPIO pins, resetting the PSRAM, + // and enabling Quad I/O (QIO) mode. It also calls the psram_cache_init + // function to configure cache parameters and read/write commands. + pub(crate) fn psram_init(config: &mut PsramConfig) { + psram_gpio_config(); + + if config.size.is_auto() { + psram_disable_qio_mode(); + + // read chip id + let mut dev_id = 0u32; + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_DEVICE_ID, + 8, // command and command bit len + 0, + 24, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + &mut dev_id as *mut _ as *mut u8, + 24, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); + info!("chip id = {:x}", dev_id); + + let size = if dev_id != 0xffffff { + const PSRAM_ID_EID_S: u32 = 16; + const PSRAM_ID_EID_M: u32 = 0xff; + const PSRAM_EID_SIZE_M: u32 = 0x07; + const PSRAM_EID_SIZE_S: u32 = 5; + + let size_id = (((dev_id >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S) + & PSRAM_EID_SIZE_M; + + const PSRAM_EID_SIZE_32MBITS: u32 = 1; + const PSRAM_EID_SIZE_64MBITS: u32 = 2; + + match size_id { + PSRAM_EID_SIZE_64MBITS => 8 * 1024 * 1024, + PSRAM_EID_SIZE_32MBITS => 4 * 1024 * 1024, + _ => 2 * 1024 * 1024, + } + } else { + 0 + }; + + info!("size is {}", size); + + config.size = PsramSize::Size(size); + } + + psram_reset_mode(); + psram_enable_qio_mode(); + + psram_cache_init(config.speed, PsramVaddrMode::Normal); + } + + // send reset command to psram, in spi mode + fn psram_reset_mode() { + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET_EN, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); // whether is program/erase operation + + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); // whether is program/erase operation + } + + /// Enter QPI mode + fn psram_enable_qio_mode() { + const PSRAM_ENTER_QMODE: u16 = 0x35; + const CS_PSRAM_SEL: u8 = 1 << 1; + + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_ENTER_QMODE, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, // whether is program/erase operation + ); + } + + /// Exit QPI mode + fn psram_disable_qio_mode() { + const PSRAM_EXIT_QMODE: u16 = 0xF5; + const CS_PSRAM_SEL: u8 = 1 << 1; + + psram_exec_cmd( + CommandMode::PsramCmdQpi, + PSRAM_EXIT_QMODE, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, // whether is program/erase operation + ); + } + + #[derive(PartialEq)] + #[allow(unused)] + enum CommandMode { + PsramCmdQpi = 0, + PsramCmdSpi = 1, + } + + #[expect(clippy::too_many_arguments)] + #[inline(always)] + fn psram_exec_cmd( + mode: CommandMode, + cmd: u16, + cmd_bit_len: u16, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + cs_mask: u8, + is_write_erase_operation: bool, + ) { + unsafe extern "C" { + /// Start a spi user command sequence + /// [`spi_num`] spi port + /// [`rx_buf`] buffer pointer to receive data + /// [`rx_len`] receive data length in byte + /// [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1 + /// [`is_write_erase`] to indicate whether this is a write or erase + /// operation, since the CPU would check permission + fn esp_rom_spi_cmd_start( + spi_num: u32, + rx_buf: *const u8, + rx_len: u16, + cs_en_mask: u8, + is_write_erase: bool, + ); + } + + unsafe { + let spi1 = SPI1::regs(); + let backup_usr = spi1.user().read().bits(); + let backup_usr1 = spi1.user1().read().bits(); + let backup_usr2 = spi1.user2().read().bits(); + let backup_ctrl = spi1.ctrl().read().bits(); + psram_set_op_mode(mode); + _psram_exec_cmd( + cmd, + cmd_bit_len, + &addr, + addr_bit_len, + dummy_bits, + mosi_data, + mosi_bit_len, + miso_data, + miso_bit_len, + ); + esp_rom_spi_cmd_start( + 1, + miso_data, + (miso_bit_len / 8) as u16, + cs_mask, + is_write_erase_operation, + ); + + spi1.user().write(|w| w.bits(backup_usr)); + spi1.user1().write(|w| w.bits(backup_usr1)); + spi1.user2().write(|w| w.bits(backup_usr2)); + spi1.ctrl().write(|w| w.bits(backup_ctrl)); + } + } + + #[expect(clippy::too_many_arguments)] + #[inline(always)] + fn _psram_exec_cmd( + cmd: u16, + cmd_bit_len: u16, + addr: *const u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + ) { + #[repr(C)] + #[allow(non_camel_case_types)] + struct esp_rom_spi_cmd_t { + cmd: u16, // Command value + cmd_bit_len: u16, // Command byte length + addr: *const u32, // Point to address value + addr_bit_len: u32, // Address byte length + tx_data: *const u32, // Point to send data buffer + tx_data_bit_len: u32, // Send data byte length. + rx_data: *mut u32, // Point to recevie data buffer + rx_data_bit_len: u32, // Recevie Data byte length. + dummy_bit_len: u32, + } + + unsafe extern "C" { + /// Config the spi user command + /// [`spi_num`] spi port + /// [`pcmd`] pointer to accept the spi command struct + fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t); + } + + let conf = esp_rom_spi_cmd_t { + cmd, + cmd_bit_len, + addr, + addr_bit_len, + tx_data: mosi_data as *const u32, + tx_data_bit_len: mosi_bit_len, + rx_data: miso_data as *mut u32, + rx_data_bit_len: miso_bit_len, + dummy_bit_len: dummy_bits, + }; + + unsafe { + esp_rom_spi_cmd_config(1, &conf); + } + } + + fn psram_set_op_mode(mode: CommandMode) { + unsafe extern "C" { + fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); + } + + const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; + const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; + + unsafe { + match mode { + CommandMode::PsramCmdQpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); + SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit()); + } + CommandMode::PsramCmdSpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); + } + } + } + } + + #[repr(C)] + struct PsRamIo { + flash_clk_io: u8, + flash_cs_io: u8, + psram_clk_io: u8, + psram_cs_io: u8, + psram_spiq_sd0_io: u8, + psram_spid_sd1_io: u8, + psram_spiwp_sd3_io: u8, + psram_spihd_sd2_io: u8, + } + + fn psram_gpio_config() { + unsafe extern "C" { + fn esp_rom_efuse_get_flash_gpio_info() -> u32; + + /// Enable Quad I/O pin functions + /// + /// Sets the HD & WP pin functions for Quad I/O modes, based on the + /// efuse SPI pin configuration. + /// + /// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O + /// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig(). + /// - If this parameter is 0, default SPI pins are used and wp_gpio_num parameter is + /// ignored. + /// - If this parameter is 1, default HSPI pins are used and wp_gpio_num parameter is + /// ignored. + /// - For other values, this parameter encodes the HD pin number and also the CLK pin + /// number. CLK pin selection is used to determine if HSPI or SPI peripheral will be + /// used (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI). + // Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral. + fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32); + } + + let psram_io = PsRamIo { + flash_clk_io: 30, // SPI_CLK_GPIO_NUM + flash_cs_io: 29, // SPI_CS0_GPIO_NUM + psram_clk_io: 30, + psram_cs_io: 26, // SPI_CS1_GPIO_NUM + psram_spiq_sd0_io: 31, // SPI_Q_GPIO_NUM + psram_spid_sd1_io: 32, // SPI_D_GPIO_NUM + psram_spiwp_sd3_io: 28, // SPI_WP_GPIO_NUM + psram_spihd_sd2_io: 27, // SPI_HD_GPIO_NUM + }; + + const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; + + unsafe { + let spiconfig = esp_rom_efuse_get_flash_gpio_info(); + if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI { + // FLASH pins(except wp / hd) are all configured via IO_MUX in + // rom. + } else { + // this case is currently not yet supported + panic!( + "Unsupported for now! The case 'FLASH pins are all configured via GPIO matrix in ROM.' is not yet supported." + ); + + // FLASH pins are all configured via GPIO matrix in ROM. + // psram_io.flash_clk_io = + // EFUSE_SPICONFIG_RET_SPICLK(spiconfig); + // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig); + // psram_io.psram_spiq_sd0_io = + // EFUSE_SPICONFIG_RET_SPIQ(spiconfig); + // psram_io.psram_spid_sd1_io = + // EFUSE_SPICONFIG_RET_SPID(spiconfig); + // psram_io.psram_spihd_sd2_io = + // EFUSE_SPICONFIG_RET_SPIHD(spiconfig); + // psram_io.psram_spiwp_sd3_io = + // esp_rom_efuse_get_flash_wp_gpio(); + } + esp_rom_spiflash_select_qio_pins(psram_io.psram_spiwp_sd3_io, spiconfig); + // s_psram_cs_io = psram_io.psram_cs_io; + } + } + + const PSRAM_IO_MATRIX_DUMMY_20M: u32 = 0; + const PSRAM_IO_MATRIX_DUMMY_40M: u32 = 0; + const PSRAM_IO_MATRIX_DUMMY_80M: u32 = 0; + + /// Register initialization for sram cache params and r/w commands + fn psram_cache_init(psram_cache_mode: PsramCacheSpeed, _vaddrmode: PsramVaddrMode) { + let mut extra_dummy = 0; + match psram_cache_mode { + PsramCacheSpeed::PsramCacheS80m => { + psram_clock_set(1); + extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M; + } + PsramCacheSpeed::PsramCacheS40m => { + psram_clock_set(2); + extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M; + } + PsramCacheSpeed::PsramCacheS26m => { + psram_clock_set(3); + extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; + } + PsramCacheSpeed::PsramCacheS20m => { + psram_clock_set(4); + extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; + } + _ => { + psram_clock_set(2); + } + } + + const PSRAM_QUAD_WRITE: u32 = 0x38; + const PSRAM_FAST_READ_QUAD: u32 = 0xEB; + const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5; + + unsafe { + let spi = SPI0::regs(); + + spi.cache_sctrl() + .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command + + spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command + + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command + + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command + + // write address for cache command. + spi.cache_sctrl() + .modify(|_, w| w.sram_addr_bitlen().bits(23)); + + spi.cache_sctrl() + .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy + + // config sram cache r/w command + spi.sram_dwr_cmd() + .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); + + spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_value() + .bits(PSRAM_QUAD_WRITE as u16) + }); + + spi.sram_drd_cmd() + .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); + + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_value() + .bits(PSRAM_FAST_READ_QUAD as u16) + }); + + // dummy, psram cache : 40m--+1dummy,80m--+2dummy + spi.cache_sctrl().modify(|_, w| { + w.sram_rdummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) + }); + + // ESP-IDF has some code here to deal with `!CONFIG_FREERTOS_UNICORE` - not + // needed for ESP32-S2 + + // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) + spi.misc().modify(|_, w| w.cs1_dis().clear_bit()); + } + } + + fn psram_clock_set(freqdiv: i8) { + const SPI_MEM_SCLKCNT_N_S: u32 = 16; + const SPI_MEM_SCLKCNT_H_S: u32 = 8; + const SPI_MEM_SCLKCNT_L_S: u32 = 0; + + if 1 >= freqdiv { + SPI0::regs() + .sram_clk() + .modify(|_, w| w.sclk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_N_S) + | (((freqdiv / 2 - 1) as u32) << SPI_MEM_SCLKCNT_H_S) + | (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_L_S); + unsafe { + SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); + } + } + } +} diff --git a/esp-hal/src/psram/esp32s3.rs b/esp-hal/src/psram/esp32s3.rs new file mode 100644 index 00000000000..d98c94db36b --- /dev/null +++ b/esp-hal/src/psram/esp32s3.rs @@ -0,0 +1,1638 @@ +use super::PsramSize; +use crate::peripherals::{EXTMEM, IO_MUX, SPI0, SPI1}; + +const EXTMEM_ORIGIN: u32 = 0x3C000000; + +/// Frequency of flash memory +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlashFreq { + /// Flash frequency 20 MHz + FlashFreq20m = 20, + /// Flash frequency 40 MHz + FlashFreq40m = 40, + /// Flash frequency 80 MHz + #[default] + FlashFreq80m = 80, + /// Flash frequency 120 MHz + /// This is not recommended, see + FlashFreq120m = 120, +} + +/// Frequency of PSRAM memory +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +pub enum SpiRamFreq { + /// PSRAM frequency 40 MHz + #[default] + Freq40m = 40, + /// PSRAM frequency 80 MHz + Freq80m = 80, + /// PSRAM frequency 120 MHz + /// This is not recommended, see + Freq120m = 120, +} + +/// Core timing configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SpiTimingConfigCoreClock { + /// Core clock 80 MHz + #[default] + SpiTimingConfigCoreClock80m = 80, + /// Core clock 120 MHz + SpiTimingConfigCoreClock120m = 120, + /// Core clock 160 MHz + SpiTimingConfigCoreClock160m = 160, + /// Core clock 240 MHz + SpiTimingConfigCoreClock240m = 240, +} + +/// PSRAM configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PsramConfig { + /// PSRAM size + pub size: PsramSize, + /// Core timing configuration + pub core_clock: Option, + /// Frequency of flash memory + pub flash_frequency: FlashFreq, + /// Frequency of PSRAM memory + pub ram_frequency: SpiRamFreq, +} + +/// Initialize PSRAM to be used for data. +/// +/// Returns the start of the mapped memory and the size +#[procmacros::ram] +pub(crate) fn init_psram(config: PsramConfig) { + let mut config = config; + + if config.core_clock.is_none() { + cfg_if::cfg_if! { + if #[cfg(psram_mode_octal)] { + config.core_clock = Some(if config.ram_frequency == SpiRamFreq::Freq80m { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock160m + } else if config.ram_frequency == SpiRamFreq::Freq120m { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock240m + } else { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m + }); + } else { + config.core_clock = Some( if config.ram_frequency == SpiRamFreq::Freq120m { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock120m + } else { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m + }); + } + } + } + + utils::psram_init(&mut config); + + const MMU_ACCESS_SPIRAM: u32 = 1 << 15; + const START_PAGE: u32 = 0; + + unsafe extern "C" { + fn Cache_Suspend_DCache(); + + fn Cache_Resume_DCache(param: u32); + + /// Set DCache mmu mapping. + /// + /// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid. + /// [`vaddr`]: u32 Virtual address in CPU address space. + /// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize. + /// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here. + /// [`num`]: u32 Pages to be set. + /// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page. + fn cache_dbus_mmu_set( + ext_ram: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + fixed: u32, + ) -> i32; + } + + let start = unsafe { + const MMU_PAGE_SIZE: u32 = 0x10000; + const ICACHE_MMU_SIZE: usize = 0x800; + const FLASH_MMU_TABLE_SIZE: usize = ICACHE_MMU_SIZE / core::mem::size_of::(); + const MMU_INVALID: u32 = 1 << 14; + const DR_REG_MMU_TABLE: u32 = 0x600C5000; + + // calculate the PSRAM start address to map + // the linker scripts can produce a gap between mapped IROM and DROM segments + // bigger than a flash page - i.e. we will see an unmapped memory slot + // start from the end and find the last mapped flash page + // + // More general information about the MMU can be found here: + // https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/system/mm.html#introduction + let mmu_table_ptr = DR_REG_MMU_TABLE as *const u32; + let mut mapped_pages = 0; + + // the bootloader is using the last page to access flash internally + // (e.g. to read the app descriptor) so we just skip that + for i in (0..(FLASH_MMU_TABLE_SIZE - 1)).rev() { + if mmu_table_ptr.add(i).read_volatile() != MMU_INVALID { + mapped_pages = (i + 1) as u32; + break; + } + } + let start = EXTMEM_ORIGIN + (MMU_PAGE_SIZE * mapped_pages); + debug!("PSRAM start address = {:x}", start); + + // If we need use SPIRAM, we should use data cache. + Cache_Suspend_DCache(); + + let cache_dbus_mmu_set_res = cache_dbus_mmu_set( + MMU_ACCESS_SPIRAM, + start, + START_PAGE << 16, + 64, + config.size.get() as u32 / 1024 / 64, // number of pages to map + 0, + ); + + EXTMEM::regs().dcache_ctrl1().modify(|_, w| { + w.dcache_shut_core0_bus() + .clear_bit() + .dcache_shut_core1_bus() + .clear_bit() + }); + + Cache_Resume_DCache(0); + + // panic AFTER resuming the cache + if cache_dbus_mmu_set_res != 0 { + panic!("cache_dbus_mmu_set failed"); + } + + start + }; + + unsafe { + super::MAPPED_PSRAM.memory_range = start as usize..start as usize + config.size.get(); + } +} + +#[cfg(psram_mode_quad)] +pub(crate) mod utils { + use procmacros::ram; + + use super::*; + + const PSRAM_RESET_EN: u16 = 0x66; + const PSRAM_RESET: u16 = 0x99; + const PSRAM_DEVICE_ID: u16 = 0x9F; + const CS_PSRAM_SEL: u8 = 1 << 1; + + #[ram] + pub(crate) fn psram_init(config: &mut super::PsramConfig) { + psram_gpio_config(); + psram_set_cs_timing(); + + if config.size.is_auto() { + psram_disable_qio_mode_spi1(); + + // read chip id + let mut dev_id = 0u32; + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_DEVICE_ID, + 8, // command and command bit len + 0, + 24, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + &mut dev_id as *mut _ as *mut u8, + 24, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); + if dev_id == 0xffffff { + warn!( + "Unknown PSRAM chip ID: {:x}. PSRAM chip not found or not supported. Check if ESP_HAL_CONFIG_PSRAM_MODE is configured correctly.", + dev_id + ); + return; + } + + info!("chip id = {:x}", dev_id); + + const PSRAM_ID_EID_S: u32 = 16; + const PSRAM_ID_EID_M: u32 = 0xff; + const PSRAM_EID_SIZE_M: u32 = 0x07; + const PSRAM_EID_SIZE_S: u32 = 5; + + let size_id = ((((dev_id) >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S) + & PSRAM_EID_SIZE_M; + + const PSRAM_EID_SIZE_32MBITS: u32 = 1; + const PSRAM_EID_SIZE_64MBITS: u32 = 2; + + let size = match size_id { + PSRAM_EID_SIZE_64MBITS => 64 / 8 * 1024 * 1024, + PSRAM_EID_SIZE_32MBITS => 32 / 8 * 1024 * 1024, + _ => 16 / 8 * 1024 * 1024, + }; + + info!("size is {}", size); + + config.size = PsramSize::Size(size); + } + + // SPI1: send psram reset command + psram_reset_mode_spi1(); + // SPI1: send QPI enable command + psram_enable_qio_mode_spi1(); + + // Do PSRAM timing tuning, we use SPI1 to do the tuning, and set the SPI0 PSRAM + // timing related registers accordingly + mspi_timing_psram_tuning(); + + // Configure SPI0 PSRAM related SPI Phases + config_psram_spi_phases(); + // Back to the high speed mode. Flash/PSRAM clocks are set to the clock that + // user selected. SPI0/1 registers are all set correctly + mspi_timing_enter_high_speed_mode(true, config); + } + + const PSRAM_CS_IO: u8 = 26; + const SPI_CS1_GPIO_NUM: u8 = 26; + const FUNC_SPICS1_SPICS1: u8 = 0; + const PIN_FUNC_GPIO: u8 = 2; + const PSRAM_SPIWP_SD3_IO: u8 = 10; + const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; + const SPICS1_OUT_IDX: u8 = 6; + + const PSRAM_QUAD_WRITE: u32 = 0x38; + const PSRAM_FAST_READ_QUAD: u32 = 0xEB; + const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 6; + const SPI_MEM_CLKCNT_N_S: u32 = 16; + const SPI_MEM_SCLKCNT_N_S: u32 = 16; + const SPI_MEM_CLKCNT_H_S: u32 = 8; + const SPI_MEM_SCLKCNT_H_S: u32 = 8; + const SPI_MEM_CLKCNT_L_S: u32 = 0; + const SPI_MEM_SCLKCNT_L_S: u32 = 0; + + unsafe extern "C" { + fn esp_rom_efuse_get_flash_gpio_info() -> u32; + + fn esp_rom_efuse_get_flash_wp_gpio() -> u8; + + fn esp_rom_gpio_connect_out_signal( + gpio_num: u8, + signal_idx: u8, + out_inv: bool, + oen_inv: bool, + ); + + /// Enable Quad I/O pin functions + /// + /// Sets the HD & WP pin functions for Quad I/O modes, based on the + /// efuse SPI pin configuration. + /// + /// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O + /// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig(). + /// - If this parameter is 0, default SPI pins are used and wp_gpio_num parameter is + /// ignored. + /// - If this parameter is 1, default HSPI pins are used and wp_gpio_num parameter is + /// ignored. + /// - For other values, this parameter encodes the HD pin number and also the CLK pin + /// number. CLK pin selection is used to determine if HSPI or SPI peripheral will be used + /// (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI). + // Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral. + fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32); + } + + // Configure PSRAM SPI0 phase related registers here according to the PSRAM chip + // requirement + #[ram] + fn config_psram_spi_phases() { + unsafe { + let spi = SPI0::regs(); + // Config CMD phase + spi.cache_sctrl() + .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command + + spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command + + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command + + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command + + spi.sram_dwr_cmd() + .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); + + spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_value() + .bits(PSRAM_QUAD_WRITE as u16) + }); + + spi.sram_drd_cmd() + .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); + + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_value() + .bits(PSRAM_FAST_READ_QUAD as u16) + }); + + // Config ADDR phase + spi.cache_sctrl() + .modify(|_, w| w.sram_addr_bitlen().bits(23)); + + // Dummy + // We set the PSRAM chip required dummy here. If timing tuning is + // needed, the dummy length will be updated in + // `mspi_timing_enter_high_speed_mode()` + spi.cache_sctrl() + .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy + + spi.cache_sctrl().modify(|_, w| { + w.sram_rdummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY - 1) as u8) + }); + + // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) + spi.misc().modify(|_, w| w.cs1_dis().clear_bit()); + } + } + + #[ram] + fn mspi_timing_psram_tuning() { + // currently we only support !SPI_TIMING_PSRAM_NEEDS_TUNING + // see https://github.com/espressif/esp-idf/blob/4e24516ee2731eb55687182d4e061b5b93a9e33f/components/esp_hw_support/mspi_timing_tuning.c#L391-L415 + } + + /// Set SPI0 FLASH and PSRAM module clock, din_num, din_mode and extra + /// dummy, according to the configuration got from timing tuning + /// function (`calculate_best_flash_tuning_config`). iF control_spi1 == + /// 1, will also update SPI1 timing registers. Should only be set to 1 when + /// do tuning. + /// + /// This function should always be called after `mspi_timing_flash_tuning` + /// or `calculate_best_flash_tuning_config` + #[ram] + fn mspi_timing_enter_high_speed_mode(control_spi1: bool, config: &PsramConfig) { + let core_clock: SpiTimingConfigCoreClock = mspi_core_clock(config); + let flash_div: u32 = flash_clock_divider(config); + let psram_div: u32 = psram_clock_divider(config); + + info!( + "PSRAM core_clock {:?}, flash_div = {}, psram_div = {}", + core_clock, flash_div, psram_div + ); + + // Set SPI01 core clock + // SPI0 and SPI1 share the register for core clock. So we only set SPI0 here. + // Set FLASH module clock + spi0_timing_config_set_core_clock(core_clock); + + spi0_timing_config_set_flash_clock(flash_div); + if control_spi1 { + spi1_timing_config_set_flash_clock(flash_div); + } + // Set PSRAM module clock + spi0_timing_config_set_psram_clock(psram_div); + + // #if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING + // set_timing_tuning_regs_as_required(true); + // #endif + } + + #[ram] + fn spi0_timing_config_set_core_clock(core_clock: SpiTimingConfigCoreClock) { + unsafe { + SPI0::regs().core_clk_sel().modify(|_, w| { + w.core_clk_sel().bits(match core_clock { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m => 0, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock120m => 1, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock160m => 2, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock240m => 3, + }) + }); + } + } + + #[ram] + fn spi0_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs() + .clock() + .modify(|_, w| w.clk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); + unsafe { + SPI0::regs().clock().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn spi1_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI1::regs() + .clock() + .modify(|_, w| w.clk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); + unsafe { + SPI1::regs().clock().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn spi0_timing_config_set_psram_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs() + .sram_clk() + .modify(|_, w| w.sclk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_SCLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_SCLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_SCLKCNT_L_S); + unsafe { + SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn mspi_core_clock(config: &PsramConfig) -> SpiTimingConfigCoreClock { + config.core_clock.unwrap_or_default() + } + + #[ram] + fn flash_clock_divider(config: &PsramConfig) -> u32 { + config.core_clock.unwrap_or_default() as u32 / config.flash_frequency as u32 + } + + #[ram] + fn psram_clock_divider(config: &PsramConfig) -> u32 { + config.core_clock.unwrap_or_default() as u32 / config.ram_frequency as u32 + } + + // send reset command to psram, in spi mode + #[ram] + fn psram_reset_mode_spi1() { + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET_EN, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); // whether is program/erase operation + + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, + ); // whether is program/erase operation + } + + #[derive(PartialEq)] + #[allow(unused)] + enum CommandMode { + PsramCmdQpi = 0, + PsramCmdSpi = 1, + } + + #[expect(clippy::too_many_arguments)] + #[ram] + fn psram_exec_cmd( + mode: CommandMode, + cmd: u16, + cmd_bit_len: u16, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + cs_mask: u8, + is_write_erase_operation: bool, + ) { + unsafe extern "C" { + /// Start a spi user command sequence + /// [`spi_num`] spi port + /// [`rx_buf`] buffer pointer to receive data + /// [`rx_len`] receive data length in byte + /// [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1 + /// [`is_write_erase`] to indicate whether this is a write or erase + /// operation, since the CPU would check permission + fn esp_rom_spi_cmd_start( + spi_num: u32, + rx_buf: *const u8, + rx_len: u16, + cs_en_mask: u8, + is_write_erase: bool, + ); + } + + unsafe { + let spi1 = SPI1::regs(); + let backup_usr = spi1.user().read().bits(); + let backup_usr1 = spi1.user1().read().bits(); + let backup_usr2 = spi1.user2().read().bits(); + let backup_ctrl = spi1.ctrl().read().bits(); + psram_set_op_mode(mode); + _psram_exec_cmd( + cmd, + cmd_bit_len, + addr, + addr_bit_len, + dummy_bits, + mosi_data, + mosi_bit_len, + miso_data, + miso_bit_len, + ); + esp_rom_spi_cmd_start( + 1, + miso_data, + (miso_bit_len / 8) as u16, + cs_mask, + is_write_erase_operation, + ); + + spi1.user().write(|w| w.bits(backup_usr)); + spi1.user1().write(|w| w.bits(backup_usr1)); + spi1.user2().write(|w| w.bits(backup_usr2)); + spi1.ctrl().write(|w| w.bits(backup_ctrl)); + } + } + + #[expect(clippy::too_many_arguments)] + #[ram] + fn _psram_exec_cmd( + cmd: u16, + cmd_bit_len: u16, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + ) { + #[repr(C)] + struct esp_rom_spi_cmd_t { + cmd: u16, // Command value + cmd_bit_len: u16, // Command byte length + addr: *const u32, // Point to address value + addr_bit_len: u32, // Address byte length + tx_data: *const u32, // Point to send data buffer + tx_data_bit_len: u32, // Send data byte length. + rx_data: *mut u32, // Point to recevie data buffer + rx_data_bit_len: u32, // Recevie Data byte length. + dummy_bit_len: u32, + } + + unsafe extern "C" { + /// Config the spi user command + /// [`spi_num`] spi port + /// [`pcmd`] pointer to accept the spi command struct + fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t); + } + + let conf = esp_rom_spi_cmd_t { + cmd, + cmd_bit_len, + addr: &addr, + addr_bit_len, + tx_data: mosi_data as *const u32, + tx_data_bit_len: mosi_bit_len, + rx_data: miso_data as *mut u32, + rx_data_bit_len: miso_bit_len, + dummy_bit_len: dummy_bits, + }; + + unsafe { + esp_rom_spi_cmd_config(1, &conf); + } + } + + #[ram] + fn psram_set_op_mode(mode: CommandMode) { + unsafe extern "C" { + fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); + } + + const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; + const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; + + unsafe { + match mode { + CommandMode::PsramCmdQpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); + SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit()); + } + CommandMode::PsramCmdSpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); + } + } + } + } + + /// Exit QPI mode + #[ram] + fn psram_disable_qio_mode_spi1() { + const PSRAM_EXIT_QMODE: u16 = 0xF5; + const CS_PSRAM_SEL: u8 = 1 << 1; + + psram_exec_cmd( + CommandMode::PsramCmdQpi, + PSRAM_EXIT_QMODE, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, // whether is program/erase operation + ); + } + + /// Enter QPI mode + #[ram] + fn psram_enable_qio_mode_spi1() { + const PSRAM_ENTER_QMODE: u16 = 0x35; + const CS_PSRAM_SEL: u8 = 1 << 1; + + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_ENTER_QMODE, + 8, // command and command bit len + 0, + 0, // address and address bit len + 0, // dummy bit len + core::ptr::null(), + 0, // tx data and tx bit len + core::ptr::null_mut(), + 0, // rx data and rx bit len + CS_PSRAM_SEL, // cs bit mask + false, // whether is program/erase operation + ); + } + + #[ram] + fn psram_set_cs_timing() { + unsafe { + // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time registers + // for PSRAM, so we only need to set SPI0 related registers here + SPI0::regs() + .spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_hold_time().bits(0)); + SPI0::regs() + .spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_setup_time().bits(0)); + SPI0::regs() + .spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_hold().set_bit()); + SPI0::regs() + .spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_setup().set_bit()); + } + } + + #[ram] + fn psram_gpio_config() { + // CS1 + let cs1_io: u8 = PSRAM_CS_IO; + if cs1_io == SPI_CS1_GPIO_NUM { + unsafe { + IO_MUX::regs() + .gpio(cs1_io as usize) + .modify(|_, w| w.mcu_sel().bits(FUNC_SPICS1_SPICS1)); + } + } else { + unsafe { + esp_rom_gpio_connect_out_signal(cs1_io, SPICS1_OUT_IDX, false, false); + + IO_MUX::regs() + .gpio(cs1_io as usize) + .modify(|_, w| w.mcu_sel().bits(PIN_FUNC_GPIO)); + } + } + + // WP HD + let mut wp_io: u8 = PSRAM_SPIWP_SD3_IO; + let spiconfig = unsafe { esp_rom_efuse_get_flash_gpio_info() }; + if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI { + // MSPI pins (except wp / hd) are all configured via IO_MUX in 1st + // bootloader. + } else { + // MSPI pins (except wp / hd) are all configured via GPIO matrix in 1st + // bootloader. + wp_io = unsafe { esp_rom_efuse_get_flash_wp_gpio() }; + } + // This ROM function will init both WP and HD pins. + unsafe { + esp_rom_spiflash_select_qio_pins(wp_io, spiconfig); + } + } +} + +#[cfg(psram_mode_octal)] +pub(crate) mod utils { + use procmacros::ram; + + use super::*; + + const OPI_PSRAM_SYNC_READ: u16 = 0x0000; + const OPI_PSRAM_SYNC_WRITE: u16 = 0x8080; + const OPI_PSRAM_REG_READ: u16 = 0x4040; + const OPI_PSRAM_REG_WRITE: u16 = 0xC0C0; + const OCT_PSRAM_RD_CMD_BITLEN: u8 = 16; + const OCT_PSRAM_WR_CMD_BITLEN: u8 = 16; + const OCT_PSRAM_ADDR_BITLEN: u8 = 32; + const OCT_PSRAM_RD_DUMMY_BITLEN: u8 = 2 * (10 - 1); + const OCT_PSRAM_WR_DUMMY_BITLEN: u8 = 2 * (5 - 1); + const OCT_PSRAM_CS1_IO: u8 = SPI_CS1_GPIO_NUM; + const OCT_PSRAM_VENDOR_ID: u8 = 0xD; + + const OCT_PSRAM_CS_SETUP_TIME: u8 = 3; + const OCT_PSRAM_CS_HOLD_TIME: u8 = 3; + const OCT_PSRAM_CS_HOLD_DELAY: u8 = 2; + + const PSRAM_SIZE_2MB: usize = 2 * 1024 * 1024; + const PSRAM_SIZE_4MB: usize = 4 * 1024 * 1024; + const PSRAM_SIZE_8MB: usize = 8 * 1024 * 1024; + const PSRAM_SIZE_16MB: usize = 16 * 1024 * 1024; + const PSRAM_SIZE_32MB: usize = 32 * 1024 * 1024; + + const SPI_CS1_GPIO_NUM: u8 = 26; + const FUNC_SPICS1_SPICS1: u8 = 0; + + const SPI_MEM_CLKCNT_N_S: u32 = 16; + const SPI_MEM_SCLKCNT_N_S: u32 = 16; + const SPI_MEM_CLKCNT_H_S: u32 = 8; + const SPI_MEM_SCLKCNT_H_S: u32 = 8; + const SPI_MEM_CLKCNT_L_S: u32 = 0; + const SPI_MEM_SCLKCNT_L_S: u32 = 0; + const ESP_ROM_SPIFLASH_OPI_DTR_MODE: u8 = 7; + + unsafe extern "C" { + // @brief To execute a flash operation command + // @param spi_num spi port + // @param mode Flash Read Mode + // @param cmd data to send in command field + // @param cmd_bit_len bit length of command field + // @param addr data to send in address field + // @param addr_bit_len bit length of address field + // @param dummy_bits bit length of dummy field + // @param mosi_data data buffer to be sent in mosi field + // @param mosi_bit_len bit length of data buffer to be sent in mosi field + // @param miso_data data buffer to accept data in miso field + // @param miso_bit_len bit length of data buffer to accept data in miso field + // @param cs_mark decide which cs pin to use. 0: cs0, 1: cs1 + // @param is_write_erase_operation to indicate whether this a write or erase + // flash operation + fn esp_rom_opiflash_exec_cmd( + spi_num: u32, + mode: u8, + cmd: u32, + cmd_bit_len: u32, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + cs_mask: u32, + is_write_erase_operation: bool, + ); + + // @brief Set data swap mode in DTR(DDR) mode + // @param spi_num spi port + // @param wr_swap to decide whether to swap fifo data in dtr write operation + // @param rd_swap to decide whether to swap fifo data in dtr read operation + fn esp_rom_spi_set_dtr_swap_mode(spi: u32, wr_swap: bool, rd_swap: bool); + + fn esp_rom_opiflash_pin_config(); + } + + /// Represents the operational mode registers of an OPI PSRAM. + #[derive(Default)] + #[repr(C)] + struct OpiPsramModeReg { + // Mode register 0 (MR0). + pub mr0: u8, + // Mode register 1 (MR1). + pub mr1: u8, + // Mode register 2 (MR2). + pub mr2: u8, + // Mode register 3 (MR3). + pub mr3: u8, + // Mode register 4 (MR4). + pub mr4: u8, + // Mode register 8 (MR8). + pub mr8: u8, + } + + #[allow(unused)] + impl OpiPsramModeReg { + fn drive_str(&self) -> u8 { + self.mr0 & 0b11 + } + + fn set_drive_str(&mut self, value: u8) { + self.mr0 &= !0b11; + self.mr0 |= value & 0b11; + } + + fn read_latency(&self) -> u8 { + (self.mr0 >> 2) & 0b111 + } + + fn set_read_latency(&mut self, value: u8) { + self.mr0 &= !(0b111 << 2); + self.mr0 |= (value & 0b111) << 2; + } + + fn lt(&self) -> u8 { + (self.mr0 >> 5) & 0b1 + } + + fn set_lt(&mut self, value: u8) { + self.mr0 &= !(0b1 << 5); + self.mr0 |= (value & 0b1) << 5; + } + + fn rsvd0_1(&self) -> u8 { + (self.mr0 >> 6) & 0b11 + } + + fn set_rsvd0_1(&mut self, value: u8) { + self.mr0 &= !(0b11 << 6); + self.mr0 |= (value & 0b11) << 6; + } + + fn vendor_id(&self) -> u8 { + self.mr1 & 0b11111 + } + + fn set_vendor_id(&mut self, value: u8) { + self.mr1 &= !0b11111; + self.mr1 |= value & 0b11111; + } + + fn rsvd0_2(&self) -> u8 { + (self.mr1 >> 5) & 0b111 + } + + fn set_rsvd0_2(&mut self, value: u8) { + self.mr1 &= !(0b111 << 5); + self.mr1 |= (value & 0b111) << 5; + } + + fn density(&self) -> u8 { + self.mr2 & 0b111 + } + + fn set_density(&mut self, value: u8) { + self.mr2 &= !0b111; + self.mr2 |= value & 0b111; + } + + fn dev_id(&self) -> u8 { + (self.mr2 >> 3) & 0b11 + } + + fn set_dev_id(&mut self, value: u8) { + self.mr2 &= !(0b11 << 3); + self.mr2 |= (value & 0b11) << 3; + } + + fn rsvd1_2(&self) -> u8 { + (self.mr2 >> 5) & 0b11 + } + + fn set_rsvd1_2(&mut self, value: u8) { + self.mr2 &= !(0b11 << 5); + self.mr2 |= (value & 0b11) << 5; + } + + fn gb(&self) -> u8 { + (self.mr2 >> 7) & 0b1 + } + + fn set_gb(&mut self, value: u8) { + self.mr2 &= !(0b1 << 7); + self.mr2 |= (value & 0b1) << 7; + } + + fn rsvd3_7(&self) -> u8 { + self.mr3 & 0b11111 + } + + fn set_rsvd3_7(&mut self, value: u8) { + self.mr3 &= !0b11111; + self.mr3 |= value & 0b11111; + } + + fn srf(&self) -> u8 { + (self.mr3 >> 5) & 0b1 + } + + fn set_srf(&mut self, value: u8) { + self.mr3 &= !(0b1 << 5); + self.mr3 |= (value & 0b1) << 5; + } + + fn vcc(&self) -> u8 { + (self.mr3 >> 6) & 0b1 + } + + fn set_vcc(&mut self, value: u8) { + self.mr3 &= !(0b1 << 6); + self.mr3 |= (value & 0b1) << 6; + } + + fn rsvd0(&self) -> u8 { + (self.mr3 >> 7) & 0b1 + } + + fn set_rsvd0(&mut self, value: u8) { + self.mr3 &= !(0b1 << 7); + self.mr3 |= (value & 0b1) << 7; + } + + fn pasr(&self) -> u8 { + self.mr4 & 0b111 + } + + fn set_pasr(&mut self, value: u8) { + self.mr4 &= !0b111; + self.mr4 |= value & 0b111; + } + + fn rf(&self) -> u8 { + (self.mr4 >> 3) & 0b1 + } + + fn set_rf(&mut self, value: u8) { + self.mr4 &= !(0b1 << 3); + self.mr4 |= (value & 0b1) << 3; + } + + fn rsvd3(&self) -> u8 { + (self.mr4 >> 4) & 0b1 + } + + fn set_rsvd3(&mut self, value: u8) { + self.mr4 &= !(0b1 << 4); + self.mr4 |= (value & 0b1) << 4; + } + + fn wr_latency(&self) -> u8 { + (self.mr4 >> 5) & 0b111 + } + + fn set_wr_latency(&mut self, value: u8) { + self.mr4 &= !(0b111 << 5); + self.mr4 |= (value & 0b111) << 5; + } + + fn bl(&self) -> u8 { + self.mr8 & 0b11 + } + + fn set_bl(&mut self, value: u8) { + self.mr8 &= !0b11; + self.mr8 |= value & 0b11; + } + + fn bt(&self) -> u8 { + (self.mr8 >> 2) & 0b1 + } + + fn set_bt(&mut self, value: u8) { + self.mr8 &= !(0b1 << 2); + self.mr8 |= (value & 0b1) << 2; + } + + fn rsvd0_4(&self) -> u8 { + (self.mr8 >> 3) & 0b11111 + } + + fn set_rsvd0_4(&mut self, value: u8) { + self.mr8 &= !(0b11111 << 3); + self.mr8 |= (value & 0b11111) << 3; + } + } + + #[ram] + pub(crate) fn psram_init(config: &mut PsramConfig) { + mspi_pin_init(); + init_psram_pins(); + set_psram_cs_timing(); + + // for now we don't support ECC + // "s_configure_psram_ecc();" + + // enter MSPI slow mode to init PSRAM device registers + spi_timing_enter_mspi_low_speed_mode(true); + + unsafe { + // set to variable dummy mode + SPI1::regs() + .ddr() + .modify(|_, w| w.spi_fmem_var_dummy().set_bit()); + esp_rom_spi_set_dtr_swap_mode(1, false, false); + } + + // Set PSRAM read latency and drive strength + let mut mode_reg = OpiPsramModeReg::default(); + mode_reg.set_lt(1); + mode_reg.set_read_latency(2); + mode_reg.set_drive_str(0); + mode_reg.set_bl(3); + mode_reg.set_bt(0); + + init_psram_mode_reg(1, &mode_reg); + // Print PSRAM info + psram_mode_reg(1, &mut mode_reg); + + print_psram_info(&mode_reg); + + if mode_reg.vendor_id() != OCT_PSRAM_VENDOR_ID { + warn!( + "Unknown PSRAM vendor ID: {:x}. PSRAM chip not found or not supported. Check if ESP_HAL_CONFIG_PSRAM_MODE is configured correctly.", + mode_reg.vendor_id() + ); + return; + } + + let psram_size = match mode_reg.density() { + 0x0 => PSRAM_SIZE_2MB, + 0x1 => PSRAM_SIZE_4MB, + 0x3 => PSRAM_SIZE_8MB, + 0x5 => PSRAM_SIZE_16MB, + 0x7 => PSRAM_SIZE_32MB, + _ => 0, + }; + info!("{} bytes of PSRAM", psram_size); + + if config.size.is_auto() { + config.size = PsramSize::Size(psram_size); + } + + // Do PSRAM timing tuning, we use SPI1 to do the tuning, and set the + // SPI0 PSRAM timing related registers accordingly + // this is unsupported for now + // spi_timing_psram_tuning(); + + // Back to the high speed mode. Flash/PSRAM clocks are set to the clock + // that user selected. SPI0/1 registers are all set correctly + spi_timing_enter_mspi_high_speed_mode(true, config); + + // Tuning may change SPI1 regs, whereas legacy spi_flash APIs rely on + // these regs. This function is to restore SPI1 init state. + spi_flash_set_rom_required_regs(); + + // Flash chip requires MSPI specifically, call this function to set them + // this is unsupported for now + // spi_flash_set_vendor_required_regs(); + + config_psram_spi_phases(); + } + + // Configure PSRAM SPI0 phase related registers here according to the PSRAM chip + // requirement + fn config_psram_spi_phases() { + unsafe { + let spi = SPI0::regs(); + // Config Write CMD phase for SPI0 to access PSRAM + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); + + spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_bitlen() + .bits(OCT_PSRAM_WR_CMD_BITLEN - 1) + }); + spi.sram_dwr_cmd() + .modify(|_, w| w.cache_sram_usr_wr_cmd_value().bits(OPI_PSRAM_SYNC_WRITE)); + + // Config Read CMD phase for SPI0 to access PSRAM + spi.cache_sctrl() + .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); + + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_bitlen() + .bits(OCT_PSRAM_RD_CMD_BITLEN - 1) + }); + spi.sram_drd_cmd() + .modify(|_, w| w.cache_sram_usr_rd_cmd_value().bits(OPI_PSRAM_SYNC_READ)); + + // Config ADDR phase + spi.cache_sctrl() + .modify(|_, w| w.sram_addr_bitlen().bits(OCT_PSRAM_ADDR_BITLEN - 1)); + spi.cache_sctrl() + .modify(|_, w| w.cache_usr_scmd_4byte().set_bit()); + + // Config RD/WR Dummy phase + spi.cache_sctrl() + .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); + spi.cache_sctrl() + .modify(|_, w| w.usr_wr_sram_dummy().set_bit()); + spi.cache_sctrl() + .modify(|_, w| w.sram_rdummy_cyclelen().bits(OCT_PSRAM_RD_DUMMY_BITLEN - 1)); + spi.spi_smem_ddr() + .modify(|_, w| w.spi_smem_var_dummy().set_bit()); + spi.cache_sctrl() + .modify(|_, w| w.sram_wdummy_cyclelen().bits(OCT_PSRAM_WR_DUMMY_BITLEN - 1)); + + spi.spi_smem_ddr().modify(|_, w| w.wdat_swp().clear_bit()); + spi.spi_smem_ddr().modify(|_, w| w.rdat_swp().clear_bit()); + spi.spi_smem_ddr().modify(|_, w| w.en().set_bit()); + + spi.sram_cmd().modify(|_, w| w.sdummy_out().set_bit()); + spi.sram_cmd().modify(|_, w| w.scmd_oct().set_bit()); + spi.sram_cmd().modify(|_, w| w.saddr_oct().set_bit()); + spi.sram_cmd().modify(|_, w| w.sdout_oct().set_bit()); + spi.sram_cmd().modify(|_, w| w.sdin_oct().set_bit()); + + spi.cache_sctrl().modify(|_, w| w.sram_oct().set_bit()); + } + } + + #[ram] + fn spi_flash_set_rom_required_regs() { + // Disable the variable dummy mode when doing timing tuning + SPI1::regs() + .ddr() + .modify(|_, w| w.spi_fmem_var_dummy().clear_bit()); + // STR /DTR mode setting is done every time when + // `esp_rom_opiflash_exec_cmd` is called + // + // Add any registers that are not set in ROM SPI flash functions here in + // the future + } + + #[ram] + fn mspi_pin_init() { + unsafe { esp_rom_opiflash_pin_config() }; + spi_timing_set_pin_drive_strength(); + // Set F4R4 board pin drive strength. TODO: IDF-3663 + } + + #[ram] + fn spi_timing_set_pin_drive_strength() { + // For now, set them all to 3. Need to check after QVL test results are out. + // TODO: IDF-3663 Set default clk + unsafe { + SPI0::regs() + .date() + .modify(|_, w| w.spi_spiclk_pad_drv_ctl_en().set_bit()); + SPI0::regs() + .date() + .modify(|_, w| w.spi_smem_spiclk_fun_drv().bits(3)); + SPI0::regs() + .date() + .modify(|_, w| w.spi_fmem_spiclk_fun_drv().bits(3)); + + // Set default mspi d0 ~ d7, dqs pin drive strength + let pins = [27usize, 28, 31, 32, 33, 34, 35, 36, 37]; + for pin in pins { + IO_MUX::regs().gpio(pin).modify(|_, w| w.fun_drv().bits(3)); + } + } + } + + fn spi_timing_enter_mspi_low_speed_mode(control_spi1: bool) { + // Here we are going to slow the SPI1 frequency to 20Mhz, so we need to + // set SPI1 din_num and din_mode regs. + // + // Because SPI0 and SPI1 share the din_num and din_mode regs, so if we + // clear SPI1 din_num and din_mode to 0, if the SPI0 flash + // module clock is still in high freq, it may not work correctly. + // + // Therefore, here we need to slow both the SPI0 and SPI1 and related + // timing tuning regs to 20Mhz configuration. + // Switch SPI1 and SPI0 clock as 20MHz, set its SPIMEM core clock as 80M and set + // clock division as 4 + spi0_timing_config_set_core_clock(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m); // SPI0 and SPI1 share the register for core clock. So we only set SPI0 here. + spi0_timing_config_set_flash_clock(4); + if control_spi1 { + // After tuning, won't touch SPI1 again + spi1_timing_config_set_flash_clock(4); + } + + // Set PSRAM module clock + spi0_timing_config_set_psram_clock(4); + + // for now we don't support tuning the timing + // "clear_timing_tuning_regs(control_spi1);" + } + + // Set SPI0 FLASH and PSRAM module clock, din_num, din_mode and extra dummy, + // according to the configuration got from timing tuning function + // (`calculate_best_flash_tuning_config`). iF control_spi1 == 1, will also + // update SPI1 timing registers. Should only be set to 1 when do tuning. + // + // This function should always be called after `spi_timing_flash_tuning` or + // `calculate_best_flash_tuning_config` + fn spi_timing_enter_mspi_high_speed_mode(control_spi1: bool, config: &PsramConfig) { + let flash_div: u32 = flash_clock_divider(config); + let psram_div: u32 = psram_clock_divider(config); + + // Set SPI01 core clock + spi0_timing_config_set_core_clock(config.core_clock.unwrap_or_default()); // SPI0 and SPI1 share the register for core clock. So we only set SPI0 here. + // Set FLASH module clock + spi0_timing_config_set_flash_clock(flash_div); + if control_spi1 { + spi1_timing_config_set_flash_clock(flash_div); + } + // Set PSRAM module clock + spi0_timing_config_set_psram_clock(psram_div); + + // for now we don't support tuning the timing + // "set_timing_tuning_regs_as_required(true);" + } + + fn set_psram_cs_timing() { + unsafe { + let spi = SPI0::regs(); + // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time, + // cs_hold_delay registers for PSRAM, so we only need to set SPI0 related + // registers here + spi.spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_hold().set_bit()); + spi.spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_setup().set_bit()); + + spi.spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_hold_time().bits(OCT_PSRAM_CS_HOLD_TIME)); + spi.spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_setup_time().bits(OCT_PSRAM_CS_SETUP_TIME)); + + // CONFIG_SPIRAM_ECC_ENABLE unsupported for now + // CS1 high time + spi.spi_smem_ac() + .modify(|_, w| w.spi_smem_cs_hold_delay().bits(OCT_PSRAM_CS_HOLD_DELAY)); + } + } + + fn init_psram_pins() { + // Set cs1 pin function + unsafe { + IO_MUX::regs() + .gpio(OCT_PSRAM_CS1_IO as usize) + .modify(|_, w| w.mcu_sel().bits(FUNC_SPICS1_SPICS1)); + } + + // Set mspi cs1 drive strength + unsafe { + IO_MUX::regs() + .gpio(OCT_PSRAM_CS1_IO as usize) + .modify(|_, w| w.fun_drv().bits(3)); + } + + // Set psram clock pin drive strength + unsafe { + SPI0::regs() + .date() + .modify(|_, w| w.spi_smem_spiclk_fun_drv().bits(3)); + } + } + + fn psram_mode_reg(spi_num: u32, out_reg: &mut OpiPsramModeReg) { + let mode = ESP_ROM_SPIFLASH_OPI_DTR_MODE; + let cmd_len: u32 = 16; + let addr_bit_len: u32 = 32; + let dummy: u32 = OCT_PSRAM_RD_DUMMY_BITLEN as u32; + let mut data_bit_len: u32 = 16; + + unsafe { + // Read MR0~1 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x0, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr0, + data_bit_len, + 1 << 1, + false, + ); + // Read MR2~3 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x2, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr2, + data_bit_len, + 1 << 1, + false, + ); + data_bit_len = 8; + // Read MR4 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x4, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr4, + data_bit_len, + 1 << 1, + false, + ); + // Read MR8 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x8, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr8, + data_bit_len, + 1 << 1, + false, + ); + } + } + + // Initialise mode registers of the PSRAM + fn init_psram_mode_reg(spi_num: u32, mode_reg_config: &OpiPsramModeReg) { + let mode = ESP_ROM_SPIFLASH_OPI_DTR_MODE; + let cmd_len: u32 = 16; + let addr: u32 = 0x0; // 0x0 is the MR0 register + let addr_bit_len: u32 = 32; + let dummy = OCT_PSRAM_RD_DUMMY_BITLEN as u32; + let mut mode_reg = OpiPsramModeReg::default(); + let data_bit_len: u32 = 16; + + // read + unsafe { + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + addr, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut mode_reg.mr0, + data_bit_len, + 1 << 1, + false, + ); + } + + // modify + mode_reg.set_lt(mode_reg_config.lt()); + mode_reg.set_read_latency(mode_reg_config.read_latency()); + mode_reg.set_drive_str(mode_reg_config.drive_str()); + + // write + unsafe { + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_WRITE as u32, + cmd_len, + addr, + addr_bit_len, + 0, + &mode_reg.mr0, + 16, + core::ptr::null_mut(), + 0, + 1 << 1, + false, + ); + } + + // CONFIG_SPIRAM_ECC_ENABLE not yet supported + } + + fn print_psram_info(reg_val: &OpiPsramModeReg) { + info!( + "vendor id : {:02x} ({})", + reg_val.vendor_id(), + if reg_val.vendor_id() == 0x0d { + "AP" + } else { + "UNKNOWN" + } + ); + info!( + "dev id : {:02x} (generation {})", + reg_val.dev_id(), + reg_val.dev_id() + 1 + ); + info!( + "density : {:02x} ({} Mbit)", + reg_val.density(), + if reg_val.density() == 0x1 { + 32 + } else if reg_val.density() == 0x3 { + 64 + } else if reg_val.density() == 0x5 { + 128 + } else if reg_val.density() == 0x7 { + 256 + } else { + 0 + } + ); + info!( + "good-die : {:02x} ({})", + reg_val.gb(), + if reg_val.gb() == 1 { "Pass" } else { "Fail" } + ); + info!( + "Latency : {:02x} ({})", + reg_val.lt(), + if reg_val.lt() == 1 { + "Fixed" + } else { + "Variable" + } + ); + info!( + "VCC : {:02x} ({})", + reg_val.vcc(), + if reg_val.vcc() == 1 { "3V" } else { "1.8V" } + ); + info!( + "SRF : {:02x} ({} Refresh)", + reg_val.srf(), + if reg_val.srf() == 0x1 { "Fast" } else { "Slow" } + ); + info!( + "BurstType : {:02x} ({} Wrap)", + reg_val.bt(), + if reg_val.bt() == 1 && reg_val.bl() != 3 { + "Hybrid" + } else { + "" + } + ); + info!( + "BurstLen : {:02x} ({} Byte)", + reg_val.bl(), + if reg_val.bl() == 0x00 { + 16 + } else if reg_val.bl() == 0x01 { + 32 + } else if reg_val.bl() == 0x10 { + 64 + } else { + 1024 + } + ); + info!( + "Readlatency : {:02x} ({} cycles@{})", + reg_val.read_latency(), + reg_val.read_latency() * 2 + 6, + if reg_val.lt() == 1 { + "Fixed" + } else { + "Variable" + } + ); + info!( + "DriveStrength: {:02x} (1/{})", + reg_val.drive_str(), + if reg_val.drive_str() == 0x00 { + 1 + } else if reg_val.drive_str() == 0x01 { + 2 + } else if reg_val.drive_str() == 0x02 { + 4 + } else { + 8 + } + ); + } + + #[ram] + fn spi0_timing_config_set_core_clock(core_clock: SpiTimingConfigCoreClock) { + unsafe { + SPI0::regs().core_clk_sel().modify(|_, w| { + w.core_clk_sel().bits(match core_clock { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m => 0, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock120m => 1, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock160m => 2, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock240m => 3, + }) + }); + } + } + + #[ram] + fn spi0_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs() + .clock() + .modify(|_, w| w.clk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); + unsafe { + SPI0::regs().clock().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn spi1_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI1::regs() + .clock() + .modify(|_, w| w.clk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); + unsafe { + SPI1::regs().clock().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn spi0_timing_config_set_psram_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs() + .sram_clk() + .modify(|_, w| w.sclk_equ_sysclk().set_bit()); + } else { + let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_SCLKCNT_N_S) + | ((freqdiv / 2 - 1) << SPI_MEM_SCLKCNT_H_S) + | ((freqdiv - 1) << SPI_MEM_SCLKCNT_L_S); + unsafe { + SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); + } + } + } + + #[ram] + fn flash_clock_divider(config: &PsramConfig) -> u32 { + config.core_clock.unwrap_or_default() as u32 / config.flash_frequency as u32 + } + + #[ram] + fn psram_clock_divider(config: &PsramConfig) -> u32 { + config.core_clock.unwrap_or_default() as u32 / config.ram_frequency as u32 + } +} diff --git a/esp-hal/src/psram/mod.rs b/esp-hal/src/psram/mod.rs new file mode 100644 index 00000000000..a2dc5f3340a --- /dev/null +++ b/esp-hal/src/psram/mod.rs @@ -0,0 +1,119 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "octal" => { + cfg(octal_psram) => "Either `Octal` or `Quad` PSRAM will be used, depending on the setting of `ESP_HAL_CONFIG_PSRAM_MODE`.", + _ => "" + } +))] +//! # PSRAM (Pseudo-static RAM, SPI RAM) driver +//! +//! ## Overview +//! +//! The `PSRAM` module provides support for accessing and controlling the +//! `Pseudo Static Random Access Memory (PSRAM)`. `PSRAM` provides additional +//! external memory to supplement the internal memory of the MCU, allowing for +//! increased storage capacity and improved performance in certain applications. +//! +//! The mapped start address for PSRAM depends on the amount of mapped flash +//! memory. +//! +//! ## Examples +//! +//! ### PSRAM as heap memory +//! +//! This example shows how to use PSRAM as heap-memory via esp-alloc. +//! You need an MCU with at least 2 MB of PSRAM memory. +//! # {octal} +//! +//! > The PSRAM example **must** be built in release mode! +//! +//! ```rust, ignore +//! # {before_snippet} +//! # extern crate alloc; +//! # use alloc::{string::String, vec::Vec}; +//! # +//! // Add PSRAM to the heap. +//! esp_alloc::psram_allocator!(&peripherals.PSRAM, esp_hal::psram); +//! +//! let mut large_vec: Vec = Vec::with_capacity(500 * 1024 / 4); +//! +//! for i in 0..(500 * 1024 / 4) { +//! large_vec.push((i & 0xff) as u32); +//! } +//! +//! let string = String::from("A string allocated in PSRAM"); +//! # {after_snippet} +//! ``` + +use core::ops::Range; + +#[cfg(feature = "psram")] +#[cfg_attr(docsrs, doc(cfg(feature = "psram")))] +#[cfg_attr(esp32, path = "esp32.rs")] +#[cfg_attr(esp32s2, path = "esp32s2.rs")] +#[cfg_attr(esp32s3, path = "esp32s3.rs")] +pub(crate) mod implem; + +#[cfg(feature = "psram")] +#[cfg_attr(docsrs, doc(cfg(feature = "psram")))] +pub use implem::*; + +/// Size of PSRAM +/// +/// [PsramSize::AutoDetect] will try to detect the size of PSRAM +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum PsramSize { + /// Detect PSRAM size + #[default] + AutoDetect, + /// A fixed PSRAM size + Size(usize), +} + +impl PsramSize { + #[cfg_attr(not(feature = "psram"), expect(unused))] + pub(crate) fn get(&self) -> usize { + match self { + PsramSize::AutoDetect => 0, + PsramSize::Size(size) => *size, + } + } + + #[cfg_attr(not(feature = "psram"), expect(unused))] + pub(crate) fn is_auto(&self) -> bool { + matches!(self, PsramSize::AutoDetect) + } +} + +/// Returns the address and size of the available in external memory. +#[cfg(feature = "psram")] +pub fn psram_raw_parts(_psram: &crate::peripherals::PSRAM<'_>) -> (*mut u8, usize) { + let range = psram_range(); + (range.start as *mut u8, range.end - range.start) +} + +// Using static mut should be fine since we are only writing to it once during +// initialization. As other tasks and interrupts are not running yet, the worst +// that can happen is, that the user creates a DMA buffer before initializing +// the HAL. This will access the PSRAM range, returning an empty range - which +// is, at that point, true. The user has no (safe) means to allocate in PSRAM +// before initializing the HAL. +#[cfg(feature = "psram")] +static mut MAPPED_PSRAM: MappedPsram = MappedPsram { memory_range: 0..0 }; + +pub(crate) fn psram_range() -> Range { + cfg_if::cfg_if! { + if #[cfg(feature = "psram")] { + #[allow(static_mut_refs)] + unsafe { MAPPED_PSRAM.memory_range.clone() } + } else { + 0..0 + } + } +} + +#[cfg(feature = "psram")] +pub(crate) struct MappedPsram { + memory_range: Range, +} diff --git a/esp-hal/src/reg_access.rs b/esp-hal/src/reg_access.rs new file mode 100644 index 00000000000..50f3c305b56 --- /dev/null +++ b/esp-hal/src/reg_access.rs @@ -0,0 +1,259 @@ +//! Utils +//! +//! # Overview +//! +//! Collection of struct which helps you write to registers. + +use core::marker::PhantomData; + +const U32_ALIGN_SIZE: usize = core::mem::size_of::(); + +pub(crate) trait EndianessConverter { + fn u32_from_bytes(bytes: [u8; 4]) -> u32; + fn u32_to_bytes(word: u32) -> [u8; 4]; +} + +/// Always use native endianess +#[allow(unused)] // only used in AES driver for now +pub(crate) struct NativeEndianess; + +impl EndianessConverter for NativeEndianess { + fn u32_from_bytes(bytes: [u8; 4]) -> u32 { + u32::from_ne_bytes(bytes) + } + + fn u32_to_bytes(word: u32) -> [u8; 4] { + u32::to_ne_bytes(word) + } +} + +/// Use BE for ESP32, NE otherwise +#[derive(Debug, Clone)] +pub(crate) struct SocDependentEndianess; + +#[cfg(not(esp32))] +impl EndianessConverter for SocDependentEndianess { + fn u32_from_bytes(bytes: [u8; 4]) -> u32 { + u32::from_ne_bytes(bytes) + } + + fn u32_to_bytes(word: u32) -> [u8; 4] { + u32::to_ne_bytes(word) + } +} + +#[cfg(esp32)] +impl EndianessConverter for SocDependentEndianess { + fn u32_from_bytes(bytes: [u8; 4]) -> u32 { + u32::from_be_bytes(bytes) + } + + fn u32_to_bytes(word: u32) -> [u8; 4] { + u32::to_be_bytes(word) + } +} + +// The alignment helper helps you write to registers that only accept u32 +// using regular u8s (bytes). It keeps a write buffer of 4 u8 (could in theory +// be 3 but less convenient). And if the incoming data is not convertable to u32 +// (i.e not a multiple of 4 in length) it will store the remainder in the +// buffer until the next call. +// +// It assumes incoming `dst` are aligned to desired layout (in future +// ptr.is_aligned can be used). It also assumes that writes are done in FIFO +// order. +#[derive(Debug, Clone)] +#[cfg_attr(esp32c5, allow(unused))] +pub(crate) struct AlignmentHelper { + buf: [u8; U32_ALIGN_SIZE], + buf_fill: usize, + phantom: PhantomData, +} + +#[cfg_attr(esp32c5, allow(unused))] +impl AlignmentHelper { + pub fn default() -> AlignmentHelper { + AlignmentHelper { + buf: [0u8; U32_ALIGN_SIZE], + buf_fill: 0, + phantom: PhantomData, + } + } +} + +#[cfg_attr(esp32c5, allow(unused))] +impl AlignmentHelper { + pub fn reset(&mut self) { + self.buf_fill = 0; + } + + // This function will write any remaining buffer to dst and return the + // amount of *bytes* written (0 means no write). If the buffer is not + // aligned to the size of the register destination, it will append the '0' + // value. + pub fn flush_to(&mut self, dst_ptr: *mut u32, offset: usize) -> usize { + let offset = offset / U32_ALIGN_SIZE; + if self.buf_fill != 0 { + for i in self.buf_fill..U32_ALIGN_SIZE { + self.buf[i] = 0; + } + + unsafe { + dst_ptr + .add(offset) + .write_volatile(E::u32_from_bytes(self.buf)); + } + + // We return the **extra** bytes appended besides those already written into the buffer. + let ret = U32_ALIGN_SIZE - self.buf_fill; + self.buf_fill = 0; + + ret + } else { + 0 + } + } + + // This function is similar to `volatile_set_memory` but will prepend data that + // was previously ingested and ensure aligned (u32) writes. + pub fn volatile_write(&mut self, dst_ptr: *mut u32, val: u8, count: usize, offset: usize) { + let count = count.div_ceil(U32_ALIGN_SIZE); + let offset = offset / U32_ALIGN_SIZE; + + let dst_ptr = unsafe { dst_ptr.add(offset) }; + + let mut cursor = if self.buf_fill != 0 { + for i in self.buf_fill..U32_ALIGN_SIZE { + self.buf[i] = val; + } + + unsafe { + dst_ptr.write_volatile(E::u32_from_bytes(self.buf)); + } + + self.buf_fill = 0; + + 1 + } else { + 0 + }; + + while cursor < count { + unsafe { + dst_ptr + .add(cursor) + .write_volatile(E::u32_from_bytes([0_u8; 4])); + } + cursor += 1; + } + } + + // This function is similar to `volatile_copy_nonoverlapping_memory`, + // however it buffers up to a u32 in order to always write to registers in + // an aligned way. Additionally it will keep stop writing when the end of + // the register (defined by `dst_bound` relative to `dst`) and returns the + // remaining data (if not possible to write everything), and if it wrote + // till dst_bound or exited early (due to lack of data). + pub fn aligned_volatile_copy<'a>( + &mut self, + dst_ptr: *mut u32, + src: &'a [u8], + dst_bound: usize, + offset: usize, + ) -> (&'a [u8], bool) { + let dst_bound = dst_bound / U32_ALIGN_SIZE; + let offset = offset / U32_ALIGN_SIZE; + + assert!(dst_bound > 0); + + let dst_ptr = unsafe { dst_ptr.add(offset) }; + + let mut nsrc = src; + let mut cursor = 0; + + if self.buf_fill != 0 { + // First prepend existing data + let max_fill = U32_ALIGN_SIZE - self.buf_fill; + let (nbuf, src) = src.split_at(core::cmp::min(src.len(), max_fill)); + nsrc = src; + + for i in 0..max_fill { + match nbuf.get(i) { + Some(v) => { + self.buf[self.buf_fill] = *v; + self.buf_fill += 1; + } + None => return (&[], false), // Used up entire buffer before filling buff_fil + } + } + + unsafe { + dst_ptr.write_volatile(E::u32_from_bytes(self.buf)); + } + cursor += 1; + + self.buf_fill = 0; + } + + if dst_bound <= offset + cursor { + return (nsrc, true); + } + + let (to_write, remaining) = nsrc.split_at(core::cmp::min( + (dst_bound - offset - cursor) * U32_ALIGN_SIZE, + (nsrc.len() / U32_ALIGN_SIZE) * U32_ALIGN_SIZE, + )); + + if !to_write.is_empty() { + for (i, v) in to_write.chunks_exact(U32_ALIGN_SIZE).enumerate() { + unsafe { + dst_ptr + .add(i + cursor) + .write_volatile(E::u32_from_bytes(v.try_into().unwrap())); + } + } + } + + // If it's data we can't store we don't need to try and align it, just wait for + // next write Generally this applies when (src/4*4) != src + let was_bounded = (offset + cursor + to_write.len() / U32_ALIGN_SIZE) == dst_bound; + + if !remaining.is_empty() && remaining.len() < 4 { + self.buf[..remaining.len()].copy_from_slice(remaining); + self.buf_fill = remaining.len(); + + return (&[], was_bounded); + } + + (remaining, was_bounded) + } + + #[allow(dead_code)] + pub fn volatile_write_regset(&mut self, dst_ptr: *mut u32, src: &[u8], dst_bound: usize) { + let dst_bound = dst_bound / U32_ALIGN_SIZE; + assert!(dst_bound > 0); + assert!(src.len() <= dst_bound * 4); + + if !src.is_empty() { + for (i, v) in src.chunks_exact(U32_ALIGN_SIZE).enumerate() { + unsafe { + dst_ptr + .add(i) + .write_volatile(E::u32_from_bytes(v.try_into().unwrap())); + } + } + } + } + + pub fn volatile_read_regset(&self, src_ptr: *const u32, dst: &mut [u8], dst_bound: usize) { + let dst_bound = dst_bound / U32_ALIGN_SIZE; + assert!(dst.len() >= dst_bound * 4); + + let chunks = dst.chunks_exact_mut(U32_ALIGN_SIZE); + for (i, chunk) in chunks.enumerate() { + let read_val: [u8; U32_ALIGN_SIZE] = + unsafe { E::u32_to_bytes(src_ptr.add(i).read_volatile()) }; + chunk.copy_from_slice(&read_val); + } + } +} diff --git a/esp-hal/src/rmt.rs b/esp-hal/src/rmt.rs new file mode 100644 index 00000000000..5187bbd363f --- /dev/null +++ b/esp-hal/src/rmt.rs @@ -0,0 +1,3391 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "freq" => { + cfg(esp32h2) => "32", + _ => "80" + }, + "channel" => { + cfg(any(esp32, esp32s2)) => "channel0", + cfg(esp32s3) => "channel7", + _ => "channel2" + }, + "channels_desc" => { + cfg(esp32) => "8 channels, each of them can be either receiver or transmitter", + cfg(esp32s2) => "4 channels, each of them can be either receiver or transmitter", + cfg(esp32s3) => "8 channels, `Channel<0>`-`Channel<3>` hardcoded for transmitting signals and `Channel<4>`-`Channel<7>` hardcoded for receiving signals", + cfg(any(esp32c3, esp32c5, esp32c6, esp32h2)) => "4 channels, `Channel<0>` and `Channel<1>` hardcoded for transmitting signals and `Channel<2>` and `Channel<3>` hardcoded for receiving signals", + } +))] +//! # Remote Control Peripheral (RMT) +//! +//! ## Overview +//! The RMT (Remote Control) module is designed to send and receive infrared +//! remote control signals. A variety of remote control protocols can be +//! encoded/decoded via software based on the RMT module. The RMT module +//! converts pulse codes stored in the module’s built-in RAM into output +//! signals, or converts input signals into pulse codes and stores them in RAM. +//! In addition, the RMT module optionally modulates its output signals with a +//! carrier wave, or optionally demodulates and filters its input signals. +//! +//! Typically, the RMT peripheral can be used in the following scenarios: +//! - Transmit or receive infrared signals, with any IR protocols, e.g., NEC +//! - General-purpose sequence generator +//! - Transmit signals in a hardware-controlled loop, with a finite or infinite number of times +//! - Modulate the carrier to the output signal or demodulate the carrier from the input signal +//! +//! ### Channels +//! +//! There are __channels_desc__. +//! +//! For more information, please refer to the +#![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", chip!(), "/api-reference/peripherals/rmt.html)")] +//! ## Configuration +//! Each TX/RX channel has the same functionality controlled by a dedicated set +//! of registers and is able to independently transmit or receive data. TX +//! channels are indicated by n which is used as a placeholder for the channel +//! number, and by m for RX channels. +//! +//! ## Examples +//! +//! ### Initialization +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::gpio::Level; +//! # use esp_hal::peripherals::Peripherals; +//! # use esp_hal::rmt::TxChannelConfig; +//! # use esp_hal::rmt::Rmt; +//! # use crate::esp_hal::rmt::TxChannelCreator; +//! let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(__freq__))?; +//! let mut channel = rmt +//! .channel0 +//! .configure_tx( +//! &TxChannelConfig::default() +//! .with_clk_divider(1) +//! .with_idle_output_level(Level::Low) +//! .with_idle_output(false) +//! .with_carrier_modulation(false) +//! .with_carrier_high(1) +//! .with_carrier_low(1) +//! .with_carrier_level(Level::Low), +//! )? +//! .with_pin(peripherals.GPIO1); +//! # {after_snippet} +//! ``` +//! +//! ### TX operation +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::delay::Delay; +//! # use esp_hal::gpio::Level; +//! # use esp_hal::rmt::{PulseCode, Rmt, TxChannelConfig, TxChannelCreator}; +//! # +//! // Configure frequency based on chip type +//! let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(__freq__))?; +//! +//! let tx_config = TxChannelConfig::default().with_clk_divider(255); +//! +//! let mut channel = rmt +//! .channel0 +//! .configure_tx(&tx_config)? +//! .with_pin(peripherals.GPIO4); +//! +//! let delay = Delay::new(); +//! +//! let mut data = [PulseCode::new(Level::High, 200, Level::Low, 50); 20]; +//! data[data.len() - 2] = PulseCode::new(Level::High, 3000, Level::Low, 500); +//! data[data.len() - 1] = PulseCode::end_marker(); +//! +//! loop { +//! let transaction = channel.transmit(&data)?; +//! channel = transaction.wait()?; +//! delay.delay_millis(500); +//! } +//! # } +//! ``` +//! +//! ### RX operation +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::rmt::{PulseCode, Rmt, RxChannelConfig, RxChannelCreator}; +//! # use esp_hal::delay::Delay; +//! # use esp_hal::gpio::{Level, Output, OutputConfig}; +//! # +//! const WIDTH: usize = 80; +//! +//! let mut out = Output::new(peripherals.GPIO5, Level::Low, OutputConfig::default()); +//! +//! // Configure frequency based on chip type +//! let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(__freq__))?; +//! +//! let rx_config = RxChannelConfig::default() +//! .with_clk_divider(1) +//! .with_idle_threshold(10000); +//! let mut channel = rmt +//! .__channel__ +//! .configure_rx(&rx_config)? +//! .with_pin(peripherals.GPIO4); +//! let delay = Delay::new(); +//! let mut data: [PulseCode; 48] = [PulseCode::default(); 48]; +//! +//! loop { +//! for x in data.iter_mut() { +//! x.reset() +//! } +//! +//! let transaction = channel.receive(&mut data)?; +//! +//! // Simulate input +//! for i in 0u32..5u32 { +//! out.set_high(); +//! delay.delay_micros(i * 10 + 20); +//! out.set_low(); +//! delay.delay_micros(i * 20 + 20); +//! } +//! +//! match transaction.wait() { +//! Ok((symbol_count, channel_res)) => { +//! channel = channel_res; +//! let mut total = 0usize; +//! for entry in &data[..symbol_count] { +//! if entry.length1() == 0 { +//! break; +//! } +//! total += entry.length1() as usize; +//! +//! if entry.length2() == 0 { +//! break; +//! } +//! total += entry.length2() as usize; +//! } +//! +//! for entry in &data[..symbol_count] { +//! if entry.length1() == 0 { +//! break; +//! } +//! +//! let count = WIDTH / (total / entry.length1() as usize); +//! let c = match entry.level1() { +//! Level::High => '-', +//! Level::Low => '_', +//! }; +//! for _ in 0..count + 1 { +//! print!("{}", c); +//! } +//! +//! if entry.length2() == 0 { +//! break; +//! } +//! +//! let count = WIDTH / (total / entry.length2() as usize); +//! let c = match entry.level2() { +//! Level::High => '-', +//! Level::Low => '_', +//! }; +//! for _ in 0..count + 1 { +//! print!("{}", c); +//! } +//! } +//! +//! println!(); +//! } +//! Err((_err, channel_res)) => { +//! channel = channel_res; +//! } +//! } +//! +//! delay.delay_millis(1500); +//! } +//! # } +//! ``` +//! +//! > Note: on ESP32 and ESP32-S2 you cannot specify a base frequency other than 80 MHz + +use core::{ + default::Default, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use enumset::{EnumSet, EnumSetType}; +use esp_sync::RawMutex; +use portable_atomic::Ordering; +#[cfg(place_rmt_driver_in_ram)] +use procmacros::ram; + +use crate::{ + Async, + Blocking, + asynch::AtomicWaker, + clock::Clocks, + gpio::{ + self, + InputConfig, + Level, + OutputConfig, + PinGuard, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + peripherals::{Interrupt, RMT}, + system::GenericPeripheralGuard, + time::Rate, +}; + +mod reader; +use reader::{ReaderState, RmtReader}; +mod writer; +use writer::{RmtWriter, WriterState}; + +/// A configuration error +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// The desired frequency is impossible to reach + UnreachableTargetFrequency, + /// The idle threshold exceeds [`MAX_RX_IDLE_THRESHOLD`] + IdleThresholdOutOfRange, + /// The memsize is 0 or larger than what the channel can support + MemsizeOutOfRange, + /// (Part of) the requested channel memory is in use by another channel + MemoryBlockNotAvailable, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::UnreachableTargetFrequency => { + write!(f, "The desired frequency is impossible to reach") + } + Self::IdleThresholdOutOfRange => write!(f, "The idle threshold is out of range"), + Self::MemsizeOutOfRange => write!(f, "The memsize is out of range"), + Self::MemoryBlockNotAvailable => { + write!(f, "Memory block is not available for channel") + } + } + } +} + +/// Errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")] +#[non_exhaustive] +pub enum Error { + /// The amount of pulses exceeds the size of the FIFO + Overflow, + /// An argument is invalid + InvalidArgument, + /// An error occurred during transmission + TransmissionError, + /// No transmission end marker found + EndMarkerMissing, + /// The data length is invalid + InvalidDataLength, + /// Receiver error most likely RMT memory overflow + ReceiverError, +} + +impl core::error::Error for Error {} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Overflow => write!(f, "The amount of pulses exceeds the size of the FIFO"), + Self::InvalidArgument => write!(f, "An argument is invalid"), + Self::TransmissionError => write!(f, "An error occurred during transmission"), + Self::EndMarkerMissing => write!(f, "No transmission end marker found"), + Self::InvalidDataLength => write!(f, "The data length is invalid"), + Self::ReceiverError => write!(f, "Receiver error most likely RMT memory overflow"), + } + } +} + +/// Convenience newtype to work with pulse codes. +/// +/// A [`PulseCode`] is represented as `u32`, with fields laid out as follows: +/// +/// | Bit 31 | Bits 30-16 | Bit 15 | Bits 14-0 | +/// |----------|------------|----------|-----------| +/// | `level2` | `length2` | `level1` | `length1` | +/// +/// Here, `level1` / `length1` correspond to the signal that is send/received first, +/// and the signal with `level2` / `length2` is send/received afterwards. +/// +/// If `length1` or `length2` are zero, this implies an end marker and transmission will +/// stop with the corresponding signal. +#[derive(Clone, Copy, Default, Eq, PartialEq)] +#[repr(transparent)] +pub struct PulseCode(pub u32); + +// Pre-compute some constants to make it obvious that the code below doesn't mix up both halves of +// a PulseCode. +const LENGTH1_SHIFT: usize = 0; +const LEVEL1_SHIFT: usize = 15; +const LENGTH2_SHIFT: usize = 16; +const LEVEL2_SHIFT: usize = 31; + +const LENGTH_MASK: u32 = 0x7FFF; +const LENGTH1_MASK: u32 = LENGTH_MASK << LENGTH1_SHIFT; +const LENGTH2_MASK: u32 = LENGTH_MASK << LENGTH2_SHIFT; + +const LEVEL1_MASK: u32 = 1 << LEVEL1_SHIFT; +const LEVEL2_MASK: u32 = 1 << LEVEL2_SHIFT; + +impl PulseCode { + /// Maximum value for the `length1` and `length2` fields. + pub const MAX_LEN: u16 = 0x7FFF; + + /// Create a new instance. + /// + /// Panics if `length1` or `length2` exceed the maximum representable range. + #[inline] + pub const fn new(level1: Level, length1: u16, level2: Level, length2: u16) -> Self { + if length1 > Self::MAX_LEN || length2 > Self::MAX_LEN { + // defmt::panic! fails const eval + core::panic!("PulseCode length out of range"); + }; + + // SAFETY: + // - We just checked that length1 and length2 are in range + unsafe { Self::new_unchecked(level1, length1, level2, length2) } + } + + /// Create a new instance. + /// + /// If `length1` or `length2` exceed the maximum representable range, they + /// will be clamped to `Self::MAX_LEN`. + #[inline] + pub const fn new_clamped(level1: Level, length1: u16, level2: Level, length2: u16) -> Self { + // Can't use lengthX.min(Self::MAX_LEN) since it is not const + let length1 = if length1 > Self::MAX_LEN { + Self::MAX_LEN + } else { + length1 + }; + let length2 = if length2 > Self::MAX_LEN { + Self::MAX_LEN + } else { + length2 + }; + + // SAFETY: + // - We just clamped length1 and length2 to the required intervals + unsafe { Self::new_unchecked(level1, length1, level2, length2) } + } + + /// Create a new instance, attempting to convert lengths to `u16` first. + /// + /// This is slightly more convenient when passing in longer integers (e.g. `u32`) resulting from + /// a preceding calculation. + /// + /// If `length1` or `length2` fail to convert to `u16` or exceed the maximum representable + /// range, this will return `None`. + #[inline] + pub fn try_new( + level1: Level, + length1: impl TryInto, + level2: Level, + length2: impl TryInto, + ) -> Option { + let (Ok(length1), Ok(length2)) = (length1.try_into(), length2.try_into()) else { + return None; + }; + if length1 > Self::MAX_LEN || length2 >= Self::MAX_LEN { + return None; + } + + // SAFETY: + // - We just checked that length1 and length2 are in range + Some(unsafe { Self::new_unchecked(level1, length1, level2, length2) }) + } + + /// Create a new instance without checking that code lengths are in range. + /// + /// # Safety + /// + /// `length1` and `length2` must be 15-bit wide, i.e. their MSB must be cleared. + #[inline] + pub const unsafe fn new_unchecked( + level1: Level, + length1: u16, + level2: Level, + length2: u16, + ) -> Self { + Self( + (level1.const_into() as u32) << LEVEL1_SHIFT + | (level2.const_into() as u32) << LEVEL2_SHIFT + | (length1 as u32) << LENGTH1_SHIFT + | (length2 as u32) << LENGTH2_SHIFT, + ) + } + + /// Create a new instance that is an end marker with `Level::Low`. + /// + /// This corresponds to the all-zero [`PulseCode`], i.e. with both level and + /// length fields set to zero, equivalent to (but more semantic than) + /// `PulseCode::from(0u32)` and [`PulseCode::default()`]. + // FIXME: Consider adding a variant with `level1`, `length1` and `level2` arguments + // which sets `length2 = 0` so that it is still guaranteed to return an end + // marker. + #[inline] + pub const fn end_marker() -> Self { + Self(0) + } + + /// Set all levels and lengths to 0. + /// + /// In other words, assigns the value of [`PulseCode::end_marker()`] to `self`. + #[inline] + pub fn reset(&mut self) { + self.0 = 0 + } + + /// Logical output level in the first pulse code interval + #[inline] + pub const fn level1(self) -> Level { + let level = (self.0 >> LEVEL1_SHIFT) & 1; + Level::const_from(0 != level) + } + + /// Logical output level in the second pulse code interval + #[inline] + pub const fn level2(self) -> Level { + let level = (self.0 >> LEVEL2_SHIFT) & 1; + Level::const_from(0 != level) + } + + /// Length of the first pulse code interval (in clock cycles) + #[inline] + pub const fn length1(self) -> u16 { + ((self.0 >> LENGTH1_SHIFT) & LENGTH_MASK) as u16 + } + + /// Length of the second pulse code interval (in clock cycles) + #[inline] + pub const fn length2(self) -> u16 { + ((self.0 >> LENGTH2_SHIFT) & LENGTH_MASK) as u16 + } + + /// Set `level1` and return the modified [`PulseCode`]. + #[inline] + pub const fn with_level1(mut self, level: Level) -> Self { + self.0 &= !LEVEL1_MASK; + self.0 |= (level.const_into() as u32) << LEVEL1_SHIFT; + self + } + + /// Set `level2` and return the modified [`PulseCode`]. + #[inline] + pub const fn with_level2(mut self, level: Level) -> Self { + self.0 &= !LEVEL2_MASK; + self.0 |= (level.const_into() as u32) << LEVEL2_SHIFT; + self + } + + /// Set `length1` and return the modified [`PulseCode`]. + /// + /// Returns `None` if `length` exceeds the representable range. + #[inline] + pub const fn with_length1(mut self, length: u16) -> Option { + if length > Self::MAX_LEN { + return None; + } + + self.0 &= !LENGTH1_MASK; + self.0 |= (length as u32) << LENGTH1_SHIFT; + Some(self) + } + + /// Set `length2` and return the modified [`PulseCode`]. + /// + /// Returns `None` if `length` exceeds the representable range. + #[inline] + pub const fn with_length2(mut self, length: u16) -> Option { + if length > Self::MAX_LEN { + return None; + } + + self.0 &= !LENGTH2_MASK; + self.0 |= (length as u32) << LENGTH2_SHIFT; + Some(self) + } + + /// Return whether this pulse code contains an end marker. + /// + /// Equivalent to `self.length1() == 0 || self.length2() == 0`. + #[inline] + pub const fn is_end_marker(self) -> bool { + self.length1() == 0 || self.length2() == 0 + } + + #[inline] + fn symbol1(self) -> char { + if self.level1().into() { 'H' } else { 'L' } + } + + #[inline] + fn symbol2(self) -> char { + if self.level2().into() { 'H' } else { 'L' } + } +} + +impl core::fmt::Debug for PulseCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "PulseCode({} {}, {} {})", + self.symbol1(), + self.length1(), + self.symbol2(), + self.length2(), + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for PulseCode { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!( + fmt, + "PulseCode({} {}, {} {})", + self.symbol1(), + self.length1(), + self.symbol2(), + self.length2(), + ) + } +} + +impl From for PulseCode { + #[inline] + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + #[inline] + fn from(code: PulseCode) -> u32 { + code.0 + } +} + +/// Memory size associated to a channel. +/// +/// This is a newtype around the number of blocks as u8, thus not requiring any +/// extra space. However, it is useful to abstract memory sizes into their own +/// type to make explicit whether they refer to a number of RAM blocks or a +/// number of pulse codes and to centralize conversion between both. +#[derive(Copy, Clone)] +struct MemSize(u8); + +impl MemSize { + /// Create from the given number of RMT RAM blocks. + #[inline] + const fn from_blocks(blocks: u8) -> Self { + Self(blocks) + } + + /// Return the number of RMT RAM blocks specified by this `MemSize`. + #[inline] + const fn blocks(self) -> u8 { + self.0 + } + + /// Return the number of RMT pulse codes specified by this `MemSize`. + #[inline] + const fn codes(self) -> usize { + self.0 as usize * property!("rmt.channel_ram_size") + } +} + +/// Marker for a channel capable of/configured for transmit operations +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Tx; + +/// Marker for a channel capable of/configured for receive operations +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Rx; + +/// A trait implemented by the `Rx` and `Tx` marker structs. +/// +/// For internal use by the driver. +pub trait Direction: Copy + Clone + core::fmt::Debug + crate::private::Sealed + Unpin { + #[doc(hidden)] + const IS_TX: bool; +} + +impl crate::private::Sealed for Tx {} + +impl crate::private::Sealed for Rx {} + +impl Direction for Tx { + const IS_TX: bool = true; +} + +impl Direction for Rx { + const IS_TX: bool = false; +} + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DynChannelAccess { + ch_idx: ChannelIndex, + _direction: PhantomData, +} + +impl DynChannelAccess { + #[inline] + unsafe fn conjure(ch_idx: ChannelIndex) -> Self { + Self { + ch_idx, + _direction: PhantomData, + } + } +} + +/// Channel configuration for TX channels +#[derive(Debug, Copy, Clone, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TxChannelConfig { + /// Channel's clock divider + clk_divider: u8, + /// Whether the idle output level is low/high + idle_output_level: Level, + /// Whether idle output is enabled + idle_output: bool, + /// Whether carrier modulation is enabled + carrier_modulation: bool, + /// Carrier high phase in ticks + carrier_high: u16, + /// Carrier low phase in ticks + carrier_low: u16, + /// Level of the carrier + carrier_level: Level, + /// The amount of memory blocks allocated to this channel + memsize: u8, +} + +impl Default for TxChannelConfig { + fn default() -> Self { + Self { + clk_divider: Default::default(), + idle_output_level: Level::Low, + idle_output: Default::default(), + carrier_modulation: Default::default(), + carrier_high: Default::default(), + carrier_low: Default::default(), + carrier_level: Level::Low, + memsize: 1, + } + } +} + +/// Channel configuration for RX channels +#[derive(Debug, Copy, Clone, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxChannelConfig { + /// Channel's clock divider + clk_divider: u8, + /// Whether carrier demodulation is enabled + carrier_modulation: bool, + /// Carrier high phase in ticks + carrier_high: u16, + /// Carrier low phase in ticks + carrier_low: u16, + /// Level of the carrier + carrier_level: Level, + /// Filter threshold in ticks + filter_threshold: u8, + /// Idle threshold in ticks, must not exceed [`MAX_RX_IDLE_THRESHOLD`] + idle_threshold: u16, + /// The amount of memory blocks allocted to this channel + memsize: u8, +} + +impl Default for RxChannelConfig { + fn default() -> Self { + Self { + clk_divider: Default::default(), + carrier_modulation: Default::default(), + carrier_high: Default::default(), + carrier_low: Default::default(), + carrier_level: Level::Low, + filter_threshold: Default::default(), + idle_threshold: Default::default(), + memsize: 1, + } + } +} + +// Channel specification + +// Looping all channels: Build up the Rmt struct and compute NUM_CHANNELS +for_each_rmt_channel!( + (all $(($num:literal)),+) => { + paste::paste! { + /// RMT Instance + pub struct Rmt<'rmt, Dm> + where + Dm: crate::DriverMode, + { + peripheral: RMT<'rmt>, + $( + #[doc = concat!("RMT Channel ", $num)] + pub []: ChannelCreator<'rmt, Dm, $num>, + )+ + _mode: PhantomData, + } + + impl<'rmt, Dm> Rmt<'rmt, Dm> + where + Dm: crate::DriverMode, + { + fn create(peripheral: RMT<'rmt>) -> Self { + Self { + peripheral, + $( + []: ChannelCreator::conjure(), + )+ + _mode: PhantomData, + } + } + } + + impl<'rmt> Rmt<'rmt, Blocking> { + /// Reconfigures the driver for asynchronous operation. + pub fn into_async(mut self) -> Rmt<'rmt, Async> { + self.set_interrupt_handler(chip_specific::async_interrupt_handler); + + Rmt { + peripheral: self.peripheral, + $( + []: unsafe { self.[].into_async() }, + )+ + _mode: PhantomData, + } + } + } + + #[allow(clippy::no_effect)] + const NUM_CHANNELS: usize = const { 0 $( + {$num; 1} )+ }; + } + }; + + // Looping channel indices: Declare input/output signals and ChannelIndex + // The number of Rx and Tx channels is identical for all chips. + (tx $(($num:literal, $idx:literal)),+) => { + paste::paste! { + // Enum of valid channel indices: For the given chip, tx/rx channels for all of these indices + // exist. (Note that channel index == channel number for esp32 and esp32s2, but not for other + // chips.) + // + // This type is useful to inform the compiler of possible values of an u8 (i.e. we use this as + // homemade refinement type) which allows it to elide bounds checks in register/field + // accessors of the PAC even when using DynChannelAccess. + // + // Cf. https://github.com/rust-lang/rust/issues/109958 regarding rustc's capabilities here. + #[doc(hidden)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + #[allow(unused)] + pub enum ChannelIndex { + $( + [] = $idx, + )+ + } + + impl ChannelIndex { + #[allow(clippy::no_effect)] + const MAX: u8 = const { 0 $( + {$idx; 1} )+ }; + } + + const OUTPUT_SIGNALS: [gpio::OutputSignal; ChannelIndex::MAX as usize] = [ + $( + gpio::OutputSignal::[], + )+ + ]; + + $( + impl<'ch, Dm> TxChannelCreator<'ch, Dm> for ChannelCreator<'ch, Dm, $num> + where + Dm: crate::DriverMode, + { + fn configure_tx( + self, + config: &TxChannelConfig, + ) -> Result, ConfigError> { + let raw = unsafe { DynChannelAccess::conjure(ChannelIndex::[]) }; + + apply_tx_config(raw, config, false)?; + + Ok(Channel { + raw, + _rmt: core::marker::PhantomData, + _pin_guard: PinGuard::new_unconnected(), + _mem_guard: MemoryGuard::new(raw), + _guard: self._guard, + }) + } + } + )+ + } + }; + + (rx $(($num:literal, $idx:literal)),+) => { + paste::paste! { + const INPUT_SIGNALS: [gpio::InputSignal; ChannelIndex::MAX as usize] = [ + $( + gpio::InputSignal::[], + )+ + ]; + + $( + impl<'ch, Dm> RxChannelCreator<'ch, Dm> for ChannelCreator<'ch, Dm, $num> + where + Dm: crate::DriverMode, + { + fn configure_rx( + self, + config: &RxChannelConfig, + ) -> Result, ConfigError> { + let raw = unsafe { DynChannelAccess::conjure(ChannelIndex::[]) }; + + apply_rx_config(raw, config, false)?; + + Ok(Channel { + raw, + _rmt: core::marker::PhantomData, + _pin_guard: PinGuard::new_unconnected(), + _mem_guard: MemoryGuard::new(raw), + _guard: self._guard, + }) + } + } + )+ + } + }; +); + +struct ChannelIndexIter(u8); + +impl Iterator for ChannelIndexIter { + type Item = ChannelIndex; + + fn next(&mut self) -> Option { + let ch_idx = self.0; + if ch_idx < ChannelIndex::MAX { + self.0 = ch_idx + 1; + Some(unsafe { ChannelIndex::from_u8_unchecked(ch_idx) }) + } else { + None + } + } +} + +impl ChannelIndex { + fn iter_all() -> ChannelIndexIter { + ChannelIndexIter(0) + } + + unsafe fn from_u8_unchecked(ch_idx: u8) -> Self { + debug_assert!(ch_idx < ChannelIndex::MAX); + unsafe { core::mem::transmute(ch_idx) } + } +} + +impl<'rmt> Rmt<'rmt, Blocking> { + /// Create a new RMT instance + /// + /// ## Errors + /// + /// This function can return + /// - [`ConfigError::UnreachableTargetFrequency`]. + pub fn new(peripheral: RMT<'rmt>, frequency: Rate) -> Result { + let clk_src = ClockSource::default(); + let div = self::chip_specific::validate_clock(clk_src, frequency)?; + + // Only create the peripheral guards after validate_clock() to avoid that this function + // contains lots of code to drop them again on error. + let this = Rmt::create(peripheral); + + self::chip_specific::configure_clock(clk_src, div); + Ok(this) + } + + /// Registers an interrupt handler for the RMT peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, Interrupt::RMT); + } + crate::interrupt::bind_handler(Interrupt::RMT, handler); + } +} + +impl crate::private::Sealed for Rmt<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Rmt<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +// Mark the channel as used and reserve the channel RAM. +// +// If this is not possible (because a preceding channel is using the RAM, or +// because subsequent channels are in use so that we can't reserve the RAM), +// restore all state and return with an error. +#[inline(never)] +fn reserve_channel_memory( + channel: u8, + state: RmtState, + memsize: MemSize, +) -> Result<(), ConfigError> { + if memsize.blocks() == 0 || memsize.blocks() > NUM_CHANNELS as u8 - channel { + return Err(ConfigError::MemsizeOutOfRange); + } + + let mut next_state = state; + for cur_channel in channel..channel + memsize.blocks() { + if RmtState::compare_exchange( + cur_channel, + RmtState::Unconfigured, + next_state, + Ordering::Acquire, + Ordering::Relaxed, + ) + .is_err() + { + RmtState::store_range_rev( + RmtState::Unconfigured, + channel..cur_channel, + Ordering::Relaxed, + ); + + return Err(ConfigError::MemoryBlockNotAvailable); + } + + // Set the first channel to `state` (`Rx`|`Tx`), the remaining (if any) to + // `Reserved` + next_state = RmtState::Reserved; + } + + Ok(()) +} + +#[inline(never)] +fn release_channel_memory(raw: DynChannelAccess) { + let channel = raw.channel(); + let memsize = raw.memsize().blocks(); + + raw.set_memsize(MemSize::from_blocks(0)); + + RmtState::store_range_rev( + RmtState::Unconfigured, + channel..channel + memsize, + Ordering::Release, + ); +} + +// We store values of type `RmtState` in the global `STATE`. However, we also need atomic access, +// thus the enum needs to be represented as AtomicU8. Thus, we end up with unsafe conversions +// between `RmtState` and `u8` to avoid constant range checks. +// This submodule wraps all accesses in a safe API which ensures that only valid enum values can +// be stored to `STATE` such that other code in this driver does not need to be concerned with +// these details. +mod state { + use core::ops::Range; + + use portable_atomic::{AtomicU8, Ordering}; + + use super::{Direction, DynChannelAccess, NUM_CHANNELS}; + + static STATE: [AtomicU8; NUM_CHANNELS] = + [const { AtomicU8::new(RmtState::Unconfigured as u8) }; NUM_CHANNELS]; + + #[derive(Copy, Clone, PartialEq, Eq)] + #[repr(u8)] + pub(super) enum RmtState { + // The channel is not configured for either rx or tx, and its memory is available + Unconfigured, + + // The channels is not in use, but one of the preceding channels is using its memory + Reserved, + + // The channel is configured for rx + Rx, + + // The channel is configured for tx + Tx, + } + + impl RmtState { + /// Check whether this state corresponds to a rx, tx, or other configuration. + #[allow(unused)] + #[inline] + pub(super) fn is_tx(&self) -> Option { + match self { + Self::Rx => Some(false), + Self::Tx => Some(true), + _ => None, + } + } + + /// Convert a `u8` to `Self` without checking that it has a valid value for the enum. + /// + /// # Safety + /// + /// - Must only be called with valid values of the RmtState discrimiant + #[allow(unused)] + #[inline] + unsafe fn from_u8_unchecked(value: u8) -> Self { + unsafe { core::mem::transmute::<_, Self>(value) } + } + + /// Load channel state from the global `STATE` by channel index. + /// + /// # Safety: + /// + /// - the `channel` number must be in 0..NUM_CHANNELS + #[allow(unused)] + #[inline] + pub(super) unsafe fn load_by_channel_number(channel: u8, ordering: Ordering) -> Self { + unsafe { Self::from_u8_unchecked(STATE[channel as usize].load(ordering)) } + } + + /// Store the given state to all channel states for an index range in reverse order. + #[inline] + pub(super) fn store_range_rev(self, range: Range, ordering: Ordering) { + for ch_num in range.rev() { + STATE[ch_num as usize].store(self as u8, ordering); + } + } + + /// Store channel state to the global `STATE` given a `DynChannelAccess`. + #[allow(unused)] + #[inline] + pub(super) fn store(self, raw: DynChannelAccess, ordering: Ordering) { + STATE[raw.channel() as usize].store(self as u8, ordering); + } + + /// Load channel state from the global `STATE` given a `DynChannelAccess`. + #[allow(unused)] + #[inline] + pub(super) fn load(raw: DynChannelAccess, ordering: Ordering) -> Self { + // SAFETY: The only implementations of RawChannelAccess are in this module and can only + // be (safely) constructed using valid channel numbers. + unsafe { Self::load_by_channel_number(raw.channel(), ordering) } + } + + /// Perform a compare_exchange on the global `STATE` by channel index. + #[inline] + pub(super) fn compare_exchange( + ch_num: u8, + current: Self, + new: Self, + success: Ordering, + failure: Ordering, + ) -> Result { + STATE[ch_num as usize] + .compare_exchange(current as u8, new as u8, success, failure) + .map(|prev| unsafe { Self::from_u8_unchecked(prev) }) + .map_err(|prev| unsafe { Self::from_u8_unchecked(prev) }) + } + } +} + +use state::RmtState; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RmtClockGuard; + +impl RmtClockGuard { + fn new() -> Self { + #[cfg(soc_has_clock_node_rmt_sclk)] + crate::soc::clocks::ClockTree::with(|clocks| { + if crate::soc::clocks::rmt_sclk_config(clocks).is_none() { + crate::soc::clocks::configure_rmt_sclk(clocks, ClockSource::default().into()); + } + + crate::soc::clocks::request_rmt_sclk(clocks); + }); + + Self + } +} + +impl Drop for RmtClockGuard { + fn drop(&mut self) { + #[cfg(soc_has_clock_node_rmt_sclk)] + crate::soc::clocks::ClockTree::with(crate::soc::clocks::release_rmt_sclk); + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct ChannelGuards { + _peripheral: GenericPeripheralGuard<{ crate::system::Peripheral::Rmt as u8 }>, + _clock: RmtClockGuard, +} + +impl ChannelGuards { + fn new() -> Self { + Self { + _peripheral: GenericPeripheralGuard::new(), + _clock: RmtClockGuard::new(), + } + } +} + +/// RMT Channel +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Channel<'ch, Dm, Dir> +where + Dm: crate::DriverMode, + Dir: Direction, +{ + raw: DynChannelAccess, + + // Holds the lifetime for which have unique access to both the channel and the pin its + // configured for. Conceptually, for 'ch, we keep the Rmt peripheral alive. + _rmt: PhantomData>, + + _pin_guard: PinGuard, + + _mem_guard: MemoryGuard, + + // Only the "outermost" Channel/ChannelCreator holds these guards, which avoids constant + // inc/dec of the reference counts on reborrow and drop. + _guard: Option, +} + +// The reborrowing API treats Channel similar to a smart pointer: Ensure that it's size is actually +// in line with that notion. +const _: () = if core::mem::size_of::>() > 4 { + core::panic!("Channel growing too large!"); +}; + +/// Per-channel size of the RMT hardware buffer (number of `PulseCode`s). +pub const CHANNEL_RAM_SIZE: usize = property!("rmt.channel_ram_size"); + +/// Whether the channel supports wrapping rx (wrapping tx is supported on all devices) +pub const HAS_RX_WRAP: bool = property!("rmt.has_rx_wrap"); + +#[inline(never)] +fn apply_tx_config( + raw: DynChannelAccess, + config: &TxChannelConfig, + reconfigure: bool, +) -> Result<(), ConfigError> { + let memsize = MemSize::from_blocks(config.memsize); + if reconfigure { + release_channel_memory(raw); + } + reserve_channel_memory(raw.channel(), RmtState::Tx, memsize)?; + + raw.set_divider(config.clk_divider); + raw.set_tx_carrier( + config.carrier_modulation, + config.carrier_high, + config.carrier_low, + config.carrier_level, + ); + raw.set_tx_idle_output(config.idle_output, config.idle_output_level); + raw.set_memsize(memsize); + + Ok(()) +} + +#[inline(never)] +fn apply_rx_config( + raw: DynChannelAccess, + config: &RxChannelConfig, + reconfigure: bool, +) -> Result<(), ConfigError> { + #[cfg_attr(any(esp32, esp32s2), allow(clippy::absurd_extreme_comparisons))] + if config.idle_threshold > MAX_RX_IDLE_THRESHOLD { + return Err(ConfigError::IdleThresholdOutOfRange); + } + + let memsize = MemSize::from_blocks(config.memsize); + if reconfigure { + release_channel_memory(raw); + } + reserve_channel_memory(raw.channel(), RmtState::Rx, memsize)?; + + raw.set_divider(config.clk_divider); + raw.set_rx_carrier( + config.carrier_modulation, + config.carrier_high, + config.carrier_low, + config.carrier_level, + ); + raw.set_rx_filter_threshold(config.filter_threshold); + raw.set_rx_idle_threshold(config.idle_threshold); + raw.set_memsize(memsize); + + Ok(()) +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct MemoryGuard { + raw: Option>, + _dir: PhantomData, +} + +impl MemoryGuard { + fn new(raw: DynChannelAccess) -> Self { + Self { + raw: Some(raw), + _dir: PhantomData, + } + } + + fn new_empty() -> Self { + Self { + raw: None, + _dir: PhantomData, + } + } +} + +impl Drop for MemoryGuard { + fn drop(&mut self) { + if let Some(raw) = self.raw { + release_channel_memory(raw); + } + } +} + +impl Channel<'_, Dm, Dir> +where + Dm: crate::DriverMode, + Dir: Direction, +{ + /// Reborrow this channel for a shorter lifetime `'a`. + pub fn reborrow<'a>(&'a mut self) -> Channel<'a, Dm, Dir> { + Channel { + raw: self.raw, + _rmt: self._rmt, + // Resources must only be released once the parent is dropped. + _pin_guard: PinGuard::new_unconnected(), + _mem_guard: MemoryGuard::new_empty(), + _guard: None, + } + } +} + +impl<'ch, Dm> Channel<'ch, Dm, Tx> +where + Dm: crate::DriverMode, +{ + /// Connect a pin to the channel's output signal. + /// + /// This will replace previous pin assignments for this signal. + pub fn with_pin(mut self, pin: impl PeripheralOutput<'ch>) -> Self { + let pin = pin.into(); + + // Make sure to not cause a pulse between enabling the output and connecting it. + let (_, level) = self.raw.tx_idle_output(); + pin.set_output_high(level.into()); + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); + + self._pin_guard = pin.connect_with_guard(self.raw.output_signal()); + + self + } + + /// Change the configuration. + /// + /// ## Errors + /// + /// This function can return + /// - [`ConfigError::MemoryBlockNotAvailable`], + /// - [`ConfigError::MemsizeOutOfRange`]. + pub fn apply_config(&mut self, config: &TxChannelConfig) -> Result<(), ConfigError> { + apply_tx_config(self.raw, config, true) + } +} + +impl<'ch, Dm> Channel<'ch, Dm, Rx> +where + Dm: crate::DriverMode, +{ + /// Connect a pin to the channel's input signal. + /// + /// This will replace previous pin assignments for this signal. + pub fn with_pin(self, pin: impl PeripheralInput<'ch>) -> Self { + let pin = pin.into(); + pin.apply_input_config(&InputConfig::default()); + pin.set_input_enable(true); + + self.raw.input_signal().connect_to(&pin); + + self + } + + /// Change the configuration. + /// + /// ## Errors + /// + /// This function can return + /// - [`ConfigError::MemoryBlockNotAvailable`], + /// - [`ConfigError::MemsizeOutOfRange`], + /// - [`ConfigError::IdleThresholdOutOfRange`]. + pub fn apply_config(&mut self, config: &RxChannelConfig) -> Result<(), ConfigError> { + apply_rx_config(self.raw, config, true) + } +} + +/// Creates a TX channel +pub trait TxChannelCreator<'ch, Dm> +where + Dm: crate::DriverMode, +{ + /// Configure the TX channel + /// + /// ## Errors + /// + /// Returns errors under the same conditions as [`Channel::apply_config`]. + fn configure_tx(self, config: &TxChannelConfig) -> Result, ConfigError> + where + Self: Sized; +} + +/// Creates a RX channel +pub trait RxChannelCreator<'ch, Dm> +where + Dm: crate::DriverMode, +{ + /// Configure the RX channel + /// + /// ## Errors + /// + /// Returns errors under the same conditions as [`Channel::apply_config`]. + fn configure_rx(self, config: &RxChannelConfig) -> Result, ConfigError> + where + Self: Sized; +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TxGuard { + raw: Option>, +} + +impl TxGuard { + fn new(raw: DynChannelAccess) -> Self { + Self { raw: Some(raw) } + } + + fn new_empty() -> Self { + Self { raw: None } + } + + /// Indicate that the transaction has completed (or was never started) and does not require + /// explicit stopping on drop. + fn set_completed(&mut self) { + self.raw = None; + } + + fn is_active(&self) -> bool { + self.raw.is_some() + } +} + +impl Drop for TxGuard { + fn drop(&mut self) { + if let Some(raw) = self.raw { + if !matches!(raw.get_tx_status(), Some(Event::Error | Event::End)) { + raw.stop_tx(); + raw.update(); + + // Block until the channel is safe to use again. + #[cfg(not(rmt_has_tx_immediate_stop))] + while !matches!(raw.get_tx_status(), Some(Event::Error | Event::End)) {} + } + + raw.unlisten_tx_interrupt(EnumSet::all()); + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RxGuard { + raw: Option>, +} + +impl RxGuard { + fn new(raw: DynChannelAccess) -> Self { + Self { raw: Some(raw) } + } + + fn new_empty() -> Self { + Self { raw: None } + } + + /// Indicate that the transaction has completed (or was never started) and does not require + /// explicit stopping on drop. + fn set_completed(&mut self) { + self.raw = None; + } +} + +impl Drop for RxGuard { + fn drop(&mut self) { + if let Some(raw) = self.raw { + raw.stop_rx(true); + raw.update(); + + raw.unlisten_rx_interrupt(EnumSet::all()); + } + } +} + +/// An in-progress transaction for a single shot TX transaction. +/// +/// If the data size exceeds the size of the internal buffer, `.poll()` or +/// `.wait()` needs to be called before the entire buffer has been sent to avoid +/// underruns. +#[must_use = "transactions need to be `poll()`ed / `wait()`ed for to ensure progress"] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TxTransaction<'ch, 'data> { + // This must go first such that it is dropped before the channel (which might disable the + // peripheral on drop)! + _guard: TxGuard, + + channel: Channel<'ch, Blocking, Tx>, + + writer: RmtWriter, + + // Remaining data that has not yet been written to channel RAM. May be empty. + remaining_data: &'data [PulseCode], +} + +impl<'ch> TxTransaction<'ch, '_> { + #[cfg_attr(place_rmt_driver_in_ram, ram)] + fn poll_internal(&mut self) -> Option { + let raw = self.channel.raw; + + let status = raw.get_tx_status(); + if status == Some(Event::Threshold) { + raw.clear_tx_interrupts(Event::Threshold); + + // `RmtWriter::write()` is safe to call even if `poll_internal` is called repeatedly + // after the data is exhausted since it returns immediately if already done. + self.writer.write(&mut self.remaining_data, raw, false); + } + + status + } + + /// Check transmission status and write new data to the hardware if + /// necessary. + /// + /// Returns whether transmission has ended (whether successfully or with an + /// error). In that case, a subsequent call to `wait()` returns immediately. + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + pub fn poll(&mut self) -> bool { + matches!(self.poll_internal(), Some(Event::Error | Event::End)) + } + + /// Wait for the transaction to complete + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + pub fn wait( + mut self, + ) -> Result, (Error, Channel<'ch, Blocking, Tx>)> { + // Not sure that all the error cases below can happen. However, it's best to + // handle them to be sure that we don't lock up here in case they can happen. + let result = loop { + match self.poll_internal() { + Some(Event::Error) => break Err(Error::TransmissionError), + Some(Event::End) => break self.writer.state().to_result(), + _ => continue, + } + }; + + self._guard.set_completed(); + + match result { + Ok(()) => Ok(self.channel), + Err(err) => Err((err, self.channel)), + } + } +} + +/// An in-progress continuous TX transaction +#[must_use = "transactions will be aborted when dropped"] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ContinuousTxTransaction<'ch> { + // This must go first such that it is dropped before the channel (which might disable the + // peripheral on drop)! + _guard: TxGuard, + + channel: Channel<'ch, Blocking, Tx>, +} + +impl<'ch> ContinuousTxTransaction<'ch> { + // FIXME: This interface isn't great, since one cannot use the waiting time until tx is stopped + // for other things! Implement a poll-like interface similar to TxTransaction! + /// Stop transaction when the current iteration ends. + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + pub fn stop_next( + self, + ) -> Result, (Error, Channel<'ch, Blocking, Tx>)> { + self.stop_impl(false) + } + + /// Stop transaction as soon as possible. + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + pub fn stop(self) -> Result, (Error, Channel<'ch, Blocking, Tx>)> { + self.stop_impl(true) + } + + #[cfg_attr(place_rmt_driver_in_ram, ram)] + fn stop_impl( + mut self, + immediate: bool, + ) -> Result, (Error, Channel<'ch, Blocking, Tx>)> { + let raw = self.channel.raw; + + let mut result = Ok(()); + if self._guard.is_active() { + // If rmt_has_tx_loop_auto_stop and the engine is stopped already, this is not + // necessary. However, explicitly stopping unconditionally makes the logic + // here much simpler and shouldn't create much overhead. + raw.set_tx_continuous(false); + if immediate { + raw.stop_tx() + } + raw.update(); + + if !immediate || !cfg!(rmt_has_tx_immediate_stop) { + loop { + match raw.get_tx_status() { + Some(Event::Error) => { + result = Err(Error::TransmissionError); + break; + } + Some(Event::End) => break, + Some(Event::LoopCount) if cfg!(rmt_has_tx_loop_auto_stop) => break, + _ => continue, + } + } + } + } + + self._guard.set_completed(); + + match result { + Ok(()) => Ok(self.channel), + Err(err) => Err((err, self.channel)), + } + } + + /// Check if the `loopcount` interrupt bit is set. + /// + /// Whether this implies that the transmission has stopped depends on the [`LoopMode`] value + /// provided when starting it. + #[cfg(rmt_has_tx_loop_count)] + pub fn is_loopcount_interrupt_set(&self) -> bool { + !self._guard.is_active() || self.channel.raw.is_tx_loopcount_interrupt_set() + } +} + +/// RMT Channel Creator +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ChannelCreator<'ch, Dm, const CHANNEL: u8> +where + Dm: crate::DriverMode, +{ + // Conceptually, this retains a reference to the main driver struct, even when the + // `ChannelCreator` is taken out of it and the `Rmt` dropped. This prevents re-inititalizing + // the `Rmt` and obtaining a duplicate `ChannelCreator`. + _rmt: PhantomData>, + + // We need to keep the peripheral and source clocks alive since the following sequence of + // events is possible: + // + // ``` + // let cc = { + // let rmt = Rmt::new(peripheral, freq); // 1 + // rmt.channel0 // 2 + // }; // 3 (drop other ChannelCreators and Rmt.peripheral) + // let ch0 = cc.configure_tx(pin, config).unwrap(); // 4 (create Channel._guard) + // ch0.transmit(...); // 5 + // ``` + // + // If there was no _guard in ChannelCreator, the peripheral would be disabled in step 3, and + // re-enabled in step 4, losing the clock configuration that was set in step 1. + _guard: Option, +} + +impl<'ch, Dm, const CHANNEL: u8> ChannelCreator<'ch, Dm, CHANNEL> +where + Dm: crate::DriverMode, +{ + fn conjure() -> Self { + Self { + _rmt: PhantomData, + _guard: Some(ChannelGuards::new()), + } + } + + unsafe fn into_async(self) -> ChannelCreator<'ch, Async, CHANNEL> { + ChannelCreator { + _rmt: PhantomData, + _guard: self._guard, + } + } + + /// Reborrow this channel creator for a shorter lifetime `'a`. + pub fn reborrow<'a>(&'a mut self) -> ChannelCreator<'a, Dm, CHANNEL> { + Self { + _rmt: PhantomData, + // Resources must only be released once the parent is dropped. + _guard: None, + } + } + + /// Unsafely steal a channel creator instance. + /// + /// # Safety + /// + /// Circumvents HAL ownership and safety guarantees and allows creating + /// multiple handles to the same peripheral structure. + #[inline] + pub unsafe fn steal() -> Self { + Self { + _rmt: PhantomData, + _guard: Some(ChannelGuards::new()), + } + } +} + +/// Loop mode for continuous transmission +/// +/// Depending on hardware support, the `loopcount` interrupt and automatic stopping of the +/// transmission upon reaching a specified loop count may not be available. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LoopMode { + /// Repeat until explicitly stopped. + Infinite, + + // FIXME: Does continuous tx trigger the End interrupt on each repetition such that it could + // be used to emulate the LoopCount interrupt on devices that lack it? + /// Repeat until explicitly stopped, and assert the loop count interrupt upon completing the + /// given number of iterations. + /// + /// Loop counts larger than [`MAX_TX_LOOPCOUNT`] will result in an error. + #[cfg(rmt_has_tx_loop_count)] + InfiniteWithInterrupt(u16), + + /// Repeat for the given number of iterations, and also set the loop count interrupt flag upon + /// completion. + /// + /// If the iteration count is 0, the transaction will complete immediately without + /// starting the transmitter. + /// + /// Loop counts larger than [`MAX_TX_LOOPCOUNT`] will result in an error. + #[cfg(rmt_has_tx_loop_auto_stop)] + Finite(u16), +} + +impl LoopMode { + #[allow(unused)] + fn get_count(self) -> u16 { + match self { + Self::Infinite => 0, + #[cfg(rmt_has_tx_loop_count)] + Self::InfiniteWithInterrupt(count) => count, + #[cfg(rmt_has_tx_loop_auto_stop)] + Self::Finite(count) => count, + } + } +} + +/// Channel in TX mode +impl<'ch> Channel<'ch, Blocking, Tx> { + /// Start transmitting the given pulse code sequence. + /// This returns a [`TxTransaction`] which can be used to wait for + /// the transaction to complete and get back the channel for further + /// use. + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub fn transmit<'data>( + self, + mut data: &'data [PulseCode], + ) -> Result, (Error, Self)> { + let raw = self.raw; + let memsize = raw.memsize(); + + let mut writer = RmtWriter::new(); + writer.write(&mut data, raw, true); + + if let WriterState::Error(e) = writer.state() { + return Err((e, self)); + } + + raw.clear_tx_interrupts(EnumSet::all()); + raw.start_send(None, memsize); + + Ok(TxTransaction { + channel: self, + writer, + remaining_data: data, + _guard: TxGuard::new(raw), + }) + } + + /// Start transmitting the given pulse code continuously. + /// + /// This returns a [`ContinuousTxTransaction`] which can be used to stop the + /// ongoing transmission and get back the channel for further use. + /// + /// The `mode` argument determines whether transmission will continue until explicitly stopped + /// or for a fixed number of iterations; see [`LoopMode`] for more details. + #[cfg_attr( + rmt_has_tx_loop_count, + doc = "When using a loop `mode` other than [`LoopMode::Infinite`], [`ContinuousTxTransaction::is_loopcount_interrupt_set`] can be used to check if the loop count is reached." + )] + /// The length of `data` cannot exceed the size of the allocated RMT RAM. + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub fn transmit_continuously( + self, + mut data: &[PulseCode], + mode: LoopMode, + ) -> Result, (Error, Self)> { + let raw = self.raw; + let memsize = raw.memsize(); + + #[cfg(rmt_has_tx_loop_count)] + if mode.get_count() > MAX_TX_LOOPCOUNT { + return Err((Error::InvalidArgument, self)); + } + + let mut writer = RmtWriter::new(); + writer.write(&mut data, raw, true); + + match writer.state() { + WriterState::Error(e) => return Err((e, self)), + WriterState::Active => return Err((Error::Overflow, self)), + WriterState::Done => (), + } + + let mut _guard = TxGuard::new(raw); + #[cfg(rmt_has_tx_loop_auto_stop)] + if mode == LoopMode::Finite(0) { + _guard.set_completed(); + } + + if _guard.is_active() { + raw.clear_tx_interrupts(EnumSet::all()); + raw.start_send(Some(mode), memsize); + } + + Ok(ContinuousTxTransaction { + channel: self, + _guard, + }) + } +} + +/// RX transaction instance +#[must_use = "transactions need to be `poll()`ed / `wait()`ed for to ensure progress"] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxTransaction<'ch, 'data> { + // This must go first such that it is dropped before the channel (which might disable the + // peripheral on drop)! + _guard: RxGuard, + + channel: Channel<'ch, Blocking, Rx>, + + reader: RmtReader, + + data: &'data mut [PulseCode], +} + +impl<'ch> RxTransaction<'ch, '_> { + #[cfg_attr(place_rmt_driver_in_ram, ram)] + fn poll_internal(&mut self) -> Option { + let raw = self.channel.raw; + + let status = raw.get_rx_status(); + + match status { + // Read all available data also on error + Some(Event::End | Event::Error) => { + // Do not clear the interrupt flags here: Subsequent calls of wait() must + // be able to observe them if this is currently called via poll() + // Rx is stopped already, but we do need to clear the rx enable flag! + // Otherwise the next `raw.update` call will start it even though that + // might not be desired. + raw.stop_rx(false); + + // `RmtReader::read()` is safe to call even if `poll_internal` is called repeatedly + // after the receiver finished since it returns immediately if already done. + self.reader.read(&mut self.data, raw, true); + } + #[cfg(rmt_has_rx_wrap)] + Some(Event::Threshold) => { + raw.clear_rx_interrupts(Event::Threshold); + + if self.reader.state == ReaderState::Active { + self.reader.read(&mut self.data, raw, false); + } + } + _ => (), + } + + status + } + + /// Check receive status + /// + /// Returns whether reception has ended (whether successfully or with an + /// error). In that case, a subsequent call to `wait()` returns immediately. + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + pub fn poll(&mut self) -> bool { + matches!(self.poll_internal(), Some(Event::Error | Event::End)) + } + + /// Wait for the transaction to complete + #[cfg_attr(place_rmt_driver_in_ram, inline(always))] + // The return type isn't nice, but the blocking API needs a broader redesign anyway. + #[allow(clippy::type_complexity)] + pub fn wait( + mut self, + ) -> Result<(usize, Channel<'ch, Blocking, Rx>), (Error, Channel<'ch, Blocking, Rx>)> { + let result = loop { + match self.poll_internal() { + Some(Event::Error) => break Err(Error::ReceiverError), + Some(Event::End) => break Ok(self.reader.total), + _ => continue, + } + }; + + self._guard.set_completed(); + + match result { + Ok(total) => Ok((total, self.channel)), + Err(err) => Err((err, self.channel)), + } + } +} + +/// Channel is RX mode +impl<'ch> Channel<'ch, Blocking, Rx> { + #[procmacros::doc_replace( + "rx_size_limit" => { + cfg(any(esp32, esp32s2)) => "The length of the received data cannot exceed the allocated RMT RAM.", + _ => "" + } + )] + /// Start receiving pulse codes into the given buffer. + /// This returns a [RxTransaction] which can be used to wait for receive to + /// complete and get back the channel for further use. + /// + /// # {rx_size_limit} + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub fn receive<'data>( + self, + data: &'data mut [PulseCode], + ) -> Result, (Error, Self)> { + let raw = self.raw; + let memsize = raw.memsize(); + + if !property!("rmt.has_rx_wrap") && data.len() > memsize.codes() { + return Err((Error::InvalidDataLength, self)); + } + + let reader = RmtReader::new(); + + raw.clear_rx_interrupts(EnumSet::all()); + raw.start_receive(true, memsize); + + Ok(RxTransaction { + channel: self, + reader, + data, + _guard: RxGuard::new(raw), + }) + } +} + +static WAKER: [AtomicWaker; NUM_CHANNELS] = [const { AtomicWaker::new() }; NUM_CHANNELS]; + +// Lock to synchronize access to registers that are shared between channels. +static RMT_LOCK: RawMutex = RawMutex::new(); + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct TxFuture<'a> { + raw: DynChannelAccess, + _phantom: PhantomData>, + writer: RmtWriter, + + // Remaining data that has not yet been written to channel RAM. May be empty. + data: &'a [PulseCode], + + _guard: TxGuard, +} + +impl core::future::Future for TxFuture<'_> { + type Output = Result<(), Error>; + + #[cfg_attr(place_rmt_driver_in_ram, ram)] + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + let raw = this.raw; + + if let WriterState::Error(err) = this.writer.state() { + return Poll::Ready(Err(err)); + } + + WAKER[raw.channel() as usize].register(ctx.waker()); + + let result = match raw.get_tx_status() { + Some(Event::Error) => Err(Error::TransmissionError), + Some(Event::End) => this.writer.state().to_result(), + Some(Event::Threshold) => { + raw.clear_tx_interrupts(Event::Threshold); + + this.writer.write(&mut this.data, raw, false); + + if this.writer.state() == WriterState::Active { + raw.listen_tx_interrupt(Event::Threshold); + } + + return Poll::Pending; + } + _ => return Poll::Pending, + }; + + this._guard.set_completed(); + + Poll::Ready(result) + } +} + +/// TX channel in async mode +impl Channel<'_, Async, Tx> { + /// Start transmitting the given pulse code sequence. + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub fn transmit(&mut self, mut data: &[PulseCode]) -> impl Future> { + let raw = self.raw; + let memsize = raw.memsize(); + + let mut writer = RmtWriter::new(); + writer.write(&mut data, raw, true); + + let _guard = if writer.state().is_ok() { + let wrap = match writer.state() { + WriterState::Error(_) => false, + WriterState::Active => true, + WriterState::Done => false, + }; + + raw.clear_tx_interrupts(EnumSet::all()); + let mut events = Event::End | Event::Error; + if wrap { + events |= Event::Threshold; + } + raw.listen_tx_interrupt(events); + raw.start_send(None, memsize); + + TxGuard::new(raw) + } else { + TxGuard::new_empty() + }; + + TxFuture { + raw, + _phantom: PhantomData, + writer, + data, + _guard, + } + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct RxFuture<'a> { + raw: DynChannelAccess, + _phantom: PhantomData>, + reader: RmtReader, + data: &'a mut [PulseCode], + _guard: RxGuard, +} + +impl core::future::Future for RxFuture<'_> { + type Output = Result; + + #[cfg_attr(place_rmt_driver_in_ram, ram)] + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + let raw = this.raw; + + if let ReaderState::Error(err) = this.reader.state { + return Poll::Ready(Err(err)); + } + + WAKER[raw.channel() as usize].register(ctx.waker()); + + let result = match raw.get_rx_status() { + // Read all available data also on error + Some(ev @ (Event::End | Event::Error)) => { + // Rx is stopped already, but we do need to clear the rx enable flag! + // Otherwise the next `raw.update` call will start it even though that + // might not be desired. + raw.stop_rx(false); + + this.reader.read(&mut this.data, raw, true); + + match ev { + Event::Error => Err(Error::ReceiverError), + _ => Ok(this.reader.total), + } + } + #[cfg(rmt_has_rx_wrap)] + Some(Event::Threshold) => { + raw.clear_rx_interrupts(Event::Threshold); + + this.reader.read(&mut this.data, raw, false); + + if this.reader.state == ReaderState::Active { + raw.listen_rx_interrupt(Event::Threshold); + } + + return Poll::Pending; + } + _ => return Poll::Pending, + }; + + this._guard.set_completed(); + + Poll::Ready(result) + } +} + +/// RX channel in async mode +impl Channel<'_, Async, Rx> { + #[procmacros::doc_replace( + "rx_size_limit" => { + cfg(any(esp32, esp32s2)) => "The length of the received data cannot exceed the allocated RMT RAM.", + _ => "" + } + )] + /// Start receiving a pulse code sequence. + /// + /// # {rx_size_limit} + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub fn receive( + &mut self, + data: &mut [PulseCode], + ) -> impl Future> { + let raw = self.raw; + let memsize = raw.memsize(); + + let mut reader = RmtReader::new(); + + let _guard = if !property!("rmt.has_rx_wrap") && data.len() > memsize.codes() { + reader.state = ReaderState::Error(Error::InvalidDataLength); + RxGuard::new_empty() + } else { + raw.clear_rx_interrupts(EnumSet::all()); + raw.listen_rx_interrupt(Event::End | Event::Error | Event::Threshold); + raw.start_receive(true, memsize); + RxGuard::new(raw) + }; + + RxFuture { + raw, + reader, + data, + _phantom: PhantomData, + _guard, + } + } +} + +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Event { + Error, + Threshold, + End, + LoopCount, +} + +impl DynChannelAccess { + #[inline] + fn channel_ram_start_offset(self) -> usize { + usize::from(self.channel()) * property!("rmt.channel_ram_size") + } + + #[inline] + fn channel_ram_start(self) -> *mut PulseCode { + unsafe { + (property!("rmt.ram_start") as *mut PulseCode).add(self.channel_ram_start_offset()) + } + } +} + +impl DynChannelAccess { + #[inline(always)] + fn output_signal(self) -> gpio::OutputSignal { + OUTPUT_SIGNALS[self.ch_idx as usize] + } + + // We could obtain `memsize` via `self.memsize()` here. However, it is already known at all + // call sites, and passing it as argument avoids a volatile read that the compiler wouldn't be + // able to deduplicate. + #[inline(always)] + fn start_send(self, loopmode: Option, memsize: MemSize) { + self.set_tx_threshold((memsize.codes() / 2) as u8); + self.set_tx_continuous(loopmode.is_some()); + #[cfg(rmt_has_tx_loop_count)] + self.set_loopmode(loopmode); + self.set_tx_wrap_mode(loopmode.is_none()); + self.update(); + self.start_tx(); + self.update(); + } + + #[inline(always)] + fn listen_tx_interrupt(self, event: impl Into>) { + self.set_tx_interrupt(event.into(), true); + } + + #[inline(always)] + fn unlisten_tx_interrupt(self, event: impl Into>) { + self.set_tx_interrupt(event.into(), false); + } +} + +impl DynChannelAccess { + #[inline(always)] + fn input_signal(self) -> gpio::InputSignal { + INPUT_SIGNALS[self.ch_idx as usize] + } + + #[inline(always)] + fn start_receive(self, _wrap: bool, _memsize: MemSize) { + #[cfg(rmt_has_rx_wrap)] + { + self.set_rx_threshold((_memsize.codes() / 2) as u16); + self.set_rx_wrap_mode(_wrap); + self.update(); + } + + self.start_rx(); + self.update(); + } + + #[inline(always)] + fn listen_rx_interrupt(self, event: impl Into>) { + self.set_rx_interrupt(event.into(), true); + } + + #[inline(always)] + fn unlisten_rx_interrupt(self, event: impl Into>) { + self.set_rx_interrupt(event.into(), false); + } +} + +for_each_rmt_clock_source!( + (all $(($name:ident, $bits:literal)),+) => { + #[derive(Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + enum ClockSource { + $( + #[allow(unused)] + $name = $bits, + )+ + } + }; + + (is_boolean) => { + impl ClockSource { + fn bit(self) -> bool { + match (self as u8) { + 0 => false, + 1 => true, + _ => unreachable!("should be removed by the compiler!"), + } + } + } + }; + + (default ($name:ident)) => { + impl ClockSource { + fn default() -> Self { + Self::$name + } + + fn bits(self) -> u8 { + self as u8 + } + + fn freq(self) -> crate::time::Rate { + match self { + #[cfg(rmt_supports_apb_clock)] + ClockSource::Apb => Clocks::get().apb_clock, + + #[cfg(rmt_supports_rcfast_clock)] + ClockSource::RcFast => { + Rate::from_hz(crate::soc::clocks::ClockTree::with( + crate::soc::clocks::rc_fast_clk_frequency, + )) + } + + #[cfg(rmt_supports_xtal_clock)] + ClockSource::Xtal => Clocks::get().xtal_clock, + + #[cfg(rmt_supports_pll80mhz_clock)] + ClockSource::Pll80MHz => Rate::from_mhz(80), + + #[cfg(rmt_supports_reftick_clock)] + ClockSource::RefTick => todo!(), + } + } + } + }; +); + +#[cfg(soc_has_clock_node_rmt_sclk)] +impl From for crate::soc::clocks::RmtSclkConfig { + fn from(value: ClockSource) -> Self { + match value { + #[cfg(rmt_supports_apb_clock)] + ClockSource::Apb => Self::ApbClk, + + #[cfg(rmt_supports_rcfast_clock)] + ClockSource::RcFast => Self::RcFastClk, + + #[cfg(rmt_supports_xtal_clock)] + ClockSource::Xtal => Self::XtalClk, + + #[cfg(rmt_supports_pll80mhz_clock)] + ClockSource::Pll80MHz => Self::PllF80m, + + #[cfg(rmt_supports_reftick_clock)] + ClockSource::RefTick => unreachable!(), + } + } +} + +// Obtain maximum value for a register field from the PAC's register spec. +macro_rules! max_from_register_spec { + ($typ:ty, $reg:ident, $spec:ident, $w:ident) => {{ + use crate::soc::pac::rmt::$reg as reg; + type Spec = reg::$spec; + const WIDTH: u8 = reg::$w::::WIDTH; + + const _: () = if WIDTH as u32 > <$typ>::BITS { + core::panic!("Unexpectedly large register WIDTH"); + }; + + // Fits into $ty according to the assertion above + ((1u32 << WIDTH) - 1) as $typ + }}; +} + +#[cfg(not(any(esp32, esp32s2)))] +mod chip_specific { + use enumset::EnumSet; + #[cfg(place_rmt_driver_in_ram)] + use procmacros::ram; + + use super::{ + ChannelIndex, + ClockSource, + ConfigError, + Direction, + DynChannelAccess, + Event, + Level, + LoopMode, + MemSize, + RMT_LOCK, + Rx, + Tx, + WAKER, + }; + use crate::{peripherals::RMT, time::Rate}; + + pub(super) fn validate_clock(source: ClockSource, frequency: Rate) -> Result { + let src_clock = source.freq(); + + if frequency > src_clock { + return Err(ConfigError::UnreachableTargetFrequency); + } + + let div = src_clock + .as_hz() + .checked_div(frequency.as_hz()) + .ok_or(ConfigError::UnreachableTargetFrequency)?; + + u8::try_from(div - 1).map_err(|_| ConfigError::UnreachableTargetFrequency) + } + + pub(super) fn configure_clock(source: ClockSource, div: u8) { + #[cfg(soc_has_clock_node_rmt_sclk)] + let _ = source; + + #[cfg(not(soc_has_pcr))] + RMT::regs().sys_conf().modify(|_, w| unsafe { + #[cfg(not(soc_has_clock_node_rmt_sclk))] + { + w.clk_en().clear_bit(); + w.sclk_sel().bits(source.bits()); + } + + w.sclk_div_num().bits(div); + w.sclk_div_a().bits(0); + w.sclk_div_b().bits(0); + w.apb_fifo_mask().set_bit() + }); + + #[cfg(soc_has_pcr)] + { + use crate::peripherals::PCR; + + PCR::regs().rmt_sclk_conf().modify(|_, w| unsafe { + #[cfg(not(soc_has_clock_node_rmt_sclk))] + cfg_if::cfg_if!( + if #[cfg(any(esp32c5, esp32c6))] { + w.sclk_sel().bits(source.bits()); + } else { + w.sclk_sel().bit(source.bit()); + } + ); + + w.sclk_div_num().bits(div); + w.sclk_div_a().bits(0); + w.sclk_div_b().bits(0); + #[cfg(not(soc_has_clock_node_rmt_sclk))] + w.sclk_en().set_bit(); + + w + }); + + RMT::regs() + .sys_conf() + .modify(|_, w| w.apb_fifo_mask().set_bit()); + } + } + + #[crate::handler] + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub(super) fn async_interrupt_handler() { + let st = RMT::regs().int_st().read(); + + for ch_idx in ChannelIndex::iter_all() { + let raw_tx = unsafe { DynChannelAccess::::conjure(ch_idx) }; + let raw_rx = unsafe { DynChannelAccess::::conjure(ch_idx) }; + let ch_idx = ch_idx as u8; + + let channel = if st.ch_tx_end(ch_idx).bit() || st.ch_tx_err(ch_idx).bit() { + raw_tx.unlisten_tx_interrupt(EnumSet::all()); + raw_tx.channel() + } else if st.ch_tx_thr_event(ch_idx).bit() { + // TxFuture will enable the interrupt again if required. + raw_tx.unlisten_tx_interrupt(Event::Threshold); + raw_tx.channel() + } else if st.ch_rx_end(ch_idx).bit() || st.ch_rx_err(ch_idx).bit() { + raw_rx.unlisten_rx_interrupt(EnumSet::all()); + raw_rx.channel() + } else if st.ch_rx_thr_event(ch_idx).bit() { + // RxFuture will enable the interrupt again if required. + raw_rx.unlisten_rx_interrupt(Event::Threshold); + raw_rx.channel() + } else { + continue; + }; + + WAKER[channel as usize].wake(); + return; + } + } + + impl DynChannelAccess { + pub fn channel(self) -> u8 { + if Dir::IS_TX { + self.ch_idx as u8 + } else { + self.ch_idx as u8 + ChannelIndex::MAX + } + } + + pub fn update(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + if Dir::IS_TX { + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| w.conf_update().set_bit()); + } else { + rmt.ch_rx_conf1(ch_idx) + .modify(|_, w| w.conf_update().set_bit()); + } + } + + pub fn set_divider(self, divider: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + if Dir::IS_TX { + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); + } else { + rmt.ch_rx_conf0(ch_idx) + .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); + } + } + + pub fn memsize(self) -> MemSize { + let rmt = RMT::regs(); + let ch_idx = self.ch_idx as usize; + + let blocks = if Dir::IS_TX { + rmt.ch_tx_conf0(ch_idx).read().mem_size().bits() + } else { + rmt.ch_rx_conf0(ch_idx).read().mem_size().bits() + }; + + MemSize::from_blocks(blocks) + } + + pub fn set_memsize(self, value: MemSize) { + let blocks = value.blocks(); + let rmt = RMT::regs(); + let ch_idx = self.ch_idx as usize; + + if Dir::IS_TX { + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| unsafe { w.mem_size().bits(blocks) }); + } else { + rmt.ch_rx_conf0(ch_idx) + .modify(|_, w| unsafe { w.mem_size().bits(blocks) }); + } + } + + #[inline(always)] + pub fn reset_channel_clock_divider(self) { + #[cfg(esp32c5)] + { + let mask = 1u32 << self.channel(); + let rmt = RMT::regs(); + + rmt.ref_cnt_rst().write(|w| unsafe { w.bits(mask) }); + rmt.ref_cnt_rst().write(|w| unsafe { w.bits(0) }); + } + } + } + + // documented in re-export below + #[allow(missing_docs)] + pub const MAX_TX_LOOPCOUNT: u16 = + max_from_register_spec!(u16, ch_tx_lim, CH_TX_LIM_SPEC, TX_LOOP_NUM_W); + + impl DynChannelAccess { + #[inline(always)] + pub fn set_loopmode(self, mode: Option) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + if let Some(mode) = mode { + rmt.ch_tx_lim(ch_idx).modify(|_, w| unsafe { + w.loop_count_reset().set_bit(); + w.tx_loop_cnt_en().bit(!matches!(mode, LoopMode::Infinite)); + w.tx_loop_num().bits(mode.get_count()); + + #[cfg(rmt_has_tx_loop_auto_stop)] + w.loop_stop_en().bit(matches!(mode, LoopMode::Finite(_))); + + w + }); + + // FIXME: Is this required? This is a WT field for esp32c6 at least + rmt.ch_tx_lim(ch_idx) + .modify(|_, w| w.loop_count_reset().clear_bit()); + } else { + rmt.ch_tx_lim(ch_idx) + .modify(|_, w| w.tx_loop_cnt_en().clear_bit()); + } + } + + #[inline(always)] + pub fn clear_tx_interrupts(self, events: impl Into>) { + let rmt = crate::peripherals::RMT::regs(); + let events = events.into(); + + rmt.int_clr().write( + #[inline(always)] + |w| { + let ch_idx = self.ch_idx as u8; + + w.ch_tx_end(ch_idx).bit(events.contains(Event::End)); + w.ch_tx_err(ch_idx).bit(events.contains(Event::Error)); + w.ch_tx_loop(ch_idx).bit(events.contains(Event::LoopCount)); + w.ch_tx_thr_event(ch_idx) + .bit(events.contains(Event::Threshold)); + + w + }, + ); + } + + pub fn set_tx_continuous(self, continuous: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| w.tx_conti_mode().bit(continuous)); + } + + pub fn set_tx_wrap_mode(self, wrap: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| w.mem_tx_wrap_en().bit(wrap)); + } + + pub fn set_tx_carrier(self, carrier: bool, high: u16, low: u16, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.chcarrier_duty(ch_idx) + .write(|w| unsafe { w.carrier_high().bits(high).carrier_low().bits(low) }); + + rmt.ch_tx_conf0(ch_idx).modify(|_, w| { + w.carrier_en().bit(carrier); + w.carrier_eff_en().set_bit(); + w.carrier_out_lv().bit(level.into()) + }); + } + + pub fn tx_idle_output(self) -> (bool, Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + let reg = rmt.ch_tx_conf0(ch_idx).read(); + + (reg.idle_out_en().bit(), reg.idle_out_lv().bit().into()) + } + + pub fn set_tx_idle_output(self, enable: bool, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_tx_conf0(ch_idx) + .modify(|_, w| w.idle_out_en().bit(enable).idle_out_lv().bit(level.into())); + } + + pub fn start_tx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + self.reset_channel_clock_divider(); + + rmt.ch_tx_conf0(ch_idx).modify(|_, w| { + w.mem_rd_rst().set_bit(); + w.apb_mem_rst().set_bit(); + w.tx_start().set_bit() + }); + } + + // Return the first flag that is set of, in order of decreasing priority, + // Event::Error, Event::End, Event::LoopCount, Event::Threshold + #[inline(always)] + pub fn get_tx_status(self) -> Option { + let rmt = crate::peripherals::RMT::regs(); + let reg = rmt.int_raw().read(); + let ch_idx = self.ch_idx as u8; + + if reg.ch_tx_end(ch_idx).bit() { + Some(Event::End) + } else if reg.ch_tx_err(ch_idx).bit() { + Some(Event::Error) + } else if reg.ch_tx_loop(ch_idx).bit() { + Some(Event::LoopCount) + } else if reg.ch_tx_thr_event(ch_idx).bit() { + Some(Event::Threshold) + } else { + None + } + } + + pub fn set_tx_threshold(self, threshold: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_tx_lim(ch_idx) + .modify(|_, w| unsafe { w.tx_lim().bits(threshold as u16) }); + } + + pub fn is_tx_loopcount_interrupt_set(self) -> bool { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as u8; + + rmt.int_raw().read().ch_tx_loop(ch_idx).bit() + } + + // Requires an update() call + pub fn stop_tx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_tx_conf0(ch_idx).modify(|_, w| w.tx_stop().set_bit()); + } + + #[inline(always)] + pub fn set_tx_interrupt(self, events: EnumSet, enable: bool) { + let rmt = crate::peripherals::RMT::regs(); + + RMT_LOCK.lock(|| { + rmt.int_ena().modify( + #[inline(always)] + |_, w| { + let ch_idx = self.ch_idx as u8; + + if events.contains(Event::Error) { + w.ch_tx_err(ch_idx).bit(enable); + } + if events.contains(Event::End) { + w.ch_tx_end(ch_idx).bit(enable); + } + if events.contains(Event::Threshold) { + w.ch_tx_thr_event(ch_idx).bit(enable); + } + w + }, + ) + }); + } + + #[allow(unused)] + pub fn hw_offset(self) -> usize { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + let offset = rmt.ch_tx_status(ch_idx).read().mem_raddr_ex().bits(); + usize::from(offset) - self.channel_ram_start_offset() + } + } + + // documented in re-export below + #[allow(missing_docs)] + pub const MAX_RX_IDLE_THRESHOLD: u16 = + max_from_register_spec!(u16, ch_rx_conf0, CH_RX_CONF0_SPEC, IDLE_THRES_W); + + impl DynChannelAccess { + #[inline(always)] + pub fn clear_rx_interrupts(self, events: impl Into>) { + let rmt = crate::peripherals::RMT::regs(); + let events = events.into(); + + rmt.int_clr().write( + #[inline(always)] + |w| { + let ch_idx = self.ch_idx as u8; + + w.ch_rx_end(ch_idx).bit(events.contains(Event::End)); + w.ch_rx_err(ch_idx).bit(events.contains(Event::Error)); + w.ch_rx_thr_event(ch_idx) + .bit(events.contains(Event::Threshold)); + w + }, + ); + } + + pub fn set_rx_wrap_mode(self, wrap: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_conf1(ch_idx) + .modify(|_, w| w.mem_rx_wrap_en().bit(wrap)); + } + + pub fn set_rx_carrier(self, carrier: bool, high: u16, low: u16, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_carrier_rm(ch_idx).write(|w| unsafe { + w.carrier_high_thres().bits(high); + w.carrier_low_thres().bits(low) + }); + + rmt.ch_rx_conf0(ch_idx).modify(|_, w| { + w.carrier_en() + .bit(carrier) + .carrier_out_lv() + .bit(level.into()) + }); + } + + pub fn start_rx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as u8; + + self.reset_channel_clock_divider(); + + for i in 1..self.memsize().blocks() { + rmt.ch_rx_conf1((ch_idx + i).into()) + .modify(|_, w| w.mem_owner().set_bit()); + } + rmt.ch_rx_conf1(ch_idx.into()).modify(|_, w| { + w.mem_owner().set_bit(); + w.mem_wr_rst().set_bit(); + w.apb_mem_rst().set_bit(); + w.rx_en().set_bit() + }); + } + + // Return the first flag that is set of, in order of decreasing priority, + // Event::Error, Event::End, Event::Threshold + #[inline(always)] + pub fn get_rx_status(self) -> Option { + let rmt = crate::peripherals::RMT::regs(); + let reg = rmt.int_raw().read(); + let ch_idx = self.ch_idx as u8; + + if reg.ch_rx_end(ch_idx).bit() { + Some(Event::End) + } else if reg.ch_rx_err(ch_idx).bit() { + Some(Event::Error) + } else if reg.ch_rx_thr_event(ch_idx).bit() { + Some(Event::Threshold) + } else { + None + } + } + + pub fn set_rx_threshold(self, threshold: u16) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_lim(ch_idx) + .modify(|_, w| unsafe { w.rx_lim().bits(threshold) }); + } + + // This is immediate and does not update state flags; do not poll on get_rx_status() + // afterwards! + // + // Requires an update() call + pub fn stop_rx(self, _force: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_conf1(ch_idx).modify(|_, w| w.rx_en().clear_bit()); + } + + pub fn set_rx_filter_threshold(self, value: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_conf1(ch_idx).modify(|_, w| unsafe { + w.rx_filter_en().bit(value > 0); + w.rx_filter_thres().bits(value) + }); + } + + pub fn set_rx_idle_threshold(self, value: u16) { + debug_assert!(value <= MAX_RX_IDLE_THRESHOLD); + + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + rmt.ch_rx_conf0(ch_idx) + .modify(|_, w| unsafe { w.idle_thres().bits(value) }); + } + + #[inline(always)] + pub fn set_rx_interrupt(self, events: EnumSet, enable: bool) { + let rmt = crate::peripherals::RMT::regs(); + + RMT_LOCK.lock(|| { + rmt.int_ena().modify( + #[inline(always)] + |_, w| { + let ch_idx = self.ch_idx as u8; + + if events.contains(Event::Error) { + w.ch_rx_err(ch_idx).bit(enable); + } + if events.contains(Event::End) { + w.ch_rx_end(ch_idx).bit(enable); + } + if events.contains(Event::Threshold) { + w.ch_rx_thr_event(ch_idx).bit(enable); + } + w + }, + ) + }); + } + + pub fn hw_offset(self) -> usize { + let rmt = crate::peripherals::RMT::regs(); + let ch_idx = self.ch_idx as usize; + + let offset = rmt.ch_rx_status(ch_idx).read().mem_waddr_ex().bits(); + usize::from(offset) - self.channel_ram_start_offset() + } + } +} + +#[cfg(any(esp32, esp32s2))] +mod chip_specific { + use enumset::EnumSet; + #[cfg(place_rmt_driver_in_ram)] + use procmacros::ram; + + use super::{ + ChannelIndex, + ClockSource, + ConfigError, + Direction, + DynChannelAccess, + Event, + Level, + MemSize, + NUM_CHANNELS, + RMT_LOCK, + Rx, + Tx, + WAKER, + }; + use crate::{peripherals::RMT, time::Rate}; + + pub(super) fn validate_clock(source: ClockSource, frequency: Rate) -> Result { + if frequency != source.freq() { + return Err(ConfigError::UnreachableTargetFrequency); + } + + Ok(1) + } + + pub(super) fn configure_clock(source: ClockSource, _div: u8) { + let rmt = RMT::regs(); + + for ch_num in 0..NUM_CHANNELS { + rmt.chconf1(ch_num) + .modify(|_, w| w.ref_always_on().bit(source.bit())); + } + + rmt.apb_conf().modify(|_, w| w.apb_fifo_mask().set_bit()); + + #[cfg(not(esp32))] + rmt.apb_conf().modify(|_, w| w.clk_en().set_bit()); + } + + #[crate::handler] + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub(super) fn async_interrupt_handler() { + let st = RMT::regs().int_st().read(); + + for ch_idx in ChannelIndex::iter_all() { + let raw_tx = unsafe { DynChannelAccess::::conjure(ch_idx) }; + let raw_rx = unsafe { DynChannelAccess::::conjure(ch_idx) }; + let ch_idx = ch_idx as u8; + + if st.ch_tx_end(ch_idx).bit() { + raw_tx.unlisten_tx_interrupt(EnumSet::all()); + } else if st.ch_rx_end(ch_idx).bit() { + raw_rx.unlisten_rx_interrupt(EnumSet::all()); + } else if st.ch_err(ch_idx).bit() { + // On error interrupts, don't bother whether the channel is in Rx or Tx mode, just + // unlisten all interrupts and wake. + raw_tx.unlisten_tx_interrupt(EnumSet::all()); + raw_rx.unlisten_rx_interrupt(EnumSet::all()); + } else if st.ch_tx_thr_event(ch_idx).bit() { + raw_tx.unlisten_tx_interrupt(Event::Threshold); + } else { + continue; + } + + // raw_tx.channel() == raw_rx.channel() == ch_idx for these devices + WAKER[ch_idx as usize].wake(); + return; + } + } + + impl DynChannelAccess { + pub fn channel(self) -> u8 { + self.ch_idx as u8 + } + + pub fn update(self) { + // no-op + } + + pub fn set_divider(self, divider: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf0(ch) + .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); + } + + pub fn memsize(self) -> MemSize { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + let blocks = rmt.chconf0(ch).read().mem_size().bits(); + MemSize::from_blocks(blocks) + } + + pub fn set_memsize(self, value: MemSize) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf0(ch) + .modify(|_, w| unsafe { w.mem_size().bits(value.blocks()) }); + } + } + + // documented in re-export below + #[allow(missing_docs)] + #[cfg(rmt_has_tx_loop_count)] + pub const MAX_TX_LOOPCOUNT: u16 = + max_from_register_spec!(u16, ch_tx_lim, CH_TX_LIM_SPEC, TX_LOOP_NUM_W); + + impl DynChannelAccess { + #[cfg(rmt_has_tx_loop_count)] + #[inline(always)] + pub fn set_loopmode(self, mode: Option) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + if let Some(mode) = mode { + rmt.ch_tx_lim(ch).modify(|_, w| unsafe { + w.loop_count_reset().set_bit(); + w.tx_loop_cnt_en() + .bit(!matches!(mode, super::LoopMode::Infinite)); + w.tx_loop_num().bits(mode.get_count()) + }); + + // FIXME: Is this required? + rmt.ch_tx_lim(ch) + .modify(|_, w| w.loop_count_reset().clear_bit()); + } else { + rmt.ch_tx_lim(ch) + .modify(|_, w| w.tx_loop_cnt_en().clear_bit()); + } + } + + #[inline(always)] + pub fn clear_tx_interrupts(self, events: impl Into>) { + let rmt = crate::peripherals::RMT::regs(); + let events = events.into(); + + rmt.int_clr().write( + #[inline(always)] + |w| { + let ch = self.ch_idx as u8; + + w.ch_tx_end(ch).bit(events.contains(Event::End)); + w.ch_err(ch).bit(events.contains(Event::Error)); + #[cfg(rmt_has_tx_loop_count)] + w.ch_tx_loop(ch).bit(events.contains(Event::LoopCount)); + w.ch_tx_thr_event(ch).bit(events.contains(Event::Threshold)); + + w + }, + ); + } + + pub fn set_tx_continuous(self, continuous: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf1(ch) + .modify(|_, w| w.tx_conti_mode().bit(continuous)); + } + + pub fn set_tx_wrap_mode(self, wrap: bool) { + let rmt = crate::peripherals::RMT::regs(); + + // this is "okay", because we use all TX channels always in wrap mode + rmt.apb_conf().modify(|_, w| w.mem_tx_wrap_en().bit(wrap)); + } + + pub fn set_tx_carrier(self, carrier: bool, high: u16, low: u16, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chcarrier_duty(ch) + .write(|w| unsafe { w.carrier_high().bits(high).carrier_low().bits(low) }); + + rmt.chconf0(ch).modify(|_, w| { + w.carrier_en() + .bit(carrier) + .carrier_out_lv() + .bit(level.into()) + }); + } + + pub fn tx_idle_output(self) -> (bool, Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + let reg = rmt.chconf1(ch).read(); + + (reg.idle_out_en().bit(), reg.idle_out_lv().bit().into()) + } + + pub fn set_tx_idle_output(self, enable: bool, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf1(ch) + .modify(|_, w| w.idle_out_en().bit(enable).idle_out_lv().bit(level.into())); + } + + pub fn start_tx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as u8; + + for i in 1..self.memsize().blocks() { + rmt.chconf1((ch + i).into()) + .modify(|_, w| w.mem_owner().clear_bit()); + } + + rmt.chconf1(ch as usize).modify(|_, w| { + w.mem_owner().clear_bit(); + w.mem_rd_rst().set_bit(); + w.apb_mem_rst().set_bit(); + w.tx_start().set_bit() + }); + } + + // Return the first flag that is set of, in order of decreasing priority, + // Event::Error, Event::End, Event::LoopCount, Event::Threshold + #[inline(always)] + pub fn get_tx_status(self) -> Option { + let rmt = crate::peripherals::RMT::regs(); + let reg = rmt.int_raw().read(); + let ch = self.ch_idx as u8; + + if reg.ch_tx_end(ch).bit() { + return Some(Event::End); + } + if reg.ch_err(ch).bit() { + return Some(Event::Error); + } + #[cfg(rmt_has_tx_loop_count)] + if reg.ch_tx_loop(ch).bit() { + return Some(Event::LoopCount); + } + if reg.ch_tx_thr_event(ch).bit() { + return Some(Event::Threshold); + } + + None + } + + pub fn set_tx_threshold(self, threshold: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.ch_tx_lim(ch) + .modify(|_, w| unsafe { w.tx_lim().bits(threshold as u16) }); + } + + #[cfg(rmt_has_tx_loop_count)] + pub fn is_tx_loopcount_interrupt_set(self) -> bool { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as u8; + + rmt.int_raw().read().ch_tx_loop(ch).bit() + } + + // It would be better to simply not define this method if not supported, but that would + // make the implementation of ContinuousTxTransaction::stop_impl much more awkward. + #[cfg(not(rmt_has_tx_loop_count))] + #[allow(unused)] + pub fn is_tx_loopcount_interrupt_set(self) -> bool { + false + } + + #[cfg(rmt_has_tx_immediate_stop)] + pub fn stop_tx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf1(ch).modify(|_, w| w.tx_stop().set_bit()); + } + + #[cfg(not(rmt_has_tx_immediate_stop))] + pub fn stop_tx(self) { + let ptr = self.channel_ram_start(); + for idx in 0..self.memsize().codes() { + unsafe { + ptr.add(idx).write_volatile(super::PulseCode::end_marker()); + } + } + } + + #[inline(always)] + pub fn set_tx_interrupt(self, events: EnumSet, enable: bool) { + let rmt = crate::peripherals::RMT::regs(); + + RMT_LOCK.lock(|| { + rmt.int_ena().modify( + #[inline(always)] + |_, w| { + let ch = self.ch_idx as u8; + + if events.contains(Event::Error) { + w.ch_err(ch).bit(enable); + } + if events.contains(Event::End) { + w.ch_tx_end(ch).bit(enable); + } + if events.contains(Event::Threshold) { + w.ch_tx_thr_event(ch).bit(enable); + } + w + }, + ) + }); + } + + #[allow(unused)] + pub fn hw_offset(self) -> usize { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + let offset = rmt.chstatus(ch).read().mem_raddr_ex().bits(); + usize::from(offset) - self.channel_ram_start_offset() + } + } + + // documented in re-export below + #[allow(missing_docs)] + pub const MAX_RX_IDLE_THRESHOLD: u16 = + max_from_register_spec!(u16, chconf0, CHCONF0_SPEC, IDLE_THRES_W); + + impl DynChannelAccess { + #[inline(always)] + pub fn clear_rx_interrupts(self, events: impl Into>) { + let rmt = crate::peripherals::RMT::regs(); + let events = events.into(); + + rmt.int_clr().write( + #[inline(always)] + |w| { + let ch = self.ch_idx as u8; + + w.ch_rx_end(ch).bit(events.contains(Event::End)); + w.ch_err(ch).bit(events.contains(Event::Error)); + + w + }, + ); + } + + pub fn set_rx_carrier(self, carrier: bool, high: u16, low: u16, level: Level) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chcarrier_duty(ch) + .write(|w| unsafe { w.carrier_high().bits(high).carrier_low().bits(low) }); + + rmt.chconf0(ch).modify(|_, w| { + w.carrier_en() + .bit(carrier) + .carrier_out_lv() + .bit(level.into()) + }); + } + + pub fn start_rx(self) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as u8; + + for i in 1..self.memsize().blocks() { + rmt.chconf1((ch + i).into()) + .modify(|_, w| w.mem_owner().set_bit()); + } + + rmt.chconf1(ch as usize).modify(|_, w| { + w.mem_owner().set_bit(); + w.mem_wr_rst().set_bit(); + w.apb_mem_rst().set_bit(); + w.rx_en().set_bit() + }); + } + + // Return the first flag that is set of, in order of decreasing priority, + // Event::Error, Event::End, Event::Threshold + #[inline(always)] + pub fn get_rx_status(self) -> Option { + let rmt = crate::peripherals::RMT::regs(); + let reg = rmt.int_raw().read(); + let ch = self.ch_idx as u8; + + if reg.ch_rx_end(ch).bit() { + Some(Event::End) + } else if reg.ch_err(ch).bit() { + Some(Event::Error) + } else { + None + } + } + + pub fn stop_rx(self, force: bool) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + // There's no direct hardware support on these chips for stopping the receiver once it + // started: It will only stop when it runs into an error or when it detects that the + // data ended (buffer end or idle threshold). Depending on the current channel + // settings, this might take a looong time. + // + // However, we do need to reliably stop the receiver on `Channel` drop to avoid + // subsequent transactions receiving some initial garbage. + // + // This code attempts to work around this limitation by + // + // 1) setting the mem_owner to an invalid value. This doesn't seem to trigger an error + // immediately, so presumably the error only occurs when a new pulse code would be + // written. + // 2) lowering the idle threshold and change other settings to make exceeding the + // threshold more likely (lower clock divider, enable and set an input filter that is + // longer than the idle threshold). The latter should have the same effect as + // reconnecting the pin to a constant level, but that tricky to do since we'd also + // need restore it afterwards. + // + // On its own, 2) should be sufficient and quickly result in an `End` condition. If + // this is overlooking anything and a new code does happen to be received, 1) should + // ensure that an `Error` condition occurs and in any case, we never block here for + // long. + if force { + if !rmt.chconf1(ch).read().rx_en().bit() { + // Don't lock up trying to stop rx when we didn't start in the first place + return; + } + + let (old_idle_thres, old_div) = rmt.chconf0(ch).from_modify(|r, w| { + let old = (r.idle_thres().bits(), r.div_cnt().bits()); + unsafe { + w.idle_thres().bits(1); + w.div_cnt().bits(1); + } + old + }); + + let (old_filter_en, old_filter_thres) = rmt.chconf1(ch).from_modify(|r, w| { + let old = (r.rx_filter_en().bit(), r.rx_filter_thres().bits()); + w.rx_en().clear_bit(); + w.rx_filter_en().bit(true); + unsafe { w.rx_filter_thres().bits(0xFF) }; + w.mem_owner().clear_bit(); + old + }); + + while !matches!(self.get_rx_status(), Some(Event::Error | Event::End)) {} + + // Restore settings + + rmt.chconf0(ch).modify(|_, w| unsafe { + w.idle_thres().bits(old_idle_thres); + w.div_cnt().bits(old_div) + }); + + rmt.chconf1(ch).modify(|_, w| { + w.rx_filter_en().bit(old_filter_en); + unsafe { w.rx_filter_thres().bits(old_filter_thres) }; + w.mem_owner().set_bit() + }); + } else { + // Only disable, don't abort. + rmt.chconf1(ch).modify(|_, w| w.rx_en().clear_bit()); + } + } + + pub fn set_rx_filter_threshold(self, value: u8) { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf1(ch).modify(|_, w| unsafe { + w.rx_filter_en().bit(value > 0); + w.rx_filter_thres().bits(value) + }); + } + + pub fn set_rx_idle_threshold(self, value: u16) { + #[allow(clippy::absurd_extreme_comparisons)] + { + debug_assert!(value <= MAX_RX_IDLE_THRESHOLD); + } + + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + rmt.chconf0(ch) + .modify(|_, w| unsafe { w.idle_thres().bits(value) }); + } + + #[inline(always)] + pub fn set_rx_interrupt(self, events: EnumSet, enable: bool) { + let rmt = crate::peripherals::RMT::regs(); + + RMT_LOCK.lock(|| { + rmt.int_ena().modify( + #[inline(always)] + |_, w| { + let ch = self.ch_idx as u8; + + if events.contains(Event::Error) { + w.ch_err(ch).bit(enable); + } + if events.contains(Event::End) { + w.ch_rx_end(ch).bit(enable); + } + w + }, + ) + }); + } + + pub fn hw_offset(self) -> usize { + let rmt = crate::peripherals::RMT::regs(); + let ch = self.ch_idx as usize; + + let offset = rmt.chstatus(ch).read().mem_waddr_ex().bits(); + usize::from(offset) - self.channel_ram_start_offset() + } + } +} + +/// The largest valid value for [`RxChannelConfig::with_idle_threshold`]. +pub use chip_specific::MAX_RX_IDLE_THRESHOLD; +/// The largest valid value for loopcounts in [`LoopMode`]. +#[cfg(rmt_has_tx_loop_count)] +pub use chip_specific::MAX_TX_LOOPCOUNT; diff --git a/esp-hal/src/rmt/reader.rs b/esp-hal/src/rmt/reader.rs new file mode 100644 index 00000000000..674be47cdca --- /dev/null +++ b/esp-hal/src/rmt/reader.rs @@ -0,0 +1,143 @@ +#[cfg(place_rmt_driver_in_ram)] +use procmacros::ram; + +use super::{DynChannelAccess, Error, PulseCode, Rx}; + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) enum ReaderState { + Active, + + Error(Error), + + Done, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) struct RmtReader { + // The position in channel RAM to continue reading from; must be either + // 0 or half the available RAM size if there's further data. + // The position may be invalid if there's no data left. + offset: u16, + + pub total: usize, + + pub state: ReaderState, +} + +impl RmtReader { + pub(super) fn new() -> Self { + Self { + offset: 0, + total: 0, + state: ReaderState::Active, + } + } + + // Copy from the hardware buffer to `data`, advancing the `data` slice accordingly. + // + // If `final_` is set, read a full buffer length, potentially wrapping around. Otherwise, fetch + // half the buffer's length. + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub(super) fn read( + &mut self, + data: &mut &mut [PulseCode], + raw: DynChannelAccess, + final_: bool, + ) { + if self.state != ReaderState::Active { + return; + } + + let ram_start = raw.channel_ram_start(); + let memsize = raw.memsize().codes(); + let offset = self.offset as usize; + + let max_count = if final_ { + let hw_offset = raw.hw_offset(); + + // self.offset -> next code we would read + // hw_offset -> next code the hardware would write + // => If both are the same, we're done, max_count = 0 + let max_count = (if offset <= hw_offset { 0 } else { memsize }) + hw_offset - offset; + + debug_assert!( + max_count == 0 && self.total == 0 + // We always enable wrapping if it is available. If it's unavailable, rx might + // stop when the buffer is full, without an end marker present! + // (Checking for two value of hw_offset here, because it's not documented what + // happens with the pointer in that case.) + || !property!("rmt.has_rx_wrap") && (hw_offset == 0 || hw_offset == memsize) + || unsafe { + raw.channel_ram_start() + .add(hw_offset.checked_sub(1).unwrap_or(memsize - 1)) + .read_volatile() + } + .is_end_marker() + ); + + max_count + } else { + memsize / 2 + }; + + let count = data.len().min(max_count); + let mut count0 = count.min(memsize - offset); + let mut count1 = count - count0; + + // Read in up to 2 chunks to allow wrapping around the buffer end. This is more efficient + // than checking in each iteration of the inner loop whether we reached the buffer end. + let mut ram_ptr = unsafe { ram_start.add(self.offset as usize) }; + + let mut data_ptr = data.as_mut_ptr(); + + loop { + let data_end = unsafe { data_ptr.add(count0) }; + while data_ptr < data_end { + // SAFETY: The iteration count `count0` is smaller than both `max_count` and + // `data.len()` such that incrementing both pointers cannot advance them beyond + // their allocation's end. + unsafe { + data_ptr.write(ram_ptr.read_volatile()); + ram_ptr = ram_ptr.add(1); + data_ptr = data_ptr.add(1); + } + } + + if count1 == 0 { + break; + } + + count0 = count1; + count1 = 0; + ram_ptr = ram_start; + } + + // Update offset as + // + // | offset | new offset | + // | ----------- + ----------- | + // | 0 | memsize / 2 | + // | memsize / 2 | 0 | + // + // If `count < max_count` or if `final_` is set, the new offset will not correspond to + // where we stopped reading, but the new offset will not be used again since further calls + // will immediately return due to `self.state != Active`. + self.offset = (memsize / 2) as u16 - self.offset; + self.total += count; + + // The panic can never trigger since count <= data.len()! + data.split_off_mut(..count).unwrap(); + + if count < max_count { + // `data` exhausted + self.state = ReaderState::Error(Error::ReceiverError); + } else if final_ { + // Caller indicated that we're done + self.state = ReaderState::Done; + } + + debug_assert!(self.offset == 0 || self.offset as usize == memsize / 2); + } +} diff --git a/esp-hal/src/rmt/writer.rs b/esp-hal/src/rmt/writer.rs new file mode 100644 index 00000000000..2f4770fd40d --- /dev/null +++ b/esp-hal/src/rmt/writer.rs @@ -0,0 +1,153 @@ +#[cfg(place_rmt_driver_in_ram)] +use procmacros::ram; + +use super::{DynChannelAccess, Error, PulseCode, Tx}; + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) enum WriterState { + // There's still data left to write + Active, + + // An error occurred, and either + // - no data was written (and transmission was never started), or + // - some data was written and the last code written is an end marker, such that transmission + // will eventually stop + Error(Error), + + // Completed without errors + Done, +} + +impl WriterState { + pub(super) fn is_ok(self) -> bool { + !matches!(self, Self::Error(_)) + } + + pub(super) fn to_result(self) -> Result<(), Error> { + match self { + // tx ended but writer wasn't done yet + Self::Active => Err(Error::TransmissionError), + Self::Error(e) => Err(e), + Self::Done => Ok(()), + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) struct RmtWriter { + // The position in channel RAM to continue writing at; must be either + // 0 or half the available RAM size if there's further data. + // The position may be invalid if there's no data left. + offset: u16, + + written: usize, + + last_code: PulseCode, + + state: WriterState, +} + +impl RmtWriter { + pub(super) fn new() -> Self { + Self { + offset: 0, + written: 0, + last_code: PulseCode::default(), + state: WriterState::Active, + } + } + + #[inline] + pub(super) fn state(&self) -> WriterState { + self.state + } + + // Copy from `data` to the hardware buffer, advancing the `data` slice accordingly. + // + // If `initial` is set, fill the entire buffer. Otherwise, append half the buffer's length from + // `data`. + #[cfg_attr(place_rmt_driver_in_ram, ram)] + pub(super) fn write( + &mut self, + data: &mut &[PulseCode], + raw: DynChannelAccess, + initial: bool, + ) { + if self.state != WriterState::Active { + return; + } + + debug_assert!(!initial || self.offset == 0); + + let ram_start = raw.channel_ram_start(); + let memsize = raw.memsize().codes(); + let ram_end = unsafe { ram_start.add(memsize) }; + + let max_count = if initial { memsize } else { memsize / 2 }; + let count = data.len().min(max_count); + + let mut ram_ptr = unsafe { ram_start.add(self.offset as usize) }; + + let mut data_ptr = data.as_ptr(); + let data_end = unsafe { data_ptr.add(count) }; + + let mut last_code = self.last_code; + while data_ptr < data_end { + // SAFETY: The iteration `count` is smaller than both `max_count` and `data.len()` such + // that incrementing both pointers cannot advance them beyond their allocation's end. + unsafe { + last_code = data_ptr.read(); + ram_ptr.write_volatile(last_code); + ram_ptr = ram_ptr.add(1); + data_ptr = data_ptr.add(1); + } + } + + self.last_code = last_code; + self.written += count; + + // If the input data was not exhausted, update offset as + // + // | initial | offset | max_count | new offset | + // | ------- + ----------- + ----------- + ----------- | + // | true | 0 | memsize | 0 | + // | false | 0 | memsize / 2 | memsize / 2 | + // | false | memsize / 2 | memsize / 2 | 0 | + // + // Otherwise, the new position is invalid but the new slice is empty and we won't use the + // offset again. In either case, the unsigned subtraction will not underflow. + self.offset = memsize as u16 - max_count as u16 - self.offset; + + // The panic can never trigger since count <= data.len()! + data.split_off(..count).unwrap(); + if data.is_empty() { + self.state = if self.written == 0 { + // data was empty + WriterState::Error(Error::InvalidArgument) + } else if last_code.is_end_marker() { + // Do not check for end markers in the inner loop above since this would + // substantially increase the instruction count there. Instead, only check the last + // code to report on error. + WriterState::Done + } else { + // Write an extra end marker to prevent looping forever with wrapping tx. + if ram_ptr < ram_end { + unsafe { ram_ptr.write_volatile(PulseCode::end_marker()) }; + WriterState::Error(Error::EndMarkerMissing) + } else { + // The buffer is full, and we can't easily write an end marker. (Short of + // overwriting some other code, which might be ok since hitting this error case + // always indicates a bug in user code.) + // Thus, remain in Active state. On the next Event::Threshold, this function + // will be called again, with data already exhausted, and hit the other arm of + // this conditional. + WriterState::Active + } + }; + } + + debug_assert!(self.offset == 0 || self.offset as usize == memsize / 2); + } +} diff --git a/esp-hal/src/rng/ll.rs b/esp-hal/src/rng/ll.rs new file mode 100644 index 00000000000..e2db32a0fda --- /dev/null +++ b/esp-hal/src/rng/ll.rs @@ -0,0 +1,94 @@ +use esp_sync::NonReentrantMutex; + +use crate::{clock::Clocks, peripherals::RNG}; + +// TODO: find a better place for these +#[inline] +#[cfg(all(soc_cpu_has_csr_pc, soc_cpu_csr_prv_mode_is_set))] +fn tee_enabled() -> bool { + false +} + +#[inline] +fn current_cpu_cycles() -> usize { + cfg_if::cfg_if! { + if #[cfg(xtensa)] { + xtensa_lx::timer::get_cycle_count() as usize + } else if #[cfg(soc_cpu_has_csr_pc)] { + const PRV_M: usize = 3; + macro_rules! read_csr_fn { + ($fnname:ident, $csr:expr) => { + #[inline] + fn $fnname() -> usize { + let r: usize; + + unsafe { + core::arch::asm!(concat!("csrrs {0}, ", $csr, ", x0"), out(reg) r); + } + + return r; + } + } + } + + read_csr_fn!(read_pccr_machine, "0x7e2"); + read_csr_fn!(read_pccr_user, "0x802"); + + fn read_prv_mode() -> usize { + #[cfg(soc_cpu_csr_prv_mode_is_set)] + if tee_enabled() { + read_csr_fn!(read_prv_m, property!("soc.cpu_csr_prv_mode", str)); + return read_prv_m(); + } + + PRV_M + } + + if read_prv_mode() == PRV_M { + read_pccr_machine() + } else { + read_pccr_user() + } + } else { + riscv::register::mcycle::read() + } + } +} + +static LAST_READ: NonReentrantMutex = NonReentrantMutex::new(0); + +fn read_one(wait_cycles: usize) -> u32 { + loop { + let random = LAST_READ.with(|last_wait_start| { + let now: usize = current_cpu_cycles(); + if now.wrapping_sub(*last_wait_start) >= wait_cycles { + *last_wait_start = now; + + Some(RNG::regs().data().read().bits()) + } else { + None + } + }); + if let Some(random) = random { + return random; + } + } +} + +pub(super) fn fill_ptr_range(data: *mut u8, len: usize) { + let clocks = Clocks::get(); + let cpu_to_apb_freq_ratio = clocks.cpu_clock / clocks.apb_clock; + let wait_cycles = cpu_to_apb_freq_ratio as usize * property!("rng.apb_cycle_wait_num"); + + let mut remaining = len; + let mut dst = data; + while remaining > 0 { + let random_bytes = read_one(wait_cycles).to_le_bytes(); + let bytes_to_copy = random_bytes.len().min(remaining); + unsafe { + dst.copy_from_nonoverlapping(random_bytes.as_ptr(), bytes_to_copy); + dst = dst.add(bytes_to_copy); + } + remaining -= bytes_to_copy; + } +} diff --git a/esp-hal/src/rng/mod.rs b/esp-hal/src/rng/mod.rs new file mode 100644 index 00000000000..492f61da09f --- /dev/null +++ b/esp-hal/src/rng/mod.rs @@ -0,0 +1,239 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "analog_pin" => { + cfg(esp32) => "GPIO32", + _ => "GPIO3" + }, + "documentation" => concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", chip!(), "/api-reference/system/random.html)") +))] +//! # Random Number Generator (RNG) +//! +//! ## Overview +//! The Random Number Generator (RNG) module provides an interface to generate +//! random numbers using the RNG peripheral on ESP chips. This driver allows you +//! to generate random numbers that can be used for various cryptographic, +//! security, or general-purpose applications. +//! +//! There are certain pre-conditions which must be met in order for the RNG to +//! produce *true* random numbers. The hardware RNG produces true random numbers +//! under any of the following conditions: +//! +//! - RF subsystem is enabled (i.e. Wi-Fi or Bluetooth are enabled). +//! - An ADC is used to generate entropy. +//! - An internal entropy source has been enabled by calling `bootloader_random_enable()` and not +//! yet disabled by calling `bootloader_random_disable()`. As the default bootloader calls +//! `bootloader_random_disable()` automatically, this option requires a custom bootloader build. +//! +//! When any of these conditions are true, samples of physical noise are +//! continuously mixed into the internal hardware RNG state to provide entropy. +//! If none of the above conditions are true, the output of the RNG should be +//! considered pseudo-random only. +//! +//! > Note that, when the Wi-Fi module is enabled, the value read from the high-speed ADC can be +//! > saturated in some extreme cases, which lowers the entropy. Thus, it is advisable to also +//! > enable the SAR ADC as the noise source for the random number generator for such cases. +//! +//! For more information, please refer to the +//! # {documentation} +//! +//! ## Configuration +//! +//! To generate pseudo-random numbers, you can create [`Rng`] at any time. +#![cfg_attr( + rng_trng_supported, + doc = r"To generate +true random numbers, you need to create an instance of [`TrngSource`]. Once you've +done that, you can create [`Trng`] instances at any time, as long as the [`TrngSource`] +is alive." +)] +//! ## Compatibility with [`rand_core`] +//! +//! The RNG drivers implement the relevant +//! traits from versions `0.6` and `0.9` of the [`rand_core`] crate. +//! +//! [`rand_core`]: https://crates.io/crates/rand_core +//! +//! ## Compatibility with [`getrandom`] +//! The driver can be used to implement a custom backend for `getrandom`. +//! +//! ### Example +//! +//! The following example demonstrates how to set up a [custom backend] using `getrandom v0.3.3`: +//! +//! ```rust, ignore +//! use getrandom::Error; +//! +//! #[no_mangle] +//! unsafe extern "Rust" fn __getrandom_v03_custom( +//! dest: *mut u8, +//! len: usize, +//! ) -> Result<(), Error> { +//! unsafe { esp_hal::rng:Rng::new().read_into_raw() }; +//! Ok(()) +//! } +//! ``` +//! +//! [`getrandom`]: https://crates.io/crates/getrandom +//! [custom backend]: https://github.com/rust-random/getrandom/tree/v0.3.3?tab=readme-ov-file#custom-backend +//! +//! ## Examples +//! +//! ### Basic RNG operation +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::rng::Rng; +//! # +//! let rng = Rng::new(); +//! +//! // Generate a random word (u32): +//! let rand_word = rng.random(); +//! +//! // Fill a buffer with random bytes: +//! let mut buf = [0u8; 16]; +//! rng.read(&mut buf); +//! +//! loop {} +//! # } +//! ``` +#![cfg_attr( + rng_trng_supported, + doc = r" +### TRNG operation +```rust, no_run +# {before_snippet} +# use esp_hal::Blocking; +# use esp_hal::peripherals::ADC1; +# use esp_hal::analog::adc::{AdcConfig, Attenuation, Adc}; +# +use esp_hal::rng::{Trng, TrngSource}; + +let mut buf = [0u8; 16]; + +// ADC is not available from now +let trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1.reborrow()); + +let trng = Trng::try_new().unwrap(); // Unwrap is safe as we have enabled TrngSource. + +// Generate true random numbers +trng.read(&mut buf); +let true_random_number = trng.random(); + +// Downgrade to Rng to allow disabling the TrngSource +let rng = trng.downgrade(); + +// Drop the true random number source. ADC is available now. +core::mem::drop(trng_source); + +let mut adc1_config = AdcConfig::new(); +let mut adc1_pin = adc1_config.enable_pin(peripherals.__analog_pin__, Attenuation::_11dB); +let mut adc1 = Adc::::new(peripherals.ADC1, adc1_config); +let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut adc1_pin))?; + +// Now we can only generate pseudo-random numbers... +rng.read(&mut buf); +let pseudo_random_number = rng.random(); + +// ... but the ADC is available for use. +let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut adc1_pin))?; +# {after_snippet} +``` +" +)] + +mod ll; + +#[cfg(all(feature = "unstable", rng_trng_supported))] +mod trng; + +#[cfg(all(feature = "unstable", rng_trng_supported))] +pub use trng::*; + +/// (Pseudo-)Random Number Generator +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub struct Rng; + +impl Rng { + /// Creates a new random number generator instance. + #[inline] + #[instability::unstable] + pub fn new() -> Self { + Self + } + + /// Reads currently available `u32` integer from `RNG`. + #[inline] + #[instability::unstable] + pub fn random(&self) -> u32 { + let mut n = [0; 4]; + self.read(&mut n); + u32::from_le_bytes(n) + } + + /// Reads enough bytes from hardware random number generator to fill + /// `buffer`. + /// + /// If any error is encountered then this function immediately returns. The + /// contents of buf are unspecified in this case. + #[inline] + #[instability::unstable] + pub fn read(&self, buffer: &mut [u8]) { + unsafe { self.read_into_raw(buffer.as_mut_ptr(), buffer.len()) }; + } + + /// Reads enough bytes from hardware random number generator to fill + /// `buffer`. + /// + /// If any error is encountered then this function immediately returns. The + /// contents of buf are unspecified in this case. + /// + /// # Safety + /// + /// `ptr` must not be `null` and valid for writes for `len` bytes. + #[inline] + #[instability::unstable] + pub unsafe fn read_into_raw(&self, ptr: *mut u8, len: usize) { + ll::fill_ptr_range(ptr, len); + } +} + +// Implement RngCore traits + +#[instability::unstable] +impl rand_core_06::RngCore for Rng { + fn next_u32(&mut self) -> u32 { + self.random() + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.fill_bytes(&mut bytes); + u64::from_le_bytes(bytes) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.read(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.read(dest); + Ok(()) + } +} + +#[instability::unstable] +impl rand_core_09::RngCore for Rng { + fn next_u32(&mut self) -> u32 { + self.random() + } + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.fill_bytes(&mut bytes); + u64::from_le_bytes(bytes) + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.read(dest); + } +} diff --git a/esp-hal/src/rng/trng.rs b/esp-hal/src/rng/trng.rs new file mode 100644 index 00000000000..b9f82d3e229 --- /dev/null +++ b/esp-hal/src/rng/trng.rs @@ -0,0 +1,232 @@ +//! TRNG implementation. +use portable_atomic::{AtomicUsize, Ordering}; + +static TRNG_ENABLED: AtomicUsize = AtomicUsize::new(0); +static TRNG_USERS: AtomicUsize = AtomicUsize::new(0); + +use super::Rng; +use crate::peripherals::{ADC1, RNG}; + +/// Ensures random numbers are cryptographically secure. +#[instability::unstable] +pub struct TrngSource<'d> { + _rng: RNG<'d>, + _adc: ADC1<'d>, +} + +impl<'d> TrngSource<'d> { + /// Enables the SAR ADC entropy source. + // TODO: this is not final. A single ADC channel should be sufficient. + #[instability::unstable] + pub fn new(_rng: RNG<'d>, _adc: ADC1<'d>) -> Self { + crate::soc::trng::ensure_randomness(); + unsafe { Self::increase_entropy_source_counter() } + Self { _rng, _adc } + } + + /// Increases the internal entropy source counter. + /// + /// # Panics + /// + /// This function panics if the internal counter overflows. + /// + /// # Safety + /// + /// This function must only be called after a new entropy source has been enabled. + #[instability::unstable] + pub unsafe fn increase_entropy_source_counter() { + if TRNG_ENABLED.fetch_add(1, Ordering::Relaxed) == usize::MAX { + panic!("TrngSource enable overflowed"); + } + } + + /// Decreases the internal entropy source counter. + /// + /// This function should only be called **before** disabling an entropy source (such as the + /// radio). + /// + /// This function should only be called as many times as + /// [`TrngSource::increase_entropy_source_counter`] was called. + /// + /// # Panics + /// + /// This function panics if the internal counter underflows. Dropping the `TrngSource` will + /// panic if this function is called more times than + /// [`TrngSource::increase_entropy_source_counter`]. + #[instability::unstable] + pub fn decrease_entropy_source_counter(_private: crate::private::Internal) { + match TRNG_ENABLED.fetch_sub(1, Ordering::Relaxed) { + 0 => panic!("TrngSource is not active"), + + 1 => assert!( + TRNG_USERS.load(Ordering::Acquire) == 0, + "TRNG cannot be disabled while it's in use" + ), + + _ => {} + } + } + + /// Returns whether the TRNG is currently enabled. + /// + /// Note that entropy sources can be disabled at any time. + #[instability::unstable] + pub fn is_enabled() -> bool { + TRNG_ENABLED.load(Ordering::Relaxed) > 0 + } + + /// Attempts to disable the TRNG. + /// + /// This function returns `Err(TrngSource)` if there are TRNG users. + /// + /// # Panics + /// + /// This function panics if the TRNG is not enabled (i.e. it has been disabled by calling + /// [`TrngSource::decrease_entropy_source_counter`] incorrectly). + #[instability::unstable] + pub fn try_disable(self) -> Result<(), Self> { + if TRNG_ENABLED + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |enabled| { + assert!(enabled > 0, "TrngSource is not active"); + if TRNG_USERS.load(Ordering::Acquire) > 0 { + return None; + } + + Some(enabled - 1) + }) + .is_err() + { + // The TRNG is in use. + return Err(self); + } + + core::mem::forget(self); + Ok(()) + } +} + +impl Drop for TrngSource<'_> { + fn drop(&mut self) { + Self::decrease_entropy_source_counter(crate::private::Internal); + crate::soc::trng::revert_trng(); + } +} + +/// Errors returned when constructing a [`Trng`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum TrngError { + /// The [`TrngSource`] is not enabled. + /// + /// This error is returned by [`Trng::try_new`] when the RNG is not configured + /// to generate true random numbers. + TrngSourceNotEnabled, +} + +/// True Random Number Generator (TRNG) +/// +/// The `Trng` struct represents a true random number generator that combines +/// the randomness from the hardware RNG and an ADC. This struct provides +/// methods to generate random numbers and fill buffers with random bytes. +/// Due to pulling the entropy source from the ADC, it uses the associated +/// registers, so to use TRNG we need to "occupy" the ADC peripheral. +#[derive(Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub struct Trng { + rng: Rng, +} + +impl Clone for Trng { + #[inline] + fn clone(&self) -> Self { + TRNG_USERS.fetch_add(1, Ordering::Acquire); + Self { rng: self.rng } + } +} + +impl Trng { + /// Attempts to create a new True Random Number Generator (TRNG) instance. + /// + /// This function returns a new `Trng` instance on success, or an error if the + /// [`TrngSource`] is not active. + #[inline] + #[instability::unstable] + pub fn try_new() -> Result { + TRNG_USERS.fetch_add(1, Ordering::Acquire); + let this = Self { rng: Rng::new() }; + if TRNG_ENABLED.load(Ordering::Acquire) == 0 { + // Dropping `this` reduces the TRNG_USERS count back (to 0 as it should be when TRNG + // is not enabled). + return Err(TrngError::TrngSourceNotEnabled); + } + Ok(this) + } + + /// Returns a new, random `u32` integer. + #[inline] + #[instability::unstable] + pub fn random(&self) -> u32 { + self.rng.random() + } + + /// Fills the provided buffer with random bytes. + #[inline] + #[instability::unstable] + pub fn read(&self, buffer: &mut [u8]) { + self.rng.read(buffer); + } + + /// Downgrades the `Trng` instance to a `Rng` instance. + #[inline] + #[instability::unstable] + pub fn downgrade(self) -> Rng { + Rng::new() + } +} + +impl Drop for Trng { + fn drop(&mut self) { + TRNG_USERS.fetch_sub(1, Ordering::Release); + } +} + +#[instability::unstable] +impl rand_core_06::RngCore for Trng { + fn next_u32(&mut self) -> u32 { + self.rng.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.rng.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.rng.fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.rng.try_fill_bytes(dest) + } +} + +#[instability::unstable] +impl rand_core_09::RngCore for Trng { + fn next_u32(&mut self) -> u32 { + self.rng.next_u32() + } + fn next_u64(&mut self) -> u64 { + self.rng.next_u64() + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.rng.fill_bytes(dest) + } +} + +#[instability::unstable] +impl rand_core_06::CryptoRng for Trng {} +#[instability::unstable] +impl rand_core_09::CryptoRng for Trng {} diff --git a/esp-hal/src/rom/mod.rs b/esp-hal/src/rom/mod.rs new file mode 100644 index 00000000000..9a015d95812 --- /dev/null +++ b/esp-hal/src/rom/mod.rs @@ -0,0 +1,28 @@ +//! # ESP ROM libraries +//! +//! ## Overview +//! The `rom` driver provides functionality related to the ROM (Read-Only +//! Memory) on ESP chips. It includes implementations for the [CRC (Cyclic +//! Redundancy Check)] and [MD5 (Message Digest 5)] algorithms. +//! +//! The driver's functionality allows users to perform CRC calculations and MD5 +//! hashing using the ROM functions provided by the ESP chip. This can be useful +//! for various applications that require data integrity checks or cryptographic +//! operations. +//! +//! It uses `CRC` error-checking techniques to detect changes in data during +//! transmission or storage. +//! +//! This module also implements the `MD5` algorithm, which is widely used for +//! cryptographic hash function. It's commonly used to verify data integrity and +//! to check whether the data has been modified. +//! +//! Safe abstractions to the additional libraries provided in the ESP's +//! Read-Only Memory. +//! +//! [CRC (Cyclic Redundancy Check)]: ./crc/index.html +//! [MD5 (Message Digest 5)]: ./md5/index.html + +pub use esp_rom_sys::rom::*; + +pub(crate) mod regi2c; diff --git a/esp-hal/src/rom/regi2c.rs b/esp-hal/src/rom/regi2c.rs new file mode 100644 index 00000000000..56d849f61da --- /dev/null +++ b/esp-hal/src/rom/regi2c.rs @@ -0,0 +1,104 @@ +use crate::soc::regi2c::{regi2c_read, regi2c_write}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct RegI2cMaster { + pub master: u8, + pub host_id: u8, +} + +impl RegI2cMaster { + pub const fn new(master: u8, host_id: u8) -> Self { + Self { master, host_id } + } + + #[allow(unused)] + pub fn write(&self, reg: u8, data: u8) { + regi2c_write(self.master, self.host_id, reg, data); + } + + #[allow(unused)] + pub fn read(&self, reg: u8) -> u8 { + regi2c_read(self.master, self.host_id, reg) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct RegI2cRegister { + master: RegI2cMaster, + reg_add: u8, +} + +impl RegI2cRegister { + pub const fn new(master: RegI2cMaster, reg_add: u8) -> Self { + Self { master, reg_add } + } + + #[allow(unused)] + pub fn write_reg(&self, data: u8) { + self.master.write(self.reg_add, data); + } + + #[allow(unused)] + pub fn read(&self) -> u8 { + self.master.read(self.reg_add) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct RawRegI2cField { + register: RegI2cRegister, + msb: u8, + lsb: u8, +} + +impl RawRegI2cField { + #[allow(unused)] + pub const fn new(register: RegI2cRegister, msb: u8, lsb: u8) -> Self { + ::core::assert!(msb < 8 + lsb); + Self { register, msb, lsb } + } + + #[allow(unused)] + pub fn read(&self) -> u8 { + let reg_raw = self.register.read(); + (reg_raw >> self.lsb) & !(u8::MAX << (self.msb - self.lsb + 1)) + } + + #[allow(unused)] + pub fn write_field(&self, data: u8) { + let bits = self.register.read(); + + let unwritten_bits = (!(u32::MAX << self.lsb) | (u32::MAX << (self.msb + 1))) as u8; + let data_mask = !(u32::MAX << (self.msb - self.lsb + 1)) as u8; + let data_bits = (data & data_mask) << self.lsb; + + self.register.write_reg((bits & unwritten_bits) | data_bits); + } +} + +macro_rules! define_regi2c { + ($(master: $master_name:ident($master:literal, $hostid:literal) { + $(reg: $register_name:ident($register:literal) { + $(field: $field_name:ident ( $msb:literal .. $lsb:literal )),* + })* + })+) => { + $( + #[allow(unused)] + pub(crate) const $master_name: RegI2cMaster = RegI2cMaster::new($master, $hostid); + + $( + #[allow(unused)] + pub(crate) const $register_name: RegI2cRegister = RegI2cRegister::new($master_name, $register); + + $( + #[allow(unused)] + pub(crate) const $field_name: RawRegI2cField = RawRegI2cField::new($register_name, $msb, $lsb); + )* + )* + )+ + }; +} +pub(crate) use define_regi2c; diff --git a/esp-hal/src/rsa/mod.rs b/esp-hal/src/rsa/mod.rs new file mode 100644 index 00000000000..b9563be028b --- /dev/null +++ b/esp-hal/src/rsa/mod.rs @@ -0,0 +1,1398 @@ +//! # RSA (Rivest–Shamir–Adleman) accelerator. +//! +//! ## Overview +//! +//! The RSA accelerator provides hardware support for high precision computation +//! used in various RSA asymmetric cipher algorithms by significantly reducing +//! their software complexity. Compared with RSA algorithms implemented solely +//! in software, this hardware accelerator can speed up RSA algorithms +//! significantly. +//! +//! ## Configuration +//! +//! The RSA accelerator also supports operands of different lengths, which +//! provides more flexibility during the computation. + +use core::{marker::PhantomData, ptr::NonNull, task::Poll}; + +use portable_atomic::{AtomicBool, Ordering}; +use procmacros::{handler, ram}; + +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + interrupt::InterruptHandler, + pac, + peripherals::{Interrupt, RSA}, + system::{Cpu, GenericPeripheralGuard, Peripheral as PeripheralEnable}, + trm_markdown_link, + work_queue::{self, Status, VTable, WorkQueue, WorkQueueDriver, WorkQueueFrontend}, +}; + +/// RSA peripheral driver. +pub struct Rsa<'d, Dm: DriverMode> { + rsa: RSA<'d>, + phantom: PhantomData, + #[cfg(not(esp32))] + _memory_guard: RsaMemoryPowerGuard, + _guard: GenericPeripheralGuard<{ PeripheralEnable::Rsa as u8 }>, +} + +// There are two distinct peripheral versions: ESP32, and all else. There is a naming split in the +// later devices, and they use different (memory size, operand size increment) parameters, but they +// are largely the same. + +/// How many words are there in an operand size increment. +/// +/// I.e. if the RSA hardware works with operands of 512, 1024, 1536, ... bits, the increment is 512 +/// bits, or 16 words. +const WORDS_PER_INCREMENT: u32 = property!("rsa.size_increment") / 32; + +#[cfg(not(esp32))] +struct RsaMemoryPowerGuard; + +#[cfg(not(esp32))] +impl RsaMemoryPowerGuard { + fn new() -> Self { + crate::peripherals::SYSTEM::regs() + .rsa_pd_ctrl() + .modify(|_, w| { + w.rsa_mem_force_pd().clear_bit(); + w.rsa_mem_force_pu().set_bit(); + w.rsa_mem_pd().clear_bit() + }); + Self + } +} + +#[cfg(not(esp32))] +impl Drop for RsaMemoryPowerGuard { + fn drop(&mut self) { + crate::peripherals::SYSTEM::regs() + .rsa_pd_ctrl() + .modify(|_, w| { + w.rsa_mem_force_pd().clear_bit(); + w.rsa_mem_force_pu().clear_bit(); + w.rsa_mem_pd().set_bit() + }); + } +} + +impl<'d> Rsa<'d, Blocking> { + /// Create a new instance in [Blocking] mode. + /// + /// Optionally an interrupt handler can be bound. + pub fn new(rsa: RSA<'d>) -> Self { + let guard = GenericPeripheralGuard::new(); + + let this = Self { + rsa, + phantom: PhantomData, + #[cfg(not(esp32))] + _memory_guard: RsaMemoryPowerGuard::new(), + _guard: guard, + }; + + while !this.ready() {} + + this + } + + /// Reconfigures the RSA driver to operate in asynchronous mode. + pub fn into_async(mut self) -> Rsa<'d, Async> { + self.set_interrupt_handler(rsa_interrupt_handler); + self.enable_disable_interrupt(true); + + Rsa { + rsa: self.rsa, + phantom: PhantomData, + #[cfg(not(esp32))] + _memory_guard: self._memory_guard, + _guard: self._guard, + } + } + + /// Enables/disables rsa interrupt. + /// + /// When enabled rsa peripheral would generate an interrupt when a operation + /// is finished. + pub fn enable_disable_interrupt(&mut self, enable: bool) { + self.internal_enable_disable_interrupt(enable); + } + + /// Registers an interrupt handler for the RSA peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.rsa.disable_peri_interrupt_on_all_cores(); + self.rsa.bind_peri_interrupt(handler); + } +} + +impl crate::private::Sealed for Rsa<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Rsa<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +impl<'d> Rsa<'d, Async> { + /// Create a new instance in [crate::Blocking] mode. + pub fn into_blocking(self) -> Rsa<'d, Blocking> { + self.internal_enable_disable_interrupt(false); + self.rsa.disable_peri_interrupt_on_all_cores(); + + crate::interrupt::disable(Cpu::current(), Interrupt::RSA); + Rsa { + rsa: self.rsa, + phantom: PhantomData, + #[cfg(not(esp32))] + _memory_guard: self._memory_guard, + _guard: self._guard, + } + } +} + +impl<'d, Dm: DriverMode> Rsa<'d, Dm> { + fn internal_enable_disable_interrupt(&self, enable: bool) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + // Can't seem to actually disable the interrupt, but esp-idf still writes the register + self.regs().interrupt().write(|w| w.interrupt().bit(enable)); + } else { + self.regs().int_ena().write(|w| w.int_ena().bit(enable)); + } + } + } + + fn regs(&self) -> &pac::rsa::RegisterBlock { + self.rsa.register_block() + } + + /// After the RSA accelerator is released from reset, the memory blocks + /// needs to be initialized, only after that peripheral should be used. + /// This function would return without an error if the memory is + /// initialized. + fn ready(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + self.regs().clean().read().clean().bit_is_set() + } else { + self.regs().query_clean().read().query_clean().bit_is_set() + } + } + } + + /// Starts the modular exponentiation operation. + fn start_modexp(&self) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + self.regs() + .modexp_start() + .write(|w| w.modexp_start().set_bit()); + } else { + self.regs() + .set_start_modexp() + .write(|w| w.set_start_modexp().set_bit()); + } + } + } + + /// Starts the multiplication operation. + fn start_multi(&self) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2, esp32s3))] { + self.regs().mult_start().write(|w| w.mult_start().set_bit()); + } else { + self.regs() + .set_start_mult() + .write(|w| w.set_start_mult().set_bit()); + } + } + } + + /// Starts the modular multiplication operation. + fn start_modmulti(&self) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + // modular-ness is encoded in the multi_mode register value + self.start_multi(); + } else if #[cfg(any(esp32s2, esp32s3))] { + self.regs() + .modmult_start() + .write(|w| w.modmult_start().set_bit()); + } else { + self.regs() + .set_start_modmult() + .write(|w| w.set_start_modmult().set_bit()); + } + } + } + + /// Clears the RSA interrupt flag. + fn clear_interrupt(&mut self) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().interrupt().write(|w| w.interrupt().set_bit()); + } else { + self.regs().int_clr().write(|w| w.int_clr().set_bit()); + } + } + } + + /// Checks if the RSA peripheral is idle. + fn is_idle(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().interrupt().read().interrupt().bit_is_set() + } else if #[cfg(any(esp32s2, esp32s3))] { + self.regs().idle().read().idle().bit_is_set() + } else { + self.regs().query_idle().read().query_idle().bit_is_set() + } + } + } + + fn wait_for_idle(&mut self) { + while !self.is_idle() {} + self.clear_interrupt(); + } + + /// Writes the result size of the multiplication. + fn write_multi_mode(&mut self, mode: u32, modular: bool) { + let mode = if cfg!(esp32) && !modular { + const NON_MODULAR: u32 = 8; + mode | NON_MODULAR + } else { + mode + }; + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().mult_mode().write(|w| unsafe { w.bits(mode) }); + } else { + self.regs().mode().write(|w| unsafe { w.bits(mode) }); + } + } + } + + /// Writes the result size of the modular exponentiation. + fn write_modexp_mode(&mut self, mode: u32) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().modexp_mode().write(|w| unsafe { w.bits(mode) }); + } else { + self.regs().mode().write(|w| unsafe { w.bits(mode) }); + } + } + } + + fn write_operand_b(&mut self, operand: &[u32]) { + for (reg, op) in self.regs().y_mem_iter().zip(operand.iter().copied()) { + reg.write(|w| unsafe { w.bits(op) }); + } + } + + fn write_modulus(&mut self, modulus: &[u32]) { + for (reg, op) in self.regs().m_mem_iter().zip(modulus.iter().copied()) { + reg.write(|w| unsafe { w.bits(op) }); + } + } + + fn write_mprime(&mut self, m_prime: u32) { + self.regs().m_prime().write(|w| unsafe { w.bits(m_prime) }); + } + + fn write_operand_a(&mut self, operand: &[u32]) { + for (reg, op) in self.regs().x_mem_iter().zip(operand.iter().copied()) { + reg.write(|w| unsafe { w.bits(op) }); + } + } + + fn write_multi_operand_b(&mut self, operand: &[u32]) { + for (reg, op) in self + .regs() + .z_mem_iter() + .skip(operand.len()) + .zip(operand.iter().copied()) + { + reg.write(|w| unsafe { w.bits(op) }); + } + } + + fn write_r(&mut self, r: &[u32]) { + for (reg, op) in self.regs().z_mem_iter().zip(r.iter().copied()) { + reg.write(|w| unsafe { w.bits(op) }); + } + } + + fn read_out(&self, outbuf: &mut [u32]) { + for (reg, op) in self.regs().z_mem_iter().zip(outbuf.iter_mut()) { + *op = reg.read().bits(); + } + } + + fn read_results(&mut self, outbuf: &mut [u32]) { + self.wait_for_idle(); + self.read_out(outbuf); + } + + /// Enables/disables constant time operation. + /// + /// Disabling constant time operation increases the performance of modular + /// exponentiation by simplifying the calculation concerning the 0 bits + /// of the exponent. I.e. the less the Hamming weight, the greater the + /// performance. + /// + /// Note: this compromises security by enabling timing-based side-channel attacks. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + #[cfg(not(esp32))] + pub fn disable_constant_time(&mut self, disable: bool) { + self.regs() + .constant_time() + .write(|w| w.constant_time().bit(disable)); + } + + /// Enables/disables search acceleration. + /// + /// When enabled it would increase the performance of modular + /// exponentiation by discarding the exponent's bits before the most + /// significant set bit. + /// + /// Note: this compromises security by effectively decreasing the key length. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + #[cfg(not(esp32))] + pub fn search_acceleration(&mut self, enable: bool) { + self.regs() + .search_enable() + .write(|w| w.search_enable().bit(enable)); + } + + /// Checks if the search functionality is enabled in the RSA hardware. + #[cfg(not(esp32))] + fn is_search_enabled(&mut self) -> bool { + self.regs() + .search_enable() + .read() + .search_enable() + .bit_is_set() + } + + /// Sets the search position in the RSA hardware. + #[cfg(not(esp32))] + fn write_search_position(&mut self, search_position: u32) { + self.regs() + .search_pos() + .write(|w| unsafe { w.bits(search_position) }); + } +} + +/// Defines the input size of an RSA operation. +pub trait RsaMode: crate::private::Sealed { + /// The input data type used for the operation. + type InputType: AsRef<[u32]> + AsMut<[u32]>; +} + +/// Defines the output type of RSA multiplications. +pub trait Multi: RsaMode { + /// The type of the output produced by the operation. + type OutputType: AsRef<[u32]> + AsMut<[u32]>; +} + +/// Defines the exponentiation and multiplication lengths for RSA operations. +pub mod operand_sizes { + for_each_rsa_exponentiation!( + ($x:literal) => { + paste::paste! { + #[doc = concat!(stringify!($x), "-bit RSA operation.")] + pub struct []; + + impl crate::private::Sealed for [] {} + impl crate::rsa::RsaMode for [] { + type InputType = [u32; $x / 32]; + } + } + }; + ); + + for_each_rsa_multiplication!( + ($x:literal) => { + impl crate::rsa::Multi for paste::paste!( [] ) { + type OutputType = [u32; $x * 2 / 32]; + } + }; + ); +} + +/// Support for RSA peripheral's modular exponentiation feature that could be +/// used to find the `(base ^ exponent) mod modulus`. +/// +/// Each operand is a little endian byte array of the same size +pub struct RsaModularExponentiation<'a, 'd, T: RsaMode, Dm: DriverMode> { + rsa: &'a mut Rsa<'d, Dm>, + phantom: PhantomData, +} + +impl<'a, 'd, T: RsaMode, Dm: DriverMode, const N: usize> RsaModularExponentiation<'a, 'd, T, Dm> +where + T: RsaMode, +{ + /// Creates an instance of `RsaModularExponentiation`. + /// + /// `m_prime` could be calculated using `-(modular multiplicative inverse of + /// modulus) mod 2^32`. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn new( + rsa: &'a mut Rsa<'d, Dm>, + exponent: &T::InputType, + modulus: &T::InputType, + m_prime: u32, + ) -> Self { + Self::write_mode(rsa); + rsa.write_operand_b(exponent); + rsa.write_modulus(modulus); + rsa.write_mprime(m_prime); + + #[cfg(not(esp32))] + if rsa.is_search_enabled() { + rsa.write_search_position(Self::find_search_pos(exponent)); + } + + Self { + rsa, + phantom: PhantomData, + } + } + + fn set_up_exponentiation(&mut self, base: &T::InputType, r: &T::InputType) { + self.rsa.write_operand_a(base); + self.rsa.write_r(r); + } + + /// Starts the modular exponentiation operation. + /// + /// `r` can be calculated using `2 ^ ( bitlength * 2 ) mod modulus`. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn start_exponentiation(&mut self, base: &T::InputType, r: &T::InputType) { + self.set_up_exponentiation(base, r); + self.rsa.start_modexp(); + } + + /// Reads the result to the given buffer. + /// + /// This is a blocking function: it waits for the RSA operation to complete, + /// then reads the results into the provided buffer. `start_exponentiation` must be + /// called before calling this function. + pub fn read_results(&mut self, outbuf: &mut T::InputType) { + self.rsa.read_results(outbuf); + } + + #[cfg(not(esp32))] + fn find_search_pos(exponent: &T::InputType) -> u32 { + for (i, byte) in exponent.iter().rev().enumerate() { + if *byte == 0 { + continue; + } + return (exponent.len() * 32) as u32 - (byte.leading_zeros() + i as u32 * 32) - 1; + } + 0 + } + + /// Sets the modular exponentiation mode for the RSA hardware. + fn write_mode(rsa: &mut Rsa<'d, Dm>) { + rsa.write_modexp_mode(N as u32 / WORDS_PER_INCREMENT - 1); + } +} + +/// Support for RSA peripheral's modular multiplication feature that could be +/// used to find the `(operand a * operand b) mod modulus`. +/// +/// Each operand is a little endian byte array of the same size +pub struct RsaModularMultiplication<'a, 'd, T, Dm> +where + T: RsaMode, + Dm: DriverMode, +{ + rsa: &'a mut Rsa<'d, Dm>, + phantom: PhantomData, +} + +impl<'a, 'd, T, Dm, const N: usize> RsaModularMultiplication<'a, 'd, T, Dm> +where + T: RsaMode, + Dm: DriverMode, +{ + /// Creates an instance of `RsaModularMultiplication`. + /// + /// - `r` can be calculated using `2 ^ ( bitlength * 2 ) mod modulus`. + /// - `m_prime` can be calculated using `-(modular multiplicative inverse of modulus) mod 2^32`. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn new( + rsa: &'a mut Rsa<'d, Dm>, + operand_a: &T::InputType, + modulus: &T::InputType, + r: &T::InputType, + m_prime: u32, + ) -> Self { + rsa.write_multi_mode(N as u32 / WORDS_PER_INCREMENT - 1, true); + + rsa.write_mprime(m_prime); + rsa.write_modulus(modulus); + rsa.write_operand_a(operand_a); + rsa.write_r(r); + + Self { + rsa, + phantom: PhantomData, + } + } + + /// Starts the modular multiplication operation. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn start_modular_multiplication(&mut self, operand_b: &T::InputType) { + self.set_up_modular_multiplication(operand_b); + self.rsa.start_modmulti(); + } + + /// Reads the result to the given buffer. + /// + /// This is a blocking function: it waits for the RSA operation to complete, + /// then reads the results into the provided buffer. `start_modular_multiplication` must be + /// called before calling this function. + pub fn read_results(&mut self, outbuf: &mut T::InputType) { + self.rsa.read_results(outbuf); + } + + fn set_up_modular_multiplication(&mut self, operand_b: &T::InputType) { + if cfg!(esp32) { + self.rsa.start_multi(); + self.rsa.wait_for_idle(); + + self.rsa.write_operand_a(operand_b); + } else { + self.rsa.write_operand_b(operand_b); + } + } +} + +/// Support for RSA peripheral's large number multiplication feature that could +/// be used to find the `operand a * operand b`. +/// +/// Each operand is a little endian byte array of the same size +pub struct RsaMultiplication<'a, 'd, T, Dm> +where + T: RsaMode + Multi, + Dm: DriverMode, +{ + rsa: &'a mut Rsa<'d, Dm>, + phantom: PhantomData, +} + +impl<'a, 'd, T, Dm, const N: usize> RsaMultiplication<'a, 'd, T, Dm> +where + T: RsaMode, + T: Multi, + Dm: DriverMode, +{ + /// Creates an instance of `RsaMultiplication`. + pub fn new(rsa: &'a mut Rsa<'d, Dm>, operand_a: &T::InputType) -> Self { + // Non-modular multiplication result is twice as wide as its operands. + rsa.write_multi_mode(2 * N as u32 / WORDS_PER_INCREMENT - 1, false); + rsa.write_operand_a(operand_a); + + Self { + rsa, + phantom: PhantomData, + } + } + + /// Starts the multiplication operation. + pub fn start_multiplication(&mut self, operand_b: &T::InputType) { + self.set_up_multiplication(operand_b); + self.rsa.start_multi(); + } + + /// Reads the result to the given buffer. + /// + /// This is a blocking function: it waits for the RSA operation to complete, + /// then reads the results into the provided buffer. `start_multiplication` must be + /// called before calling this function. + pub fn read_results(&mut self, outbuf: &mut T::OutputType) + where + T: Multi, + { + self.rsa.read_results(outbuf); + } + + fn set_up_multiplication(&mut self, operand_b: &T::InputType) { + self.rsa.write_multi_operand_b(operand_b); + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); +// TODO: this should only be needed for ESP32 +static SIGNALED: AtomicBool = AtomicBool::new(false); + +/// `Future` that waits for the RSA operation to complete. +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct RsaFuture<'a, 'd> { + driver: &'a Rsa<'d, Async>, +} + +impl<'a, 'd> RsaFuture<'a, 'd> { + fn new(driver: &'a Rsa<'d, Async>) -> Self { + SIGNALED.store(false, Ordering::Relaxed); + + driver.internal_enable_disable_interrupt(true); + + Self { driver } + } + + fn is_done(&self) -> bool { + SIGNALED.load(Ordering::Acquire) + } +} + +impl Drop for RsaFuture<'_, '_> { + fn drop(&mut self) { + self.driver.internal_enable_disable_interrupt(false); + } +} + +impl core::future::Future for RsaFuture<'_, '_> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + WAKER.register(cx.waker()); + if self.is_done() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl RsaModularExponentiation<'_, '_, T, Async> +where + T: RsaMode, +{ + /// Asynchronously performs an RSA modular exponentiation operation. + pub async fn exponentiation( + &mut self, + base: &T::InputType, + r: &T::InputType, + outbuf: &mut T::InputType, + ) { + self.set_up_exponentiation(base, r); + let fut = RsaFuture::new(self.rsa); + self.rsa.start_modexp(); + fut.await; + self.rsa.read_out(outbuf); + } +} + +impl RsaModularMultiplication<'_, '_, T, Async> +where + T: RsaMode, +{ + /// Asynchronously performs an RSA modular multiplication operation. + pub async fn modular_multiplication( + &mut self, + operand_b: &T::InputType, + outbuf: &mut T::InputType, + ) { + if cfg!(esp32) { + let fut = RsaFuture::new(self.rsa); + self.rsa.start_multi(); + fut.await; + + self.rsa.write_operand_a(operand_b); + } else { + self.set_up_modular_multiplication(operand_b); + } + + let fut = RsaFuture::new(self.rsa); + self.rsa.start_modmulti(); + fut.await; + self.rsa.read_out(outbuf); + } +} + +impl RsaMultiplication<'_, '_, T, Async> +where + T: RsaMode, +{ + /// Asynchronously performs an RSA multiplication operation. + pub async fn multiplication( + &mut self, + operand_b: &T::InputType, + outbuf: &mut T::OutputType, + ) where + T: Multi, + { + self.set_up_multiplication(operand_b); + let fut = RsaFuture::new(self.rsa); + self.rsa.start_multi(); + fut.await; + self.rsa.read_out(outbuf); + } +} + +#[handler] +/// Interrupt handler for RSA. +pub(super) fn rsa_interrupt_handler() { + let rsa = RSA::regs(); + SIGNALED.store(true, Ordering::Release); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + rsa.interrupt().write(|w| w.interrupt().set_bit()); + } else { + rsa.int_clr().write(|w| w.int_clr().set_bit()); + } + } + + WAKER.wake(); +} + +static RSA_WORK_QUEUE: WorkQueue = WorkQueue::new(); +const RSA_VTABLE: VTable = VTable { + post: |driver, item| { + // Start processing immediately. + let driver = unsafe { RsaBackend::from_raw(driver) }; + Some(driver.process_item(item)) + }, + poll: |driver, item| { + let driver = unsafe { RsaBackend::from_raw(driver) }; + driver.process_item(item) + }, + cancel: |driver, item| { + let driver = unsafe { RsaBackend::from_raw(driver) }; + driver.cancel(item) + }, + stop: |driver| { + let driver = unsafe { RsaBackend::from_raw(driver) }; + driver.deinitialize() + }, +}; + +#[derive(Default)] +enum RsaBackendState<'d> { + #[default] + Idle, + Initializing(Rsa<'d, Blocking>), + Ready(Rsa<'d, Blocking>), + #[cfg(esp32)] + ModularMultiplicationRoundOne(Rsa<'d, Blocking>), + Processing(Rsa<'d, Blocking>), +} + +#[procmacros::doc_replace] +/// RSA processing backend. +/// +/// The backend processes work items placed in the RSA work queue. The backend needs to be created +/// and started for operations to be processed. This allows you to perform operations on the RSA +/// accelerator without carrying around the peripheral singleton, or the driver. +/// +/// The [`RsaContext`] struct can enqueue work items that this backend will process. +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::rsa::{RsaBackend, RsaContext, operand_sizes::Op512}; +/// # +/// let mut rsa_backend = RsaBackend::new(peripherals.RSA); +/// let _driver = rsa_backend.start(); +/// +/// async fn perform_512bit_big_number_multiplication( +/// operand_a: &[u32; 16], +/// operand_b: &[u32; 16], +/// result: &mut [u32; 32], +/// ) { +/// let mut rsa = RsaContext::new(); +/// +/// let mut handle = rsa.multiply::(operand_a, operand_b, result); +/// handle.wait().await; +/// } +/// # {after_snippet} +/// ``` +pub struct RsaBackend<'d> { + peri: RSA<'d>, + state: RsaBackendState<'d>, +} + +impl<'d> RsaBackend<'d> { + #[procmacros::doc_replace] + /// Creates a new RSA backend. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::rsa::RsaBackend; + /// # + /// let mut rsa = RsaBackend::new(peripherals.RSA); + /// # {after_snippet} + /// ``` + pub fn new(rsa: RSA<'d>) -> Self { + Self { + peri: rsa, + state: RsaBackendState::Idle, + } + } + + #[procmacros::doc_replace] + /// Registers the RSA driver to process RSA operations. + /// + /// The driver stops operating when the returned object is dropped. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::rsa::RsaBackend; + /// # + /// let mut rsa = RsaBackend::new(peripherals.RSA); + /// // Start the backend, which allows processing RSA operations. + /// let _backend = rsa.start(); + /// # {after_snippet} + /// ``` + pub fn start(&mut self) -> RsaWorkQueueDriver<'_, 'd> { + RsaWorkQueueDriver { + inner: WorkQueueDriver::new(self, RSA_VTABLE, &RSA_WORK_QUEUE), + } + } + + // WorkQueue callbacks. They may run in any context. + + unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self { + unsafe { ptr.cast::>().as_mut() } + } + + fn process_item(&mut self, item: &mut RsaWorkItem) -> work_queue::Poll { + match core::mem::take(&mut self.state) { + RsaBackendState::Idle => { + let driver = Rsa { + rsa: unsafe { self.peri.clone_unchecked() }, + phantom: PhantomData, + #[cfg(not(esp32))] + _memory_guard: RsaMemoryPowerGuard::new(), + _guard: GenericPeripheralGuard::new(), + }; + self.state = RsaBackendState::Initializing(driver); + work_queue::Poll::Pending(true) + } + RsaBackendState::Initializing(mut rsa) => { + // Wait for the peripheral to finish initializing. Ideally we need a way to + // instruct the work queue to wake the polling task immediately. + self.state = if rsa.ready() { + rsa.set_interrupt_handler(rsa_work_queue_handler); + rsa.enable_disable_interrupt(true); + RsaBackendState::Ready(rsa) + } else { + RsaBackendState::Initializing(rsa) + }; + work_queue::Poll::Pending(true) + } + RsaBackendState::Ready(mut rsa) => { + #[cfg(not(esp32))] + { + rsa.disable_constant_time(!item.constant_time); + rsa.search_acceleration(item.search_acceleration); + } + + match item.operation { + RsaOperation::Multiplication { x, y } => { + let n = x.len() as u32; + rsa.write_operand_a(unsafe { x.as_ref() }); + + // Non-modular multiplication result is twice as wide as its operands. + rsa.write_multi_mode(2 * n / WORDS_PER_INCREMENT - 1, false); + rsa.write_multi_operand_b(unsafe { y.as_ref() }); + rsa.start_multi(); + } + + RsaOperation::ModularMultiplication { + x, + #[cfg(not(esp32))] + y, + m, + m_prime, + r: r_inv, + .. + } => { + let n = x.len() as u32; + rsa.write_operand_a(unsafe { x.as_ref() }); + + rsa.write_multi_mode(n / WORDS_PER_INCREMENT - 1, true); + + #[cfg(not(esp32))] + rsa.write_operand_b(unsafe { y.as_ref() }); + + rsa.write_modulus(unsafe { m.as_ref() }); + rsa.write_mprime(m_prime); + rsa.write_r(unsafe { r_inv.as_ref() }); + + rsa.start_modmulti(); + + #[cfg(esp32)] + { + // ESP32 requires a two-step process where Y needs to be written to the + // X memory. + self.state = RsaBackendState::ModularMultiplicationRoundOne(rsa); + + return work_queue::Poll::Pending(false); + } + } + RsaOperation::ModularExponentiation { + x, + y, + m, + m_prime, + r_inv, + } => { + let n = x.len() as u32; + rsa.write_operand_a(unsafe { x.as_ref() }); + + rsa.write_modexp_mode(n / WORDS_PER_INCREMENT - 1); + rsa.write_operand_b(unsafe { y.as_ref() }); + rsa.write_modulus(unsafe { m.as_ref() }); + rsa.write_mprime(m_prime); + rsa.write_r(unsafe { r_inv.as_ref() }); + + #[cfg(not(esp32))] + if item.search_acceleration { + fn find_search_pos(exponent: &[u32]) -> u32 { + for (i, byte) in exponent.iter().rev().enumerate() { + if *byte == 0 { + continue; + } + return (exponent.len() * 32) as u32 + - (byte.leading_zeros() + i as u32 * 32) + - 1; + } + 0 + } + rsa.write_search_position(find_search_pos(unsafe { y.as_ref() })); + } + + rsa.start_modexp(); + } + } + + self.state = RsaBackendState::Processing(rsa); + + work_queue::Poll::Pending(false) + } + + #[cfg(esp32)] + RsaBackendState::ModularMultiplicationRoundOne(mut rsa) => { + if rsa.is_idle() { + let RsaOperation::ModularMultiplication { y, .. } = item.operation else { + unreachable!(); + }; + + // Y needs to be written to the X memory. + rsa.write_operand_a(unsafe { y.as_ref() }); + rsa.start_modmulti(); + + self.state = RsaBackendState::Processing(rsa); + } else { + // Wait for the operation to complete + self.state = RsaBackendState::ModularMultiplicationRoundOne(rsa); + } + work_queue::Poll::Pending(false) + } + + RsaBackendState::Processing(rsa) => { + if rsa.is_idle() { + rsa.read_out(unsafe { item.result.as_mut() }); + + self.state = RsaBackendState::Ready(rsa); + work_queue::Poll::Ready(Status::Completed) + } else { + self.state = RsaBackendState::Processing(rsa); + work_queue::Poll::Pending(false) + } + } + } + } + + fn cancel(&mut self, _item: &mut RsaWorkItem) { + // Drop the driver to reset it. We don't read the result, so the work item remains + // unchanged, effectively cancelling it. + self.state = RsaBackendState::Idle; + } + + fn deinitialize(&mut self) { + self.state = RsaBackendState::Idle; + } +} + +/// An active work queue driver. +/// +/// This object must be kept around, otherwise RSA operations will never complete. +/// +/// For a usage example, see [`RsaBackend`]. +pub struct RsaWorkQueueDriver<'t, 'd> { + inner: WorkQueueDriver<'t, RsaBackend<'d>, RsaWorkItem>, +} + +impl<'t, 'd> RsaWorkQueueDriver<'t, 'd> { + /// Finishes processing the current work queue item, then stops the driver. + pub fn stop(self) -> impl Future { + self.inner.stop() + } +} + +#[derive(Clone)] +struct RsaWorkItem { + // Acceleration options + #[cfg(not(esp32))] + search_acceleration: bool, + #[cfg(not(esp32))] + constant_time: bool, + + // The operation to execute. + operation: RsaOperation, + result: NonNull<[u32]>, +} + +unsafe impl Sync for RsaWorkItem {} +unsafe impl Send for RsaWorkItem {} + +#[derive(Clone)] +enum RsaOperation { + // Z = X * Y + // len(Z) = len(X) + len(Y) + Multiplication { + x: NonNull<[u32]>, + y: NonNull<[u32]>, + }, + // Z = X * Y mod M + ModularMultiplication { + x: NonNull<[u32]>, + y: NonNull<[u32]>, + m: NonNull<[u32]>, + r: NonNull<[u32]>, + m_prime: u32, + }, + // Z = X ^ Y mod M + ModularExponentiation { + x: NonNull<[u32]>, + y: NonNull<[u32]>, + m: NonNull<[u32]>, + r_inv: NonNull<[u32]>, + m_prime: u32, + }, +} + +#[handler] +#[ram] +fn rsa_work_queue_handler() { + if !RSA_WORK_QUEUE.process() { + // The queue may indicate that it needs to be polled again. In this case, we do not clear + // the interrupt bit, which causes the interrupt to be re-handled. + cfg_if::cfg_if! { + if #[cfg(esp32)] { + RSA::regs().interrupt().write(|w| w.interrupt().set_bit()); + } else { + RSA::regs().int_clr().write(|w| w.int_clr().set_bit()); + } + } + } +} + +/// An RSA work queue user. +/// +/// This object allows performing [big number multiplication][Self::multiply], [big number modular +/// multiplication][Self::modular_multiply] and [big number modular +/// exponentiation][Self::modular_exponentiate] with hardware acceleration. To perform these +/// operations, the [`RsaBackend`] must be started, otherwise these operations will never complete. +#[cfg_attr( + not(esp32), + doc = " \nThe context is created with a secure configuration by default. You can enable hardware acceleration + options using [enable_search_acceleration][Self::enable_search_acceleration] and + [enable_acceleration][Self::enable_acceleration] when appropriate." +)] +#[derive(Clone)] +pub struct RsaContext { + frontend: WorkQueueFrontend, +} + +impl Default for RsaContext { + fn default() -> Self { + Self::new() + } +} + +impl RsaContext { + /// Creates a new context. + pub fn new() -> Self { + Self { + frontend: WorkQueueFrontend::new(RsaWorkItem { + #[cfg(not(esp32))] + search_acceleration: false, + #[cfg(not(esp32))] + constant_time: true, + operation: RsaOperation::Multiplication { + x: NonNull::from(&[]), + y: NonNull::from(&[]), + }, + result: NonNull::from(&mut []), + }), + } + } + + #[cfg(not(esp32))] + /// Enables search acceleration. + /// + /// When enabled it would increase the performance of modular + /// exponentiation by discarding the exponent's bits before the most + /// significant set bit. + /// + /// > ⚠️ Note: this compromises security by effectively decreasing the key length. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn enable_search_acceleration(&mut self) { + self.frontend.data_mut().search_acceleration = true; + } + + #[cfg(not(esp32))] + /// Enables acceleration by disabling constant time operation. + /// + /// Disabling constant time operation increases the performance of modular + /// exponentiation by simplifying the calculation concerning the 0 bits + /// of the exponent. I.e. the less the Hamming weight, the greater the + /// performance. + /// + /// > ⚠️ Note: this compromises security by enabling timing-based side-channel attacks. + /// + /// For more information refer to the + #[doc = trm_markdown_link!("rsa")] + pub fn enable_acceleration(&mut self) { + self.frontend.data_mut().constant_time = false; + } + + fn post(&mut self) -> RsaHandle<'_> { + RsaHandle(self.frontend.post(&RSA_WORK_QUEUE)) + } + + #[procmacros::doc_replace] + /// Starts a modular exponentiation operation, performing `Z = X ^ Y mod M`. + /// + /// Software needs to pre-calculate the following values: + /// + /// - `r`: `2 ^ ( bitlength * 2 ) mod M`. + /// - `m_prime` can be calculated using `-(modular multiplicative inverse of M) mod 2^32`. + /// + /// It is relatively easy to calculate these values using the `crypto-bigint` crate: + /// + /// ```rust,no_run + /// # {before_snippet} + /// use crypto_bigint::{U512, Uint}; + /// const fn compute_r(modulus: &U512) -> U512 { + /// let mut d = [0_u32; U512::LIMBS * 2 + 1]; + /// d[d.len() - 1] = 1; + /// let d = Uint::from_words(d); + /// d.const_rem(&modulus.resize()).0.resize() + /// } + /// + /// const fn compute_mprime(modulus: &U512) -> u32 { + /// let m_inv = modulus.inv_mod2k(32).to_words()[0]; + /// (-1 * m_inv as i64 & (u32::MAX as i64)) as u32 + /// } + /// + /// // Inputs + /// const X: U512 = Uint::from_be_hex( + /// "c7f61058f96db3bd87dbab08ab03b4f7f2f864eac249144adea6a65f97803b719d8ca980b7b3c0389c1c7c6\ + /// 7dc353c5e0ec11f5fc8ce7f6073796cc8f73fa878", + /// ); + /// const Y: U512 = Uint::from_be_hex( + /// "1763db3344e97be15d04de4868badb12a38046bb793f7630d87cf100aa1c759afac15a01f3c4c83ec2d2f66\ + /// 6bd22f71c3c1f075ec0e2cb0cb29994d091b73f51", + /// ); + /// const M: U512 = Uint::from_be_hex( + /// "6b6bb3d2b6cbeb45a769eaa0384e611e1b89b0c9b45a045aca1c5fd6e8785b38df7118cf5dd45b9b63d293b\ + /// 67aeafa9ba25feb8712f188cb139b7d9b9af1c361", + /// ); + /// + /// // Values derived using the functions we defined above: + /// let r = compute_r(&M); + /// let mprime = compute_mprime(&M); + /// + /// use esp_hal::rsa::{RsaContext, operand_sizes::Op512}; + /// + /// // Now perform the actual computation: + /// let mut rsa = RsaContext::new(); + /// let mut outbuf = [0; 16]; + /// let mut handle = rsa.modular_multiply::( + /// X.as_words(), + /// Y.as_words(), + /// M.as_words(), + /// r.as_words(), + /// mprime, + /// &mut outbuf, + /// ); + /// handle.wait_blocking(); + /// # {after_snippet} + /// ``` + /// + /// The calculation is done asynchronously. This function returns an [`RsaHandle`] that can be + /// used to poll the status of the calculation, to wait for it to finish, or to cancel the + /// operation (by dropping the handle). + /// + /// When the operation is completed, the result will be stored in `result`. + pub fn modular_exponentiate<'t, OP>( + &'t mut self, + x: &'t OP::InputType, + y: &'t OP::InputType, + m: &'t OP::InputType, + r: &'t OP::InputType, + m_prime: u32, + result: &'t mut OP::InputType, + ) -> RsaHandle<'t> + where + OP: RsaMode, + { + self.frontend.data_mut().operation = RsaOperation::ModularExponentiation { + x: NonNull::from(x.as_ref()), + y: NonNull::from(y.as_ref()), + m: NonNull::from(m.as_ref()), + r_inv: NonNull::from(r.as_ref()), + m_prime, + }; + self.frontend.data_mut().result = NonNull::from(result.as_mut()); + self.post() + } + + /// Starts a modular multiplication operation, performing `Z = X * Y mod M`. + /// + /// Software needs to pre-calculate the following values: + /// + /// - `r`: `2 ^ ( bitlength * 2 ) mod M`. + /// - `m_prime` can be calculated using `-(modular multiplicative inverse of M) mod 2^32`. + /// + /// For an example how these values can be calculated and used, see + /// [Self::modular_exponentiate]. + /// + /// The calculation is done asynchronously. This function returns an [`RsaHandle`] that can be + /// used to poll the status of the calculation, to wait for it to finish, or to cancel the + /// operation (by dropping the handle). + /// + /// When the operation is completed, the result will be stored in `result`. + pub fn modular_multiply<'t, OP>( + &'t mut self, + x: &'t OP::InputType, + y: &'t OP::InputType, + m: &'t OP::InputType, + r: &'t OP::InputType, + m_prime: u32, + result: &'t mut OP::InputType, + ) -> RsaHandle<'t> + where + OP: RsaMode, + { + self.frontend.data_mut().operation = RsaOperation::ModularMultiplication { + x: NonNull::from(x.as_ref()), + y: NonNull::from(y.as_ref()), + m: NonNull::from(m.as_ref()), + r: NonNull::from(r.as_ref()), + m_prime, + }; + self.frontend.data_mut().result = NonNull::from(result.as_mut()); + self.post() + } + + #[procmacros::doc_replace] + /// Starts a multiplication operation, performing `Z = X * Y`. + /// + /// The calculation is done asynchronously. This function returns an [`RsaHandle`] that can be + /// used to poll the status of the calculation, to wait for it to finish, or to cancel the + /// operation (by dropping the handle). + /// + /// When the operation is completed, the result will be stored in `result`. The `result` is + /// twice as wide as the inputs. + /// + /// ## Example + /// + /// ```rust,no_run + /// # {before_snippet} + /// + /// // Inputs + /// # let x: [u32; 16] = [0; 16]; + /// # let y: [u32; 16] = [0; 16]; + /// // let x: [u32; 16] = [...]; + /// // let y: [u32; 16] = [...]; + /// let mut outbuf = [0; 32]; + /// + /// use esp_hal::rsa::{RsaContext, operand_sizes::Op512}; + /// + /// // Now perform the actual computation: + /// let mut rsa = RsaContext::new(); + /// let mut handle = rsa.multiply::(&x, &y, &mut outbuf); + /// handle.wait_blocking(); + /// # {after_snippet} + /// ``` + pub fn multiply<'t, OP>( + &'t mut self, + x: &'t OP::InputType, + y: &'t OP::InputType, + result: &'t mut OP::OutputType, + ) -> RsaHandle<'t> + where + OP: Multi, + { + self.frontend.data_mut().operation = RsaOperation::Multiplication { + x: NonNull::from(x.as_ref()), + y: NonNull::from(y.as_ref()), + }; + self.frontend.data_mut().result = NonNull::from(result.as_mut()); + self.post() + } +} + +/// The handle to the pending RSA operation. +pub struct RsaHandle<'t>(work_queue::Handle<'t, RsaWorkItem>); + +impl RsaHandle<'_> { + /// Polls the status of the work item. + #[inline] + pub fn poll(&mut self) -> bool { + self.0.poll() + } + + /// Blocks until the work item to be processed. + #[inline] + pub fn wait_blocking(mut self) { + while !self.poll() {} + } + + /// Waits for the work item to be processed. + #[inline] + pub fn wait(&mut self) -> impl Future { + self.0.wait() + } +} diff --git a/esp-hal/src/rtc_cntl/mod.rs b/esp-hal/src/rtc_cntl/mod.rs new file mode 100644 index 00000000000..5122dd35351 --- /dev/null +++ b/esp-hal/src/rtc_cntl/mod.rs @@ -0,0 +1,753 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Real-Time Control and Low-power Management (RTC_CNTL) +//! +//! ## Overview +//! +//! The RTC_CNTL peripheral is responsible for managing the low-power modes on +//! the chip. +//! +//! ## Configuration +//! +//! It also includes the necessary configurations and constants for clock +//! sources and low-power management. The driver provides the following features +//! and functionalities: +//! +//! * Clock Configuration +//! * Calibration +//! * Low-Power Management +//! * Handling Watchdog Timers +#![cfg_attr( + lp_timer_driver_supported, + doc = r#" +## Examples + +### Get time in ms from the RTC Timer + +```rust, no_run +# {before_snippet} +# use core::time::Duration; +# use esp_hal::{delay::Delay, rtc_cntl::Rtc}; + +let rtc = Rtc::new(peripherals.LPWR); +let delay = Delay::new(); + +loop { + // Print the current RTC time in milliseconds + let time_ms = rtc.current_time_us() / 1000; + delay.delay_millis(1000); + + // Set the time to half a second in the past + let new_time = rtc.current_time_us() - 500_000; + rtc.set_current_time_us(new_time); +} +# } +``` + +### RWDT usage +```rust, no_run +# {before_snippet} +# use core::cell::RefCell; +# use critical_section::Mutex; +# use esp_hal::delay::Delay; +# use esp_hal::rtc_cntl::Rtc; +# use esp_hal::rtc_cntl::Rwdt; +# use esp_hal::rtc_cntl::RwdtStage; +static RWDT: Mutex>> = Mutex::new(RefCell::new(None)); + +let mut delay = Delay::new(); +let mut rtc = Rtc::new(peripherals.LPWR); + +rtc.set_interrupt_handler(interrupt_handler); +rtc.rwdt + .set_timeout(RwdtStage::Stage0, Duration::from_millis(2000)); +rtc.rwdt.listen(); + +critical_section::with(|cs| RWDT.borrow_ref_mut(cs).replace(rtc.rwdt)); +# {after_snippet} + +// Where the `LP_WDT` interrupt handler is defined as: +# use core::cell::RefCell; +# use critical_section::Mutex; +# use esp_hal::rtc_cntl::Rwdt; +# use esp_hal::rtc_cntl::RwdtStage; +static RWDT: Mutex>> = Mutex::new(RefCell::new(None)); + +// Handle the corresponding interrupt +#[esp_hal::handler] +fn interrupt_handler() { + critical_section::with(|cs| { + println!("RWDT Interrupt"); + + let mut rwdt = RWDT.borrow_ref_mut(cs); + if let Some(rwdt) = rwdt.as_mut() { + rwdt.clear_interrupt(); + + println!("Restarting in 5 seconds..."); + + rwdt.set_timeout(RwdtStage::Stage0, Duration::from_millis(5000)); + rwdt.unlisten(); + } + }); +} +``` + +### Get time in ms from the RTC Timer +```rust, no_run +# {before_snippet} +# use esp_hal::{delay::Delay, rtc_cntl::Rtc}; + +let rtc = Rtc::new(peripherals.LPWR); +let delay = Delay::new(); + +loop { + // Get the current RTC time in milliseconds + let time_ms = rtc.current_time_us() / 1000; + delay.delay_millis(1000); + + // Set the time to half a second in the past + let new_time = rtc.current_time_us() - 500_000; + rtc.set_current_time_us(new_time); +} +# } +``` +"# +)] +pub use self::rtc::SocResetReason; +#[cfg(sleep_driver_supported)] +use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers}; +#[cfg_attr(not(lp_timer_driver_supported), expect(unused))] +use crate::{ + interrupt::{self, InterruptHandler}, + peripherals::Interrupt, +}; +use crate::{ + peripherals::LPWR, + system::{Cpu, SleepSource}, + time::Duration, +}; +// only include sleep where it's been implemented +#[cfg(sleep_driver_supported)] +pub mod sleep; + +#[cfg_attr(esp32, path = "rtc/esp32.rs")] +#[cfg_attr(esp32c2, path = "rtc/esp32c2.rs")] +#[cfg_attr(esp32c3, path = "rtc/esp32c3.rs")] +#[cfg_attr(esp32c5, path = "rtc/esp32c5.rs")] +#[cfg_attr(esp32c6, path = "rtc/esp32c6.rs")] +#[cfg_attr(esp32h2, path = "rtc/esp32h2.rs")] +#[cfg_attr(esp32s2, path = "rtc/esp32s2.rs")] +#[cfg_attr(esp32s3, path = "rtc/esp32s3.rs")] +pub(crate) mod rtc; + +cfg_if::cfg_if! { + if #[cfg(any(esp32c6, esp32h2, esp32c5))] { + use crate::peripherals::LP_WDT; + #[cfg(not(esp32c5))] + use crate::peripherals::LP_TIMER; + use crate::peripherals::LP_AON; + } else { + use crate::peripherals::LPWR as LP_WDT; + use crate::peripherals::LPWR as LP_TIMER; + use crate::peripherals::LPWR as LP_AON; + } +} + +bitflags::bitflags! { + #[allow(unused)] + struct WakeupReason: u32 { + const NoSleep = 0; + #[cfg(pm_support_ext0_wakeup)] + /// EXT0 GPIO wakeup + const ExtEvent0Trig = 1 << 0; + #[cfg(pm_support_ext1_wakeup)] + /// EXT1 GPIO wakeup + const ExtEvent1Trig = 1 << 1; + /// GPIO wakeup (light sleep only) + const GpioTrigEn = 1 << 2; + #[cfg(not(any(esp32c6, esp32h2)))] + /// Timer wakeup + const TimerTrigEn = 1 << 3; + #[cfg(any(esp32c6, esp32h2))] + /// Timer wakeup + const TimerTrigEn = 1 << 4; + #[cfg(pm_support_wifi_wakeup)] + /// MAC wakeup (light sleep only) + const WifiTrigEn = 1 << 5; + /// UART0 wakeup (light sleep only) + const Uart0TrigEn = 1 << 6; + /// UART1 wakeup (light sleep only) + const Uart1TrigEn = 1 << 7; + #[cfg(pm_support_touch_sensor_wakeup)] + /// Touch wakeup + const TouchTrigEn = 1 << 8; + #[cfg(ulp_supported)] + /// ULP wakeup + const UlpTrigEn = 1 << 9; + #[cfg(pm_support_bt_wakeup)] + /// BT wakeup (light sleep only) + const BtTrigEn = 1 << 10; + #[cfg(riscv_coproc_supported)] + const CocpuTrigEn = 1 << 11; + #[cfg(riscv_coproc_supported)] + const CocpuTrapTrigEn = 1 << 13; + } +} + +/// Low-power Management +pub struct Rtc<'d> { + _inner: LPWR<'d>, + /// Reset Watchdog Timer. + pub rwdt: Rwdt, + /// Super Watchdog + #[cfg(swd)] + pub swd: Swd, +} + +impl<'d> Rtc<'d> { + /// Create a new instance in [crate::Blocking] mode. + /// + /// Optionally an interrupt handler can be bound. + pub fn new(rtc_cntl: LPWR<'d>) -> Self { + Self { + _inner: rtc_cntl, + rwdt: Rwdt(()), + #[cfg(swd)] + swd: Swd(()), + } + } + + /// Get the time since boot in the raw register units. + #[cfg(lp_timer_driver_supported)] + fn time_since_boot_raw(&self) -> u64 { + let rtc_cntl = LP_TIMER::regs(); + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + rtc_cntl.time_update().write(|w| w.time_update().set_bit()); + while rtc_cntl.time_update().read().time_valid().bit_is_clear() { + // Might take 1 RTC slowclk period, don't flood RTC bus + crate::rom::ets_delay_us(1); + } + + let h = rtc_cntl.time1().read().time_hi().bits(); + let l = rtc_cntl.time0().read().time_lo().bits(); + } else if #[cfg(any(esp32c6, esp32h2))] { + rtc_cntl.update().write(|w| w.main_timer_update().set_bit()); + + let h = rtc_cntl + .main_buf0_high() + .read() + .main_timer_buf0_high() + .bits(); + let l = rtc_cntl.main_buf0_low().read().main_timer_buf0_low().bits(); + } else { + rtc_cntl.time_update().write(|w| w.time_update().set_bit()); + + let h = rtc_cntl.time_high0().read().timer_value0_high().bits(); + let l = rtc_cntl.time_low0().read().timer_value0_low().bits(); + } + } + + ((h as u64) << 32) | (l as u64) + } + + /// Get the time elapsed since the last power-on reset. + /// + /// It should be noted that any reset or sleep, other than a power-up reset, will not stop or + /// reset the RTC timer. + #[cfg(lp_timer_driver_supported)] + pub fn time_since_power_up(&self) -> Duration { + Duration::from_micros(crate::clock::rtc_ticks_to_us(self.time_since_boot_raw())) + } + + /// Read the current value of the boot time registers in microseconds. + #[cfg(lp_timer_driver_supported)] + fn boot_time_us(&self) -> u64 { + // For more info on about how RTC setting works and what it has to do with boot time, see https://github.com/esp-rs/esp-hal/pull/1883 + + // In terms of registers, STORE2 and STORE3 are used on all current chips + // (esp32, esp32p4, esp32h2, esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, + // esp32s2, esp32s3) + + // In terms of peripherals: + + // - LPWR is used on the following chips: esp32, esp32p4, esp32c2, esp32c3, esp32s2, esp32s3 + + // - LP_AON is used on the following chips: esp32c5, esp32c6, esp32c61, esp32h2 + + // For registers and peripherals used in esp-idf, see https://github.com/search?q=repo%3Aespressif%2Fesp-idf+RTC_BOOT_TIME_LOW_REG+RTC_BOOT_TIME_HIGH_REG+path%3A**%2Frtc.h&type=code + + let rtc_cntl = LP_AON::regs(); + + let l = rtc_cntl.store2().read().bits() as u64; + let h = rtc_cntl.store3().read().bits() as u64; + + // https://github.com/espressif/esp-idf/blob/23e4823f17a8349b5e03536ff7653e3e584c9351/components/newlib/port/esp_time_impl.c#L115 + l + (h << 32) + } + + /// Set the current value of the boot time registers in microseconds. + #[cfg(lp_timer_driver_supported)] + fn set_boot_time_us(&self, boot_time_us: u64) { + // Please see `boot_time_us` for documentation on registers and peripherals + // used for certain SOCs. + + let rtc_cntl = LP_AON::regs(); + + // https://github.com/espressif/esp-idf/blob/23e4823/components/newlib/port/esp_time_impl.c#L102-L103 + rtc_cntl + .store2() // Low bits + .write(|w| unsafe { w.bits((boot_time_us & 0xffff_ffff) as u32) }); + rtc_cntl + .store3() // High bits + .write(|w| unsafe { w.bits((boot_time_us >> 32) as u32) }); + } + + #[procmacros::doc_replace] + /// Get the current time in microseconds. + /// + /// # Example + /// + /// This example shows how to get the weekday of the current time in + /// New York using the `jiff` crate. This example works in core-only + /// environments without dynamic memory allocation. + /// + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::rtc_cntl::Rtc; + /// use jiff::{ + /// Timestamp, + /// tz::{self, TimeZone}, + /// }; + /// + /// static TZ: TimeZone = tz::get!("America/New_York"); + /// + /// let rtc = Rtc::new(peripherals.LPWR); + /// let now = Timestamp::from_microsecond(rtc.current_time_us() as i64)?; + /// let weekday_in_new_york = now.to_zoned(TZ.clone()).weekday(); + /// # {after_snippet} + /// ``` + #[cfg(lp_timer_driver_supported)] + pub fn current_time_us(&self) -> u64 { + // Current time is boot time + time since boot + + let rtc_time_us = self.time_since_power_up().as_micros(); + let boot_time_us = self.boot_time_us(); + let wrapped_boot_time_us = u64::MAX - boot_time_us; + + // We can detect if we wrapped the boot time by checking if rtc time is greater + // than the amount of time we would've wrapped. + if rtc_time_us > wrapped_boot_time_us { + // We also just checked that this won't overflow + rtc_time_us - wrapped_boot_time_us + } else { + boot_time_us + rtc_time_us + } + } + + /// Set the current time in microseconds. + #[cfg(lp_timer_driver_supported)] + pub fn set_current_time_us(&self, current_time_us: u64) { + // Current time is boot time + time since boot (rtc time) + // So boot time = current time - time since boot (rtc time) + + let rtc_time_us = self.time_since_power_up().as_micros(); + if current_time_us < rtc_time_us { + // An overflow would happen if we subtracted rtc_time_us from current_time_us. + // To work around this, we can wrap around u64::MAX by subtracting the + // difference between the current time and the time since boot. + // Subtracting time since boot and adding current new time is equivalent and + // avoids overflow. We just checked that rtc_time_us is less than time_us + // so this won't overflow. + self.set_boot_time_us(u64::MAX - rtc_time_us + current_time_us) + } else { + self.set_boot_time_us(current_time_us - rtc_time_us) + } + } + + /// Enter deep sleep and wake with the provided `wake_sources`. + /// + /// In Deep-sleep mode, the CPUs, most of the RAM, and all digital + /// peripherals that are clocked from APB_CLK are powered off. + /// + /// You can use the [`#[esp_hal::ram(persistent)]`][procmacros::ram] + /// attribute to persist a variable though deep sleep. + #[cfg(sleep_deep_sleep)] + pub fn sleep_deep(&mut self, wake_sources: &[&dyn WakeSource]) -> ! { + let config = RtcSleepConfig::deep(); + self.sleep(&config, wake_sources); + unreachable!(); + } + + /// Enter light sleep and wake with the provided `wake_sources`. + #[cfg(sleep_light_sleep)] + pub fn sleep_light(&mut self, wake_sources: &[&dyn WakeSource]) { + let config = RtcSleepConfig::default(); + self.sleep(&config, wake_sources); + } + + /// Enter sleep with the provided `config` and wake with the provided + /// `wake_sources`. + #[cfg(sleep_driver_supported)] + pub fn sleep(&mut self, config: &RtcSleepConfig, wake_sources: &[&dyn WakeSource]) { + let mut config = *config; + let mut wakeup_triggers = WakeTriggers::default(); + for wake_source in wake_sources { + wake_source.apply(self, &mut wakeup_triggers, &mut config) + } + + config.apply(); + + config.start_sleep(wakeup_triggers); + config.finish_sleep(); + } + + pub(crate) const RTC_DISABLE_ROM_LOG: u32 = 1; + + /// Temporarily disable log messages of the ROM bootloader. + /// + /// If you need to permanently disable the ROM bootloader messages, you'll + /// need to set the corresponding eFuse. + pub fn disable_rom_message_printing(&self) { + // Corresponding documentation: + // ESP32-S3: TRM v1.5 chapter 8.3 + // ESP32-H2: TRM v0.5 chapter 8.2.3 + + LP_AON::regs() + .store4() + .modify(|r, w| unsafe { w.bits(r.bits() | Self::RTC_DISABLE_ROM_LOG) }); + } + + /// Register an interrupt handler for the RTC. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + #[cfg(lp_timer_driver_supported)] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + cfg_if::cfg_if! { + if #[cfg(any(esp32c6, esp32h2))] { + let interrupt = Interrupt::LP_WDT; + } else { + let interrupt = Interrupt::RTC_CORE; + } + } + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + interrupt::bind_handler(interrupt, handler); + } +} + +impl crate::private::Sealed for Rtc<'_> {} + +#[instability::unstable] +#[cfg(lp_timer_driver_supported)] +impl crate::interrupt::InterruptConfigurable for Rtc<'_> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +/// Behavior of the RWDT stage if it times out. +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub enum RwdtStageAction { + /// No effect on the system. + Off = 0, + /// Trigger an interrupt. + Interrupt = 1, + /// Reset the CPU core. + ResetCpu = 2, + /// Reset the main system. + /// The power management unit and RTC peripherals will not be reset. + ResetCore = 3, + /// Reset the main system, power management unit and RTC peripherals. + ResetSystem = 4, +} + +/// RWDT stages. +/// +/// Timer stages allow for a timer to have a series of different timeout values +/// and corresponding expiry action. +#[derive(Debug, Clone, Copy)] +pub enum RwdtStage { + /// RWDT stage 0. + Stage0, + /// RWDT stage 1. + Stage1, + /// RWDT stage 2. + Stage2, + /// RWDT stage 3. + Stage3, +} + +/// RTC Watchdog Timer. +pub struct Rwdt(()); + +/// RTC Watchdog Timer driver. +impl Rwdt { + /// Enable the watchdog timer instance. + /// Watchdog starts with default settings (`stage 0` resets the system, the + /// others are deactivated) + pub fn enable(&mut self) { + self.set_enabled(true); + } + + /// Disable the watchdog timer instance. + pub fn disable(&mut self) { + self.set_enabled(false); + } + + /// Listen for interrupts on stage 0. + pub fn listen(&mut self) { + let rtc_cntl = LP_WDT::regs(); + + self.set_write_protection(false); + + // Configure STAGE0 to trigger an interrupt upon expiration + rtc_cntl + .wdtconfig0() + .modify(|_, w| unsafe { w.wdt_stg0().bits(RwdtStageAction::Interrupt as u8) }); + + rtc_cntl.int_ena().modify(|_, w| w.wdt().set_bit()); + + self.set_write_protection(true); + } + + /// Stop listening for interrupts on stage 0. + pub fn unlisten(&mut self) { + let rtc_cntl = LP_WDT::regs(); + + self.set_write_protection(false); + + // Configure STAGE0 to reset the main system and the RTC upon expiration. + rtc_cntl + .wdtconfig0() + .modify(|_, w| unsafe { w.wdt_stg0().bits(RwdtStageAction::ResetSystem as u8) }); + + rtc_cntl.int_ena().modify(|_, w| w.wdt().clear_bit()); + + self.set_write_protection(true); + } + + /// Clear interrupt. + pub fn clear_interrupt(&mut self) { + self.set_write_protection(false); + + LP_WDT::regs() + .int_clr() + .write(|w| w.wdt().clear_bit_by_one()); + + self.set_write_protection(true); + } + + /// Check if the interrupt is set. + pub fn is_interrupt_set(&self) -> bool { + LP_WDT::regs().int_st().read().wdt().bit_is_set() + } + + /// Feed the watchdog timer. + pub fn feed(&mut self) { + self.set_write_protection(false); + LP_WDT::regs().wdtfeed().write(|w| w.wdt_feed().set_bit()); + self.set_write_protection(true); + } + + fn set_write_protection(&mut self, enable: bool) { + let wkey = if enable { 0u32 } else { 0x50D8_3AA1 }; + + LP_WDT::regs() + .wdtwprotect() + .write(|w| unsafe { w.bits(wkey) }); + } + + fn set_enabled(&mut self, enable: bool) { + let rtc_cntl = LP_WDT::regs(); + + self.set_write_protection(false); + + if !enable { + rtc_cntl.wdtconfig0().modify(|_, w| unsafe { w.bits(0) }); + } else { + rtc_cntl + .wdtconfig0() + .write(|w| w.wdt_flashboot_mod_en().bit(false)); + + rtc_cntl + .wdtconfig0() + .modify(|_, w| w.wdt_en().bit(enable).wdt_pause_in_slp().bit(enable)); + + // Apply default settings for WDT + unsafe { + rtc_cntl.wdtconfig0().modify(|_, w| { + w.wdt_stg0().bits(RwdtStageAction::ResetSystem as u8); + w.wdt_cpu_reset_length().bits(7); + w.wdt_sys_reset_length().bits(7); + w.wdt_stg1().bits(RwdtStageAction::Off as u8); + w.wdt_stg2().bits(RwdtStageAction::Off as u8); + w.wdt_stg3().bits(RwdtStageAction::Off as u8); + w.wdt_en().set_bit() + }); + } + } + + self.set_write_protection(true); + } + + /// Configure timeout value for the selected stage. + pub fn set_timeout(&mut self, stage: RwdtStage, timeout: Duration) { + let rtc_cntl = LP_WDT::regs(); + + let timeout_raw = crate::clock::us_to_rtc_ticks(timeout.as_micros()) as u32; + self.set_write_protection(false); + + let config_reg = match stage { + RwdtStage::Stage0 => rtc_cntl.wdtconfig1(), + RwdtStage::Stage1 => rtc_cntl.wdtconfig2(), + RwdtStage::Stage2 => rtc_cntl.wdtconfig3(), + RwdtStage::Stage3 => rtc_cntl.wdtconfig4(), + }; + + #[cfg(not(esp32))] + let timeout_raw = timeout_raw >> (1 + crate::efuse::rwdt_multiplier()); + + config_reg.modify(|_, w| unsafe { w.hold().bits(timeout_raw) }); + + self.set_write_protection(true); + } + + /// Set the action for a specific stage. + pub fn set_stage_action(&mut self, stage: RwdtStage, action: RwdtStageAction) { + self.set_write_protection(false); + + LP_WDT::regs().wdtconfig0().modify(|_, w| unsafe { + match stage { + RwdtStage::Stage0 => w.wdt_stg0().bits(action as u8), + RwdtStage::Stage1 => w.wdt_stg1().bits(action as u8), + RwdtStage::Stage2 => w.wdt_stg2().bits(action as u8), + RwdtStage::Stage3 => w.wdt_stg3().bits(action as u8), + } + }); + + self.set_write_protection(true); + } +} + +/// Super Watchdog +#[cfg(swd)] +pub struct Swd(()); + +/// Super Watchdog driver +#[cfg(swd)] +impl Swd { + /// Enable the watchdog timer instance + pub fn enable(&mut self) { + self.set_enabled(true); + } + + /// Disable the watchdog timer instance + pub fn disable(&mut self) { + self.set_enabled(false); + } + + /// Enable/disable write protection for WDT registers + fn set_write_protection(&mut self, enable: bool) { + #[cfg(not(any(esp32c6, esp32h2)))] + let wkey = if enable { 0u32 } else { 0x8F1D_312A }; + #[cfg(any(esp32c6, esp32h2))] + let wkey = if enable { 0u32 } else { 0x50D8_3AA1 }; + + LP_WDT::regs() + .swd_wprotect() + .write(|w| unsafe { w.swd_wkey().bits(wkey) }); + } + + fn set_enabled(&mut self, enable: bool) { + self.set_write_protection(false); + + LP_WDT::regs() + .swd_conf() + .write(|w| w.swd_auto_feed_en().bit(!enable)); + + self.set_write_protection(true); + } +} + +/// Return reset reason. +pub fn reset_reason(cpu: Cpu) -> Option { + let reason = crate::rom::rtc_get_reset_reason(cpu as u32); + + SocResetReason::from_repr(reason as usize) +} + +/// Return wakeup reason. +pub fn wakeup_cause() -> SleepSource { + if reset_reason(Cpu::ProCpu) != Some(SocResetReason::CoreDeepSleep) { + return SleepSource::Undefined; + } + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let wakeup_cause_bits = LPWR::regs().wakeup_state().read().wakeup_cause().bits() as u32; + } else if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let wakeup_cause_bits = crate::peripherals::PMU::regs() + .slp_wakeup_status0() + .read() + .wakeup_cause() + .bits(); + } else { + let wakeup_cause_bits = LPWR::regs().slp_wakeup_cause().read().wakeup_cause().bits(); + } + } + + let wakeup_cause = WakeupReason::from_bits_retain(wakeup_cause_bits); + + if wakeup_cause.contains(WakeupReason::TimerTrigEn) { + return SleepSource::Timer; + } + if wakeup_cause.contains(WakeupReason::GpioTrigEn) { + return SleepSource::Gpio; + } + if wakeup_cause.intersects(WakeupReason::Uart0TrigEn | WakeupReason::Uart1TrigEn) { + return SleepSource::Uart; + } + + #[cfg(pm_support_ext0_wakeup)] + if wakeup_cause.contains(WakeupReason::ExtEvent0Trig) { + return SleepSource::Ext0; + } + #[cfg(pm_support_ext1_wakeup)] + if wakeup_cause.contains(WakeupReason::ExtEvent1Trig) { + return SleepSource::Ext1; + } + + #[cfg(pm_support_touch_sensor_wakeup)] + if wakeup_cause.contains(WakeupReason::TouchTrigEn) { + return SleepSource::TouchPad; + } + + #[cfg(ulp_supported)] + if wakeup_cause.contains(WakeupReason::UlpTrigEn) { + return SleepSource::Ulp; + } + + #[cfg(pm_support_wifi_wakeup)] + if wakeup_cause.contains(WakeupReason::WifiTrigEn) { + return SleepSource::Wifi; + } + + #[cfg(pm_support_bt_wakeup)] + if wakeup_cause.contains(WakeupReason::BtTrigEn) { + return SleepSource::BT; + } + + #[cfg(riscv_coproc_supported)] + if wakeup_cause.contains(WakeupReason::CocpuTrigEn) { + return SleepSource::Ulp; + } else if wakeup_cause.contains(WakeupReason::CocpuTrapTrigEn) { + return SleepSource::CocpuTrapTrig; + } + + SleepSource::Undefined +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32.rs b/esp-hal/src/rtc_cntl/rtc/esp32.rs new file mode 100644 index 00000000000..f8cfff06ead --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32.rs @@ -0,0 +1,51 @@ +use strum::FromRepr; + +pub(crate) fn init() {} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +/// SOC Reset Reason. +pub enum SocResetReason { + /// Power on reset + ChipPowerOn = 0x01, + /// Software resets the digital core + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// SDIO module resets the digital core + CoreSdio = 0x06, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU + /// + /// In ESP-IDF there are `Cpu0Mwdt1` and `Cpu1Mwdt1`, however they have the + /// same values. + CpuMwdt0 = 0x0B, + /// Software resets CPU + /// + /// In ESP-IDF there are `Cpu0Sw` and `Cpu1Sw`, however they have the same + /// values. + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU + /// + /// In ESP-IDF there are `Cpu0RtcWdt` and `Cpu1RtcWdt`, however they have + /// the same values. + Cpu0RtcWdt = 0x0D, + /// CPU0 resets CPU1 by DPORT_APPCPU_RESETTING + Cpu1Cpu0 = 0x0E, + /// Reset when the VDD voltage is not stable + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32c2.rs b/esp-hal/src/rtc_cntl/rtc/esp32c2.rs new file mode 100644 index 00000000000..740b3d07ec0 --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32c2.rs @@ -0,0 +1,185 @@ +use strum::FromRepr; + +use crate::{ + peripherals::{APB_CTRL, EXTMEM, LPWR, SPI0, SPI1, SYSTEM}, + soc::regi2c, +}; + +pub(crate) fn init() { + let rtc_cntl = LPWR::regs(); + + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + + unsafe { + rtc_cntl.timer1().modify(|_, w| { + w.pll_buf_wait().bits(20u8); + w.ck8m_wait().bits(20u8) + }); + + rtc_cntl.timer5().modify(|_, w| w.min_slp_val().bits(2u8)); + } + + calibrate_ocode(); + + set_rtc_dig_dbias(); + + clock_control_init(); + + power_control_init(); + + unsafe { + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + } + + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(0); +} + +fn calibrate_ocode() {} + +fn set_rtc_dig_dbias() {} + +/// Perform clock control related initialization +fn clock_control_init() { + let extmem = EXTMEM::regs(); + let spi_mem_0 = SPI0::regs(); + let spi_mem_1 = SPI1::regs(); + + // Clear CMMU clock force on + extmem + .cache_mmu_power_ctrl() + .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); + + // Clear tag clock force on + extmem + .icache_tag_power_ctrl() + .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); + + // Clear register clock force on + spi_mem_0.clock_gate().modify(|_, w| w.clk_en().clear_bit()); + spi_mem_1.clock_gate().modify(|_, w| w.clk_en().clear_bit()); +} + +/// Perform power control related initialization +fn power_control_init() { + let rtc_cntl = LPWR::regs(); + let system = SYSTEM::regs(); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + // Cancel XTAL force PU if no need to force power up + // Cannot cancel XTAL force PU if PLL is force power on + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().clear_bit()); + + // Force PD APLL + rtc_cntl.ana_conf().modify(|_, w| { + w.plla_force_pu().clear_bit(); + w.plla_force_pd().set_bit(); + // Open SAR_I2C protect function to avoid SAR_I2C + // Reset when rtc_ldo is low. + w.i2c_reset_por_force_pd().clear_bit() + }); + + // Cancel BBPLL force PU if setting no force power up + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().clear_bit(); + w.bbpll_i2c_force_pu().clear_bit(); + w.bb_i2c_force_pu().clear_bit() + }); + + rtc_cntl + .rtc_cntl() + .modify(|_, w| w.regulator_force_pu().clear_bit()); + + // If this mask is enabled, all soc memories cannot enter power down mode. + // We should control soc memory power down mode from RTC, + // so we will not touch this register any more. + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + rtc_sleep_pu(); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_force_pu().clear_bit()); + + rtc_cntl + .dig_iso() + .modify(|_, w| w.dg_wrap_force_noiso().clear_bit()); + + // Cancel digital PADS force no iso + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); + + // If SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0, + // the CPU clock will be closed when CPU enter WAITI mode. + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold().clear_bit(); + w.dg_pad_force_noiso().clear_bit() + }); +} + +/// Configure whether certain peripherals are powered down in deep sleep +fn rtc_sleep_pu() { + LPWR::regs() + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().clear_bit()); + + APB_CTRL::regs().front_end_mem_pd().modify(|_, w| { + w.dc_mem_force_pu().clear_bit(); + w.pbus_mem_force_pu().clear_bit(); + w.agc_mem_force_pu().clear_bit() + }); + APB_CTRL::regs().mem_power_up().modify(|_, w| unsafe { + w.sram_power_up().bits(0u8); + w.rom_power_up().bits(0u8) + }); +} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +/// SOC Reset Reason. +pub enum SocResetReason { + /// Power on reset + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// Glitch on clock resets the digital core and rtc module + SysClkGlitch = 0x13, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// JTAG resets the CPU 0 + Cpu0Jtag = 0x18, +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32c3.rs b/esp-hal/src/rtc_cntl/rtc/esp32c3.rs new file mode 100644 index 00000000000..0df20e1cde4 --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32c3.rs @@ -0,0 +1,226 @@ +use strum::FromRepr; + +use crate::{ + peripherals::{APB_CTRL, EXTMEM, LPWR, SPI0, SPI1, SYSTEM}, + soc::regi2c, +}; + +pub(crate) fn init() { + let rtc_cntl = LPWR::regs(); + + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + + rtc_cntl.ana_conf().modify(|_, w| w.pvtmon_pu().clear_bit()); + + unsafe { + rtc_cntl + .timer1() + .modify(|_, w| w.pll_buf_wait().bits(20u8).ck8m_wait().bits(20u8)); + rtc_cntl.timer5().modify(|_, w| w.min_slp_val().bits(2u8)); + + // Set default powerup & wait time + rtc_cntl.timer3().modify(|_, w| { + w.wifi_powerup_timer().bits(1u8); + w.wifi_wait_timer().bits(1u16); + w.bt_powerup_timer().bits(1u8); + w.bt_wait_timer().bits(1u16) + }); + rtc_cntl.timer4().modify(|_, w| { + w.cpu_top_powerup_timer().bits(1u8); + w.cpu_top_wait_timer().bits(1u16); + w.dg_wrap_powerup_timer().bits(1u8); + w.dg_wrap_wait_timer().bits(1u16) + }); + rtc_cntl.timer6().modify(|_, w| { + w.dg_peri_powerup_timer().bits(1u8); + w.dg_peri_wait_timer().bits(1u16) + }); + } + + calibrate_ocode(); + + set_rtc_dig_dbias(); + + clock_control_init(); + + power_control_init(); + + unsafe { + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + } + + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(0); +} + +fn calibrate_ocode() {} + +fn set_rtc_dig_dbias() {} + +/// Perform clock control related initialization +fn clock_control_init() { + let extmem = EXTMEM::regs(); + let spi_mem_0 = SPI0::regs(); + let spi_mem_1 = SPI1::regs(); + + // Clear CMMU clock force on + extmem + .cache_mmu_power_ctrl() + .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); + + // Clear tag clock force on + extmem + .icache_tag_power_ctrl() + .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); + + // Clear register clock force on + spi_mem_0.clock_gate().modify(|_, w| w.clk_en().clear_bit()); + spi_mem_1.clock_gate().modify(|_, w| w.clk_en().clear_bit()); +} + +/// Perform power control related initialization +fn power_control_init() { + let rtc_cntl = LPWR::regs(); + let system = SYSTEM::regs(); + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + // Cancel XTAL force PU if no need to force power up + // Cannot cancel XTAL force PU if PLL is force power on + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().clear_bit()); + + // Force PD APLL + rtc_cntl.ana_conf().modify(|_, w| { + w.plla_force_pu().clear_bit(); + w.plla_force_pd().set_bit(); + // Open SAR_I2C protect function to avoid SAR_I2C + // Reset when rtc_ldo is low. + w.reset_por_force_pd().clear_bit() + }); + + // Cancel BBPLL force PU if setting no force power up + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().clear_bit(); + w.bbpll_i2c_force_pu().clear_bit(); + w.bb_i2c_force_pu().clear_bit() + }); + rtc_cntl.rtc_cntl().modify(|_, w| { + w.regulator_force_pu().clear_bit(); + w.dboost_force_pu().clear_bit(); + w.dboost_force_pd().set_bit() + }); + + // If this mask is enabled, all soc memories cannot enter power down mode. + // We should control soc memory power down mode from RTC, + // so we will not touch this register any more. + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + rtc_sleep_pu(); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.dg_wrap_force_pu().clear_bit(); + w.wifi_force_pu().clear_bit(); + w.bt_force_pu().clear_bit(); + w.cpu_top_force_pu().clear_bit(); + w.dg_peri_force_pu().clear_bit() + }); + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso().clear_bit(); + w.wifi_force_noiso().clear_bit(); + w.bt_force_noiso().clear_bit(); + w.cpu_top_force_noiso().clear_bit(); + w.dg_peri_force_noiso().clear_bit() + }); + + // Cancel digital PADS force no iso + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); + + // If SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0, + // the CPU clock will be closed when CPU enter WAITI mode. + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold().clear_bit(); + w.dg_pad_force_noiso().clear_bit() + }); +} + +/// Configure whether certain peripherals are powered down in deep sleep +fn rtc_sleep_pu() { + let rtc_cntl = LPWR::regs(); + let apb_ctrl = APB_CTRL::regs(); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.lslp_mem_force_pu().clear_bit(); + w.fastmem_force_lpu().clear_bit() + }); + + apb_ctrl.front_end_mem_pd().modify(|_, w| { + w.dc_mem_force_pu().clear_bit(); + w.pbus_mem_force_pu().clear_bit(); + w.agc_mem_force_pu().clear_bit() + }); + apb_ctrl.mem_power_up().modify(|_, w| unsafe { + w.sram_power_up().bits(0u8); + w.rom_power_up().bits(0u8) + }); +} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU 0 + Cpu0Mwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// Glitch on clock resets the digital core and rtc module + SysClkGlitch = 0x13, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// USB UART resets the digital core + CoreUsbUart = 0x15, + /// USB JTAG resets the digital core + CoreUsbJtag = 0x16, + /// Glitch on power resets the digital core + CorePwrGlitch = 0x17, +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32c5.rs b/esp-hal/src/rtc_cntl/rtc/esp32c5.rs new file mode 100644 index 00000000000..764583049d8 --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32c5.rs @@ -0,0 +1,58 @@ +use strum::FromRepr; + +pub(crate) fn init() {} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU 0 + Cpu0Mwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// USB UART resets the digital core + CoreUsbUart = 0x15, + /// USB JTAG resets the digital core + CoreUsbJtag = 0x16, + /// JTAG resets CPU + Cpu0JtagCpu = 0x18, + /// Power glitch resets CPU + PowerGlitch = 0x19, + /// CPU lockup reset + CpuLockup = 0x1A, +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32c6.rs b/esp-hal/src/rtc_cntl/rtc/esp32c6.rs new file mode 100644 index 00000000000..44f45c347a9 --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32c6.rs @@ -0,0 +1,1178 @@ +// Note: the PMU setup is based on esp-idf v5.1.2. Related code should be based +// on the same version until documentation is released and the code can be +// reasoned about. + +use strum::FromRepr; + +use crate::{ + peripherals::{LP_CLKRST, MODEM_LPCON, MODEM_SYSCON, PCR, PMU}, + soc::{ + clocks::{ClockTree, SocRootClkConfig}, + regi2c, + }, +}; + +fn pmu_power_domain_force_default() { + // for bypass reserved power domain + + // PMU_HP_PD_TOP + PMU::regs().power_pd_top_cntl().modify(|_, w| { + w.force_top_reset().bit(false); // pmu_ll_hp_set_power_force_reset + w.force_top_iso().bit(false); // pmu_ll_hp_set_power_force_isolate + w.force_top_pu().bit(false); // pmu_ll_hp_set_power_force_power_up + w.force_top_no_reset().bit(false); // pmu_ll_hp_set_power_force_no_reset + w.force_top_no_iso().bit(false); // pmu_ll_hp_set_power_force_no_isolate + w.force_top_pd().bit(false) // pmu_ll_hp_set_power_force_power_down + }); + + // PMU_HP_PD_HP_AON + PMU::regs().power_pd_hpaon_cntl().modify(|_, w| { + w.force_hp_aon_reset().bit(false); // pmu_ll_hp_set_power_force_reset + w.force_hp_aon_iso().bit(false); // pmu_ll_hp_set_power_force_isolate + w.force_hp_aon_pu().bit(false); // pmu_ll_hp_set_power_force_power_up + w.force_hp_aon_no_reset().bit(false); // pmu_ll_hp_set_power_force_no_reset + w.force_hp_aon_no_iso().bit(false); // pmu_ll_hp_set_power_force_no_isolate + w.force_hp_aon_pd().bit(false) // pmu_ll_hp_set_power_force_power_down + }); + + // PMU_HP_PD_CPU + PMU::regs().power_pd_hpcpu_cntl().modify(|_, w| { + w.force_hp_cpu_reset().bit(false); // pmu_ll_hp_set_power_force_reset + w.force_hp_cpu_iso().bit(false); // pmu_ll_hp_set_power_force_isolate + w.force_hp_cpu_pu().bit(false); // pmu_ll_hp_set_power_force_power_up + w.force_hp_cpu_no_reset().bit(false); // pmu_ll_hp_set_power_force_no_reset + w.force_hp_cpu_no_iso().bit(false); // pmu_ll_hp_set_power_force_no_isolate + w.force_hp_cpu_pd().bit(false) // pmu_ll_hp_set_power_force_power_down + }); + + // PMU_HP_PD_WIFI + PMU::regs().power_pd_hpwifi_cntl().modify(|_, w| { + w.force_hp_wifi_reset().bit(false); // pmu_ll_hp_set_power_force_reset + w.force_hp_wifi_iso().bit(false); // pmu_ll_hp_set_power_force_isolate + w.force_hp_wifi_pu().bit(false); // pmu_ll_hp_set_power_force_power_up + w.force_hp_wifi_no_reset().bit(false); // pmu_ll_hp_set_power_force_no_reset + w.force_hp_wifi_no_iso().bit(false); // pmu_ll_hp_set_power_force_no_isolate + w.force_hp_wifi_pd().bit(false) // pmu_ll_hp_set_power_force_power_down + }); + + // Isolate all memory banks while sleeping, avoid memory leakage current + + PMU::regs().power_pd_mem_cntl().modify(|_, w| unsafe { + w.force_hp_mem_no_iso().bits(0) // pmu_ll_hp_set_memory_no_isolate + }); + + PMU::regs().power_pd_lpperi_cntl().modify(|_, w| { + w.force_lp_peri_reset().bit(false); // pmu_ll_lp_set_power_force_reset + w.force_lp_peri_iso().bit(false); // pmu_ll_lp_set_power_force_isolate + w.force_lp_peri_pu().bit(false); // pmu_ll_lp_set_power_force_power_up + w.force_lp_peri_no_reset().bit(false); // pmu_ll_lp_set_power_force_no_reset + w.force_lp_peri_no_iso().bit(false); // pmu_ll_lp_set_power_force_no_isolate + w.force_lp_peri_pd().bit(false) // pmu_ll_lp_set_power_force_power_down + }); +} + +fn modem_clock_domain_power_state_icg_map_init() { + // C6 has SOC_PM_SUPPORT_PMU_MODEM_STATE defined + + // const ICG_NOGATING_SLEEP: u8 = 1 << 0; // unused + const ICG_NOGATING_MODEM: u8 = 1 << 1; + const ICG_NOGATING_ACTIVE: u8 = 1 << 2; + + const ICG_NOGATING_ACTIVE_MODEM: u8 = ICG_NOGATING_ACTIVE | ICG_NOGATING_MODEM; + + // the ICG code's bit 0, 1 and 2 indicates the ICG state + // of pmu SLEEP, MODEM and ACTIVE mode respectively + MODEM_SYSCON::regs() + .clk_conf_power_st() + .modify(|_, w| unsafe { + w.clk_modem_apb_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_syscon_ll_set_modem_apb_icg_bitmap + w.clk_modem_peri_st_map().bits(ICG_NOGATING_ACTIVE); // modem_syscon_ll_set_modem_periph_icg_bitmap + w.clk_wifi_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_syscon_ll_set_wifi_icg_bitmap + w.clk_bt_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_syscon_ll_set_bt_icg_bitmap + w.clk_fe_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_syscon_ll_set_fe_icg_bitmap + w.clk_zb_st_map().bits(ICG_NOGATING_ACTIVE_MODEM) // modem_syscon_ll_set_ieee802154_icg_bitmap + }); + + MODEM_LPCON::regs() + .clk_conf_power_st() + .modify(|_, w| unsafe { + w.clk_lp_apb_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_lpcon_ll_set_lp_apb_icg_bitmap + w.clk_i2c_mst_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_lpcon_ll_set_i2c_master_icg_bitmap + w.clk_coex_st_map().bits(ICG_NOGATING_ACTIVE_MODEM); // modem_lpcon_ll_set_coex_icg_bitmap + w.clk_wifipwr_st_map().bits(ICG_NOGATING_ACTIVE_MODEM) // modem_lpcon_ll_set_wifipwr_icg_bitmap + }); +} + +enum RtcSlowClockSource { + /// Select RC_SLOW_CLK as RTC_SLOW_CLK source + RcSlow = 0, + + /// Select XTAL32K_CLK as RTC_SLOW_CLK source + XTAL32K = 1, + + /// Select RC32K_CLK as RTC_SLOW_CLK source + RC32K = 2, + + /// Select OSC_SLOW_CLK (external slow clock) as RTC_SLOW_CLK source + OscSlow = 3, + + /// Invalid RTC_SLOW_CLK source + Invalid, +} + +impl RtcSlowClockSource { + fn current() -> Self { + // clk_ll_rtc_slow_get_src() + match LP_CLKRST::regs().lp_clk_conf().read().slow_clk_sel().bits() { + 0 => Self::RcSlow, + 1 => Self::XTAL32K, + 2 => Self::RC32K, + 3 => Self::OscSlow, + _ => Self::Invalid, + } + } +} + +#[allow(unused)] +enum ModemClockLpclkSource { + RcSlow = 0, + RcFast, + MainXtal, + RC32K, + XTAL32K, + EXT32K, +} + +impl From for ModemClockLpclkSource { + fn from(src: RtcSlowClockSource) -> Self { + match src { + RtcSlowClockSource::RcSlow => Self::RcSlow, + RtcSlowClockSource::XTAL32K => Self::XTAL32K, + RtcSlowClockSource::RC32K => Self::RC32K, + RtcSlowClockSource::OscSlow => Self::EXT32K, + _ => Self::RcSlow, + } + } +} + +fn modem_clock_hal_deselect_all_wifi_lpclk_source() { + MODEM_LPCON::regs().wifi_lp_clk_conf().modify(|_, w| { + w.clk_wifipwr_lp_sel_osc_slow().clear_bit(); + w.clk_wifipwr_lp_sel_osc_fast().clear_bit(); + w.clk_wifipwr_lp_sel_xtal32k().clear_bit(); + w.clk_wifipwr_lp_sel_xtal().clear_bit() + }); +} + +fn modem_clock_hal_select_wifi_lpclk_source(src: ModemClockLpclkSource) { + MODEM_LPCON::regs() + .wifi_lp_clk_conf() + .modify(|_, w| match src { + ModemClockLpclkSource::RcSlow => w.clk_wifipwr_lp_sel_osc_slow().set_bit(), + ModemClockLpclkSource::RcFast => w.clk_wifipwr_lp_sel_osc_fast().set_bit(), + ModemClockLpclkSource::MainXtal => w.clk_wifipwr_lp_sel_xtal().set_bit(), + + ModemClockLpclkSource::RC32K + | ModemClockLpclkSource::XTAL32K + | ModemClockLpclkSource::EXT32K => w.clk_wifipwr_lp_sel_xtal32k().set_bit(), + }); + + MODEM_LPCON::regs() + .modem_32k_clk_conf() + .modify(|_, w| unsafe { + match src { + ModemClockLpclkSource::RcSlow + | ModemClockLpclkSource::RcFast + | ModemClockLpclkSource::MainXtal => w, + + ModemClockLpclkSource::RC32K => w.clk_modem_32k_sel().bits(1), + ModemClockLpclkSource::XTAL32K => w.clk_modem_32k_sel().bits(0), + ModemClockLpclkSource::EXT32K => w.clk_modem_32k_sel().bits(2), + } + }); +} + +fn modem_lpcon_ll_set_wifi_lpclk_divisor_value(divider: u16) { + MODEM_LPCON::regs() + .wifi_lp_clk_conf() + .modify(|_, w| unsafe { w.clk_wifipwr_lp_div_num().bits(divider) }); +} + +fn modem_clock_hal_enable_wifipwr_clock(enable: bool) { + MODEM_LPCON::regs() + .clk_conf() + .modify(|_, w| w.clk_wifipwr_en().bit(enable)); +} + +// PHY, BT, IEEE802154 are not used by the init code so they are unimplemented +fn modem_clock_select_lp_clock_source_wifi(src: ModemClockLpclkSource, divider: u16) { + modem_clock_hal_deselect_all_wifi_lpclk_source(); + modem_clock_hal_select_wifi_lpclk_source(src); + modem_lpcon_ll_set_wifi_lpclk_divisor_value(divider); + modem_clock_hal_enable_wifipwr_clock(true); +} + +const fn hp_retention_regdma_config(dir: u8, entry: u8) -> u8 { + (((dir) << 2) | (entry & 0x3)) & 0x7 +} + +const HP_CALI_DBIAS: u8 = 25; +const LP_CALI_DBIAS: u8 = 26; + +const ICG_MODEM_CODE_SLEEP: u8 = 0; +const ICG_MODEM_CODE_MODEM: u8 = 1; +const ICG_MODEM_CODE_ACTIVE: u8 = 2; + +const HP_SYSCLK_XTAL: u8 = 0; +const HP_SYSCLK_PLL: u8 = 1; + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_power_t.0 + pub struct HpDigPower(u32); + + pub bool, vdd_spi_pd_en, set_vdd_spi_pd_en: 21; + pub bool, mem_dslp , set_mem_dslp : 22; + pub u8, mem_pd_en , set_mem_pd_en : 26, 23; + pub bool, wifi_pd_en , set_wifi_pd_en : 27; + pub bool, cpu_pd_en , set_cpu_pd_en : 29; + pub bool, aon_pd_en , set_aon_pd_en : 30; + pub bool, top_pd_en , set_top_pd_en : 31; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_power_t.1 + pub struct HpClkPower(u32); + + pub bool, i2c_iso_en , set_i2c_iso_en : 26; + pub bool, i2c_retention, set_i2c_retention: 27; + pub bool, xpd_bb_i2c , set_xpd_bb_i2c : 28; + pub bool, xpd_bbpll_i2c, set_xpd_bbpll_i2c: 29; + pub bool, xpd_bbpll , set_xpd_bbpll : 30; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_power_t.2 + pub struct HpXtalPower(u32); + + pub bool, xpd_xtal , set_xpd_xtal : 31; +} + +#[derive(Clone, Copy, Default)] +// pmu_sleep_power_config_t.0 +pub struct HpSysPower { + // This is a best-guess assignment of the variants in the union `pmu_hp_power_t` union + // In esp-idf, all three fields are `pmu_hp_power_t` + pub dig_power: HpDigPower, + pub clk: HpClkPower, + pub xtal: HpXtalPower, +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_sys_cntl_reg_t + pub struct HpSysCntlReg(u32); + + pub bool, uart_wakeup_en , set_uart_wakeup_en : 24; + pub bool, lp_pad_hold_all, set_lp_pad_hold_all: 25; + pub bool, hp_pad_hold_all, set_hp_pad_hold_all: 26; + pub bool, dig_pad_slp_sel, set_dig_pad_slp_sel: 27; + pub bool, dig_pause_wdt , set_dig_pause_wdt : 28; + pub bool, dig_cpu_stall , set_dig_cpu_stall : 29; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_icg_modem_reg_t + pub struct HpIcgModem(u32); + + pub u8, code, set_code: 31, 30; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_sysclk_reg_t + pub struct HpSysclk(u32); + + pub bool, dig_sysclk_nodiv , set_dig_sysclk_nodiv : 26; + pub bool, icg_sysclk_en , set_icg_sysclk_en : 27; + pub bool, sysclk_slp_sel , set_sysclk_slp_sel : 28; + pub bool, icg_slp_sel , set_icg_slp_sel : 29; + pub u8, dig_sysclk_sel , set_dig_sysclk_sel : 31, 30; +} + +// pmu_hp_system_clock_param_t +#[derive(Clone, Copy, Default)] +struct SystemClockParam { + icg_func: u32, + icg_apb: u32, + icg_modem: HpIcgModem, + sysclk: HpSysclk, +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_analog_t.0 + pub struct HpAnalogBias(u32); + + pub bool, xpd_bias , set_xpd_bias : 25; + pub u8, dbg_atten , set_dbg_atten : 29, 26; + pub bool, pd_cur , set_pd_cur : 30; + pub bool, bias_sleep, set_bias_sleep: 31; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_analog_t.1 + pub struct HpAnalogRegulator0(u32); + + // Only HP_ACTIVE modem under hp system is valid + pub u8, lp_dbias_vol , set_lp_dbias_vol : 8, 4; + // Only HP_ACTIVE modem under hp system is valid + pub u8, hp_dbias_vol , set_hp_dbias_vol : 13, 9; + // Only HP_ACTIVE modem under hp system is valid + pub bool, dbias_sel , set_dbias_sel : 14; + // Only HP_ACTIVE modem under hp system is valid + pub bool, dbias_init , set_dbias_init : 15; + + pub bool, slp_mem_xpd , set_slp_mem_xpd : 16; + pub bool, slp_logic_xpd , set_slp_logic_xpd : 17; + pub bool, xpd , set_xpd : 18; + pub u8, slp_mem_dbias , set_slp_mem_dbias : 22, 19; + pub u8, slp_logic_dbias, set_slp_logic_dbias: 26, 23; + pub u8, dbias , set_dbias : 31, 27; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_analog_t.2 + pub struct HpAnalogRegulator1(u32); + + pub u32, drv_b , set_drv_b : 31, 8; +} + +#[derive(Clone, Copy, Default)] +// pmu_hp_analog_t +pub struct HpAnalog { + pub bias: HpAnalogBias, + pub regulator0: HpAnalogRegulator0, + pub regulator1: HpAnalogRegulator1, +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_backup_reg_t/active + pub struct HpActiveBackup(u32); + + pub u8, hp_sleep2active_backup_modem_clk_code, set_hp_sleep2active_backup_modem_clk_code: 5, 4; + pub u8, hp_modem2active_backup_modem_clk_code, set_hp_modem2active_backup_modem_clk_code: 7, 6; + pub bool, hp_active_retention_mode , set_hp_active_retention_mode : 10; + pub bool, hp_sleep2active_retention_en , set_hp_sleep2active_retention_en : 11; + pub bool, hp_modem2active_retention_en , set_hp_modem2active_retention_en : 12; + pub u8, hp_sleep2active_backup_clk_sel , set_hp_sleep2active_backup_clk_sel : 15, 14; + pub u8, hp_modem2active_backup_clk_sel , set_hp_modem2active_backup_clk_sel : 17, 16; + pub u8, hp_sleep2active_backup_mode , set_hp_sleep2active_backup_mode : 22, 20; + pub u8, hp_modem2active_backup_mode , set_hp_modem2active_backup_mode : 25, 23; + pub bool, hp_sleep2active_backup_en , set_hp_sleep2active_backup_en : 29; + pub bool, hp_modem2active_backup_en , set_hp_modem2active_backup_en : 30; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_backup_reg_t/modem + pub struct HpModemBackup(u32); + + pub u8, hp_sleep2modem_backup_modem_clk_code , set_hp_sleep2modem_backup_modem_clk_code : 5, 4; + pub bool, hp_modem_retention_mode , set_hp_modem_retention_mode : 10; + pub bool, hp_sleep2modem_retention_en , set_hp_sleep2modem_retention_en : 11; + pub u8, hp_sleep2modem_backup_clk_sel , set_hp_sleep2modem_backup_clk_sel : 15, 14; + pub u8, hp_sleep2modem_backup_mode , set_hp_sleep2modem_backup_mode : 22, 20; + pub bool, hp_sleep2modem_backup_en , set_hp_sleep2modem_backup_en : 29; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_hp_backup_reg_t/sleep + pub struct HpSleepBackup(u32); + + pub u8, hp_modem2sleep_backup_modem_clk_code , set_hp_modem2sleep_backup_modem_clk_code : 7, 6; + pub u8, hp_active2sleep_backup_modem_clk_code, set_hp_active2sleep_backup_modem_clk_code: 9, 8; + pub bool, hp_sleep_retention_mode , set_hp_sleep_retention_mode : 10; + pub bool, hp_modem2sleep_retention_en , set_hp_modem2sleep_retention_en : 12; + pub bool, hp_active2sleep_retention_en , set_hp_active2sleep_retention_en : 13; + pub u8, hp_modem2sleep_backup_clk_sel , set_hp_modem2sleep_backup_clk_sel : 17, 16; + pub u8, hp_active2sleep_backup_clk_sel , set_hp_active2sleep_backup_clk_sel : 19, 18; + pub u8, hp_modem2sleep_backup_mode , set_hp_modem2sleep_backup_mode : 25, 23; + pub u8, hp_active2sleep_backup_mode , set_hp_active2sleep_backup_mode : 28, 26; + pub bool, hp_modem2sleep_backup_en , set_hp_modem2sleep_backup_en : 30; + pub bool, hp_active2sleep_backup_en , set_hp_active2sleep_backup_en : 31; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // custom based on `PMU_ICG_FUNC_ENA_*` bitflag constants + pub struct HpBackupClk(u32); + + pub bool, gdma , set_gdma : 0; + pub bool, spi2 , set_spi2 : 1; + pub bool, i2s_rx , set_i2s_rx : 2; + pub bool, uart0 , set_uart0 : 3; + pub bool, uart1 , set_uart1 : 4; + pub bool, uhci , set_uhci : 5; + pub bool, usb_device , set_usb_device : 6; + pub bool, i2s_tx , set_i2s_tx : 7; + pub bool, regdma , set_regdma : 8; + pub bool, retention , set_retention : 9; + pub bool, mem_monitor , set_mem_monitor : 10; + pub bool, sdio_slave , set_sdio_slave : 11; + pub bool, tsens , set_tsens : 12; + pub bool, tg1 , set_tg1 : 13; + pub bool, tg0 , set_tg0 : 14; + pub bool, hpbus , set_hpbus : 15; + pub bool, soc_etm , set_soc_etm : 16; + pub bool, hpcore , set_hpcore : 17; + pub bool, systimer , set_systimer : 18; + pub bool, sec , set_sec : 19; + pub bool, saradc , set_saradc : 20; + pub bool, rmt , set_rmt : 21; + pub bool, pwm , set_pwm : 22; + pub bool, pvt_monitor , set_pvt_monitor : 23; + pub bool, parl_tx , set_parl_tx : 24; + pub bool, parl_rx , set_parl_rx : 25; + pub bool, mspi , set_mspi : 26; + pub bool, ledc , set_ledc : 27; + pub bool, iomux , set_iomux : 28; + pub bool, i2c , set_i2c : 29; + pub bool, can1 , set_can1 : 30; + pub bool, can0 , set_can0 : 31; +} + +macro_rules! hp_system_init { + ($state:ident => $s:ident) => { + paste::paste! { + unsafe { + // Default configuration of hp-system power in active, modem and sleep modes + PMU::regs().[<$state _dig_power >]().modify(|_, w| w.bits($s.power.dig_power.0)); + PMU::regs().[<$state _hp_ck_power >]().modify(|_, w| w.bits($s.power.clk.0)); + PMU::regs().[<$state _xtal >]().modify(|_, w| w + .[<$state _xpd_xtal >]().bit($s.power.xtal.xpd_xtal()) + ); + + // Default configuration of hp-system clock in active, modem and sleep modes + PMU::regs().[<$state _icg_hp_func >]().write(|w| w.bits($s.clock.icg_func)); + PMU::regs().[<$state _icg_hp_apb >]().write(|w| w.bits($s.clock.icg_apb)); + PMU::regs().[<$state _icg_modem >]().write(|w| w + .[<$state _dig_icg_modem_code >]().bits($s.clock.icg_modem.code()) + ); + PMU::regs().[<$state _sysclk >]().modify(|_, w| w + .[<$state _dig_sys_clk_no_div >]().bit($s.clock.sysclk.dig_sysclk_nodiv()) + .[<$state _icg_sys_clock_en >]().bit($s.clock.sysclk.icg_sysclk_en()) + .[<$state _sys_clk_slp_sel >]().bit($s.clock.sysclk.sysclk_slp_sel()) + .[<$state _icg_slp_sel >]().bit($s.clock.sysclk.icg_slp_sel()) + .[<$state _dig_sys_clk_sel >]().bits($s.clock.sysclk.dig_sysclk_sel()) + ); + + // Default configuration of hp-system digital sub-system in active, modem + // and sleep modes + PMU::regs().[<$state _hp_sys_cntl >]().modify(|_, w| w + .[<$state _uart_wakeup_en >]().bit($s.syscntl.uart_wakeup_en()) + .[<$state _lp_pad_hold_all >]().bit($s.syscntl.lp_pad_hold_all()) + .[<$state _hp_pad_hold_all >]().bit($s.syscntl.hp_pad_hold_all()) + .[<$state _dig_pad_slp_sel >]().bit($s.syscntl.dig_pad_slp_sel()) + .[<$state _dig_pause_wdt >]().bit($s.syscntl.dig_pause_wdt()) + .[<$state _dig_cpu_stall >]().bit($s.syscntl.dig_cpu_stall()) + ); + + // Default configuration of hp-system analog sub-system in active, modem and + // sleep modes + PMU::regs().[<$state _bias >]().modify(|_, w| w + .[<$state _xpd_bias >]().bit($s.anlg.bias.xpd_bias()) + .[<$state _dbg_atten >]().bits($s.anlg.bias.dbg_atten()) + .[<$state _pd_cur >]().bit($s.anlg.bias.pd_cur()) + .sleep().bit($s.anlg.bias.bias_sleep()) + ); + + PMU::regs().[<$state _hp_regulator0 >]().modify(|_, w| w + .[<$state _hp_regulator_slp_mem_xpd >]().bit($s.anlg.regulator0.slp_mem_xpd()) + .[<$state _hp_regulator_slp_logic_xpd >]().bit($s.anlg.regulator0.slp_logic_xpd()) + .[<$state _hp_regulator_xpd >]().bit($s.anlg.regulator0.xpd()) + .[<$state _hp_regulator_slp_mem_dbias >]().bits($s.anlg.regulator0.slp_mem_dbias()) + .[<$state _hp_regulator_slp_logic_dbias >]().bits($s.anlg.regulator0.slp_logic_dbias()) + .[<$state _hp_regulator_dbias >]().bits($s.anlg.regulator0.dbias()) + ); + + PMU::regs().[<$state _hp_regulator1 >]().modify(|_, w| w + .[<$state _hp_regulator_drv_b >]().bits($s.anlg.regulator1.drv_b()) + ); + + // Default configuration of hp-system retention sub-system in active, modem + // and sleep modes + PMU::regs().[<$state _backup >]().write(|w| w.bits($s.retention)); + PMU::regs().[<$state _backup_clk >]().write(|w| w.bits($s.backup_clk)); + } + } + }; +} + +struct HpSystemInit { + power: HpSysPower, + clock: SystemClockParam, + syscntl: HpSysCntlReg, + anlg: HpAnalog, + retention: u32, + backup_clk: u32, +} +impl HpSystemInit { + fn active() -> Self { + // pmu_hp_system_init_default + + let mut power = HpSysPower::default(); + power.dig_power.set_vdd_spi_pd_en(false); + power.dig_power.set_wifi_pd_en(false); + power.dig_power.set_cpu_pd_en(false); + power.dig_power.set_aon_pd_en(false); + power.dig_power.set_top_pd_en(false); + power.dig_power.set_mem_pd_en(0); + power.dig_power.set_mem_dslp(false); + + power.clk.set_i2c_iso_en(false); + power.clk.set_i2c_retention(false); + power.clk.set_xpd_bb_i2c(true); + power.clk.set_xpd_bbpll_i2c(true); + power.clk.set_xpd_bbpll(true); + + power.xtal.set_xpd_xtal(true); + + let mut clock = SystemClockParam { + icg_func: 0xffffffff, + icg_apb: 0xffffffff, + ..SystemClockParam::default() + }; + clock.icg_modem.set_code(ICG_MODEM_CODE_ACTIVE); + clock.sysclk.set_dig_sysclk_nodiv(false); + clock.sysclk.set_icg_sysclk_en(true); + clock.sysclk.set_sysclk_slp_sel(false); + clock.sysclk.set_icg_slp_sel(false); + clock.sysclk.set_dig_sysclk_sel(HP_SYSCLK_XTAL); + + let mut syscntl = HpSysCntlReg::default(); + syscntl.set_uart_wakeup_en(false); + syscntl.set_lp_pad_hold_all(false); + syscntl.set_hp_pad_hold_all(false); + syscntl.set_dig_pad_slp_sel(false); + syscntl.set_dig_pause_wdt(false); + syscntl.set_dig_cpu_stall(false); + + // PMU_HP_ACTIVE_ANALOG_CONFIG_DEFAULT + let mut anlg = HpAnalog::default(); + anlg.bias.set_xpd_bias(true); + anlg.bias.set_dbg_atten(0x0); + anlg.bias.set_pd_cur(false); + anlg.bias.set_bias_sleep(false); + + // TODO: These 4 aren't applied currently? + anlg.regulator0.set_lp_dbias_vol(0xD); + anlg.regulator0.set_hp_dbias_vol(0x1C); + anlg.regulator0.set_dbias_sel(true); + anlg.regulator0.set_dbias_init(true); + + anlg.regulator0.set_slp_mem_xpd(false); + anlg.regulator0.set_slp_logic_xpd(false); + anlg.regulator0.set_xpd(true); + anlg.regulator0.set_slp_mem_dbias(0); + anlg.regulator0.set_slp_logic_dbias(0); + anlg.regulator0.set_dbias(HP_CALI_DBIAS); + + anlg.regulator1.set_drv_b(0); + + let mut retention = HpActiveBackup::default(); + retention.set_hp_sleep2active_backup_modem_clk_code(2); + retention.set_hp_modem2active_backup_modem_clk_code(2); + retention.set_hp_active_retention_mode(false); + retention.set_hp_sleep2active_retention_en(false); + retention.set_hp_modem2active_retention_en(false); + retention.set_hp_sleep2active_backup_clk_sel(0); + retention.set_hp_modem2active_backup_clk_sel(1); + retention.set_hp_sleep2active_backup_mode(hp_retention_regdma_config(0, 0)); + retention.set_hp_modem2active_backup_mode(hp_retention_regdma_config(0, 2)); + retention.set_hp_sleep2active_backup_en(false); + retention.set_hp_modem2active_backup_en(false); + + let mut backup_clk = HpBackupClk::default(); + backup_clk.set_regdma(true); + backup_clk.set_tg0(true); + backup_clk.set_tg1(true); + backup_clk.set_hpbus(true); + backup_clk.set_mspi(true); + backup_clk.set_iomux(true); + backup_clk.set_spi2(true); + backup_clk.set_uart0(true); + backup_clk.set_systimer(true); + + Self { + power, + clock, + syscntl, + anlg, + retention: retention.0, + backup_clk: backup_clk.0, + } + } + + fn modem() -> Self { + let mut power = HpSysPower::default(); + power.dig_power.set_vdd_spi_pd_en(false); + power.dig_power.set_wifi_pd_en(false); + power.dig_power.set_cpu_pd_en(true); + power.dig_power.set_aon_pd_en(false); + power.dig_power.set_top_pd_en(false); + power.dig_power.set_mem_pd_en(0); + power.dig_power.set_mem_dslp(false); + + power.clk.set_xpd_bb_i2c(true); + power.clk.set_xpd_bbpll_i2c(true); + power.clk.set_xpd_bbpll(true); + power.clk.set_i2c_iso_en(false); + power.clk.set_i2c_retention(false); + + power.xtal.set_xpd_xtal(true); + + let mut clock = SystemClockParam { + icg_func: 0, + icg_apb: 0, + ..SystemClockParam::default() + }; + clock.icg_modem.set_code(ICG_MODEM_CODE_MODEM); + clock.sysclk.set_dig_sysclk_nodiv(false); + clock.sysclk.set_icg_sysclk_en(true); + clock.sysclk.set_sysclk_slp_sel(true); + clock.sysclk.set_icg_slp_sel(true); + clock.sysclk.set_dig_sysclk_sel(HP_SYSCLK_PLL); + + let mut syscntl = HpSysCntlReg::default(); + syscntl.set_uart_wakeup_en(true); + syscntl.set_lp_pad_hold_all(false); + syscntl.set_hp_pad_hold_all(false); + syscntl.set_dig_pad_slp_sel(false); + syscntl.set_dig_pause_wdt(true); + syscntl.set_dig_cpu_stall(true); + + let mut anlg = HpAnalog::default(); + anlg.bias.set_xpd_bias(false); + anlg.bias.set_dbg_atten(0x0); + anlg.bias.set_pd_cur(false); + anlg.bias.set_bias_sleep(false); + + anlg.regulator0.set_slp_mem_xpd(false); + anlg.regulator0.set_slp_logic_xpd(false); + anlg.regulator0.set_xpd(true); + anlg.regulator0.set_slp_mem_dbias(0); + anlg.regulator0.set_slp_logic_dbias(0); + anlg.regulator0.set_dbias(HP_CALI_DBIAS); + + anlg.regulator1.set_drv_b(0); + + let mut retention = HpModemBackup::default(); + retention.set_hp_sleep2modem_backup_modem_clk_code(1); + retention.set_hp_modem_retention_mode(false); + retention.set_hp_sleep2modem_retention_en(false); + retention.set_hp_sleep2modem_backup_clk_sel(0); + retention.set_hp_sleep2modem_backup_mode(hp_retention_regdma_config(0, 1)); + retention.set_hp_sleep2modem_backup_en(false); + + let mut backup_clk = HpBackupClk::default(); + backup_clk.set_regdma(true); + backup_clk.set_tg0(true); + backup_clk.set_tg1(true); + backup_clk.set_hpbus(true); + backup_clk.set_mspi(true); + backup_clk.set_iomux(true); + backup_clk.set_spi2(true); + backup_clk.set_uart0(true); + backup_clk.set_systimer(true); + + Self { + power, + clock, + syscntl, + anlg, + retention: retention.0, + backup_clk: backup_clk.0, + } + } + + fn sleep() -> Self { + let mut power = HpSysPower::default(); + power.dig_power.set_vdd_spi_pd_en(true); + power.dig_power.set_mem_dslp(false); + power.dig_power.set_mem_pd_en(0); + power.dig_power.set_wifi_pd_en(true); + power.dig_power.set_cpu_pd_en(false); + power.dig_power.set_aon_pd_en(false); + power.dig_power.set_top_pd_en(false); + + power.clk.set_i2c_iso_en(true); + power.clk.set_i2c_retention(true); + power.clk.set_xpd_bb_i2c(true); + power.clk.set_xpd_bbpll_i2c(false); + power.clk.set_xpd_bbpll(false); + + power.xtal.set_xpd_xtal(false); + + let mut clock = SystemClockParam { + icg_func: 0, + icg_apb: 0, + ..SystemClockParam::default() + }; + clock.icg_modem.set_code(ICG_MODEM_CODE_SLEEP); + clock.sysclk.set_dig_sysclk_nodiv(false); + clock.sysclk.set_icg_sysclk_en(false); + clock.sysclk.set_sysclk_slp_sel(true); + clock.sysclk.set_icg_slp_sel(true); + clock.sysclk.set_dig_sysclk_sel(HP_SYSCLK_XTAL); + + let mut anlg = HpAnalog::default(); + anlg.bias.set_xpd_bias(false); + anlg.bias.set_dbg_atten(0x0); + anlg.bias.set_pd_cur(false); + anlg.bias.set_bias_sleep(false); + + anlg.regulator0.set_slp_mem_xpd(false); + anlg.regulator0.set_slp_logic_xpd(false); + anlg.regulator0.set_xpd(true); + anlg.regulator0.set_slp_mem_dbias(0); + anlg.regulator0.set_slp_logic_dbias(0); + anlg.regulator0.set_dbias(1); + + anlg.regulator1.set_drv_b(0); + + let mut retention = HpSleepBackup::default(); + retention.set_hp_modem2sleep_backup_modem_clk_code(0); + retention.set_hp_active2sleep_backup_modem_clk_code(2); + retention.set_hp_sleep_retention_mode(false); + retention.set_hp_modem2sleep_retention_en(false); + retention.set_hp_active2sleep_retention_en(false); + retention.set_hp_modem2sleep_backup_clk_sel(0); + retention.set_hp_active2sleep_backup_clk_sel(0); + retention.set_hp_modem2sleep_backup_mode(hp_retention_regdma_config(1, 1)); + retention.set_hp_active2sleep_backup_mode(hp_retention_regdma_config(1, 0)); + retention.set_hp_modem2sleep_backup_en(false); + retention.set_hp_active2sleep_backup_en(false); + + let mut backup_clk = HpBackupClk::default(); + backup_clk.set_regdma(true); + backup_clk.set_tg0(true); + backup_clk.set_tg1(true); + backup_clk.set_hpbus(true); + backup_clk.set_mspi(true); + backup_clk.set_iomux(true); + backup_clk.set_spi2(true); + backup_clk.set_uart0(true); + backup_clk.set_systimer(true); + + let mut syscntl = HpSysCntlReg::default(); + syscntl.set_uart_wakeup_en(true); + syscntl.set_lp_pad_hold_all(false); + syscntl.set_hp_pad_hold_all(false); + syscntl.set_dig_pad_slp_sel(true); + syscntl.set_dig_pause_wdt(true); + syscntl.set_dig_cpu_stall(true); + + Self { + power, + clock, + syscntl, + anlg, + retention: retention.0, + backup_clk: backup_clk.0, + } + } + + fn init_default() { + let active = Self::active(); + let modem = Self::modem(); + let sleep = Self::sleep(); + + hp_system_init!(hp_active => active); + hp_system_init!(hp_modem => modem); + hp_system_init!(hp_sleep => sleep); + + unsafe { + // Some PMU initial parameter configuration + PMU::regs() + .imm_modem_icg() + .write(|w| w.update_dig_icg_modem_en().bit(true)); + PMU::regs() + .imm_sleep_sysclk() + .write(|w| w.update_dig_icg_switch().bit(true)); + + const PMU_SLEEP_PROTECT_HP_LP_SLEEP: u8 = 2; + PMU::regs() + .slp_wakeup_cntl3() + .modify(|_, w| w.sleep_prt_sel().bits(PMU_SLEEP_PROTECT_HP_LP_SLEEP)); + } + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_power_t.0 + pub struct LpDigPower(u32); + + pub u32, mem_dslp , set_mem_dslp : 30; + pub u32, peri_pd_en, set_peri_pd_en: 31; + +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_power_t.1 + pub struct LpClkPower(u32); + + pub u32, xpd_xtal32k, set_xpd_xtal32k: 28; + pub u32, xpd_rc32k , set_xpd_rc32k : 29; + pub u32, xpd_fosc , set_xpd_fosc : 30; + pub u32, pd_osc , set_pd_osc : 31; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_power_t.2 + pub struct LpXtalPower(u32); + + pub bool, xpd_xtal , set_xpd_xtal : 31; +} + +#[derive(Clone, Copy, Default)] +// pmu_sleep_power_config_t.1 +pub struct LpSysPower { + // This is a best-guess assignment of the variants in the union `pmu_lp_power_t` union + // In esp-idf, all three fields are `pmu_lp_power_t` + pub dig_power: LpDigPower, + pub clk_power: LpClkPower, + pub xtal: LpXtalPower, +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_analog_t.0 + pub struct LpAnalogBias(u32); + + pub bool, xpd_bias , set_xpd_bias : 25; + pub u8, dbg_atten , set_dbg_atten : 29, 26; + pub bool, pd_cur , set_pd_cur : 30; + pub bool, bias_sleep, set_bias_sleep: 31; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_analog_t.1 + pub struct LpAnalogRegulator0(u32); + + pub bool, slp_xpd , set_slp_xpd : 21; + pub bool, xpd , set_xpd : 22; + pub u8, slp_dbias, set_slp_dbias: 26, 23; + pub u8, dbias , set_dbias : 31, 27; +} + +bitfield::bitfield! { + #[derive(Clone, Copy, Default)] + // pmu_lp_analog_t.2 + pub struct LpAnalogRegulator1(u32); + + pub u8, drv_b , set_drv_b : 31, 28; +} + +#[derive(Clone, Copy, Default)] +// pmu_lp_analog_t +pub struct LpAnalog { + pub bias: LpAnalogBias, + pub regulator0: LpAnalogRegulator0, + pub regulator1: LpAnalogRegulator1, +} + +macro_rules! lp_system_init { + ($state:ident => $s:ident) => { + paste::paste! { + unsafe { + // Default configuration of lp-system power in active and sleep modes + PMU::regs().[< $state _dig_power >]().modify(|_, w| w.bits($s.dig_power.0)); + PMU::regs().[< $state _ck_power >]().modify(|_, w| w.bits($s.clk_power.0)); + + // Default configuration of lp-system analog sub-system in active and sleep modes + PMU::regs().[< $state _regulator0 >]().modify(|_, w| w + .[< $state _regulator_slp_xpd >]().bit($s.analog_regulator0.slp_xpd()) + .[< $state _regulator_xpd >]().bit($s.analog_regulator0.xpd()) + .[< $state _regulator_slp_dbias >]().bits($s.analog_regulator0.slp_dbias()) + .[< $state _regulator_dbias >]().bits($s.analog_regulator0.dbias()) + ); + + PMU::regs().[< $state _regulator1 >]().modify(|_, w| w + .[< $state _regulator_drv_b >]().bits($s.analog_regulator1.drv_b()) + ); + } + } + }; +} + +struct LpSystemInit { + dig_power: LpDigPower, + clk_power: LpClkPower, + xtal: LpXtalPower, + bias: LpAnalogBias, + analog_regulator0: LpAnalogRegulator0, + analog_regulator1: LpAnalogRegulator1, +} +impl LpSystemInit { + fn active() -> Self { + let mut dig_power = LpDigPower::default(); + dig_power.set_peri_pd_en(false); + dig_power.set_mem_dslp(false); + + let mut clk_power = LpClkPower::default(); + clk_power.set_xpd_xtal32k(true); + clk_power.set_xpd_rc32k(true); + clk_power.set_xpd_fosc(true); + + let mut analog_regulator0 = LpAnalogRegulator0::default(); + analog_regulator0.set_slp_xpd(false); + analog_regulator0.set_xpd(true); + analog_regulator0.set_slp_dbias(0); + analog_regulator0.set_dbias(26); + + let mut analog_regulator1 = LpAnalogRegulator1::default(); + analog_regulator1.set_drv_b(0); + + Self { + dig_power, + clk_power, + xtal: LpXtalPower::default(), + bias: LpAnalogBias::default(), + analog_regulator0, + analog_regulator1, + } + } + + fn sleep() -> Self { + let mut dig_power = LpDigPower::default(); + dig_power.set_mem_dslp(true); + dig_power.set_peri_pd_en(false); + + let mut clk_power = LpClkPower::default(); + clk_power.set_xpd_xtal32k(false); + clk_power.set_xpd_rc32k(false); + clk_power.set_xpd_fosc(false); + clk_power.set_pd_osc(false); + + let mut xtal = LpXtalPower::default(); + xtal.set_xpd_xtal(false); + + let mut analog_bias = LpAnalogBias::default(); + analog_bias.set_xpd_bias(false); + analog_bias.set_dbg_atten(0); + analog_bias.set_pd_cur(true); + analog_bias.set_bias_sleep(true); + + let mut analog_regulator0 = LpAnalogRegulator0::default(); + analog_regulator0.set_slp_xpd(false); + analog_regulator0.set_xpd(true); + analog_regulator0.set_slp_dbias(0); + analog_regulator0.set_dbias(12); + + let mut analog_regulator1 = LpAnalogRegulator1::default(); + analog_regulator1.set_drv_b(0); + + Self { + dig_power, + clk_power, + xtal, + bias: analog_bias, + analog_regulator0, + analog_regulator1, + } + } + + fn init_default() { + let active = Self::active(); + let sleep = Self::sleep(); + + lp_system_init!(hp_sleep_lp => active); + lp_system_init!(lp_sleep_lp => sleep); + + PMU::regs() + .lp_sleep_xtal() + .modify(|_, w| w.lp_sleep_xpd_xtal().bit(sleep.xtal.xpd_xtal())); + + PMU::regs().lp_sleep_bias().modify(|_, w| unsafe { + w.lp_sleep_xpd_bias().bit(sleep.bias.xpd_bias()); // pmu_ll_lp_set_bias_xpd + w.lp_sleep_dbg_atten().bits(sleep.bias.dbg_atten()); // pmu_ll_lp_set_bias_dbg_atten + w.lp_sleep_pd_cur().bit(sleep.bias.pd_cur()); // pmu_ll_lp_set_bias_pd_cur + w.sleep().bit(sleep.bias.bias_sleep()) // pmu_ll_lp_set_bias_sleep + }); + } +} + +pub(crate) fn init() { + // pmu_init() + PMU::regs() + .rf_pwc() + .modify(|_, w| w.perif_i2c_rstb().set_bit().xpd_perif_i2c().set_bit()); + + regi2c::I2C_DIG_REG_ENIF_RTC_DREG.write_field(1); + regi2c::I2C_DIG_REG_ENIF_DIG_DREG.write_field(1); + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + + HpSystemInit::init_default(); + LpSystemInit::init_default(); + + pmu_power_domain_force_default(); + + // esp_perip_clk_init() + modem_clock_domain_power_state_icg_map_init(); + + // During system initialization, the low-power clock source of the modem + // (WiFi, BLE or Coexist) follows the configuration of the slow clock source + // of the system. If the WiFi, BLE or Coexist module needs a higher + // precision sleep clock (for example, the BLE needs to use the main XTAL + // oscillator (40 MHz) to provide the clock during the sleep process in some + // scenarios), the module needs to switch to the required clock source by + // itself. + // TODO - WIFI-5233 + let modem_lpclk_src = ModemClockLpclkSource::from(RtcSlowClockSource::current()); + + modem_clock_select_lp_clock_source_wifi(modem_lpclk_src, 0); + modem_clk_domain_active_state_icg_map_preinit(); +} + +fn modem_clk_domain_active_state_icg_map_preinit() { + unsafe { + // Configure modem ICG code in PMU_ACTIVE state + PMU::regs() + .hp_active_icg_modem() + .modify(|_, w| w.hp_active_dig_icg_modem_code().bits(ICG_MODEM_CODE_ACTIVE)); + + // Disable clock gating for MODEM_APB, I2C_MST and LP_APB clock domains in + // PMU_ACTIVE state + MODEM_SYSCON::regs() + .clk_conf_power_st() + .modify(|_, w| w.clk_modem_apb_st_map().bits(1 << ICG_MODEM_CODE_ACTIVE)); + MODEM_LPCON::regs().clk_conf_power_st().modify(|_, w| { + w.clk_i2c_mst_st_map() + .bits(1 << ICG_MODEM_CODE_ACTIVE) + .clk_lp_apb_st_map() + .bits(1 << ICG_MODEM_CODE_ACTIVE) + }); + + // Software trigger force update modem ICG code and ICG switch + PMU::regs() + .imm_modem_icg() + .write(|w| w.update_dig_icg_modem_en().set_bit()); + PMU::regs() + .imm_sleep_sysclk() + .write(|w| w.update_dig_icg_switch().set_bit()); + + // The following is part of rtc_clk_init + + LP_CLKRST::regs() + .fosc_cntl() + .modify(|_, w| w.fosc_dfreq().bits(100)); + + regi2c::I2C_DIG_REG_SCK_DCAP.write_reg(128); + + LP_CLKRST::regs() + .rc32k_cntl() + .modify(|_, w| w.rc32k_dfreq().bits(700)); + + regi2c::I2C_DIG_REG_ENIF_RTC_DREG.write_field(1); + regi2c::I2C_DIG_REG_ENIF_DIG_DREG.write_field(1); + + PMU::regs() + .hp_active_hp_regulator0() + .modify(|_, w| w.hp_active_hp_regulator_dbias().bits(HP_CALI_DBIAS)); + PMU::regs() + .hp_sleep_lp_regulator0() + .modify(|_, w| w.hp_sleep_lp_regulator_dbias().bits(LP_CALI_DBIAS)); + + // clk_ll_rc_fast_tick_conf + PCR::regs() + .ctrl_tick_conf() + .modify(|_, w| w.fosc_tick_num().bits(255)); + } +} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// SDIO Core reset + CoreSDIO = 0x06, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU 0 + Cpu0Mwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// USB UART resets the digital core + CoreUsbUart = 0x15, + /// USB JTAG resets the digital core + CoreUsbJtag = 0x16, + /// JTAG resets CPU + Cpu0JtagCpu = 0x18, +} + +#[derive(Clone, Copy)] +pub(crate) struct SavedClockConfig { + /// The clock from which CPU clock is derived + old_soc_root_clk: Option, +} + +impl SavedClockConfig { + pub(crate) fn save(clocks: &ClockTree) -> Self { + let old_soc_root_clk = clocks.soc_root_clk(); + + SavedClockConfig { old_soc_root_clk } + } + + // rtc_clk_cpu_freq_set_config + pub(crate) fn restore(self, clocks: &mut ClockTree) { + if let Some(old_soc_root_clk) = self.old_soc_root_clk { + crate::soc::clocks::configure_soc_root_clk(clocks, old_soc_root_clk); + } + } +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32h2.rs b/esp-hal/src/rtc_cntl/rtc/esp32h2.rs new file mode 100644 index 00000000000..a2f1b5c7a45 --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32h2.rs @@ -0,0 +1,202 @@ +use strum::FromRepr; + +use crate::{peripherals::PMU, soc::regi2c}; + +pub(crate) fn init() { + // * No peripheral reg i2c power up required on the target */ + regi2c::I2C_PMU_EN_I2C_RTC_DREG.write_field(0); + regi2c::I2C_PMU_EN_I2C_DIG_DREG.write_field(0); + regi2c::I2C_PMU_EN_I2C_RTC_DREG_SLP.write_field(0); + regi2c::I2C_PMU_EN_I2C_DIG_DREG_SLP.write_field(0); + regi2c::I2C_PMU_OR_XPD_RTC_REG.write_field(0); + regi2c::I2C_PMU_OR_XPD_DIG_REG.write_field(0); + regi2c::I2C_PMU_OR_XPD_TRX.write_field(0); + regi2c::I2C_BIAS_DREG_0P8.write_field(8); + + let pmu = PMU::regs(); + unsafe { + pmu.power_pd_top_cntl().write(|w| w.bits(0)); + pmu.power_pd_hpaon_cntl().write(|w| w.bits(0)); + pmu.power_pd_hpcpu_cntl().write(|w| w.bits(0)); + pmu.power_pd_hpperi_reserve().write(|w| w.bits(0)); + pmu.power_pd_hpwifi_cntl().write(|w| w.bits(0)); + pmu.power_pd_lpperi_cntl().write(|w| w.bits(0)); + + pmu.hp_active_hp_regulator0() + .modify(|_, w| w.hp_active_hp_regulator_dbias().bits(25)); + pmu.hp_sleep_lp_regulator0() + .modify(|_, w| w.hp_sleep_lp_regulator_dbias().bits(26)); + + pmu.hp_sleep_dig_power().modify(|_, w| { + w.hp_sleep_vdd_spi_pd_en() + .set_bit() + .hp_sleep_pd_hp_wifi_pd_en() + .set_bit() + .hp_sleep_pd_hp_cpu_pd_en() + .set_bit() + .hp_sleep_pd_top_pd_en() + .set_bit() + }); + + pmu.hp_active_hp_ck_power().modify(|_, w| { + w.hp_active_xpd_bbpll() + .set_bit() + .hp_active_xpd_bb_i2c() + .set_bit() + .hp_active_xpd_bbpll_i2c() + .set_bit() + }); + + pmu.hp_active_sysclk().modify(|_, w| { + w.hp_active_icg_sys_clock_en() + .set_bit() + .hp_active_sys_clk_slp_sel() + .clear_bit() + .hp_active_icg_slp_sel() + .clear_bit() + }); + pmu.hp_sleep_sysclk().modify(|_, w| { + w.hp_sleep_icg_sys_clock_en() + .clear_bit() + .hp_sleep_sys_clk_slp_sel() + .set_bit() + .hp_sleep_icg_slp_sel() + .set_bit() + }); + + pmu.slp_wakeup_cntl5() + .modify(|_, w| w.lp_ana_wait_target().bits(15)); + pmu.slp_wakeup_cntl7() + .modify(|_, w| w.ana_wait_target().bits(1700)); + } +} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU 0 + Cpu0Mwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// Glitch on clock resets the digital core and rtc module + SysClkGlitch = 0x13, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// USB UART resets the digital core + CoreUsbUart = 0x15, + /// USB JTAG resets the digital core + CoreUsbJtag = 0x16, + /// Glitch on power resets the digital core + CorePwrGlitch = 0x17, +} + +bitfield::bitfield! { + /// Representation of `PMU_HP_{ACTIVE,SLEEP}_DIG_POWER_REG` registers. + #[derive(Clone, Copy, Default)] + pub struct HpDigPower(u32); + + pub bool, vdd_spi_pd_en, set_vdd_spi_pd_en: 21; + pub bool, mem_dslp , set_mem_dslp : 22; + pub bool, modem_pd_en , set_modem_pd_en : 27; + pub bool, cpu_pd_en , set_cpu_pd_en : 29; + pub bool, top_pd_en , set_top_pd_en : 31; +} + +bitfield::bitfield! { + /// Representation of `PMU_HP_{ACTIVE,SLEEP}_HP_CK_POWER_REG` registers. + #[derive(Clone, Copy, Default)] + pub struct HpClkPower(u32); + + pub bool, xpd_bbpll , set_xpd_bbpll : 30; +} + +bitfield::bitfield! { + /// Representation of `PMU_{HP_ACTIVE,HP_SLEEP,LP_SLEEP}_XTAL_REG` register. + #[derive(Clone, Copy, Default)] + pub struct XtalPower(u32); + + pub bool, xpd_xtal , set_xpd_xtal : 31; +} + +/// Combined HP system power settings. +#[derive(Clone, Copy, Default)] +pub struct HpSysPower { + pub dig_power: HpDigPower, + pub clk: HpClkPower, + pub xtal: XtalPower, +} + +bitfield::bitfield! { + /// Representation of `PMU_{HP,LP}_SLEEP_LP_DIG_POWER_REG`. + #[derive(Clone, Copy, Default)] + pub struct LpDigPower(u32); + + pub bool, bod_source_sel, set_bod_source_sel : 27; + pub u32, vddbat_mode, set_vddbat_mode : 29, 28; + pub u32, mem_dslp , set_mem_dslp : 30; + +} + +bitfield::bitfield! { + /// Representation of `PMU_{HP,LP}_SLEEP_LP_CK_POWER_REG`. + #[derive(Clone, Copy, Default)] + pub struct LpClkPower(u32); + + pub u32, xpd_xtal32k, set_xpd_xtal32k: 28; + pub u32, xpd_fosc , set_xpd_fosc : 30; +} + +/// Combined LP system power settings. +#[derive(Clone, Copy, Default)] +pub struct LpSysPower { + pub dig_power: LpDigPower, + pub clk_power: LpClkPower, + pub xtal: XtalPower, +} + +bitfield::bitfield! { + /// Representation of `PMU_HP_{ACTIVE,SLEEP}_HP_SYS_CNTL_REG` register. + #[derive(Clone, Copy, Default)] + pub struct HpSysCntlReg(u32); + + pub bool, uart_wakeup_en , set_uart_wakeup_en : 24; + pub bool, lp_pad_hold_all, set_lp_pad_hold_all: 25; + pub bool, hp_pad_hold_all, set_hp_pad_hold_all: 26; + pub bool, dig_pad_slp_sel, set_dig_pad_slp_sel: 27; + pub bool, dig_pause_wdt , set_dig_pause_wdt : 28; + pub bool, dig_cpu_stall , set_dig_cpu_stall : 29; +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32s2.rs b/esp-hal/src/rtc_cntl/rtc/esp32s2.rs new file mode 100644 index 00000000000..2d4ec8f532b --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32s2.rs @@ -0,0 +1,50 @@ +use strum::FromRepr; + +pub(crate) fn init() {} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU 0 + Cpu0Mwdt0 = 0x0B, + /// Software resets CPU 0 by RTC_CNTL_SW_PROCPU_RST + Cpu0Sw = 0x0C, + /// RTC watch dog resets CPU 0 + Cpu0RtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU 0 + Cpu0Mwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// Glitch on clock resets the digital core and rtc module + SysClkGlitch = 0x13, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, +} diff --git a/esp-hal/src/rtc_cntl/rtc/esp32s3.rs b/esp-hal/src/rtc_cntl/rtc/esp32s3.rs new file mode 100644 index 00000000000..84bc643cf3d --- /dev/null +++ b/esp-hal/src/rtc_cntl/rtc/esp32s3.rs @@ -0,0 +1,68 @@ +use strum::FromRepr; + +pub(crate) fn init() {} + +// Terminology: +// +// CPU Reset: Reset CPU core only, once reset done, CPU will execute from +// reset vector +// Core Reset: Reset the whole digital system except RTC sub-system +// System Reset: Reset the whole digital system, including RTC sub-system +// Chip Reset: Reset the whole chip, including the analog part + +/// SOC Reset Reason. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] +pub enum SocResetReason { + /// Power on reset + /// + /// In ESP-IDF this value (0x01) can *also* be `ChipBrownOut` or + /// `ChipSuperWdt`, however that is not really compatible with Rust-style + /// enums. + ChipPowerOn = 0x01, + /// Software resets the digital core by RTC_CNTL_SW_SYS_RST + CoreSw = 0x03, + /// Deep sleep reset the digital core + CoreDeepSleep = 0x05, + /// Main watch dog 0 resets digital core + CoreMwdt0 = 0x07, + /// Main watch dog 1 resets digital core + CoreMwdt1 = 0x08, + /// RTC watch dog resets digital core + CoreRtcWdt = 0x09, + /// Main watch dog 0 resets CPU + /// + /// In ESP-IDF there are `Cpu0Mwdt0` and `Cpu1Mwdt0`, however they have the + /// same values. + CpuMwdt0 = 0x0B, + /// Software resets CPU by RTC_CNTL_SW_(PRO|APP)CPU_RST + /// + /// In ESP-IDF there are `Cpu0Sw` and `Cpu1Sw`, however they have the same + /// values. + CpuSw = 0x0C, + /// RTC watch dog resets CPU + /// + /// In ESP-IDF there are `Cpu0RtcWdt` and `Cpu1RtcWdt`, however they have + /// the same values. + CpuRtcWdt = 0x0D, + /// VDD voltage is not stable and resets the digital core + SysBrownOut = 0x0F, + /// RTC watch dog resets digital core and rtc module + SysRtcWdt = 0x10, + /// Main watch dog 1 resets CPU + /// + /// In ESP-IDF there are `Cpu0Mwdt1` and `Cpu1Mwdt1`, however they have the + /// same values. + CpuMwdt1 = 0x11, + /// Super watch dog resets the digital core and rtc module + SysSuperWdt = 0x12, + /// Glitch on clock resets the digital core and rtc module + SysClkGlitch = 0x13, + /// eFuse CRC error resets the digital core + CoreEfuseCrc = 0x14, + /// USB UART resets the digital core + CoreUsbUart = 0x15, + /// USB JTAG resets the digital core + CoreUsbJtag = 0x16, + /// Glitch on power resets the digital core + CorePwrGlitch = 0x17, +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32.rs b/esp-hal/src/rtc_cntl/sleep/esp32.rs new file mode 100644 index 00000000000..c61dfa34048 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32.rs @@ -0,0 +1,595 @@ +use super::{Ext0WakeupSource, Ext1WakeupSource, TimerWakeupSource, WakeSource, WakeTriggers}; +use crate::{ + gpio::{RtcFunction, RtcPin}, + peripherals::{BB, DPORT, I2S0, LPWR, NRX, RTC_IO}, + rtc_cntl::{Rtc, sleep::WakeupLevel}, +}; + +// Approximate mapping of voltages to RTC_CNTL_DBIAS_WAK, RTC_CNTL_DBIAS_SLP, +// RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DIG_DBIAS_SLP values. +// Valid if RTC_CNTL_DBG_ATTEN is 0. +/// RTC digital bias setting corresponding to 0.90V. +pub const RTC_CNTL_DBIAS_0V90: u8 = 0; +/// RTC digital bias setting corresponding to 0.95V. +pub const RTC_CNTL_DBIAS_0V95: u8 = 1; +/// RTC digital bias setting corresponding to 1.00V. +pub const RTC_CNTL_DBIAS_1V00: u8 = 2; +/// RTC digital bias setting corresponding to 1.05V. +pub const RTC_CNTL_DBIAS_1V05: u8 = 3; +/// RTC digital bias setting corresponding to 1.10V. +pub const RTC_CNTL_DBIAS_1V10: u8 = 4; +/// RTC digital bias setting corresponding to 1.15V. +pub const RTC_CNTL_DBIAS_1V15: u8 = 5; +/// RTC digital bias setting corresponding to 1.20V. +pub const RTC_CNTL_DBIAS_1V20: u8 = 6; +/// RTC digital bias setting corresponding to 1.25V. +pub const RTC_CNTL_DBIAS_1V25: u8 = 7; + +// Various delays to be programmed into power control state machines +/// Time (in microseconds) for waiting the XTL buffer to stabilize during sleep. +pub const RTC_CNTL_XTL_BUF_WAIT_SLP_US: u32 = 1000; +/// Cycles to wait for PLL buffer stabilization. +pub const RTC_CNTL_PLL_BUF_WAIT_SLP_CYCLES: u8 = 1; +/// Cycles to wait for the 8MHz clock to stabilize. +pub const RTC_CNTL_CK8M_WAIT_SLP_CYCLES: u8 = 4; +/// Delay in cycles for wakeup signal to be applied. +pub const RTC_CNTL_WAKEUP_DELAY_CYCLES: u8 = 7; +/// Power-up cycles for other blocks. +pub const RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES: u8 = 1; +/// Wait cycles for other blocks. +pub const RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES: u16 = 1; +/// Minimum sleep value (in cycles). +pub const RTC_CNTL_MIN_SLP_VAL_MIN: u8 = 128; +/// Default debug attenuation value. +pub const RTC_CNTL_DBG_ATTEN_DEFAULT: u8 = 3; + +/// Power-up cycles for RTC memory. +pub const RTC_MEM_POWERUP_CYCLES: u8 = RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES; +/// Wait cycles for RTC memory. +pub const RTC_MEM_WAIT_CYCLES: u16 = RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES; +/// Power-up cycles for ROM and RAM. +pub const ROM_RAM_POWERUP_CYCLES: u8 = RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES; +/// Wait cycles for ROM and RAM. +pub const ROM_RAM_WAIT_CYCLES: u16 = RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES; +/// Power-up cycles for Wi-Fi. +pub const WIFI_POWERUP_CYCLES: u8 = RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES; +/// Wait cycles for Wi-Fi. +pub const WIFI_WAIT_CYCLES: u16 = RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES; +/// Power-up cycles for RTC components. +pub const RTC_POWERUP_CYCLES: u8 = RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES; +/// Wait cycles for RTC components. +pub const RTC_WAIT_CYCLES: u16 = RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES; +/// Power-up cycles for the digital wrap components. +pub const DG_WRAP_POWERUP_CYCLES: u8 = RTC_CNTL_OTHER_BLOCKS_POWERUP_CYCLES; +/// Wait cycles for the digital wrap components. +pub const DG_WRAP_WAIT_CYCLES: u16 = RTC_CNTL_OTHER_BLOCKS_WAIT_CYCLES; + +/// Default wait cycles for the 8MHz clock. +pub const RTC_CNTL_CK8M_WAIT_DEFAULT: u8 = 20; +/// Default wait cycles to enable the 8MHz clock. +pub const RTC_CK8M_ENABLE_WAIT_DEFAULT: u8 = 5; +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + LPWR::regs() + .slp_timer0() + .write(|w| w.slp_val_lo().bits((time_in_ticks & 0xffffffff) as u32)); + + LPWR::regs().slp_timer1().write(|w| { + w.slp_val_hi() + .bits(((time_in_ticks >> 32) & 0xffff) as u16) + .main_timer_alarm_en() + .set_bit() + }); + } + } +} + +impl WakeSource for Ext0WakeupSource

    { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_ext0(true); + + // set pin to RTC function + self.pin + .borrow_mut() + .rtc_set_config(true, true, RtcFunction::Rtc); + + unsafe { + // set pin register field + RTC_IO::regs() + .ext_wakeup0() + .modify(|_, w| w.sel().bits(self.pin.borrow().rtc_number())); + // set level register field + LPWR::regs() + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup0_lv().bit(self.level == WakeupLevel::High)); + } + } +} + +impl Drop for Ext0WakeupSource

    { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + self.pin + .borrow_mut() + .rtc_set_config(true, false, RtcFunction::Rtc); + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_ext1(true); + + // set pins to RTC function + let mut pins = self.pins.borrow_mut(); + let mut bits = 0u32; + for pin in pins.iter_mut() { + pin.rtc_set_config(true, true, RtcFunction::Rtc); + bits |= 1 << pin.rtc_number(); + } + + // clear previous wakeup status + LPWR::regs() + .ext_wakeup1() + .modify(|_, w| w.status_clr().set_bit()); + // set pin register field + + LPWR::regs() + .ext_wakeup1() + .modify(|_, w| unsafe { w.sel().bits(bits) }); + + // set level register field + LPWR::regs() + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup1_lv().bit(self.level == WakeupLevel::High)); + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for pin in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Configuration for the RTC sleep behavior. + pub struct RtcSleepConfig(u32); + impl Debug; + /// force normal voltage in sleep mode (digital domain memory) + pub lslp_mem_inf_fpu, set_lslp_mem_inf_fpu: 0; + /// force normal voltage in sleep mode (RTC memory) + pub rtc_mem_inf_fpu, set_rtc_mem_inf_fpu: 1; + /// keep low voltage in sleep mode (even if ULP/touch is used) + pub rtc_mem_inf_follow_cpu, set_rtc_mem_inf_follow_cpu: 2; + /// power down RTC fast memory + pub rtc_fastmem_pd_en, set_rtc_fastmem_pd_en: 3; + /// power down RTC slow memory + pub rtc_slowmem_pd_en, set_rtc_slowmem_pd_en: 4; + /// power down RTC peripherals + pub rtc_peri_pd_en, set_rtc_peri_pd_en: 5; + /// power down WiFi + pub wifi_pd_en, set_wifi_pd_en: 6; + /// Power down Internal 8M oscillator + pub int_8m_pd_en, set_int_8m_pd_en: 7; + /// power down main RAM and ROM + pub rom_mem_pd_en, set_rom_mem_pd_en: 8; + /// power down digital domain + pub deep_slp, set_deep_slp: 9; + /// enable WDT flashboot mode + pub wdt_flashboot_mod_en, set_wdt_flashboot_mod_en: 10; + /// bias for digital domain, in active mode + pub u8, dig_dbias_wak, set_dig_dbias_wak: 13, 11; + /// bias for digital domain, in sleep mode + pub u8, dig_dbias_slp, set_dig_dbias_slp: 16, 14; + /// bias for RTC domain, in active mode + pub u8, rtc_dbias_wak, set_rtc_dbias_wak: 19, 17; + /// bias for RTC domain, in sleep mode + pub u8, rtc_dbias_slp, set_rtc_dbias_slp: 22, 20; + /// remove all peripheral force power up flags + pub lslp_meminf_pd, set_lslp_meminf_pd: 23; + /// power down VDDSDIO regulator + pub vddsdio_pd_en, set_vddsdio_pd_en: 24; + /// keep main XTAL powered up in sleep + pub xtal_fpu, set_xtal_fpu: 25; + /// enable deep sleep reject + pub deep_slp_reject, set_deep_slp_reject: 26; + /// enable light sleep reject + pub light_slp_reject, set_light_slp_reject: 27; +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_lslp_meminf_pd(true); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_dig_dbias_wak(RTC_CNTL_DBIAS_1V10); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_rtc_dbias_wak(RTC_CNTL_DBIAS_1V10); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg + } +} + +impl RtcSleepConfig { + /// Configures the RTC for deep sleep mode. + pub fn deep() -> Self { + let mut cfg = Self::default(); + cfg.set_deep_slp(true); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_0V90); + // cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_0V90); + cfg.set_vddsdio_pd_en(true); + cfg.set_int_8m_pd_en(true); + cfg.set_xtal_fpu(false); + cfg.set_wifi_pd_en(true); + cfg.set_rom_mem_pd_en(true); + cfg.set_rtc_peri_pd_en(true); + cfg.set_rtc_fastmem_pd_en(true); + cfg.set_rtc_slowmem_pd_en(true); + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + // settings derived from esp-idf after basic boot + let rtc_cntl = LPWR::regs(); + + rtc_cntl.options0().modify(|_, w| { + w.bias_core_force_pu() + .clear_bit() + .bias_core_folw_8m() + .set_bit() + .bias_i2c_force_pu() + .clear_bit() + .bias_i2c_folw_8m() + .set_bit() + .bias_force_nosleep() + .clear_bit() + .bias_sleep_folw_8m() + .set_bit() + .xtl_force_pu() + .clear_bit() + }); + + rtc_cntl.reg().modify(|_, w| { + w.force_pu() + .clear_bit() + .dboost_force_pu() + .clear_bit() + .dboost_force_pd() + .set_bit() + }); + + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_force_pu() + .clear_bit() + .fastmem_force_pu() + .clear_bit() + .force_noiso() + .clear_bit() + .slowmem_force_noiso() + .clear_bit() + .fastmem_force_noiso() + .clear_bit() + }); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.dg_wrap_force_pu() + .clear_bit() + .wifi_force_pu() + .clear_bit() + .wifi_force_pd() + .set_bit() + .inter_ram4_force_pu() + .clear_bit() + .inter_ram3_force_pu() + .clear_bit() + .inter_ram2_force_pu() + .clear_bit() + .inter_ram1_force_pu() + .clear_bit() + .inter_ram0_force_pu() + .clear_bit() + .rom0_force_pu() + .clear_bit() + .lslp_mem_force_pu() + .clear_bit() + }); + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso() + .clear_bit() + .wifi_force_noiso() + .clear_bit() + .wifi_force_iso() + .set_bit() + .inter_ram4_force_noiso() + .clear_bit() + .inter_ram3_force_noiso() + .clear_bit() + .inter_ram2_force_noiso() + .clear_bit() + .inter_ram1_force_noiso() + .clear_bit() + .inter_ram0_force_noiso() + .clear_bit() + .rom0_force_noiso() + .clear_bit() + .dg_pad_force_unhold() + .clear_bit() + .dg_pad_force_noiso() + .clear_bit() + }); + + rtc_cntl.int_ena().modify(|_, w| w.brown_out().set_bit()); + } + + pub(crate) fn apply(&self) { + // like esp-idf rtc_sleep_init() + unsafe { + let rtc_cntl = LPWR::regs(); + + rtc_cntl.timer5().modify(|_, w| { + w.min_slp_val() + .bits(RTC_CNTL_MIN_SLP_VAL_MIN) + // set rtc memory timer + .rtcmem_powerup_timer() + .bits(RTC_MEM_POWERUP_CYCLES) + .rtcmem_wait_timer() + .bits(RTC_MEM_WAIT_CYCLES) + }); + + rtc_cntl.timer3().modify(|_, w| { + w + // set rom&ram timer + .rom_ram_powerup_timer() + .bits(ROM_RAM_POWERUP_CYCLES) + .rom_ram_wait_timer() + .bits(ROM_RAM_WAIT_CYCLES) + // set wifi timer + .wifi_powerup_timer() + .bits(WIFI_POWERUP_CYCLES) + .wifi_wait_timer() + .bits(WIFI_WAIT_CYCLES) + }); + + rtc_cntl.timer4().modify(|_, w| { + w + // set rtc peri timer + .powerup_timer() + .bits(RTC_POWERUP_CYCLES) + .wait_timer() + .bits(RTC_WAIT_CYCLES) + // set digital wrap timer + .dg_wrap_powerup_timer() + .bits(DG_WRAP_POWERUP_CYCLES) + .dg_wrap_wait_timer() + .bits(DG_WRAP_WAIT_CYCLES) + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().bit(self.lslp_mem_inf_fpu())); + + // remove all peripheral force power up flags + if self.lslp_meminf_pd() { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().clear_bit()); + + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_force_pu() + .clear_bit() + .fastmem_force_pu() + .clear_bit() + }); + + // esp-idf also clears these: + + DPORT::regs() + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + I2S0::regs() + .pd_conf() + .modify(|_, w| w.plc_mem_force_pu().clear_bit().fifo_force_pu().clear_bit()); + + BB::regs() + .bbpd_ctrl() + .modify(|_, w| w.fft_force_pu().clear_bit().dc_est_force_pu().clear_bit()); + + NRX::regs().nrxpd_ctrl().modify(|_, w| { + w.rx_rot_force_pu() + .clear_bit() + .vit_force_pu() + .clear_bit() + .demap_force_pu() + .clear_bit() + }); + // (&*esp32::FE::ptr()).gen_ctrl.modify(|_, w| w + // .iq_est_force_pu().clear_bit() + // ); + // + // (&*esp32::FE2::ptr()).tx_interp_ctrl.modify(|_, w| w + // .inf_force_pu().clear_bit() + // ); + } + + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_folw_cpu() + .bit(self.rtc_mem_inf_follow_cpu()) + .fastmem_folw_cpu() + .bit(self.rtc_mem_inf_follow_cpu()) + // TODO: does this need to be optional based on if there is something stored in + // fastmem? + //.fastmem_pd_en().bit(self.rtc_fastmem_pd_en()) + .fastmem_force_pu() + .bit(!self.rtc_fastmem_pd_en()) + .fastmem_force_lpu() + .bit(!self.rtc_fastmem_pd_en()) + .fastmem_force_noiso() + .bit(!self.rtc_fastmem_pd_en()) + .slowmem_pd_en() + .bit(self.rtc_slowmem_pd_en()) + .slowmem_force_pu() + .bit(!self.rtc_slowmem_pd_en()) + .slowmem_force_noiso() + .bit(!self.rtc_slowmem_pd_en()) + .slowmem_force_lpu() + .bit(!self.rtc_slowmem_pd_en()) + .pd_en() + .bit(self.rtc_peri_pd_en()) + }); + + // rtc_cntl.dig_pwc.modify(|_, w| w + // .wifi_pd_en().bit(self.wifi_pd_en()) + // .rom0_pd_en().bit(self.rom_mem_pd_en()) + // ); + + if self.deep_slp() { + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso() + .clear_bit() + .wifi_force_noiso() + .clear_bit() + .dg_pad_force_iso() + .clear_bit() + .dg_pad_force_noiso() + .clear_bit() + }); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.dg_wrap_pd_en() + .set_bit() + .dg_wrap_force_pu() + .clear_bit() + .dg_wrap_force_pd() + .clear_bit() + }); + + rtc_cntl.options0().modify(|_, w| { + w.bias_force_nosleep() + .clear_bit() + .bb_i2c_force_pu() + .clear_bit() + }); + + rtc_cntl.ana_conf().modify(|_, w| { + w.ckgen_i2c_pu() + .clear_bit() + .pll_i2c_pu() + .clear_bit() + .rfrx_pbus_pu() + .clear_bit() + .txrf_i2c_pu() + .clear_bit() + }); + } else { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().clear_bit()); + + rtc_cntl.bias_conf().modify(|_, w| w.dbg_atten().bits(0)); + } + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(self.xtal_fpu())); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().bit(!self.int_8m_pd_en())); + + // enable VDDSDIO control by state machine + + rtc_cntl.sdio_conf().modify(|_, w| { + w.sdio_force() + .clear_bit() + .sdio_pd_en() + .bit(self.vddsdio_pd_en()) + }); + + rtc_cntl.reg().modify(|_, w| { + w.dbias_slp() + .bits(self.rtc_dbias_slp()) + .dbias_wak() + .bits(self.rtc_dbias_wak()) + .dig_dbias_slp() + .bits(self.dig_dbias_slp()) + .dig_dbias_wak() + .bits(self.dig_dbias_wak()) + }); + + rtc_cntl.slp_reject_conf().modify(|_, w| { + w.deep_slp_reject_en() + .bit(self.deep_slp_reject()) + .light_slp_reject_en() + .bit(self.light_slp_reject()) + }); + } + } + + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + LPWR::regs() + .reset_state() + .modify(|_, w| w.procpu_stat_vector_sel().set_bit()); + + // set bits for what can wake us up + LPWR::regs() + .wakeup_state() + .modify(|_, w| unsafe { w.wakeup_ena().bits(wakeup_triggers.0) }); + + LPWR::regs() + .state0() + .write(|w| w.sleep_en().set_bit().slp_wakeup().set_bit()); + } + + pub(crate) fn finish_sleep(&self) { + // In deep sleep mode, we never get here + LPWR::regs().int_clr().write(|w| { + w.slp_reject() + .clear_bit_by_one() + .slp_wakeup() + .clear_bit_by_one() + }); + + // restore DBG_ATTEN to the default value + + LPWR::regs() + .bias_conf() + .modify(|_, w| unsafe { w.dbg_atten().bits(RTC_CNTL_DBG_ATTEN_DEFAULT) }); + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c2.rs b/esp-hal/src/rtc_cntl/sleep/esp32c2.rs new file mode 100644 index 00000000000..a92346e3d46 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32c2.rs @@ -0,0 +1,745 @@ +use super::{TimerWakeupSource, WakeSource, WakeTriggers, WakeupLevel}; +use crate::{ + gpio::{RtcFunction, RtcPinWithResistors}, + peripherals::{APB_CTRL, BB, EXTMEM, GPIO, IO_MUX, LPWR, SPI0, SPI1, SYSTEM}, + rtc_cntl::{Rtc, sleep::RtcioWakeupSource}, + soc::regi2c, +}; + +// Approximate mapping of voltages to RTC_CNTL_DBIAS_WAK, RTC_CNTL_DBIAS_SLP, +// RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DIG_DBIAS_SLP values. +// Valid if RTC_CNTL_DBG_ATTEN is 0. +/// Digital bias voltage level of 0.90V. +pub const RTC_CNTL_DBIAS_0V90: u8 = 13; +/// Digital bias voltage level of 0.95V. +pub const RTC_CNTL_DBIAS_0V95: u8 = 16; +/// Digital bias voltage level of 1.00V. +pub const RTC_CNTL_DBIAS_1V00: u8 = 18; +/// Digital bias voltage level of 1.05V. +pub const RTC_CNTL_DBIAS_1V05: u8 = 20; +/// Digital bias voltage level of 1.10V. +pub const RTC_CNTL_DBIAS_1V10: u8 = 23; +/// Digital bias voltage level of 1.15V. +pub const RTC_CNTL_DBIAS_1V15: u8 = 25; +/// Digital bias voltage level of 1.20V. +pub const RTC_CNTL_DBIAS_1V20: u8 = 28; +/// Digital bias voltage level of 1.25V. +pub const RTC_CNTL_DBIAS_1V25: u8 = 30; +/// Digital bias voltage level of approximately 1.34V. +pub const RTC_CNTL_DBIAS_1V30: u8 = 31; + +/// Default attenuation setting during light sleep, with a voltage drop. +pub const RTC_CNTL_DBG_ATTEN_LIGHTSLEEP_DEFAULT: u8 = 5; +/// No attenuation (no voltage drop) during light sleep. +pub const RTC_CNTL_DBG_ATTEN_LIGHTSLEEP_NODROP: u8 = 0; +/// Default attenuation setting during deep sleep, with maximum voltage drop. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT: u8 = 15; +/// No attenuation (no voltage drop) during deep sleep. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_NODROP: u8 = 0; + +/// Default bias setting during sleep mode. +pub const RTC_CNTL_BIASSLP_SLEEP_DEFAULT: u8 = 1; +/// Keeps the bias for ultra-low power sleep mode always on. +pub const RTC_CNTL_BIASSLP_SLEEP_ON: u8 = 0; + +/// Default power-down current setting during sleep mode. +pub const RTC_CNTL_PD_CUR_SLEEP_DEFAULT: u8 = 1; +/// Keeps the power-down current setting for sleep mode always on. +pub const RTC_CNTL_PD_CUR_SLEEP_ON: u8 = 0; + +/// Default driver bias setting for the digital domain during sleep mode. +pub const RTC_CNTL_DG_VDD_DRV_B_SLP_DEFAULT: u8 = 254; + +/// Default debug attenuation setting for the monitor mode. +pub const RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT: u8 = 0; +/// Default bias setting for sleep mode in the monitor mode. +pub const RTC_CNTL_BIASSLP_MONITOR_DEFAULT: bool = false; +/// Default power-down current setting for the monitor mode. +pub const RTC_CNTL_PD_CUR_MONITOR_DEFAULT: bool = false; + +/// Default number of cycles to wait for the PLL buffer to stabilize. +pub const RTC_CNTL_PLL_BUF_WAIT_DEFAULT: u8 = 20; +/// Default number of cycles to wait for the XTL buffer to stabilize. +pub const RTC_CNTL_XTL_BUF_WAIT_DEFAULT: u8 = 100; +/// Default number of cycles to wait for the internal 8MHz clock to stabilize. +pub const RTC_CNTL_CK8M_WAIT_DEFAULT: u8 = 20; +/// Default number of cycles required to enable the internal 8MHz clock. +pub const RTC_CK8M_ENABLE_WAIT_DEFAULT: u8 = 5; + +/// Minimum number of cycles for sleep duration. +pub const RTC_CNTL_MIN_SLP_VAL_MIN: u8 = 2; + +/// Power-up cycles for other hardware blocks. +pub const OTHER_BLOCKS_POWERUP: u8 = 1; +/// Wait cycles for other hardware blocks to stabilize. +pub const OTHER_BLOCKS_WAIT: u16 = 1; + +/// Disables GPIO interrupt. +pub const GPIO_INTR_DISABLE: u8 = 0; +/// Sets GPIO interrupt to trigger on a low level signal. +pub const GPIO_INTR_LOW_LEVEL: u8 = 4; +/// Sets GPIO interrupt to trigger on a high level signal. +pub const GPIO_INTR_HIGH_LEVEL: u8 = 5; + +/// Specifies the function configuration for GPIO pins. +pub const PIN_FUNC_GPIO: u8 = 1; +/// Index for signaling GPIO output. +pub const SIG_GPIO_OUT_IDX: u32 = 128; +/// Maximum number of GPIO pins supported. +pub const GPIO_NUM_MAX: usize = 22; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + let rtc_cntl = LPWR::regs(); + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + rtc_cntl + .slp_timer0() + .write(|w| w.slp_val_lo().bits((time_in_ticks & 0xffffffff) as u32)); + + rtc_cntl + .int_clr() + .write(|w| w.main_timer().clear_bit_by_one()); + + rtc_cntl.slp_timer1().write(|w| { + w.slp_val_hi().bits(((time_in_ticks >> 32) & 0xffff) as u16); + w.main_timer_alarm_en().set_bit() + }); + } + } +} + +impl RtcioWakeupSource<'_, '_> { + fn apply_pin(&self, pin: &mut dyn RtcPinWithResistors, level: WakeupLevel) { + // The pullup/pulldown part is like in gpio_deep_sleep_wakeup_prepare + let level = match level { + WakeupLevel::High => { + pin.rtcio_pullup(false); + pin.rtcio_pulldown(true); + GPIO_INTR_HIGH_LEVEL + } + WakeupLevel::Low => { + pin.rtcio_pullup(true); + pin.rtcio_pulldown(false); + GPIO_INTR_LOW_LEVEL + } + }; + pin.rtcio_pad_hold(true); + + // apply_wakeup does the same as idf's esp_deep_sleep_enable_gpio_wakeup + unsafe { + pin.apply_wakeup(true, level); + } + } +} + +fn isolate_digital_gpio() { + // like esp_sleep_isolate_digital_gpio + let rtc_cntl = LPWR::regs(); + let io_mux = IO_MUX::regs(); + let gpio = GPIO::regs(); + + let dig_iso = &rtc_cntl.dig_iso().read(); + let deep_sleep_hold_is_en = + !dig_iso.dg_pad_force_unhold().bit() && dig_iso.dg_pad_autohold_en().bit(); + if !deep_sleep_hold_is_en { + return; + } + + // TODO: assert that the task stack is not in external ram + + for pin_num in 0..GPIO_NUM_MAX { + let pin_hold = rtc_cntl.dig_pad_hold().read().bits() & (1 << pin_num) != 0; + if !pin_hold { + // input disable, like gpio_ll_input_disable + io_mux.gpio(pin_num).modify(|_, w| w.fun_ie().clear_bit()); + // output disable, like gpio_ll_output_disable + unsafe { + gpio.func_out_sel_cfg(pin_num) + .modify(|_, w| w.bits(SIG_GPIO_OUT_IDX)); + } + + // disable pull-up and pull-down + io_mux.gpio(pin_num).modify(|_, w| w.fun_wpu().clear_bit()); + io_mux.gpio(pin_num).modify(|_, w| w.fun_wpd().clear_bit()); + + // make pad work as gpio (otherwise, deep_sleep bottom current will rise) + io_mux + .gpio(pin_num) + .modify(|_, w| unsafe { w.mcu_sel().bits(RtcFunction::Digital as u8) }); + } + } +} + +impl WakeSource for RtcioWakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + let mut pins = self.pins.borrow_mut(); + + if pins.is_empty() { + return; + } + + triggers.set_gpio(true); + + // If deep sleep is enabled, esp_start_sleep calls + // gpio_deep_sleep_wakeup_prepare which sets these pullup and + // pulldown values. But later in esp_start_sleep it calls + // esp_sleep_isolate_digital_gpio, which disables the pullup and pulldown (but + // only if it isn't held). + // But it looks like gpio_deep_sleep_wakeup_prepare enables hold for all pins + // in the wakeup mask. + // + // So: all pins in the wake mask should get this treatment here, and all pins + // not in the wake mask should get + // - pullup and pulldowns disabled + // - input and output disabled, and + // - their func should get set to GPIO. + // But this last block of things gets skipped if hold is disabled globally (see + // gpio_ll_deep_sleep_hold_is_en) + + LPWR::regs() + .cntl_gpio_wakeup() + .modify(|_, w| w.gpio_pin_clk_gate().set_bit()); + + LPWR::regs() + .ext_wakeup_conf() + .modify(|_, w| w.gpio_wakeup_filter().set_bit()); + + if sleep_config.deep_slp() { + for (pin, level) in pins.iter_mut() { + self.apply_pin(*pin, *level); + } + + isolate_digital_gpio(); + } + + // like rtc_cntl_ll_gpio_clear_wakeup_status, as called from + // gpio_deep_sleep_wakeup_prepare + LPWR::regs() + .cntl_gpio_wakeup() + .modify(|_, w| w.gpio_wakeup_status_clr().set_bit()); + LPWR::regs() + .cntl_gpio_wakeup() + .modify(|_, w| w.gpio_wakeup_status_clr().clear_bit()); + } +} + +// impl Drop for RtcioWakeupSource<'_, '_> { +// fn drop(&mut self) { +// should we have saved the pin configuration first? +// set pin back to IO_MUX (input_enable and func have no effect when pin is sent +// to IO_MUX) +// let mut pins = self.pins.borrow_mut(); +// for (pin, _level) in pins.iter_mut() { +// pin.rtc_set_config(true, false, RtcFunction::Rtc); +// } +// } +// } + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// RTC Configuration. + pub struct RtcConfig(u32); + impl Debug; + /// Number of rtc_fast_clk cycles to wait for 8M clock to be ready + pub u8, ck8m_wait, set_ck8m_wait: 7, 0; + /// Number of rtc_fast_clk cycles to wait for XTAL clock to be ready + pub u8, xtal_wait, set_xtal_wait: 15, 8; + /// Number of rtc_fast_clk cycles to wait for PLL clock to be ready + pub u8, pll_wait, set_pll_wait: 23, 16; + /// Perform clock control related initialization. + pub clkctl_init, set_clkctl_init: 24; + /// Perform power control related initialization. + pub pwrctl_init, set_pwrctl_init: 25; + /// Force power down RTC_DBOOST + pub rtc_dboost_fpd, set_rtc_dboost_fpd: 26; + /// Keep the XTAL oscillator powered up in sleep. + pub xtal_fpu, set_xtal_fpu: 27; + /// Keep the BBPLL oscillator powered up in sleep. + pub bbpll_fpu, set_bbpll_fpu: 28; + /// Enable clock gating when the CPU is in wait-for-interrupt state. + pub cpu_waiti_clk_gate, set_cpu_waiti_clk_gate: 29; + /// Calibrate Ocode to make bandgap voltage more precise. + pub cali_ocode, set_cali_ocode: 30; +} + +impl Default for RtcConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_ck8m_wait(RTC_CNTL_CK8M_WAIT_DEFAULT); + cfg.set_xtal_wait(RTC_CNTL_XTL_BUF_WAIT_DEFAULT); + cfg.set_pll_wait(RTC_CNTL_PLL_BUF_WAIT_DEFAULT); + cfg.set_clkctl_init(true); + cfg.set_pwrctl_init(true); + cfg.set_rtc_dboost_fpd(true); + cfg.set_cpu_waiti_clk_gate(true); + cfg.set_bbpll_fpu(true); + cfg + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Configuration for RTC initialization. + pub struct RtcInitConfig(u128); + impl Debug; + /// Number of cycles required to power up WiFi + pub u8, wifi_powerup_cycles, set_wifi_powerup_cycles: 6, 0; + /// Number of wait cycles for WiFi to stabilize + pub u16, wifi_wait_cycles, set_wifi_wait_cycles: 15, 7; + /// Number of cycles required to power up Bluetooth + pub u8, bt_powerup_cycles, set_bt_powerup_cycles: 22, 16; + /// Number of wait cycles for Bluetooth to stabilize + pub u16, bt_wait_cycles, set_bt_wait_cycles: 31, 23; + /// Number of cycles required to power up the top CPU + pub u8, cpu_top_powerup_cycles, set_cpu_top_powerup_cycles: 38, 32; + /// Number of wait cycles for the top CPU to stabilize + pub u16, cpu_top_wait_cycles, set_cpu_top_wait_cycles: 47, 39; + /// Number of cycles required to power up the digital wrapper + pub u8, dg_wrap_powerup_cycles, set_dg_wrap_powerup_cycles: 54, 48; + /// Number of wait cycles for the digital wrapper to stabilize + pub u16, dg_wrap_wait_cycles, set_dg_wrap_wait_cycles: 63, 55; + /// Number of cycles required to power up the digital peripherals + pub u8, dg_peri_powerup_cycles, set_dg_peri_powerup_cycles: 70, 64; + /// Number of wait cycles for the digital peripherals to stabilize + pub u16, dg_peri_wait_cycles, set_dg_peri_wait_cycles: 79, 71; +} + +impl Default for RtcInitConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_wifi_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_bt_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_cpu_top_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_dg_wrap_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_dg_peri_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_wifi_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_bt_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_cpu_top_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_dg_wrap_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_dg_peri_wait_cycles(OTHER_BLOCKS_WAIT); + cfg + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Configuration for RTC sleep mode. + pub struct RtcSleepConfig(u64); + impl Debug; + /// force normal voltage in sleep mode (digital domain memory) + pub lslp_mem_inf_fpu, set_lslp_mem_inf_fpu: 0; + /// keep low voltage in sleep mode (even if ULP/touch is used) + pub rtc_mem_inf_follow_cpu, set_rtc_mem_inf_follow_cpu: 1; + /// power down RTC fast memory + pub rtc_fastmem_pd_en, set_rtc_fastmem_pd_en: 2; + /// power down RTC slow memory + pub rtc_slowmem_pd_en, set_rtc_slowmem_pd_en: 3; + /// power down RTC peripherals + pub rtc_peri_pd_en, set_rtc_peri_pd_en: 4; + /// power down WiFi + pub wifi_pd_en, set_wifi_pd_en: 5; + /// power down BT + pub bt_pd_en, set_bt_pd_en: 6; + /// power down CPU, but not restart when lightsleep. + pub cpu_pd_en, set_cpu_pd_en: 7; + /// Power down Internal 8M oscillator + pub int_8m_pd_en, set_int_8m_pd_en: 8; + /// power down digital peripherals + pub dig_peri_pd_en, set_dig_peri_pd_en: 9; + /// power down digital domain + pub deep_slp, set_deep_slp: 10; + /// enable WDT flashboot mode + pub wdt_flashboot_mod_en, set_wdt_flashboot_mod_en: 11; + /// set bias for digital domain, in sleep mode + pub u8, dig_dbias_slp, set_dig_dbias_slp: 16, 12; + /// set bias for RTC domain, in sleep mode + pub u8, rtc_dbias_slp, set_rtc_dbias_slp: 21, 17; + /// voltage parameter, in sleep mode + pub u8, dbg_atten_slp, set_dbg_atten_slp: 25, 22; + /// circuit control parameter, in monitor mode + pub bias_sleep_monitor, set_bias_sleep_monitor: 26; + /// circuit control parameter, in sleep mode + pub bias_sleep_slp, set_bias_sleep_slp: 27; + /// circuit control parameter, in monitor mode + pub pd_cur_slp, set_pd_cur_slp: 28; + /// power down VDDSDIO regulator + pub vddsdio_pd_en, set_vddsdio_pd_en: 29; + /// keep main XTAL powered up in sleep + pub xtal_fpu, set_xtal_fpu: 30; + /// keep rtc regulator powered up in sleep + pub rtc_regulator_fpu, set_rtc_regulator_fpu: 31; + /// enable deep sleep reject + pub deep_slp_reject, set_deep_slp_reject: 32; + /// enable light sleep reject + pub light_slp_reject, set_light_slp_reject: 33; +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg + } +} + +const DR_REG_NRX_BASE: u32 = 0x6001CC00; +const DR_REG_FE_BASE: u32 = 0x60006000; +const DR_REG_FE2_BASE: u32 = 0x60005000; + +const NRXPD_CTRL: u32 = DR_REG_NRX_BASE + 0x00d4; +const FE_GEN_CTRL: u32 = DR_REG_FE_BASE + 0x0090; +const FE2_TX_INTERP_CTRL: u32 = DR_REG_FE2_BASE + 0x00f0; + +const SYSCON_SRAM_POWER_UP: u8 = 0x0000000F; +const SYSCON_ROM_POWER_UP: u8 = 0x00000003; + +const NRX_RX_ROT_FORCE_PU: u32 = 1 << 5; +const NRX_VIT_FORCE_PU: u32 = 1 << 3; +const NRX_DEMAP_FORCE_PU: u32 = 1 << 1; + +const FE_IQ_EST_FORCE_PU: u32 = 1 << 5; +const FE2_TX_INF_FORCE_PU: u32 = 1 << 10; + +fn modify_register(reg: u32, mask: u32, value: u32) { + let reg = reg as *mut u32; + + unsafe { reg.write_volatile((reg.read_volatile() & !mask) | value) }; +} + +fn register_modify_bits(reg: u32, bits: u32, set: bool) { + if set { + modify_register(reg, bits, bits); + } else { + modify_register(reg, bits, 0); + } +} + +fn rtc_sleep_pu(val: bool) { + LPWR::regs() + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().bit(val)); + + APB_CTRL::regs().front_end_mem_pd().modify(|_r, w| { + w.dc_mem_force_pu() + .bit(val) + .pbus_mem_force_pu() + .bit(val) + .agc_mem_force_pu() + .bit(val) + }); + + BB::regs() + .bbpd_ctrl() + .modify(|_r, w| w.fft_force_pu().bit(val).dc_est_force_pu().bit(val)); + + register_modify_bits( + NRXPD_CTRL, + NRX_RX_ROT_FORCE_PU | NRX_VIT_FORCE_PU | NRX_DEMAP_FORCE_PU, + val, + ); + + register_modify_bits(FE_GEN_CTRL, FE_IQ_EST_FORCE_PU, val); + + register_modify_bits(FE2_TX_INTERP_CTRL, FE2_TX_INF_FORCE_PU, val); + + APB_CTRL::regs().mem_power_up().modify(|_r, w| unsafe { + w.sram_power_up() + .bits(if val { SYSCON_SRAM_POWER_UP } else { 0 }); + w.rom_power_up() + .bits(if val { SYSCON_ROM_POWER_UP } else { 0 }) + }); +} + +impl RtcSleepConfig { + /// Configures the RTC for deep sleep mode. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + let mut cfg = Self::default(); + + cfg.set_lslp_mem_inf_fpu(false); + cfg.set_rtc_mem_inf_follow_cpu(true); // ? + cfg.set_rtc_fastmem_pd_en(true); + cfg.set_rtc_slowmem_pd_en(true); + cfg.set_rtc_peri_pd_en(true); + cfg.set_wifi_pd_en(true); + cfg.set_bt_pd_en(true); + cfg.set_cpu_pd_en(true); + cfg.set_int_8m_pd_en(true); + + cfg.set_dig_peri_pd_en(true); + cfg.set_dig_dbias_slp(0); // because of dig_peri_pd_en + + cfg.set_deep_slp(true); + cfg.set_wdt_flashboot_mod_en(false); + cfg.set_vddsdio_pd_en(true); + cfg.set_xtal_fpu(false); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + + // because of dig_peri_pd_en + cfg.set_rtc_regulator_fpu(false); + cfg.set_dbg_atten_slp(RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT); + + // because of xtal_fpu + cfg.set_bias_sleep_monitor(true); + cfg.set_bias_sleep_slp(true); + cfg.set_pd_cur_slp(true); + + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + let cfg = RtcConfig::default(); + + // settings derived from esp_clk_init -> rtc_init + let rtc_cntl = LPWR::regs(); + let extmem = EXTMEM::regs(); + let system = SYSTEM::regs(); + + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + + unsafe { + rtc_cntl.timer1().modify(|_, w| { + w.pll_buf_wait().bits(cfg.pll_wait()); + w.ck8m_wait().bits(cfg.ck8m_wait()) + }); + + // Moved from rtc sleep to rtc init to save sleep function running time + // set shortest possible sleep time limit + + rtc_cntl + .timer5() + .modify(|_, w| w.min_slp_val().bits(RTC_CNTL_MIN_SLP_VAL_MIN)); + + // TODO: something about cali_ocode + + // Reset RTC bias to default value (needed if waking up from deep sleep) + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(RTC_CNTL_DBIAS_1V10); + + // LDO dbias initialization + // TODO: this modifies g_rtc_dbias_pvt_non_240m and g_dig_dbias_pvt_non_240m. + // We're using a high enough default but we should read from the efuse. + // set_rtc_dig_dbias(); + + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(RTC_CNTL_DBIAS_1V25); + regi2c::I2C_DIG_REG_EXT_DIG_DREG.write_field(RTC_CNTL_DBIAS_1V25); + + if cfg.clkctl_init() { + // clear CMMU clock force on + + extmem + .cache_mmu_power_ctrl() + .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); + // clear tag clock force on + + extmem + .icache_tag_power_ctrl() + .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); + + // clear register clock force on + SPI0::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + SPI1::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + } + + if cfg.pwrctl_init() { + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(cfg.xtal_fpu() || cfg.bbpll_fpu())); + + // cancel bbpll force pu if setting no force power up + + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().bit(cfg.bbpll_fpu()); + w.bbpll_i2c_force_pu().bit(cfg.bbpll_fpu()); + w.bb_i2c_force_pu().bit(cfg.bbpll_fpu()) + }); + + rtc_cntl + .rtc_cntl() + .modify(|_, w| w.regulator_force_pu().clear_bit()); + + // If this mask is enabled, all soc memories cannot enter power down mode + // We should control soc memory power down mode from RTC, so we will not touch + // this register any more + + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + // If this pd_cfg is set to 1, all memory won't enter low power mode during + // light sleep If this pd_cfg is set to 0, all memory will enter low + // power mode during light sleep + rtc_sleep_pu(false); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_force_pu().clear_bit()); + + rtc_cntl + .dig_iso() + .modify(|_, w| w.dg_wrap_force_noiso().clear_bit()); + + // if SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0 , the cpu clk will be closed when cpu + // enter WAITI mode + + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().bit(!cfg.cpu_waiti_clk_gate())); + + // cancel digital PADS force no iso + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold().clear_bit(); + w.dg_pad_force_noiso().clear_bit() + }); + } + + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(1); + } + } + + pub(crate) fn apply(&self) { + // like esp-idf rtc_sleep_init() + let rtc_cntl = LPWR::regs(); + + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + unsafe { + assert!(!self.pd_cur_slp() || self.bias_sleep_slp()); + + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(self.rtc_dbias_slp()); + regi2c::I2C_DIG_REG_EXT_DIG_DREG_SLEEP.write_field(self.dig_dbias_slp()); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_monitor() + .bits(RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT); + // We have config values for this in self, so I don't know why we're setting + // hardcoded defaults for these next two. It's what IDF does... + w.bias_sleep_monitor().bit(RTC_CNTL_BIASSLP_MONITOR_DEFAULT); + w.pd_cur_monitor().bit(RTC_CNTL_PD_CUR_MONITOR_DEFAULT) + }); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_deep_slp().bits(self.dbg_atten_slp()); + w.bias_sleep_deep_slp().bit(self.bias_sleep_slp()); + w.pd_cur_deep_slp().bit(self.pd_cur_slp()) + }); + + if self.deep_slp() { + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(0); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().set_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w.ckgen_i2c_pu().clear_bit(); + w.pll_i2c_pu().clear_bit(); + w.rfrx_pbus_pu().clear_bit(); + w.txrf_i2c_pu().clear_bit() + }); + + rtc_cntl + .options0() + .modify(|_, w| w.bb_i2c_force_pu().clear_bit()); + } else { + rtc_cntl.bias_conf().modify(|_, w| { + w.dg_vdd_drv_b_slp_en().set_bit(); + w.dg_vdd_drv_b_slp().bits(RTC_CNTL_DG_VDD_DRV_B_SLP_DEFAULT) + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().clear_bit()); + } + + rtc_cntl + .rtc_cntl() + .modify(|_, w| w.regulator_force_pu().bit(self.rtc_regulator_fpu())); + + rtc_cntl.clk_conf().modify(|_, w| { + w.ck8m_force_pu().bit(!self.int_8m_pd_en()); + w.ck8m_force_nogating().bit(!self.int_8m_pd_en()) + }); + + // enable VDDSDIO control by state machine + + rtc_cntl.dig_pwc().modify(|_, w| { + w.vdd_spi_pwr_force().clear_bit(); + w.vdd_spi_pd_en().bit(self.vddsdio_pd_en()) + }); + + rtc_cntl.slp_reject_conf().modify(|_, w| { + w.deep_slp_reject_en().bit(self.deep_slp_reject()); + w.light_slp_reject_en().bit(self.light_slp_reject()) + }); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(self.xtal_fpu())); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.xtal_global_force_nogating().bit(self.xtal_fpu())); + } + } + + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + // set bits for what can wake us up + LPWR::regs() + .wakeup_state() + .modify(|_, w| unsafe { w.wakeup_ena().bits(wakeup_triggers.0.into()) }); + + LPWR::regs() + .state0() + .write(|w| w.sleep_en().set_bit().slp_wakeup().set_bit()); + } + + pub(crate) fn finish_sleep(&self) { + // In deep sleep mode, we never get here + + LPWR::regs().int_clr().write(|w| { + w.slp_reject().clear_bit_by_one(); + w.slp_wakeup().clear_bit_by_one() + }); + + // restore config if it is a light sleep + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c3.rs b/esp-hal/src/rtc_cntl/sleep/esp32c3.rs new file mode 100644 index 00000000000..d0b0086294c --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32c3.rs @@ -0,0 +1,870 @@ +use super::{TimerWakeupSource, WakeSource, WakeTriggers, WakeupLevel}; +use crate::{ + gpio::{RtcFunction, RtcPinWithResistors}, + peripherals::{APB_CTRL, BB, EXTMEM, FE, FE2, GPIO, IO_MUX, LPWR, NRX, SPI0, SPI1, SYSTEM}, + rtc_cntl::{Rtc, sleep::RtcioWakeupSource}, + soc::regi2c, +}; + +// Approximate mapping of voltages to RTC_CNTL_DBIAS_WAK, RTC_CNTL_DBIAS_SLP, +// RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DIG_DBIAS_SLP values. +// Valid if RTC_CNTL_DBG_ATTEN is 0. +/// Digital bias voltage level of 0.90V. +pub const RTC_CNTL_DBIAS_0V90: u8 = 13; +/// Digital bias voltage level of 0.95V. +pub const RTC_CNTL_DBIAS_0V95: u8 = 16; +/// Digital bias voltage level of 1.00V. +pub const RTC_CNTL_DBIAS_1V00: u8 = 18; +/// Digital bias voltage level of 1.05V. +pub const RTC_CNTL_DBIAS_1V05: u8 = 20; +/// Digital bias voltage level of 1.10V. +pub const RTC_CNTL_DBIAS_1V10: u8 = 23; +/// Digital bias voltage level of 1.15V. +pub const RTC_CNTL_DBIAS_1V15: u8 = 25; +/// Digital bias voltage level of 1.20V. +pub const RTC_CNTL_DBIAS_1V20: u8 = 28; +/// Digital bias voltage level of 1.25V. +pub const RTC_CNTL_DBIAS_1V25: u8 = 30; +/// Digital bias voltage level of approximately 1.34V. +pub const RTC_CNTL_DBIAS_1V30: u8 = 31; + +/// Default attenuation setting during light sleep, with a voltage drop. +pub const RTC_CNTL_DBG_ATTEN_LIGHTSLEEP_DEFAULT: u8 = 5; +/// No attenuation (no voltage drop) during light sleep. +pub const RTC_CNTL_DBG_ATTEN_LIGHTSLEEP_NODROP: u8 = 0; +/// Default attenuation setting during deep sleep, with maximum voltage drop. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT: u8 = 15; +/// No attenuation (no voltage drop) during deep sleep. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_NODROP: u8 = 0; + +/// Default bias setting during sleep mode. +pub const RTC_CNTL_BIASSLP_SLEEP_DEFAULT: u8 = 1; +/// Keeps the bias for ultra-low power sleep mode always on. +pub const RTC_CNTL_BIASSLP_SLEEP_ON: u8 = 0; + +/// Default power-down current setting during sleep mode. +pub const RTC_CNTL_PD_CUR_SLEEP_DEFAULT: u8 = 1; +/// Keeps the power-down current setting for sleep mode always on. +pub const RTC_CNTL_PD_CUR_SLEEP_ON: u8 = 0; + +/// Default driver bias setting for the digital domain during sleep mode. +pub const RTC_CNTL_DG_VDD_DRV_B_SLP_DEFAULT: u8 = 254; + +/// Default debug attenuation setting for the monitor mode. +pub const RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT: u8 = 0; +/// Default bias setting for sleep mode in the monitor mode. +pub const RTC_CNTL_BIASSLP_MONITOR_DEFAULT: bool = false; +/// Default power-down current setting for the monitor mode. +pub const RTC_CNTL_PD_CUR_MONITOR_DEFAULT: bool = false; + +/// Default number of cycles to wait for the PLL buffer to stabilize. +pub const RTC_CNTL_PLL_BUF_WAIT_DEFAULT: u8 = 20; +/// Default number of cycles to wait for the XTL buffer to stabilize. +pub const RTC_CNTL_XTL_BUF_WAIT_DEFAULT: u8 = 100; +/// Default number of cycles to wait for the internal 8MHz clock to stabilize. +pub const RTC_CNTL_CK8M_WAIT_DEFAULT: u8 = 20; +/// Default number of cycles required to enable the internal 8MHz clock. +pub const RTC_CK8M_ENABLE_WAIT_DEFAULT: u8 = 5; + +/// Minimum number of cycles for sleep duration. +pub const RTC_CNTL_MIN_SLP_VAL_MIN: u8 = 2; + +/// Power-up cycles for other hardware blocks. +pub const OTHER_BLOCKS_POWERUP: u8 = 1; +/// Wait cycles for other hardware blocks to stabilize. +pub const OTHER_BLOCKS_WAIT: u16 = 1; + +/// Disables GPIO interrupt. +pub const GPIO_INTR_DISABLE: u8 = 0; +/// Sets GPIO interrupt to trigger on a low level signal. +pub const GPIO_INTR_LOW_LEVEL: u8 = 4; +/// Sets GPIO interrupt to trigger on a high level signal. +pub const GPIO_INTR_HIGH_LEVEL: u8 = 5; + +/// Specifies the function configuration for GPIO pins. +pub const PIN_FUNC_GPIO: u8 = 1; +/// Index for signaling GPIO output. +pub const SIG_GPIO_OUT_IDX: u32 = 128; +/// Maximum number of GPIO pins supported. +pub const GPIO_NUM_MAX: usize = 22; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + LPWR::regs() + .slp_timer0() + .write(|w| w.slp_val_lo().bits((time_in_ticks & 0xffffffff) as u32)); + + LPWR::regs() + .int_clr() + .write(|w| w.main_timer().clear_bit_by_one()); + + LPWR::regs().slp_timer1().write(|w| { + w.slp_val_hi() + .bits(((time_in_ticks >> 32) & 0xffff) as u16) + .main_timer_alarm_en() + .set_bit() + }); + } + } +} + +impl RtcioWakeupSource<'_, '_> { + fn apply_pin(&self, pin: &mut dyn RtcPinWithResistors, level: WakeupLevel) { + // The pullup/pulldown part is like in gpio_deep_sleep_wakeup_prepare + let level = match level { + WakeupLevel::High => { + pin.rtcio_pullup(false); + pin.rtcio_pulldown(true); + GPIO_INTR_HIGH_LEVEL + } + WakeupLevel::Low => { + pin.rtcio_pullup(true); + pin.rtcio_pulldown(false); + GPIO_INTR_LOW_LEVEL + } + }; + pin.rtcio_pad_hold(true); + + // apply_wakeup does the same as idf's esp_deep_sleep_enable_gpio_wakeup + unsafe { + pin.apply_wakeup(true, level); + } + } +} + +fn isolate_digital_gpio() { + // like esp_sleep_isolate_digital_gpio + let rtc_cntl = LPWR::regs(); + let io_mux = IO_MUX::regs(); + let gpio = GPIO::regs(); + + let dig_iso = &rtc_cntl.dig_iso().read(); + let deep_sleep_hold_is_en = + !dig_iso.dg_pad_force_unhold().bit() && dig_iso.dg_pad_autohold_en().bit(); + if !deep_sleep_hold_is_en { + return; + } + + // TODO: assert that the task stack is not in external ram + + for pin_num in 0..GPIO_NUM_MAX { + let pin_hold = rtc_cntl.dig_pad_hold().read().bits() & (1 << pin_num) != 0; + if !pin_hold { + // input disable, like gpio_ll_input_disable + io_mux.gpio(pin_num).modify(|_, w| w.fun_ie().clear_bit()); + // output disable, like gpio_ll_output_disable + unsafe { + gpio.func_out_sel_cfg(pin_num) + .modify(|_, w| w.bits(SIG_GPIO_OUT_IDX)); + } + + // disable pull-up and pull-down + io_mux.gpio(pin_num).modify(|_, w| w.fun_wpu().clear_bit()); + io_mux.gpio(pin_num).modify(|_, w| w.fun_wpd().clear_bit()); + + // make pad work as gpio (otherwise, deep_sleep bottom current will rise) + io_mux + .gpio(pin_num) + .modify(|_, w| unsafe { w.mcu_sel().bits(RtcFunction::Digital as u8) }); + } + } +} + +impl WakeSource for RtcioWakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + let mut pins = self.pins.borrow_mut(); + + if pins.is_empty() { + return; + } + + triggers.set_gpio(true); + + // If deep sleep is enabled, esp_start_sleep calls + // gpio_deep_sleep_wakeup_prepare which sets these pullup and + // pulldown values. But later in esp_start_sleep it calls + // esp_sleep_isolate_digital_gpio, which disables the pullup and pulldown (but + // only if it isn't held). + // But it looks like gpio_deep_sleep_wakeup_prepare enables hold for all pins + // in the wakeup mask. + // + // So: all pins in the wake mask should get this treatment here, and all pins + // not in the wake mask should get + // - pullup and pulldowns disabled + // - input and output disabled, and + // - their func should get set to GPIO. + // But this last block of things gets skipped if hold is disabled globally (see + // gpio_ll_deep_sleep_hold_is_en) + + LPWR::regs() + .gpio_wakeup() + .modify(|_, w| w.gpio_pin_clk_gate().set_bit()); + + LPWR::regs() + .ext_wakeup_conf() + .modify(|_, w| w.gpio_wakeup_filter().set_bit()); + + if sleep_config.deep_slp() { + for (pin, level) in pins.iter_mut() { + self.apply_pin(*pin, *level); + } + + isolate_digital_gpio(); + } + + // like rtc_cntl_ll_gpio_clear_wakeup_status, as called from + // gpio_deep_sleep_wakeup_prepare + LPWR::regs() + .gpio_wakeup() + .modify(|_, w| w.gpio_wakeup_status_clr().set_bit()); + LPWR::regs() + .gpio_wakeup() + .modify(|_, w| w.gpio_wakeup_status_clr().clear_bit()); + } +} + +// impl Drop for RtcioWakeupSource<'_, '_> { +// fn drop(&mut self) { +// should we have saved the pin configuration first? +// set pin back to IO_MUX (input_enable and func have no effect when pin is sent +// to IO_MUX) +// let mut pins = self.pins.borrow_mut(); +// for (pin, _level) in pins.iter_mut() { +// pin.rtc_set_config(true, false, RtcFunction::Rtc); +// } +// } +// } + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// RTC Configuration. + pub struct RtcConfig(u32); + impl Debug; + /// Number of rtc_fast_clk cycles to wait for 8M clock to be ready + pub u8, ck8m_wait, set_ck8m_wait: 7, 0; + /// Number of rtc_fast_clk cycles to wait for XTAL clock to be ready + pub u8, xtal_wait, set_xtal_wait: 15, 8; + /// Number of rtc_fast_clk cycles to wait for PLL clock to be ready + pub u8, pll_wait, set_pll_wait: 23, 16; + /// Perform clock control related initialization. + pub clkctl_init, set_clkctl_init: 24; + /// Perform power control related initialization. + pub pwrctl_init, set_pwrctl_init: 25; + /// Force power down `RTC_DBOOST`. + pub rtc_dboost_fpd, set_rtc_dboost_fpd: 26; + /// Keep the XTAL oscillator powered up in sleep. + pub xtal_fpu, set_xtal_fpu: 27; + /// Keep the BBPLL oscillator powered up in sleep. + pub bbpll_fpu, set_bbpll_fpu: 28; + /// Enable clock gating when the CPU is in wait-for-interrupt state. + pub cpu_waiti_clk_gate, set_cpu_waiti_clk_gate: 29; + /// Calibrate Ocode to make bandgap voltage more precise. + pub cali_ocode, set_cali_ocode: 30; +} + +impl Default for RtcConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_ck8m_wait(RTC_CNTL_CK8M_WAIT_DEFAULT); + cfg.set_xtal_wait(RTC_CNTL_XTL_BUF_WAIT_DEFAULT); + cfg.set_pll_wait(RTC_CNTL_PLL_BUF_WAIT_DEFAULT); + cfg.set_clkctl_init(true); + cfg.set_pwrctl_init(true); + cfg.set_rtc_dboost_fpd(true); + cfg.set_cpu_waiti_clk_gate(true); + cfg + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Configuration for RTC initialization. + pub struct RtcInitConfig(u128); + impl Debug; + /// Number of cycles required to power up WiFi + pub u8, wifi_powerup_cycles, set_wifi_powerup_cycles: 6, 0; + /// Number of wait cycles for WiFi to stabilize + pub u16, wifi_wait_cycles, set_wifi_wait_cycles: 15, 7; + /// Number of cycles required to power up Bluetooth + pub u8, bt_powerup_cycles, set_bt_powerup_cycles: 22, 16; + /// Number of wait cycles for Bluetooth to stabilize + pub u16, bt_wait_cycles, set_bt_wait_cycles: 31, 23; + /// Number of cycles required to power up the top CPU + pub u8, cpu_top_powerup_cycles, set_cpu_top_powerup_cycles: 38, 32; + /// Number of wait cycles for the top CPU to stabilize + pub u16, cpu_top_wait_cycles, set_cpu_top_wait_cycles: 47, 39; + /// Number of cycles required to power up the digital wrapper + pub u8, dg_wrap_powerup_cycles, set_dg_wrap_powerup_cycles: 54, 48; + /// Number of wait cycles for the digital wrapper to stabilize + pub u16, dg_wrap_wait_cycles, set_dg_wrap_wait_cycles: 63, 55; + /// Number of cycles required to power up the digital peripherals + pub u8, dg_peri_powerup_cycles, set_dg_peri_powerup_cycles: 70, 64; + /// Number of wait cycles for the digital peripherals to stabilize + pub u16, dg_peri_wait_cycles, set_dg_peri_wait_cycles: 79, 71; +} + +impl Default for RtcInitConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_wifi_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_bt_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_cpu_top_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_dg_wrap_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_dg_peri_powerup_cycles(OTHER_BLOCKS_POWERUP); + cfg.set_wifi_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_bt_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_cpu_top_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_dg_wrap_wait_cycles(OTHER_BLOCKS_WAIT); + cfg.set_dg_peri_wait_cycles(OTHER_BLOCKS_WAIT); + cfg + } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Configuration for RTC sleep mode. + pub struct RtcSleepConfig(u64); + impl Debug; + /// force normal voltage in sleep mode (digital domain memory) + pub lslp_mem_inf_fpu, set_lslp_mem_inf_fpu: 0; + /// keep low voltage in sleep mode (even if ULP/touch is used) + pub rtc_mem_inf_follow_cpu, set_rtc_mem_inf_follow_cpu: 1; + /// power down RTC fast memory + pub rtc_fastmem_pd_en, set_rtc_fastmem_pd_en: 2; + /// power down RTC slow memory + pub rtc_slowmem_pd_en, set_rtc_slowmem_pd_en: 3; + /// power down RTC peripherals + pub rtc_peri_pd_en, set_rtc_peri_pd_en: 4; + /// power down WiFi + pub wifi_pd_en, set_wifi_pd_en: 5; + /// power down BT + pub bt_pd_en, set_bt_pd_en: 6; + /// power down CPU, but not restart when lightsleep. + pub cpu_pd_en, set_cpu_pd_en: 7; + /// Power down Internal 8M oscillator + pub int_8m_pd_en, set_int_8m_pd_en: 8; + /// power down digital peripherals + pub dig_peri_pd_en, set_dig_peri_pd_en: 9; + /// power down digital domain + pub deep_slp, set_deep_slp: 10; + /// enable WDT flashboot mode + pub wdt_flashboot_mod_en, set_wdt_flashboot_mod_en: 11; + /// set bias for digital domain, in sleep mode + pub u8, dig_dbias_slp, set_dig_dbias_slp: 16, 12; + /// set bias for RTC domain, in sleep mode + pub u8, rtc_dbias_slp, set_rtc_dbias_slp: 21, 17; + /// voltage parameter, in sleep mode + pub u8, dbg_atten_slp, set_dbg_atten_slp: 25, 22; + /// circuit control parameter, in monitor mode + pub bias_sleep_monitor, set_bias_sleep_monitor: 26; + /// circuit control parameter, in sleep mode + pub bias_sleep_slp, set_bias_sleep_slp: 27; + /// circuit control parameter, in monitor mode + pub pd_cur_slp, set_pd_cur_slp: 28; + /// power down VDDSDIO regulator + pub vddsdio_pd_en, set_vddsdio_pd_en: 29; + /// keep main XTAL powered up in sleep + pub xtal_fpu, set_xtal_fpu: 30; + /// keep rtc regulator powered up in sleep + pub rtc_regulator_fpu, set_rtc_regulator_fpu: 31; + /// enable deep sleep reject + pub deep_slp_reject, set_deep_slp_reject: 32; + /// enable light sleep reject + pub light_slp_reject, set_light_slp_reject: 33; +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg + } +} + +const SYSCON_SRAM_POWER_UP: u8 = 0x0000000F; +const SYSCON_ROM_POWER_UP: u8 = 0x00000003; + +fn rtc_sleep_pu(val: bool) { + LPWR::regs() + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().bit(val).fastmem_force_lpu().bit(val)); + + APB_CTRL::regs().front_end_mem_pd().modify(|_r, w| { + w.dc_mem_force_pu() + .bit(val) + .pbus_mem_force_pu() + .bit(val) + .agc_mem_force_pu() + .bit(val) + }); + + BB::regs() + .bbpd_ctrl() + .modify(|_r, w| w.fft_force_pu().bit(val).dc_est_force_pu().bit(val)); + + NRX::regs().nrxpd_ctrl().modify(|_, w| { + w.rx_rot_force_pu() + .bit(val) + .vit_force_pu() + .bit(val) + .demap_force_pu() + .bit(val) + }); + + FE::regs() + .gen_ctrl() + .modify(|_, w| w.iq_est_force_pu().bit(val)); + + FE2::regs() + .tx_interp_ctrl() + .modify(|_, w| w.tx_inf_force_pu().bit(val)); + + APB_CTRL::regs().mem_power_up().modify(|_r, w| unsafe { + w.sram_power_up() + .bits(if val { SYSCON_SRAM_POWER_UP } else { 0 }) + .rom_power_up() + .bits(if val { SYSCON_ROM_POWER_UP } else { 0 }) + }); +} + +impl RtcSleepConfig { + /// Configures the RTC for deep sleep mode. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + let mut cfg = Self::default(); + + cfg.set_lslp_mem_inf_fpu(false); + cfg.set_rtc_mem_inf_follow_cpu(true); // ? + cfg.set_rtc_fastmem_pd_en(true); + cfg.set_rtc_slowmem_pd_en(true); + cfg.set_rtc_peri_pd_en(true); + cfg.set_wifi_pd_en(true); + cfg.set_bt_pd_en(true); + cfg.set_cpu_pd_en(true); + cfg.set_int_8m_pd_en(true); + + cfg.set_dig_peri_pd_en(true); + cfg.set_dig_dbias_slp(0); // because of dig_peri_pd_en + + cfg.set_deep_slp(true); + cfg.set_wdt_flashboot_mod_en(false); + cfg.set_vddsdio_pd_en(true); + cfg.set_xtal_fpu(false); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + + // because of dig_peri_pd_en + cfg.set_rtc_regulator_fpu(false); + cfg.set_dbg_atten_slp(RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT); + + // because of xtal_fpu + cfg.set_bias_sleep_monitor(true); + cfg.set_bias_sleep_slp(true); + cfg.set_pd_cur_slp(true); + + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + let cfg = RtcConfig::default(); + + // settings derived from esp_clk_init -> rtc_init + let rtc_cntl = LPWR::regs(); + let extmem = EXTMEM::regs(); + let system = SYSTEM::regs(); + + unsafe { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().clear_bit()); + + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + + rtc_cntl.ana_conf().modify(|_, w| w.pvtmon_pu().clear_bit()); + + rtc_cntl.timer1().modify(|_, w| { + w.pll_buf_wait().bits(cfg.pll_wait()); + w.ck8m_wait().bits(cfg.ck8m_wait()) + }); + + // Moved from rtc sleep to rtc init to save sleep function running time + // set shortest possible sleep time limit + + rtc_cntl + .timer5() + .modify(|_, w| w.min_slp_val().bits(RTC_CNTL_MIN_SLP_VAL_MIN)); + + let init_cfg = RtcInitConfig::default(); + + rtc_cntl.timer3().modify(|_, w| { + w + // set wifi timer + .wifi_powerup_timer() + .bits(init_cfg.wifi_powerup_cycles()) + .wifi_wait_timer() + .bits(init_cfg.wifi_wait_cycles()) + // set bt timer + .bt_powerup_timer() + .bits(init_cfg.bt_powerup_cycles()) + .bt_wait_timer() + .bits(init_cfg.bt_wait_cycles()) + }); + + rtc_cntl.timer4().modify(|_, w| { + w.cpu_top_powerup_timer() + .bits(init_cfg.cpu_top_powerup_cycles()) + .cpu_top_wait_timer() + .bits(init_cfg.cpu_top_wait_cycles()) + // set digital wrap timer + .dg_wrap_powerup_timer() + .bits(init_cfg.dg_wrap_powerup_cycles()) + .dg_wrap_wait_timer() + .bits(init_cfg.dg_wrap_wait_cycles()) + }); + + rtc_cntl.timer6().modify(|_, w| { + w.dg_peri_powerup_timer() + .bits(init_cfg.dg_peri_powerup_cycles()) + .dg_peri_wait_timer() + .bits(init_cfg.dg_peri_wait_cycles()) + }); + + // TODO: something about cali_ocode + + // Reset RTC bias to default value (needed if waking up from deep sleep) + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(RTC_CNTL_DBIAS_1V10); + + // LDO dbias initialization + // TODO: this modifies g_rtc_dbias_pvt_non_240m and g_dig_dbias_pvt_non_240m. + // We're using a high enough default but we should read from the efuse. + // set_rtc_dig_dbias(); + + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(RTC_CNTL_DBIAS_1V25); + regi2c::I2C_DIG_REG_EXT_DIG_DREG.write_field(RTC_CNTL_DBIAS_1V25); + + if cfg.clkctl_init() { + // clear CMMU clock force on + + extmem + .cache_mmu_power_ctrl() + .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); + // clear tag clock force on + + extmem + .icache_tag_power_ctrl() + .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); + // clear register clock force on + SPI0::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + SPI1::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + } + + if cfg.pwrctl_init() { + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(cfg.xtal_fpu() || cfg.bbpll_fpu())); + + // force pd APLL + + rtc_cntl + .ana_conf() + .modify(|_, w| w.plla_force_pu().clear_bit().plla_force_pd().set_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w + // open sar_i2c protect function to avoid sar_i2c reset when rtc_ldo is low. + .reset_por_force_pd() + .clear_bit() + }); + + // cancel bbpll force pu if setting no force power up + + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu() + .bit(cfg.bbpll_fpu()) + .bbpll_i2c_force_pu() + .bit(cfg.bbpll_fpu()) + .bb_i2c_force_pu() + .bit(cfg.bbpll_fpu()) + }); + + rtc_cntl.rtc_cntl().modify(|_, w| { + w.regulator_force_pu() + .clear_bit() + .dboost_force_pu() + .clear_bit() + .dboost_force_pd() + .bit(cfg.rtc_dboost_fpd()) + }); + + // If this mask is enabled, all soc memories cannot enter power down mode + // We should control soc memory power down mode from RTC, so we will not touch + // this register any more + + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + // If this pd_cfg is set to 1, all memory won't enter low power mode during + // light sleep If this pd_cfg is set to 0, all memory will enter low + // power mode during light sleep + rtc_sleep_pu(false); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.dg_wrap_force_pu() + .clear_bit() + .wifi_force_pu() + .clear_bit() + .bt_force_pu() + .clear_bit() + .cpu_top_force_pu() + .clear_bit() + .dg_peri_force_pu() + .clear_bit() + }); + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso() + .clear_bit() + .wifi_force_noiso() + .clear_bit() + .bt_force_noiso() + .clear_bit() + .cpu_top_force_noiso() + .clear_bit() + .dg_peri_force_noiso() + .clear_bit() + }); + + // if SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0 , the cpu clk will be closed when cpu + // enter WAITI mode + + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().bit(!cfg.cpu_waiti_clk_gate())); + + // cancel digital PADS force no iso + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold() + .clear_bit() + .dg_pad_force_noiso() + .clear_bit() + }); + } + + // force power down modem(wifi and ble) power domain + + rtc_cntl + .dig_iso() + .modify(|_, w| w.wifi_force_iso().set_bit().bt_force_iso().set_bit()); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().set_bit().bt_force_pd().set_bit()); + + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(1); + } + } + + pub(crate) fn apply(&self) { + // like esp-idf rtc_sleep_init() + let rtc_cntl = LPWR::regs(); + + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + if self.wifi_pd_en() { + rtc_cntl.dig_iso().modify(|_, w| { + w.wifi_force_noiso() + .clear_bit() + .wifi_force_iso() + .clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pu().clear_bit().wifi_pd_en().set_bit()); + } else { + rtc_cntl.dig_pwc().modify(|_, w| w.wifi_pd_en().clear_bit()); + } + if self.bt_pd_en() { + rtc_cntl + .dig_iso() + .modify(|_, w| w.bt_force_noiso().clear_bit().bt_force_iso().clear_bit()); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.bt_force_pu().clear_bit().bt_pd_en().set_bit()); + } else { + rtc_cntl.dig_pwc().modify(|_, w| w.bt_pd_en().clear_bit()); + } + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.cpu_top_pd_en().bit(self.cpu_pd_en())); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_peri_pd_en().bit(self.dig_peri_pd_en())); + + unsafe { + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_monitor() + .bits(RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT) + // We have config values for this in self, so I don't know why we're setting + // hardcoded defaults for these next two. It's what IDF does... + .bias_sleep_monitor() + .bit(RTC_CNTL_BIASSLP_MONITOR_DEFAULT) + .pd_cur_monitor() + .bit(RTC_CNTL_PD_CUR_MONITOR_DEFAULT) + }); + + assert!(!self.pd_cur_slp() || self.bias_sleep_slp()); + + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(self.rtc_dbias_slp()); + regi2c::I2C_DIG_REG_EXT_DIG_DREG_SLEEP.write_field(self.dig_dbias_slp()); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_deep_slp() + .bits(self.dbg_atten_slp()) + .bias_sleep_deep_slp() + .bit(self.bias_sleep_slp()) + .pd_cur_deep_slp() + .bit(self.pd_cur_slp()) + }); + + if self.deep_slp() { + regi2c::I2C_ULP_IR_FORCE_XPD_CK.write_field(0); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().set_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w.ckgen_i2c_pu() + .clear_bit() + .pll_i2c_pu() + .clear_bit() + .rfrx_pbus_pu() + .clear_bit() + .txrf_i2c_pu() + .clear_bit() + }); + + rtc_cntl + .options0() + .modify(|_, w| w.bb_i2c_force_pu().clear_bit()); + } else { + rtc_cntl.bias_conf().modify(|_, w| { + w.dg_vdd_drv_b_slp_en() + .set_bit() + .dg_vdd_drv_b_slp() + .bits(RTC_CNTL_DG_VDD_DRV_B_SLP_DEFAULT) + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().clear_bit()); + } + + // mem force pu + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().set_bit()); + + rtc_cntl + .rtc_cntl() + .modify(|_, w| w.regulator_force_pu().bit(self.rtc_regulator_fpu())); + + rtc_cntl.clk_conf().modify(|_, w| { + w.ck8m_force_pu() + .bit(!self.int_8m_pd_en()) + .ck8m_force_nogating() + .bit(!self.int_8m_pd_en()) + }); + + // enable VDDSDIO control by state machine + + rtc_cntl.sdio_conf().modify(|_, w| { + w.sdio_force() + .clear_bit() + .sdio_reg_pd_en() + .bit(self.vddsdio_pd_en()) + }); + + rtc_cntl.slp_reject_conf().modify(|_, w| { + w.deep_slp_reject_en() + .bit(self.deep_slp_reject()) + .light_slp_reject_en() + .bit(self.light_slp_reject()) + }); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(self.xtal_fpu())); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.xtal_global_force_nogating().bit(self.xtal_fpu())); + } + } + + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + // set bits for what can wake us up + LPWR::regs() + .wakeup_state() + .modify(|_, w| unsafe { w.wakeup_ena().bits(wakeup_triggers.0.into()) }); + + LPWR::regs() + .state0() + .write(|w| w.sleep_en().set_bit().slp_wakeup().set_bit()); + } + + pub(crate) fn finish_sleep(&self) { + // In deep sleep mode, we never get here + LPWR::regs().int_clr().write(|w| { + w.slp_reject().clear_bit_by_one(); + w.slp_wakeup().clear_bit_by_one() + }); + + // restore config if it is a light sleep + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c6.rs b/esp-hal/src/rtc_cntl/sleep/esp32c6.rs new file mode 100644 index 00000000000..a57cd2a4a00 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32c6.rs @@ -0,0 +1,1045 @@ +use core::ops::Not; + +use crate::{ + clock::RtcClock, + gpio::RtcFunction, + rtc_cntl::{ + Rtc, + rtc::{HpAnalog, HpSysCntlReg, HpSysPower, LpAnalog, LpSysPower, SavedClockConfig}, + sleep::{ + Ext1WakeupSource, + TimerWakeupSource, + WakeFromLpCoreWakeupSource, + WakeSource, + WakeTriggers, + WakeupLevel, + }, + }, + soc::clocks::{ClockTree, Timg0CalibrationClockConfig}, +}; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + + let lp_timer = unsafe { &*esp32c6::LP_TIMER::ptr() }; + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + lp_timer.tar0_high().write(|w| { + w.main_timer_tar_high0() + .bits(((time_in_ticks >> 32) & 0xffff) as u16) + }); + lp_timer.tar0_low().write(|w| { + w.main_timer_tar_low0() + .bits((time_in_ticks & 0xffffffff) as u32) + }); + lp_timer + .int_clr() + .write(|w| w.soc_wakeup().clear_bit_by_one()); + lp_timer + .tar0_high() + .modify(|_, w| w.main_timer_tar_en0().set_bit()); + } + } +} + +impl Ext1WakeupSource<'_, '_> { + /// Returns the currently configured wakeup pins. + fn wakeup_pins() -> u8 { + unsafe { lp_aon().ext_wakeup_cntl().read().ext_wakeup_sel().bits() } + } + + fn wake_io_reset() { + use crate::gpio::RtcPin; + + fn uninit_pin(pin: impl RtcPin, wakeup_pins: u8) { + if wakeup_pins & (1 << pin.number()) != 0 { + pin.rtcio_pad_hold(false); + pin.rtc_set_config(false, false, RtcFunction::Rtc); + } + } + + let wakeup_pins = Ext1WakeupSource::wakeup_pins(); + uninit_pin(unsafe { crate::peripherals::GPIO0::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO1::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO2::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO3::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO4::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO5::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO6::steal() }, wakeup_pins); + uninit_pin(unsafe { crate::peripherals::GPIO7::steal() }, wakeup_pins); + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + // We don't have to keep the LP domain powered if we hold the wakeup pin states. + triggers.set_ext1(true); + + // set pins to RTC function + let mut pins = self.pins.borrow_mut(); + let mut pin_mask = 0u8; + let mut level_mask = 0u8; + for (pin, level) in pins.iter_mut() { + pin_mask |= 1 << pin.number(); + level_mask |= match level { + WakeupLevel::High => 1 << pin.number(), + WakeupLevel::Low => 0, + }; + + pin.rtc_set_config(true, true, RtcFunction::Rtc); + pin.rtcio_pad_hold(true); + } + + unsafe { + // clear previous wakeup status + lp_aon() + .ext_wakeup_cntl() + .modify(|_, w| w.ext_wakeup_status_clr().set_bit()); + + // set pin + level register fields + lp_aon().ext_wakeup_cntl().modify(|r, w| { + w.ext_wakeup_sel() + .bits(r.ext_wakeup_sel().bits() | pin_mask) + .ext_wakeup_lv() + .bits(r.ext_wakeup_lv().bits() & !pin_mask | level_mask) + }); + } + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for (pin, _level) in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +impl WakeSource for WakeFromLpCoreWakeupSource { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_lp_core(true); + } +} + +/// Configuration for controlling the behavior during sleep modes. +#[derive(Clone, Copy)] +// pmu_sleep_analog_config_t +pub struct AnalogSleepConfig { + /// High-power system configuration. + pub hp_sys: HpAnalog, + // pub lp_sys_active: LpAnalog, // unused + /// Low-power system analog configuration. + pub lp_sys_sleep: LpAnalog, +} + +impl AnalogSleepConfig { + fn defaults_deep_sleep() -> Self { + Self { + // PMU_SLEEP_ANALOG_DSLP_CONFIG_DEFAULT + hp_sys: { + let mut cfg = HpAnalog::default(); + + cfg.bias.set_pd_cur(false); + cfg.bias.set_bias_sleep(false); + cfg.regulator0.set_xpd(false); + cfg.bias.set_dbg_atten(0); + + cfg + }, + // lp_sys_active: LpAnalog::default(), + lp_sys_sleep: { + let mut cfg = LpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_slp_xpd(false); + cfg.regulator0.set_slp_dbias(0); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(12); + cfg.regulator0.set_dbias(23); // 0.7V + + cfg + }, + } + } + + fn defaults_light_sleep(pd_flags: PowerDownFlags) -> Self { + let mut this = Self { + // PMU_SLEEP_ANALOG_LSLP_CONFIG_DEFAULT + hp_sys: { + let mut cfg = HpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(0); + cfg.regulator0.set_dbias(1); // 0.6V + + cfg + }, + // lp_sys_active: LpAnalog::default(), + lp_sys_sleep: { + let mut cfg = LpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_slp_xpd(false); + cfg.regulator0.set_slp_dbias(0); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(0); + cfg.regulator0.set_dbias(12); // 0.7V + + cfg + }, + }; + + if !pd_flags.pd_xtal() { + this.hp_sys.bias.set_pd_cur(false); + this.hp_sys.bias.set_bias_sleep(false); + this.hp_sys.regulator0.set_dbias(25); + + this.lp_sys_sleep.bias.set_pd_cur(false); + this.lp_sys_sleep.bias.set_bias_sleep(false); + this.lp_sys_sleep.regulator0.set_dbias(26); + } + + this + } + + fn apply(&self) { + // pmu_sleep_analog_init + + unsafe { + // HP SLEEP (hp_sleep_*) + pmu().hp_sleep_bias().modify(|_, w| { + w.hp_sleep_dbg_atten() // pmu_ll_hp_set_dbg_atten + .bits(self.hp_sys.bias.dbg_atten()) + .hp_sleep_pd_cur() // pmu_ll_hp_set_current_power_off + .bit(self.hp_sys.bias.pd_cur()) + .sleep() // pmu_ll_hp_set_bias_sleep_enable + .bit(self.hp_sys.bias.bias_sleep()) + }); + pmu().hp_sleep_hp_regulator0().modify(|_, w| { + w.hp_sleep_hp_regulator_xpd() // pmu_ll_hp_set_regulator_xpd + .bit(self.hp_sys.regulator0.xpd()) + .hp_sleep_hp_regulator_dbias() // pmu_ll_hp_set_regulator_dbias + .bits(self.hp_sys.regulator0.dbias()) + }); + pmu().hp_sleep_hp_regulator1().modify(|_, w| { + w.hp_sleep_hp_regulator_drv_b() // pmu_ll_hp_set_regulator_driver_bar + .bits(self.hp_sys.regulator1.drv_b()) + }); + + // LP SLEEP (lp_sleep_*) + pmu().lp_sleep_bias().modify(|_, w| { + w.lp_sleep_dbg_atten() // pmu_ll_lp_set_dbg_atten + .bits(self.lp_sys_sleep.bias.dbg_atten()) + .lp_sleep_pd_cur() // pmu_ll_lp_set_current_power_off + .bit(self.lp_sys_sleep.bias.pd_cur()) + .sleep() // pmu_ll_lp_set_bias_sleep_enable + .bit(self.lp_sys_sleep.bias.bias_sleep()) + }); + + pmu().lp_sleep_lp_regulator0().modify(|_, w| { + w.lp_sleep_lp_regulator_slp_xpd() // pmu_ll_lp_set_regulator_slp_xpd + .bit(self.lp_sys_sleep.regulator0.slp_xpd()) + .lp_sleep_lp_regulator_xpd() // pmu_ll_lp_set_regulator_xpd + .bit(self.lp_sys_sleep.regulator0.xpd()) + .lp_sleep_lp_regulator_slp_dbias() // pmu_ll_lp_set_regulator_sleep_dbias + .bits(self.lp_sys_sleep.regulator0.slp_dbias()) + .lp_sleep_lp_regulator_dbias() // pmu_ll_lp_set_regulator_dbias + .bits(self.lp_sys_sleep.regulator0.dbias()) + }); + + pmu().lp_sleep_lp_regulator1().modify(|_, w| { + w.lp_sleep_lp_regulator_drv_b() // pmu_ll_lp_set_regulator_driver_bar + .bits(self.lp_sys_sleep.regulator1.drv_b()) + }); + } + } +} + +/// Configuration for controlling the behavior of digital peripherals during +/// sleep modes. +#[derive(Clone, Copy)] +// pmu_sleep_digital_config_t +pub struct DigitalSleepConfig { + /// High-power system control register configuration. + pub syscntl: HpSysCntlReg, +} + +impl DigitalSleepConfig { + fn defaults_light_sleep(pd_flags: PowerDownFlags) -> Self { + Self { + // PMU_SLEEP_DIGITAL_LSLP_CONFIG_DEFAULT + syscntl: { + let mut cfg = HpSysCntlReg::default(); + + cfg.set_dig_pad_slp_sel(pd_flags.pd_top().not()); + + cfg + }, + } + } + + fn apply(&self) { + // pmu_sleep_digital_init + unsafe { + pmu().hp_sleep_hp_sys_cntl().modify(|_, w| { + w.hp_sleep_dig_pad_slp_sel() + .bit(self.syscntl.dig_pad_slp_sel()) + }) + }; + } +} + +/// Configuration for controlling the power settings of high-power and low-power +/// systems during sleep modes. +#[derive(Clone, Copy)] +// pmu_sleep_power_config_t +pub struct PowerSleepConfig { + /// Power configuration for the high-power system during sleep. + pub hp_sys: HpSysPower, + /// Power configuration for the low-power system when it is active. + pub lp_sys_active: LpSysPower, + /// Power configuration for the low-power system when it is in sleep mode. + pub lp_sys_sleep: LpSysPower, +} + +impl PowerSleepConfig { + fn defaults(pd_flags: PowerDownFlags) -> Self { + let mut this = Self { + hp_sys: HpSysPower::default(), + lp_sys_active: LpSysPower::default(), + lp_sys_sleep: LpSysPower::default(), + }; + this.apply_flags(pd_flags); + this + } + + fn apply_flags(&mut self, pd_flags: PowerDownFlags) { + // PMU_SLEEP_POWER_CONFIG_DEFAULT + self.hp_sys + .dig_power + .set_vdd_spi_pd_en(pd_flags.pd_vddsdio()); + self.hp_sys.dig_power.set_wifi_pd_en(pd_flags.pd_modem()); + self.hp_sys.dig_power.set_cpu_pd_en(pd_flags.pd_cpu()); + self.hp_sys.dig_power.set_aon_pd_en(pd_flags.pd_hp_aon()); + self.hp_sys.dig_power.set_top_pd_en(pd_flags.pd_top()); + + self.hp_sys.clk.set_i2c_iso_en(true); + self.hp_sys.clk.set_i2c_retention(true); + + self.hp_sys.xtal.set_xpd_xtal(pd_flags.pd_xtal().not()); + + self.lp_sys_active.clk_power.set_xpd_xtal32k(true); + self.lp_sys_active.clk_power.set_xpd_rc32k(true); + self.lp_sys_active.clk_power.set_xpd_fosc(true); + + self.lp_sys_sleep + .dig_power + .set_peri_pd_en(pd_flags.pd_lp_periph()); + self.lp_sys_sleep.dig_power.set_mem_dslp(true); + + self.lp_sys_sleep + .clk_power + .set_xpd_xtal32k(pd_flags.pd_xtal32k().not()); + self.lp_sys_sleep + .clk_power + .set_xpd_rc32k(pd_flags.pd_rc32k().not()); + self.lp_sys_sleep + .clk_power + .set_xpd_fosc(pd_flags.pd_rc_fast().not()); + + self.lp_sys_sleep + .xtal + .set_xpd_xtal(pd_flags.pd_xtal().not()); + } + + fn apply(&self) { + // pmu_sleep_power_init + + unsafe { + // HP SLEEP (hp_sleep_*) + pmu() + .hp_sleep_dig_power() + .modify(|_, w| w.bits(self.hp_sys.dig_power.0)); + pmu() + .hp_sleep_hp_ck_power() + .modify(|_, w| w.bits(self.hp_sys.clk.0)); + pmu() + .hp_sleep_xtal() + .modify(|_, w| w.hp_sleep_xpd_xtal().bit(self.hp_sys.xtal.xpd_xtal())); + + // LP ACTIVE (hp_sleep_lp_*) + pmu() + .hp_sleep_lp_dig_power() + .modify(|_, w| w.bits(self.lp_sys_active.dig_power.0)); + pmu() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.bits(self.lp_sys_active.clk_power.0)); + + // LP SLEEP (lp_sleep_*) + pmu() + .lp_sleep_lp_dig_power() + .modify(|_, w| w.bits(self.lp_sys_sleep.dig_power.0)); + pmu() + .lp_sleep_lp_ck_power() + .modify(|_, w| w.bits(self.lp_sys_sleep.clk_power.0)); + pmu() + .lp_sleep_xtal() + .modify(|_, w| w.lp_sleep_xpd_xtal().bit(self.lp_sys_sleep.xtal.xpd_xtal())); + } + } +} + +/// Parameters for high-power system configurations during sleep modes. +#[derive(Clone, Copy)] +// pmu_hp_param_t +pub struct HpParam { + /// Number of cycles to wait for the modem to wake up. + pub modem_wakeup_wait_cycle: u32, + /// Number of cycles to wait for the analog component stabilization. + pub analog_wait_target_cycle: u16, + /// Number of cycles to wait for the digital power-down sequence. + pub digital_power_down_wait_cycle: u16, + /// Number of cycles to wait for the digital power supply to stabilize. + pub digital_power_supply_wait_cycle: u16, + /// Number of cycles to wait for the digital power-up sequence. + pub digital_power_up_wait_cycle: u16, + /// Number of cycles to wait for the PLL to stabilize. + pub pll_stable_wait_cycle: u16, + /// Number of cycles to wait for modifying the ICG control. + pub modify_icg_cntl_wait_cycle: u8, + /// Number of cycles to wait for switching the ICG coйntrol. + pub switch_icg_cntl_wait_cycle: u8, + /// Minimum sleep time measured in slow clock cycles. + pub min_slp_slow_clk_cycle: u8, +} + +/// Parameters for low-power system configurations during sleep modes. +#[derive(Clone, Copy)] +// pmu_lp_param_t +pub struct LpParam { + /// Number of cycles to wait for the digital power supply to stabilize. + pub digital_power_supply_wait_cycle: u16, + /// Minimum sleep time measured in slow clock cycles. + pub min_slp_slow_clk_cycle: u8, + /// Number of cycles to wait for the analog component stabilization. + pub analog_wait_target_cycle: u8, + /// Number of cycles to wait for the digital power-down sequence. + pub digital_power_down_wait_cycle: u8, + /// Number of cycles to wait for the digital power-up sequence. + pub digital_power_up_wait_cycle: u8, +} + +/// Parameters for high-power and low-power system configurations during sleep +/// modes. +#[derive(Clone, Copy)] +// pmu_hp_lp_param_t +pub struct HpLpParam { + /// Union of two u16 variants + pub xtal_stable_wait_cycle: u16, +} + +/// Configuration of parameters for sleep modes +#[derive(Clone, Copy)] +// pmu_sleep_param_config_t +pub struct ParamSleepConfig { + /// Configuration of high-power system parameters. + pub hp_sys: HpParam, + /// Configuration of low-power system parameters. + pub lp_sys: LpParam, + /// Shared configuration parameters for high-power and low-power systems. + pub hp_lp: HpLpParam, +} +impl ParamSleepConfig { + const PMU_SLEEP_PARAM_CONFIG_DEFAULT: Self = Self { + hp_sys: HpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 2419, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + modem_wakeup_wait_cycle: 20700, + pll_stable_wait_cycle: 2, + + digital_power_down_wait_cycle: 0, + modify_icg_cntl_wait_cycle: 0, + switch_icg_cntl_wait_cycle: 0, + }, + lp_sys: LpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 23, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + + digital_power_down_wait_cycle: 0, + }, + hp_lp: HpLpParam { + xtal_stable_wait_cycle: 30, + }, + }; + + fn apply(&self) { + // pmu_sleep_param_init + + unsafe { + pmu().slp_wakeup_cntl3().modify(|_, w| { + w.hp_min_slp_val() // pmu_ll_hp_set_min_sleep_cycle + .bits(self.hp_sys.min_slp_slow_clk_cycle) + .lp_min_slp_val() // pmu_ll_lp_set_min_sleep_cycle + .bits(self.lp_sys.min_slp_slow_clk_cycle) + }); + + pmu().slp_wakeup_cntl7().modify(|_, w| { + w.ana_wait_target() // pmu_ll_hp_set_analog_wait_target_cycle + .bits(self.hp_sys.analog_wait_target_cycle) + }); + + pmu().power_wait_timer0().modify(|_, w| { + w.dg_hp_wait_timer() // pmu_ll_hp_set_digital_power_supply_wait_cycle + .bits(self.hp_sys.digital_power_supply_wait_cycle) + .dg_hp_powerup_timer() // pmu_ll_hp_set_digital_power_up_wait_cycle + .bits(self.hp_sys.digital_power_up_wait_cycle) + }); + + pmu().power_wait_timer1().modify(|_, w| { + w.dg_lp_wait_timer() // pmu_ll_lp_set_digital_power_supply_wait_cycle + .bits(self.lp_sys.digital_power_supply_wait_cycle) + .dg_lp_powerup_timer() // pmu_ll_lp_set_digital_power_up_wait_cycle + .bits(self.lp_sys.digital_power_up_wait_cycle) + }); + + pmu().slp_wakeup_cntl5().modify(|_, w| { + w.lp_ana_wait_target() // pmu_ll_lp_set_analog_wait_target_cycle + .bits(self.lp_sys.analog_wait_target_cycle) + .modem_wait_target() // pmu_ll_hp_set_modem_wakeup_wait_cycle + .bits(self.hp_sys.modem_wakeup_wait_cycle) + }); + pmu().power_ck_wait_cntl().modify(|_, w| { + w.wait_xtl_stable() // pmu_ll_hp_set_xtal_stable_wait_cycle + .bits(self.hp_lp.xtal_stable_wait_cycle) + .wait_pll_stable() // pmu_ll_hp_set_pll_stable_wait_cycle + .bits(self.hp_sys.pll_stable_wait_cycle) + }); + } + } + + fn defaults(config: SleepTimeConfig, pd_flags: PowerDownFlags, pd_xtal: bool) -> Self { + let mut param = Self::PMU_SLEEP_PARAM_CONFIG_DEFAULT; + + // pmu_sleep_param_config_default + param.hp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(MachineConstants::HP_MIN_SLP_TIME_US) as u8; + param.hp_sys.analog_wait_target_cycle = + config.us_to_fastclk(MachineConstants::HP_ANALOG_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_POWER_UP_WAIT_TIME_US) as u16; + param.hp_sys.pll_stable_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_PLL_WAIT_STABLE_TIME_US) as u16; + + let hw_wait_time_us = config.pmu_sleep_calculate_hw_wait_time(pd_flags); + + let modem_wakeup_wait_time_us = (config.sleep_time_adjustment + + MachineConstants::MODEM_STATE_SKIP_TIME_US + + MachineConstants::HP_REGDMA_RF_ON_WORK_TIME_US) + .saturating_sub(hw_wait_time_us); + param.hp_sys.modem_wakeup_wait_cycle = config.us_to_fastclk(modem_wakeup_wait_time_us); + + param.lp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(MachineConstants::LP_MIN_SLP_TIME_US) as u8; + param.lp_sys.analog_wait_target_cycle = + config.us_to_slowclk(MachineConstants::LP_ANALOG_WAIT_TIME_US) as u8; + param.lp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(MachineConstants::LP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.lp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(MachineConstants::LP_POWER_UP_WAIT_TIME_US) as u8; + + // This looks different from esp-idf but it is the same: + // Both `xtal_stable_wait_cycle` and `xtal_stable_wait_slow_clk_cycle` are + // u16 variants of the same union + param.hp_lp.xtal_stable_wait_cycle = if pd_xtal { + config.us_to_slowclk(MachineConstants::LP_XTAL_WAIT_STABLE_TIME_US) as u16 + } else { + config.us_to_fastclk(MachineConstants::HP_XTAL_WAIT_STABLE_TIME_US) as u16 + }; + + param + } +} + +#[derive(Clone, Copy)] +struct SleepTimeConfig { + sleep_time_adjustment: u32, + // TODO: esp-idf does some calibration here to determine slowclk_period + slowclk_period: u32, + fastclk_period: u32, +} + +const CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ: u32 = 160; + +impl SleepTimeConfig { + const RTC_CLK_CAL_FRACT: u32 = 19; + + fn rtc_clk_cal_fast(slowclk_cycles: u32) -> u32 { + RtcClock::calibrate(Timg0CalibrationClockConfig::RcFastDivClk, slowclk_cycles) + } + + fn new(_deep: bool) -> Self { + // https://github.com/espressif/esp-idf/commit/e1d24ebd7f43c7c7ded183bc8800b20af3bf014b + + // Calibrate rtc slow clock + // TODO: do an actual calibration instead of a read + let slowclk_period = unsafe { lp_aon().store1().read().data().bits() }; + + // Calibrate rtc fast clock, only PMU supported chips sleep process is needed. + const FAST_CLK_SRC_CAL_CYCLES: u32 = 2048; + let fastclk_period = Self::rtc_clk_cal_fast(FAST_CLK_SRC_CAL_CYCLES); + + Self { + sleep_time_adjustment: 0, + slowclk_period, + fastclk_period, + } + } + + fn light_sleep(pd_flags: PowerDownFlags) -> Self { + const LIGHT_SLEEP_TIME_OVERHEAD_US: u32 = 56; + + let mut this = Self::new(false); + + let sw = LIGHT_SLEEP_TIME_OVERHEAD_US; // TODO + let hw = this.pmu_sleep_calculate_hw_wait_time(pd_flags); + + this.sleep_time_adjustment = sw + hw; + + this + } + + fn deep_sleep() -> Self { + let mut this = Self::new(true); + + this.sleep_time_adjustment = 250 + 100 * 240 / CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; + + this + } + + fn us_to_slowclk(&self, us: u32) -> u32 { + (us << Self::RTC_CLK_CAL_FRACT) / self.slowclk_period + } + + fn slowclk_to_us(&self, rtc_cycles: u32) -> u32 { + (rtc_cycles * self.slowclk_period) >> Self::RTC_CLK_CAL_FRACT + } + + fn us_to_fastclk(&self, us: u32) -> u32 { + (us << Self::RTC_CLK_CAL_FRACT) / self.fastclk_period + } + + fn pmu_sleep_calculate_hw_wait_time(&self, pd_flags: PowerDownFlags) -> u32 { + // LP core hardware wait time, microsecond + let lp_wakeup_wait_time_us = self.slowclk_to_us(MachineConstants::LP_WAKEUP_WAIT_CYCLE); + let lp_clk_switch_time_us = self.slowclk_to_us(MachineConstants::LP_CLK_SWITCH_CYCLE); + let lp_clk_power_on_wait_time_us = if pd_flags.pd_xtal() { + MachineConstants::LP_XTAL_WAIT_STABLE_TIME_US + } else { + self.slowclk_to_us(MachineConstants::LP_CLK_POWER_ON_WAIT_CYCLE) + }; + + let lp_hw_wait_time_us = MachineConstants::LP_MIN_SLP_TIME_US + + MachineConstants::LP_ANALOG_WAIT_TIME_US + + lp_clk_power_on_wait_time_us + + lp_wakeup_wait_time_us + + lp_clk_switch_time_us + + MachineConstants::LP_POWER_SUPPLY_WAIT_TIME_US + + MachineConstants::LP_POWER_UP_WAIT_TIME_US; + + // HP core hardware wait time, microsecond + let hp_digital_power_up_wait_time_us = MachineConstants::HP_POWER_SUPPLY_WAIT_TIME_US + + MachineConstants::HP_POWER_UP_WAIT_TIME_US; + let hp_regdma_wait_time_us = u32::max( + MachineConstants::HP_REGDMA_S2M_WORK_TIME_US + + MachineConstants::HP_REGDMA_M2A_WORK_TIME_US, + MachineConstants::HP_REGDMA_S2A_WORK_TIME_US, + ); + let hp_clock_wait_time_us = MachineConstants::HP_XTAL_WAIT_STABLE_TIME_US + + MachineConstants::HP_PLL_WAIT_STABLE_TIME_US; + + let hp_hw_wait_time_us = MachineConstants::HP_ANALOG_WAIT_TIME_US + + u32::max( + hp_digital_power_up_wait_time_us + hp_regdma_wait_time_us, + hp_clock_wait_time_us, + ); + + #[rustfmt::skip] // ASCII art + // When the SOC wakeup (lp timer or GPIO wakeup) and Modem wakeup (Beacon wakeup) complete, + // the soc wakeup will be delayed until the RF is turned on in Modem state. + // + // modem wakeup TBTT, RF on by HW + // | | + // \|/ \|/ + // PMU_HP_ACTIVE /------ + // PMU_HP_MODEM /------------////////////////// + // PMU_HP_SLEEP ----------------------////////////////// + // /|\ /|\ /|\ /|\ /|\ /|\ + // |<- some hw wait ->| | | |<- M2A switch ->| + // | slow cycles & | soc wakeup | | + // | FOSC cycles |<- S2M switch ->| | + // | | + // |<-- PMU guard time, also the maximum time for the SOC -->| + // | wake-up delay | + // + const CONFIG_ESP_RADIO_ENHANCED_LIGHT_SLEEP: bool = true; + + let (rf_on_protect_time_us, sync_time_us) = if CONFIG_ESP_RADIO_ENHANCED_LIGHT_SLEEP { + ( + MachineConstants::HP_REGDMA_RF_ON_WORK_TIME_US, + MachineConstants::HP_CLOCK_DOMAIN_SYNC_TIME_US, + ) + } else { + (0, 0) + }; + + lp_hw_wait_time_us + hp_hw_wait_time_us + sync_time_us + rf_on_protect_time_us + } +} + +/// Configuration for the RTC sleep behavior. +#[derive(Clone, Copy)] +// pmu_sleep_config_t + deep sleep flag + pd flags +pub struct RtcSleepConfig { + /// Deep Sleep flag + pub deep: bool, + /// Power Down flags + pub pd_flags: PowerDownFlags, +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + // from pmu_sleep_param_config_default + // sleep flags will be applied by wakeup methods and apply + + Self { + deep: false, + pd_flags: PowerDownFlags(0), + } + } +} + +unsafe fn pmu<'a>() -> &'a esp32c6::pmu::RegisterBlock { + unsafe { &*esp32c6::PMU::ptr() } +} + +unsafe fn lp_aon<'a>() -> &'a esp32c6::lp_aon::RegisterBlock { + unsafe { &*esp32c6::LP_AON::ptr() } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Power domains to be powered down during sleep + pub struct PowerDownFlags(u32); + + /// Controls the power-down status of the top power domain. + pub u32, pd_top , set_pd_top : 0; + /// Controls the power-down status of the VDD_SDIO power domain. + pub u32, pd_vddsdio , set_pd_vddsdio : 1; + /// Controls the power-down status of the modem power domain. + pub u32, pd_modem , set_pd_modem : 2; + /// Controls the power-down status of the high-performance peripheral power domain. + pub u32, pd_hp_periph, set_pd_hp_periph: 3; + /// Controls the power-down status of the CPU power domain. + pub u32, pd_cpu , set_pd_cpu : 4; + /// Controls the power-down status of the high-performance always-on domain. + pub u32, pd_hp_aon , set_pd_hp_aon : 5; + /// Controls the power-down status of memory group 0. + pub u32, pd_mem_g0 , set_pd_mem_g0 : 6; + /// Controls the power-down status of memory group 1. + pub u32, pd_mem_g1 , set_pd_mem_g1 : 7; + /// Controls the power-down status of memory group 2. + pub u32, pd_mem_g2 , set_pd_mem_g2 : 8; + /// Controls the power-down status of memory group 3. + pub u32, pd_mem_g3 , set_pd_mem_g3 : 9; + /// Controls the power-down status of the crystal oscillator. + pub u32, pd_xtal , set_pd_xtal : 10; + /// Controls the power-down status of the fast RC oscillator. + pub u32, pd_rc_fast , set_pd_rc_fast : 11; + /// Controls the power-down status of the 32kHz crystal oscillator. + pub u32, pd_xtal32k , set_pd_xtal32k : 12; + /// Controls the power-down status of the 32kHz RC oscillator. + pub u32, pd_rc32k , set_pd_rc32k : 13; + /// Controls the power-down status of the low-power peripheral domain. + pub u32, pd_lp_periph, set_pd_lp_periph: 14; +} + +impl PowerDownFlags { + /// Checks whether all memory groups (G0, G1, G2, G3) are powered down. + pub fn pd_mem(self) -> bool { + self.pd_mem_g0() && self.pd_mem_g1() && self.pd_mem_g2() && self.pd_mem_g3() + } + + /// Sets the power-down status for all memory groups (G0, G1, G2, G3) at + /// once. + pub fn set_pd_mem(&mut self, value: bool) { + self.set_pd_mem_g0(value); + self.set_pd_mem_g1(value); + self.set_pd_mem_g2(value); + self.set_pd_mem_g3(value); + } +} + +// Constants defined in `PMU_SLEEP_MC_DEFAULT()` +struct MachineConstants; +impl MachineConstants { + const LP_MIN_SLP_TIME_US: u32 = 450; + const LP_WAKEUP_WAIT_CYCLE: u32 = 4; + const LP_ANALOG_WAIT_TIME_US: u32 = 154; + const LP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + const LP_CLK_SWITCH_CYCLE: u32 = 1; + const LP_CLK_POWER_ON_WAIT_CYCLE: u32 = 1; + const LP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + const LP_POWER_UP_WAIT_TIME_US: u32 = 2; + + const HP_MIN_SLP_TIME_US: u32 = 450; + const HP_CLOCK_DOMAIN_SYNC_TIME_US: u32 = 150; + const HP_SYSTEM_DFS_UP_WORK_TIME_US: u32 = 124; + const HP_ANALOG_WAIT_TIME_US: u32 = 154; + const HP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + const HP_POWER_UP_WAIT_TIME_US: u32 = 2; + const HP_REGDMA_S2M_WORK_TIME_US: u32 = 172; + const HP_REGDMA_S2A_WORK_TIME_US: u32 = 480; + const HP_REGDMA_M2A_WORK_TIME_US: u32 = 278; + // Unused, but defined in esp-idf. May be needed later. + // const HP_REGDMA_A2S_WORK_TIME_US: u32 = 382; + const HP_REGDMA_RF_ON_WORK_TIME_US: u32 = 70; + // Unused, but defined in esp-idf. May be needed later. + // const HP_REGDMA_RF_OFF_WORK_TIME_US: u32 = 23; + const HP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + const HP_PLL_WAIT_STABLE_TIME_US: u32 = 1; + + const MODEM_STATE_SKIP_TIME_US: u32 = Self::HP_REGDMA_M2A_WORK_TIME_US + + Self::HP_SYSTEM_DFS_UP_WORK_TIME_US + + Self::LP_MIN_SLP_TIME_US; +} + +impl RtcSleepConfig { + /// Returns whether the device is in deep sleep mode. + pub fn deep_slp(&self) -> bool { + self.deep + } + + /// Configures the device for deep sleep mode with ultra-low power settings. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + Self { + deep: true, + ..Self::default() + } + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + Self::wake_io_reset(); + } + + fn wake_io_reset() { + // loosely based on esp_deep_sleep_wakeup_io_reset + Ext1WakeupSource::wake_io_reset(); + } + + /// Finalize power-down flags, apply configuration based on the flags. + pub(crate) fn apply(&mut self) { + if self.deep { + // force-disable certain power domains + self.pd_flags.set_pd_top(true); + self.pd_flags.set_pd_vddsdio(true); + self.pd_flags.set_pd_modem(true); + self.pd_flags.set_pd_hp_periph(true); + self.pd_flags.set_pd_cpu(true); + self.pd_flags.set_pd_mem(true); + self.pd_flags.set_pd_xtal(true); + self.pd_flags.set_pd_hp_aon(true); + self.pd_flags.set_pd_lp_periph(true); + self.pd_flags.set_pd_xtal32k(true); + self.pd_flags.set_pd_rc32k(true); + self.pd_flags.set_pd_rc_fast(true); + } + } + + /// Configures wakeup options and enters sleep. + /// + /// This function does not return if deep sleep is requested. + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + const PMU_EXT0_WAKEUP_EN: u32 = 1 << 0; + const PMU_EXT1_WAKEUP_EN: u32 = 1 << 1; + const PMU_GPIO_WAKEUP_EN: u32 = 1 << 2; + const PMU_LP_TIMER_WAKEUP_EN: u32 = 1 << 4; + const PMU_WIFI_SOC_WAKEUP_EN: u32 = 1 << 5; + const PMU_UART0_WAKEUP_EN: u32 = 1 << 6; + const PMU_UART1_WAKEUP_EN: u32 = 1 << 7; + const PMU_SDIO_WAKEUP_EN: u32 = 1 << 8; + const PMU_BLE_SOC_WAKEUP_EN: u32 = 1 << 10; + const PMU_LP_CORE_WAKEUP_EN: u32 = 1 << 11; + const PMU_USB_WAKEUP_EN: u32 = 1 << 14; + const MODEM_REJECT: u32 = 1 << 16; + + const RTC_SLEEP_REJECT_MASK: u32 = PMU_EXT0_WAKEUP_EN + | PMU_EXT1_WAKEUP_EN + | PMU_GPIO_WAKEUP_EN + | PMU_LP_TIMER_WAKEUP_EN + | PMU_WIFI_SOC_WAKEUP_EN + | PMU_UART0_WAKEUP_EN + | PMU_UART1_WAKEUP_EN + | PMU_SDIO_WAKEUP_EN + | PMU_BLE_SOC_WAKEUP_EN + | PMU_LP_CORE_WAKEUP_EN + | PMU_USB_WAKEUP_EN; + + let wakeup_mask = wakeup_triggers.0 as u32; + let reject_mask = if self.deep { + 0 + } else { + // TODO: MODEM_REJECT if s_sleep_modem.wifi.phy_link != NULL + let reject_mask = RTC_SLEEP_REJECT_MASK | MODEM_REJECT; + wakeup_mask & reject_mask + }; + + let cpu_freq_config = ClockTree::with(|clocks| { + let cpu_freq_config = SavedClockConfig::save(clocks); + crate::soc::clocks::configure_soc_root_clk( + clocks, + crate::soc::clocks::SocRootClkConfig::Xtal, + ); + cpu_freq_config + }); + + // pmu_sleep_config_default + pmu_sleep_init. + + let power = PowerSleepConfig::defaults(self.pd_flags); + power.apply(); + + // Needs to happen after rtc_clk_cpu_freq_set_xtal + let config = if self.deep { + SleepTimeConfig::deep_sleep() + } else { + SleepTimeConfig::light_sleep(self.pd_flags) + }; + + let mut param = + ParamSleepConfig::defaults(config, self.pd_flags, power.hp_sys.xtal.xpd_xtal()); + + if self.deep { + const PMU_LP_ANALOG_WAIT_TARGET_TIME_DSLP_US: u32 = 500; + param.lp_sys.analog_wait_target_cycle = + config.us_to_slowclk(PMU_LP_ANALOG_WAIT_TARGET_TIME_DSLP_US) as u8; + + AnalogSleepConfig::defaults_deep_sleep().apply(); + } else { + AnalogSleepConfig::defaults_light_sleep(self.pd_flags).apply(); + DigitalSleepConfig::defaults_light_sleep(self.pd_flags).apply(); + } + + param.apply(); + + // like esp-idf pmu_sleep_start() + + unsafe { + // lp_aon_hal_inform_wakeup_type - tells ROM which wakeup stub to run + lp_aon() + .store9() + .modify(|r, w| w.bits(r.bits() & !0x01 | self.deep as u32)); + + // pmu_ll_hp_set_wakeup_enable + pmu().slp_wakeup_cntl2().write(|w| w.bits(wakeup_mask)); + + // pmu_ll_hp_set_reject_enable + pmu().slp_wakeup_cntl1().modify(|_, w| { + w.slp_reject_en() + .bit(true) + .sleep_reject_ena() + .bits(reject_mask) + }); + + // pmu_ll_hp_clear_reject_cause + pmu() + .slp_wakeup_cntl4() + .write(|w| w.slp_reject_cause_clr().bit(true)); + + pmu().int_clr().write(|w| { + w.sw() // pmu_ll_hp_clear_sw_intr_status + .clear_bit_by_one() + .soc_sleep_reject() // pmu_ll_hp_clear_reject_intr_status + .clear_bit_by_one() + .soc_wakeup() // pmu_ll_hp_clear_wakeup_intr_status + .clear_bit_by_one() + }); + + // misc_modules_sleep_prepare + + // TODO: IDF-7370 + #[cfg(not(soc_has_pmu))] + if !(self.deep && wakeup_triggers.touch) { + APB_SARADC::regs() + .ctrl() + .modify(|_, w| w.saradc2_pwdet_drv().bit(false)); + } + + // Start entry into sleep mode + + // pmu_ll_hp_set_sleep_enable + pmu().slp_wakeup_cntl0().write(|w| w.sleep_req().bit(true)); + + // In pd_cpu lightsleep and deepsleep mode, we never get here + loop { + let int_raw = pmu().int_raw().read(); + if int_raw.soc_wakeup().bit_is_set() || int_raw.soc_sleep_reject().bit_is_set() { + break; + } + } + } + + // esp-idf returns if the sleep was rejected, we don't return anything + + ClockTree::with(|clocks| { + cpu_freq_config.restore(clocks); + }); + } + + /// Cleans up after sleep + pub(crate) fn finish_sleep(&self) { + // like esp-idf pmu_sleep_finish() + // In "pd_cpu lightsleep" and "deepsleep" modes we never get here + + // esp-idf returns if the sleep was rejected, we do nothing + // pmu_ll_hp_is_sleep_reject(PMU_instance()->hal->dev) + + Self::wake_io_reset(); + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32h2.rs b/esp-hal/src/rtc_cntl/sleep/esp32h2.rs new file mode 100644 index 00000000000..7052d43cc45 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32h2.rs @@ -0,0 +1,847 @@ +use core::ops::Not; + +use crate::{ + clock::RtcClock, + gpio::{AnyPin, Input, InputConfig, Pull, RtcPin}, + peripherals::APB_SARADC, + rtc_cntl::{ + Rtc, + rtc::{HpSysCntlReg, HpSysPower, LpSysPower}, + sleep::{Ext1WakeupSource, TimerWakeupSource, WakeSource, WakeTriggers, WakeupLevel}, + }, + soc::clocks::{ClockTree, CpuClkConfig, HpRootClkConfig, Timg0CalibrationClockConfig}, +}; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + + let lp_timer = unsafe { &*esp32h2::LP_TIMER::ptr() }; + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + lp_timer.tar0_high().write(|w| { + w.main_timer_tar_high0() + .bits(((time_in_ticks >> 32) & 0xffff) as u16) + }); + lp_timer.tar0_low().write(|w| { + w.main_timer_tar_low0() + .bits((time_in_ticks & 0xffffffff) as u32) + }); + lp_timer + .int_clr() + .write(|w| w.soc_wakeup_int_clr().set_bit()); + lp_timer + .tar0_high() + .modify(|_, w| w.main_timer_tar_en0().set_bit()); + } + } +} + +impl Ext1WakeupSource<'_, '_> { + /// Returns the currently configured wakeup pins. + fn wakeup_pins() -> u8 { + unsafe { lp_aon().ext_wakeup_cntl().read().ext_wakeup_sel().bits() } + } + + /// Resets the pins that had been configured as wakeup trigger to their default state. + fn wake_io_reset() { + fn uninit_pin(pin: impl RtcPin, wakeup_pins: u8) { + if wakeup_pins & (1 << pin.rtc_number()) != 0 { + pin.rtcio_pad_hold(false); + pin.degrade().init_gpio(); + } + } + + let wakeup_pins = Ext1WakeupSource::wakeup_pins(); + for_each_lp_function! { + (($_rtc:ident, LP_GPIOn, $n:literal), $gpio:ident) => { + uninit_pin(unsafe { $crate::peripherals::$gpio::steal() }, wakeup_pins); + }; + } + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_ext1(true); + sleep_config.need_pd_top = true; + + // ext1_wakeup_prepare + let mut pins = self.pins.borrow_mut(); + let mut pin_mask = 0u8; + let mut level_mask = 0u8; + for (pin, level) in pins.iter_mut() { + pin_mask |= 1 << pin.rtc_number(); + level_mask |= match level { + WakeupLevel::High => 1 << pin.rtc_number(), + WakeupLevel::Low => 0, + }; + + pin.rtcio_pad_hold(true); + Input::new( + unsafe { AnyPin::steal(pin.number()) }, + InputConfig::default().with_pull(match level { + WakeupLevel::High => Pull::Down, + WakeupLevel::Low => Pull::Up, + }), + ); + } + + unsafe { + // clear previous wakeup status + lp_aon() + .ext_wakeup_cntl() + .modify(|_, w| w.ext_wakeup_status_clr().set_bit()); + + // set pin + level register fields + lp_aon().ext_wakeup_cntl().modify(|r, w| { + w.ext_wakeup_sel() + .bits(r.ext_wakeup_sel().bits() | pin_mask) + .ext_wakeup_lv() + .bits(r.ext_wakeup_lv().bits() & !pin_mask | level_mask) + }); + } + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // reset GPIOs to default state + let mut pins = self.pins.borrow_mut(); + for (pin, _level) in pins.iter_mut() { + pin.rtcio_pad_hold(false); + unsafe { AnyPin::steal(pin.number()) }.init_gpio(); + } + } +} + +/// Configuration for controlling the behavior during sleep modes. +#[derive(Clone, Copy)] +// Note: from all the fields defined in ESP-IDF's `pmu_sleep_analog_config_t` +// the only one documented in Hardware Technical Reference Manual is `hp_sys.analog.xpd` +// (corresponds to `PMU_HP_ACTIVE_HP_REGULATOR_XPD` in the Manual). Therefore only this +// field is stored as bool without any nested structures. +pub struct AnalogSleepConfig { + /// High-power system configuration. + pub regulator_xpd: bool, +} + +impl AnalogSleepConfig { + fn defaults_deep_sleep() -> Self { + Self { + // PMU_SLEEP_ANALOG_DSLP_CONFIG_DEFAULT + regulator_xpd: false, + } + } + + fn defaults_light_sleep() -> Self { + Self { + // PMU_SLEEP_ANALOG_LSLP_CONFIG_DEFAULT + regulator_xpd: true, + } + } + + fn apply(&self) { + // based on pmu_sleep_analog_init, with undocumented registers omitted + unsafe { + pmu().hp_sleep_hp_regulator0().modify(|_, w| { + w.hp_sleep_hp_regulator_xpd() // pmu_ll_hp_set_regulator_xpd + .bit(self.regulator_xpd) + }); + } + } +} + +/// Configuration for controlling the behavior of digital peripherals during +/// sleep modes. +#[derive(Clone, Copy)] +// pmu_sleep_digital_config_t +pub struct DigitalSleepConfig { + /// High-power system control register configuration. + pub syscntl: HpSysCntlReg, + /// ICG function control flags. + pub icg_func: u32, + /// Indicates whether deep sleep mode is requested. + pub deep_sleep: bool, +} + +impl DigitalSleepConfig { + fn defaults_light_sleep(pd_flags: PowerDownFlags) -> Self { + Self { + // PMU_SLEEP_DIGITAL_LSLP_CONFIG_DEFAULT + syscntl: { + let mut cfg = HpSysCntlReg::default(); + + cfg.set_dig_pad_slp_sel(pd_flags.pd_top().not()); + + cfg + }, + icg_func: 0xffff_ffff, // TODO: ESP-IDF determines this using get_sleep_clock_icg_flags + deep_sleep: false, + } + } + + fn defaults_deep_sleep() -> Self { + Self { + // PMU_SLEEP_DIGITAL_DSLP_CONFIG_DEFAULT + syscntl: HpSysCntlReg::default(), + icg_func: 0, + deep_sleep: true, + } + } + + fn apply(&self) { + // pmu_sleep_digital_init + unsafe { + pmu().hp_sleep_sysclk().modify(|_, w| { + w.hp_sleep_icg_sys_clock_en().bit(self.icg_func != 0) // pmu_ll_hp_set_icg_sysclk_enable + }); + pmu().hp_sleep_icg_hp_func().modify(|_, w| { + w.hp_sleep_dig_icg_func_en().bits(self.icg_func) // pmu_ll_hp_set_icg_func + }); + if !self.deep_sleep { + pmu().hp_sleep_hp_sys_cntl().modify(|_, w| { + w.hp_sleep_dig_pad_slp_sel() + .bit(self.syscntl.dig_pad_slp_sel()) // pmu_ll_hp_set_dig_pad_slp_sel + }); + } + }; + } +} + +/// Configuration for controlling the power settings of high-power and low-power +/// systems during sleep modes. +#[derive(Clone, Copy)] +// pmu_sleep_power_config_t +pub struct PowerSleepConfig { + /// Power configuration for the high-power system during sleep. + pub hp_sys: HpSysPower, + /// Power configuration for the low-power system when it is active. + pub lp_sys_active: LpSysPower, + /// Power configuration for the low-power system when it is in sleep mode. + pub lp_sys_sleep: LpSysPower, +} + +impl PowerSleepConfig { + fn defaults(pd_flags: PowerDownFlags) -> Self { + let mut this = Self { + hp_sys: HpSysPower::default(), + lp_sys_active: LpSysPower::default(), + lp_sys_sleep: LpSysPower::default(), + }; + this.apply_flags(pd_flags); + this + } + + fn apply_flags(&mut self, pd_flags: PowerDownFlags) { + // PMU_SLEEP_POWER_CONFIG_DEFAULT + self.hp_sys + .dig_power + .set_vdd_spi_pd_en(pd_flags.pd_vddsdio()); + self.hp_sys.dig_power.set_modem_pd_en(pd_flags.pd_modem()); + self.hp_sys.dig_power.set_cpu_pd_en(pd_flags.pd_cpu()); + self.hp_sys.dig_power.set_top_pd_en(pd_flags.pd_top()); + + self.hp_sys.clk.set_xpd_bbpll(false); + + self.hp_sys.xtal.set_xpd_xtal(pd_flags.pd_xtal().not()); + + self.lp_sys_active.clk_power.set_xpd_xtal32k(true); + self.lp_sys_active.clk_power.set_xpd_fosc(true); + + self.lp_sys_sleep.dig_power.set_mem_dslp(true); + self.lp_sys_sleep.dig_power.set_vddbat_mode(0); + self.lp_sys_sleep.dig_power.set_bod_source_sel(true); + + self.lp_sys_sleep + .clk_power + .set_xpd_xtal32k(pd_flags.pd_xtal32k().not()); + self.lp_sys_sleep + .clk_power + .set_xpd_fosc(pd_flags.pd_rc_fast().not()); + + self.lp_sys_sleep + .xtal + .set_xpd_xtal(pd_flags.pd_xtal().not()); + } + + fn apply(&self) { + // pmu_sleep_power_init + unsafe { + // HP SLEEP (hp_sleep_*) + pmu() + .hp_sleep_dig_power() + .modify(|_, w| w.bits(self.hp_sys.dig_power.0)); + pmu() + .hp_sleep_hp_ck_power() + .modify(|_, w| w.bits(self.hp_sys.clk.0)); + pmu() + .hp_sleep_xtal() + .modify(|_, w| w.hp_sleep_xpd_xtal().bit(self.hp_sys.xtal.xpd_xtal())); + + // LP ACTIVE (hp_sleep_lp_*) + pmu() + .hp_sleep_lp_dig_power() + .modify(|_, w| w.bits(self.lp_sys_active.dig_power.0)); + pmu() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.bits(self.lp_sys_active.clk_power.0)); + + // LP SLEEP (lp_sleep_*) + pmu() + .lp_sleep_lp_dig_power() + .modify(|_, w| w.bits(self.lp_sys_sleep.dig_power.0)); + pmu() + .lp_sleep_lp_ck_power() + .modify(|_, w| w.bits(self.lp_sys_sleep.clk_power.0)); + pmu() + .lp_sleep_xtal() + .modify(|_, w| w.lp_sleep_xpd_xtal().bit(self.lp_sys_sleep.xtal.xpd_xtal())); + } + } +} + +/// Parameters for high-power system configurations during sleep modes. +#[derive(Clone, Copy)] +// pmu_hp_param_t +pub struct HpParam { + /// Number of cycles to wait for the modem to wake up. + pub modem_wakeup_wait_cycle: u32, + /// Number of cycles to wait for the analog component stabilization. + pub analog_wait_target_cycle: u16, + /// Number of cycles to wait for the digital power-down sequence. + pub digital_power_down_wait_cycle: u16, + /// Number of cycles to wait for the digital power supply to stabilize. + pub digital_power_supply_wait_cycle: u16, + /// Number of cycles to wait for the digital power-up sequence. + pub digital_power_up_wait_cycle: u16, + /// Number of cycles to wait for the PLL to stabilize. + pub pll_stable_wait_cycle: u16, + /// Number of cycles to wait for modifying the ICG control. + pub modify_icg_cntl_wait_cycle: u8, + /// Number of cycles to wait for switching the ICG control. + pub switch_icg_cntl_wait_cycle: u8, + /// Minimum sleep time measured in slow clock cycles. + pub min_slp_slow_clk_cycle: u8, +} + +/// Parameters for low-power system configurations during sleep modes. +#[derive(Clone, Copy)] +// pmu_lp_param_t +pub struct LpParam { + /// Number of cycles to wait for the digital power supply to stabilize. + pub digital_power_supply_wait_cycle: u16, + /// Minimum sleep time measured in slow clock cycles. + pub min_slp_slow_clk_cycle: u8, + /// Number of cycles to wait for the analog component stabilization. + pub analog_wait_target_cycle: u8, + /// Number of cycles to wait for the digital power-down sequence. + pub digital_power_down_wait_cycle: u8, + /// Number of cycles to wait for the digital power-up sequence. + pub digital_power_up_wait_cycle: u8, +} + +/// Parameters for high-power and low-power system configurations during sleep +/// modes. +#[derive(Clone, Copy)] +// pmu_hp_lp_param_t +pub struct HpLpParam { + /// Union of two u16 variants + pub xtal_stable_wait_cycle: u16, +} + +/// Configuration of parameters for sleep modes +#[derive(Clone, Copy)] +// pmu_sleep_param_config_t +pub struct ParamSleepConfig { + /// Configuration of high-power system parameters. + pub hp_sys: HpParam, + /// Configuration of low-power system parameters. + pub lp_sys: LpParam, + /// Shared configuration parameters for high-power and low-power systems. + pub hp_lp: HpLpParam, +} +impl ParamSleepConfig { + const PMU_SLEEP_PARAM_CONFIG_DEFAULT: Self = Self { + hp_sys: HpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 1700, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + modem_wakeup_wait_cycle: 0, + pll_stable_wait_cycle: 2, + + digital_power_down_wait_cycle: 0, + modify_icg_cntl_wait_cycle: 0, + switch_icg_cntl_wait_cycle: 0, + }, + lp_sys: LpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 15, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + + digital_power_down_wait_cycle: 0, + }, + hp_lp: HpLpParam { + xtal_stable_wait_cycle: 30, + }, + }; + + fn apply(&self) { + // pmu_sleep_param_init + unsafe { + pmu().slp_wakeup_cntl3().modify(|_, w| { + w.hp_min_slp_val() // pmu_ll_hp_set_min_sleep_cycle + .bits(self.hp_sys.min_slp_slow_clk_cycle) + .lp_min_slp_val() // pmu_ll_lp_set_min_sleep_cycle + .bits(self.lp_sys.min_slp_slow_clk_cycle) + }); + + pmu().slp_wakeup_cntl7().modify(|_, w| { + w.ana_wait_target() // pmu_ll_hp_set_analog_wait_target_cycle + .bits(self.hp_sys.analog_wait_target_cycle) + }); + + pmu().slp_wakeup_cntl5().modify(|_, w| { + w.lp_ana_wait_target() // pmu_ll_lp_set_analog_wait_target_cycle + .bits(self.lp_sys.analog_wait_target_cycle) + }); + + pmu().power_wait_timer0().modify(|_, w| { + w.dg_hp_wait_timer() // pmu_ll_hp_set_digital_power_supply_wait_cycle + .bits(self.hp_sys.digital_power_supply_wait_cycle) + .dg_hp_powerup_timer() // pmu_ll_hp_set_digital_power_up_wait_cycle + .bits(self.hp_sys.digital_power_up_wait_cycle) + }); + + pmu().power_wait_timer1().modify(|_, w| { + w.dg_lp_wait_timer() // pmu_ll_lp_set_digital_power_supply_wait_cycle + .bits(self.lp_sys.digital_power_supply_wait_cycle) + .dg_lp_powerup_timer() // pmu_ll_lp_set_digital_power_up_wait_cycle + .bits(self.lp_sys.digital_power_up_wait_cycle) + }); + + pmu().power_ck_wait_cntl().modify(|_, w| { + w.wait_xtl_stable() // pmu_ll_set_xtal_stable_wait_cycle + .bits(self.hp_lp.xtal_stable_wait_cycle) + .wait_pll_stable() // pmu_ll_set_pll_stable_wait_cycle + .bits(self.hp_sys.pll_stable_wait_cycle) + }); + } + } + + fn defaults(config: SleepTimeConfig, pd_xtal: bool) -> Self { + let mut param = Self::PMU_SLEEP_PARAM_CONFIG_DEFAULT; + + // pmu_sleep_param_config_default + param.hp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(machine_constants::HP_MIN_SLP_TIME_US) as u8; + param.hp_sys.analog_wait_target_cycle = + config.us_to_fastclk(machine_constants::HP_ANALOG_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(machine_constants::HP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(machine_constants::HP_POWER_UP_WAIT_TIME_US) as u16; + param.hp_sys.pll_stable_wait_cycle = + config.us_to_fastclk(machine_constants::HP_PLL_WAIT_STABLE_TIME_US) as u16; + + param.lp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(machine_constants::LP_MIN_SLP_TIME_US) as u8; + param.lp_sys.analog_wait_target_cycle = + config.us_to_slowclk(machine_constants::LP_ANALOG_WAIT_TIME_US) as u8; + param.lp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(machine_constants::LP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.lp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(machine_constants::LP_POWER_UP_WAIT_TIME_US) as u8; + + // This looks different from esp-idf but it is the same: + // Both `xtal_stable_wait_cycle` and `xtal_stable_wait_slow_clk_cycle` are + // u16 variants of the same union + param.hp_lp.xtal_stable_wait_cycle = if pd_xtal { + config.us_to_slowclk(machine_constants::LP_XTAL_WAIT_STABLE_TIME_US) as u16 + } else { + config.us_to_fastclk(machine_constants::HP_XTAL_WAIT_STABLE_TIME_US) as u16 + }; + + param + } +} + +#[derive(Clone, Copy)] +struct SleepTimeConfig { + sleep_time_adjustment: u32, // TODO: use this adjustment for calculating wakeup time + slowclk_period: u32, + fastclk_period: u32, +} + +const CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ: u32 = 96; + +impl SleepTimeConfig { + const RTC_CLK_CAL_FRACT: u32 = 19; + + fn rtc_clk_cal_fast(slowclk_cycles: u32) -> u32 { + RtcClock::calibrate(Timg0CalibrationClockConfig::RcFastDivClk, slowclk_cycles) + } + + fn new() -> Self { + let slowclk_period = unsafe { lp_aon().store1().read().data().bits() }; + + // Calibrate rtc fast clock, only PMU supported chips sleep process is needed. + const FAST_CLK_SRC_CAL_CYCLES: u32 = 2048; + let fastclk_period = Self::rtc_clk_cal_fast(FAST_CLK_SRC_CAL_CYCLES); + + Self { + sleep_time_adjustment: 0, + slowclk_period, + fastclk_period, + } + } + + fn light_sleep(pd_flags: PowerDownFlags) -> Self { + const LIGHT_SLEEP_TIME_OVERHEAD_US: u32 = 9; + + let mut this = Self::new(); + + let sw = LIGHT_SLEEP_TIME_OVERHEAD_US; + let hw = this.pmu_sleep_calculate_hw_wait_time(pd_flags); + + this.sleep_time_adjustment = sw + hw; + + this + } + + fn deep_sleep() -> Self { + let mut this = Self::new(); + + this.sleep_time_adjustment = 250 + 100 * 240 / CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; + + this + } + + fn us_to_slowclk(&self, us: u32) -> u32 { + (us << Self::RTC_CLK_CAL_FRACT) / self.slowclk_period + } + + fn slowclk_to_us(&self, rtc_cycles: u32) -> u32 { + (rtc_cycles * self.slowclk_period) >> Self::RTC_CLK_CAL_FRACT + } + + fn us_to_fastclk(&self, us: u32) -> u32 { + (us << Self::RTC_CLK_CAL_FRACT) / self.fastclk_period + } + + fn pmu_sleep_calculate_hw_wait_time(&self, pd_flags: PowerDownFlags) -> u32 { + let lp_clk_switch_time_us = self.slowclk_to_us(machine_constants::LP_CLK_SWITCH_CYCLE); + let lp_clk_power_on_wait_time_us = if pd_flags.pd_xtal() { + machine_constants::LP_XTAL_WAIT_STABLE_TIME_US + } else { + self.slowclk_to_us(machine_constants::LP_CLK_POWER_ON_WAIT_CYCLE) + }; + + let lp_hw_wait_time_us = machine_constants::LP_MIN_SLP_TIME_US + + machine_constants::LP_ANALOG_WAIT_TIME_US + + lp_clk_power_on_wait_time_us + + lp_clk_switch_time_us + + machine_constants::LP_POWER_SUPPLY_WAIT_TIME_US + + machine_constants::LP_POWER_UP_WAIT_TIME_US; + + let hp_digital_power_up_wait_time_us = machine_constants::HP_POWER_SUPPLY_WAIT_TIME_US + + machine_constants::HP_POWER_UP_WAIT_TIME_US; + let hp_regdma_wait_time_us = if pd_flags.pd_top() { + machine_constants::HP_REGDMA_S2A_WORK_TIME_PD_TOP_US + } else { + machine_constants::HP_REGDMA_S2A_WORK_TIME_PU_TOP_US + }; + let hp_clock_wait_time_us = machine_constants::HP_XTAL_WAIT_STABLE_TIME_US + + machine_constants::HP_PLL_WAIT_STABLE_TIME_US; + + let hp_hw_wait_time_us = machine_constants::HP_ANALOG_WAIT_TIME_US + + hp_digital_power_up_wait_time_us + + hp_regdma_wait_time_us + + hp_clock_wait_time_us; + + lp_hw_wait_time_us + hp_hw_wait_time_us + } +} + +/// Configuration for the RTC sleep behavior. +#[derive(Clone, Copy)] +pub struct RtcSleepConfig { + /// Deep Sleep flag + pub deep: bool, + /// Power Down flags + pub pd_flags: PowerDownFlags, + /// Indicates whether the top power domain should remain enabled during sleep. + need_pd_top: bool, +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + // from pmu_sleep_param_config_default + // sleep flags will be applied by wakeup methods and apply + + Self { + deep: false, + pd_flags: PowerDownFlags(0), + need_pd_top: false, + } + } +} + +unsafe fn pmu<'a>() -> &'a esp32h2::pmu::RegisterBlock { + unsafe { &*esp32h2::PMU::ptr() } +} + +unsafe fn lp_aon<'a>() -> &'a esp32h2::lp_aon::RegisterBlock { + unsafe { &*esp32h2::LP_AON::ptr() } +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Power domains to be powered down during sleep + pub struct PowerDownFlags(u32); + + /// Controls the power-down status of the top power domain. + pub u32, pd_top , set_pd_top : 0; + /// Controls the power-down status of the VDD_SDIO power domain. + pub u32, pd_vddsdio , set_pd_vddsdio : 1; + /// Controls the power-down status of the modem power domain. + pub u32, pd_modem , set_pd_modem : 2; + /// Controls the power-down status of the CPU power domain. + pub u32, pd_cpu , set_pd_cpu : 3; + /// Controls the power-down status of the crystal oscillator. + pub u32, pd_xtal , set_pd_xtal : 4; + /// Controls the power-down status of the fast RC oscillator. + pub u32, pd_rc_fast , set_pd_rc_fast : 5; + /// Controls the power-down status of the 32kHz crystal oscillator. + pub u32, pd_xtal32k , set_pd_xtal32k : 6; +} + +/// Constants defined in `PMU_SLEEP_MC_DEFAULT()` +mod machine_constants { + pub const LP_MIN_SLP_TIME_US: u32 = 450; + pub const LP_ANALOG_WAIT_TIME_US: u32 = 154; + pub const LP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + pub const LP_CLK_SWITCH_CYCLE: u32 = 1; + pub const LP_CLK_POWER_ON_WAIT_CYCLE: u32 = 1; + pub const LP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + pub const LP_POWER_UP_WAIT_TIME_US: u32 = 2; + + pub const HP_MIN_SLP_TIME_US: u32 = 450; + pub const HP_ANALOG_WAIT_TIME_US: u32 = 154; + pub const HP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + pub const HP_POWER_UP_WAIT_TIME_US: u32 = 2; + pub const HP_REGDMA_S2A_WORK_TIME_PD_TOP_US: u32 = 480; + pub const HP_REGDMA_S2A_WORK_TIME_PU_TOP_US: u32 = 390; + pub const HP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + pub const HP_PLL_WAIT_STABLE_TIME_US: u32 = 50; +} + +impl RtcSleepConfig { + /// Returns whether the device is in deep sleep mode. + pub fn deep_slp(&self) -> bool { + self.deep + } + + /// Configures the device for deep sleep mode with ultra-low power settings. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + Self { + deep: true, + ..Self::default() + } + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + Self::wake_io_reset(); + } + + fn wake_io_reset() { + Ext1WakeupSource::wake_io_reset(); + } + + /// Finalize power-down flags, apply configuration based on the flags. + pub(crate) fn apply(&mut self) { + if self.deep { + // force-disable certain power domains + self.pd_flags.set_pd_top(self.need_pd_top.not()); + self.pd_flags.set_pd_vddsdio(true); + self.pd_flags.set_pd_modem(true); + self.pd_flags.set_pd_cpu(true); + self.pd_flags.set_pd_xtal(true); + self.pd_flags.set_pd_rc_fast(true); + self.pd_flags.set_pd_xtal32k(true); + } else if self.need_pd_top { + self.pd_flags.set_pd_top(false); + } + } + + /// Configures wakeup options and enters sleep. + /// + /// This function does not return if deep sleep is requested. + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + const PMU_EXT1_WAKEUP_EN: u32 = 1 << 1; + const PMU_GPIO_WAKEUP_EN: u32 = 1 << 2; + const PMU_LP_TIMER_WAKEUP_EN: u32 = 1 << 4; + const PMU_UART0_WAKEUP_EN: u32 = 1 << 6; + const PMU_UART1_WAKEUP_EN: u32 = 1 << 7; + const PMU_BLE_SOC_WAKEUP_EN: u32 = 1 << 10; + + const RTC_SLEEP_REJECT_MASK: u32 = PMU_EXT1_WAKEUP_EN + | PMU_GPIO_WAKEUP_EN + | PMU_LP_TIMER_WAKEUP_EN + | PMU_UART0_WAKEUP_EN + | PMU_UART1_WAKEUP_EN + | PMU_BLE_SOC_WAKEUP_EN; + + let wakeup_mask = wakeup_triggers.0 as u32; + let reject_mask = if self.deep { + 0 + } else { + let reject_mask = RTC_SLEEP_REJECT_MASK; + wakeup_mask & reject_mask + }; + + let cpu_freq_config = ClockTree::with(|clocks| { + let cpu_freq_config = SavedClockConfig::save(clocks); + crate::soc::clocks::configure_hp_root_clk( + clocks, + crate::soc::clocks::HpRootClkConfig::Xtal, + ); + crate::soc::clocks::configure_cpu_clk(clocks, crate::soc::clocks::CpuClkConfig::new(0)); + cpu_freq_config + }); + + // misc_modules_sleep_prepare + // TODO: IDF-7370 + + APB_SARADC::regs() + .ctrl() + .modify(|_, w| w.saradc2_pwdet_drv().bit(false)); + + // pmu_sleep_config_default + pmu_sleep_init + + let power = PowerSleepConfig::defaults(self.pd_flags); + power.apply(); + + // Needs to happen after rtc_clk_cpu_freq_set_xtal + let config = if self.deep { + SleepTimeConfig::deep_sleep() + } else { + SleepTimeConfig::light_sleep(self.pd_flags) + }; + + let param = ParamSleepConfig::defaults(config, power.hp_sys.xtal.xpd_xtal()); + + if self.deep { + DigitalSleepConfig::defaults_deep_sleep().apply(); + AnalogSleepConfig::defaults_deep_sleep().apply(); + } else { + DigitalSleepConfig::defaults_light_sleep(self.pd_flags).apply(); + AnalogSleepConfig::defaults_light_sleep().apply(); + } + + param.apply(); + + // pmu_sleep_start + + unsafe { + // lp_aon_hal_inform_wakeup_type - tells ROM which wakeup stub to run + lp_aon() + .store9() + .modify(|r, w| w.bits(r.bits() & !0x01 | self.deep as u32)); + + // pmu_ll_hp_set_wakeup_enable + pmu().slp_wakeup_cntl2().write(|w| w.bits(wakeup_mask)); + + // pmu_ll_hp_set_reject_enable + pmu().slp_wakeup_cntl1().modify(|_, w| { + w.slp_reject_en() + .bit(true) + .sleep_reject_ena() + .bits(reject_mask) + }); + + pmu().int_clr().write(|w| { + w.soc_wakeup() // pmu_ll_hp_clear_wakeup_intr_status + .clear_bit_by_one() + .soc_sleep_reject() // pmu_ll_hp_clear_reject_intr_status + .clear_bit_by_one() + }); + + // pmu_ll_hp_clear_reject_cause + pmu() + .slp_wakeup_cntl4() + .write(|w| w.slp_reject_cause_clr().bit(true)); + + // Start entry into sleep mode + + // pmu_ll_hp_set_sleep_enable + pmu().slp_wakeup_cntl0().write(|w| w.sleep_req().bit(true)); + + loop { + let int_raw = pmu().int_raw().read(); + if int_raw.soc_wakeup().bit_is_set() || int_raw.soc_sleep_reject().bit_is_set() { + break; + } + } + } + + // esp-idf returns if the sleep was rejected, we don't return anything + + ClockTree::with(|clocks| { + cpu_freq_config.restore(clocks); + }); + } + + /// Cleans up after sleep + pub(crate) fn finish_sleep(&self) { + Self::wake_io_reset(); + } +} + +#[derive(Clone, Copy)] +pub(crate) struct SavedClockConfig { + /// The clock from which CPU clock is derived + old_hp_root_clk: Option, + + /// CPU divider + old_cpu_divider: Option, +} + +impl SavedClockConfig { + pub(crate) fn save(clocks: &ClockTree) -> Self { + let old_hp_root_clk = clocks.hp_root_clk(); + let old_cpu_divider = clocks.cpu_clk(); + + SavedClockConfig { + old_hp_root_clk, + old_cpu_divider, + } + } + + // rtc_clk_cpu_freq_set_config + pub(crate) fn restore(self, clocks: &mut ClockTree) { + if let Some(old_hp_root_clk) = self.old_hp_root_clk { + crate::soc::clocks::configure_hp_root_clk(clocks, old_hp_root_clk); + } + if let Some(old_cpu_divider) = self.old_cpu_divider { + crate::soc::clocks::configure_cpu_clk(clocks, old_cpu_divider); + } + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s2.rs b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs new file mode 100644 index 00000000000..3b645f5667d --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs @@ -0,0 +1,808 @@ +use super::{ + Ext0WakeupSource, + Ext1WakeupSource, + TimerWakeupSource, + WakeSource, + WakeTriggers, + WakeupLevel, +}; +use crate::{ + gpio::{RtcFunction, RtcPin}, + peripherals::{EXTMEM, LPWR, RTC_IO, SENS, SPI0, SPI1, SYSTEM}, + rtc_cntl::{Rtc, sleep::RtcioWakeupSource}, + soc::regi2c, +}; + +// Approximate mapping of voltages to RTC_CNTL_DBIAS_WAK, RTC_CNTL_DBIAS_SLP, +// RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DIG_DBIAS_SLP values. +// Valid if RTC_CNTL_DBG_ATTEN is 0. +/// Digital bias setting for 0.90V. +pub const RTC_CNTL_DBIAS_0V90: u8 = 0; +/// Digital bias setting for 0.95V. +pub const RTC_CNTL_DBIAS_0V95: u8 = 1; +/// Digital bias setting for 1.00V. +pub const RTC_CNTL_DBIAS_1V00: u8 = 2; +/// Digital bias setting for 1.05V. +pub const RTC_CNTL_DBIAS_1V05: u8 = 3; +/// Digital bias setting for 1.10V. +pub const RTC_CNTL_DBIAS_1V10: u8 = 4; +/// Digital bias setting for 1.15V. +pub const RTC_CNTL_DBIAS_1V15: u8 = 5; +/// Digital bias setting for 1.20V. +pub const RTC_CNTL_DBIAS_1V20: u8 = 6; +/// Digital bias setting for 1.25V. +pub const RTC_CNTL_DBIAS_1V25: u8 = 7; +/// Default monitor debug attenuation value. +pub const RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT: u8 = 0; +/// ULP co-processor touch start wait time during sleep, set to maximum. +pub const RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP: u16 = 0xFF; +/// ULP co-processor touch start wait time default value. +pub const RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT: u16 = 0x10; +/// Default wait time for PLL buffer during startup. +pub const RTC_CNTL_PLL_BUF_WAIT_DEFAULT: u8 = 20; +/// Default wait time for CK8M during startup. +pub const RTC_CNTL_CK8M_WAIT_DEFAULT: u8 = 20; +/// Default wait time for XTL buffer during startup. +pub const RTC_CNTL_XTL_BUF_WAIT_DEFAULT: u8 = 100; +/// Minimum sleep value. +pub const RTC_CNTL_MIN_SLP_VAL_MIN: u8 = 2; +/// Deep sleep debug attenuation setting for ultra-low power mode. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT: u8 = 15; +/// Power-up setting for other blocks. +pub const OTHER_BLOCKS_POWERUP: u8 = 1; +/// Wait cycles for other blocks. +pub const OTHER_BLOCKS_WAIT: u16 = 1; +/// WiFi power-up cycles. +pub const WIFI_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// WiFi wait cycles. +pub const WIFI_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// RTC power-up cycles. +pub const RTC_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// RTC wait cycles. +pub const RTC_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// DG wrap power-up cycles. +pub const DG_WRAP_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// DG wrap wait cycles. +pub const DG_WRAP_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// DG peripheral power-up cycles. +pub const DG_PERI_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// DG peripheral wait cycles. +pub const DG_PERI_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// RTC memory power-up cycles. +pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// RTC memory wait cycles. +pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + let rtc_cntl = LPWR::regs(); + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + rtc_cntl + .slp_timer0() + .write(|w| w.slp_val_lo().bits((time_in_ticks & 0xffffffff) as u32)); + + rtc_cntl + .int_clr() + .write(|w| w.main_timer().clear_bit_by_one()); + + rtc_cntl.slp_timer1().write(|w| { + w.slp_val_hi().bits(((time_in_ticks >> 32) & 0xffff) as u16); + w.main_timer_alarm_en().set_bit() + }); + } + } +} + +impl WakeSource for Ext0WakeupSource

    { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_ext0(true); + + // TODO: disable clock when not in use + SENS::regs() + .sar_io_mux_conf() + .modify(|_, w| w.iomux_clk_gate_en().set_bit()); + + // set pin to RTC function + self.pin + .borrow_mut() + .rtc_set_config(true, true, RtcFunction::Rtc); + + // rtcio_hal_ext0_set_wakeup_pin + unsafe { + let rtc_io = RTC_IO::regs(); + // set pin register field + rtc_io + .ext_wakeup0() + .modify(|_, w| w.sel().bits(self.pin.borrow().rtc_number())); + // set level register field + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup0_lv().bit(self.level == WakeupLevel::High)); + } + } +} + +impl Drop for Ext0WakeupSource

    { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + self.pin + .borrow_mut() + .rtc_set_config(true, false, RtcFunction::Rtc); + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_ext1(true); + + // TODO: disable clock when not in use + SENS::regs() + .sar_io_mux_conf() + .modify(|_, w| w.iomux_clk_gate_en().set_bit()); + + // set pins to RTC function + let mut pins = self.pins.borrow_mut(); + let mut bits = 0u32; + for pin in pins.iter_mut() { + pin.rtc_set_config(true, true, RtcFunction::Rtc); + pin.rtcio_pad_hold(true); + bits |= 1 << pin.rtc_number(); + } + + unsafe { + let rtc_cntl = LPWR::regs(); + // clear previous wakeup status + rtc_cntl + .ext_wakeup1() + .modify(|_, w| w.status_clr().set_bit()); + // set pin register field + rtc_cntl.ext_wakeup1().modify(|_, w| w.sel().bits(bits)); + // set level register field + rtc_cntl + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup1_lv().bit(self.level == WakeupLevel::High)); + } + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for pin in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +impl RtcioWakeupSource<'_, '_> { + fn apply_pin(&self, pin: &mut dyn RtcPin, level: WakeupLevel) { + let rtcio = RTC_IO::regs(); + + pin.rtc_set_config(true, true, RtcFunction::Rtc); + + rtcio.pin(pin.number() as usize).modify(|_, w| unsafe { + w.gpio_pin_wakeup_enable().set_bit(); + w.gpio_pin_int_type().bits(match level { + WakeupLevel::Low => 4, + WakeupLevel::High => 5, + }) + }); + } +} + +impl WakeSource for RtcioWakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + let mut pins = self.pins.borrow_mut(); + + if pins.is_empty() { + return; + } + + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_gpio(true); + + // Since we only use RTCIO pins, we can keep deep sleep enabled. + let sens = crate::peripherals::SENS::regs(); + + // TODO: disable clock when not in use + sens.sar_io_mux_conf() + .modify(|_, w| w.iomux_clk_gate_en().set_bit()); + + for (pin, level) in pins.iter_mut() { + self.apply_pin(*pin, *level); + } + } +} + +impl Drop for RtcioWakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for (pin, _level) in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +bitfield::bitfield! { + /// Configuration for the RTC sleep behavior. + #[derive(Clone, Copy)] + pub struct RtcSleepConfig(u64); + impl Debug; + /// force normal voltage in sleep mode (digital domain memory) + pub lslp_mem_inf_fpu, set_lslp_mem_inf_fpu: 0; + /// keep low voltage in sleep mode (even if ULP/touch is used) + pub rtc_mem_inf_follow_cpu, set_rtc_mem_inf_follow_cpu: 1; + /// power down RTC fast memory + pub rtc_fastmem_pd_en, set_rtc_fastmem_pd_en: 2; + /// power down RTC slow memory + pub rtc_slowmem_pd_en, set_rtc_slowmem_pd_en: 3; + /// power down RTC peripherals + pub rtc_peri_pd_en, set_rtc_peri_pd_en: 4; + /// power down Wifi + pub wifi_pd_en, set_wifi_pd_en: 5; + /// Power down Internal 8M oscillator + pub int_8m_pd_en, set_int_8m_pd_en: 6; + /// power down digital domain + pub deep_slp, set_deep_slp: 8; + /// enable WDT flashboot mode + pub wdt_flashboot_mod_en, set_wdt_flashboot_mod_en: 9; + /// set bias for digital domain, in sleep mode + pub u8, dig_dbias_slp, set_dig_dbias_slp: 12, 10; + /// set bias for RTC domain, in sleep mode + pub u8, rtc_dbias_slp, set_rtc_dbias_slp: 16, 13; + /// circuit control parameter, in monitor mode + pub bias_sleep_monitor, set_bias_sleep_monitor: 17; + /// voltage parameter, in sleep mode + pub u8, dbg_atten_slp, set_dbg_atten_slp: 22, 18; + /// circuit control parameter, in sleep mode + pub bias_sleep_slp, set_bias_sleep_slp: 23; + /// circuit control parameter, in monitor mode + pub pd_cur_monitor, set_pd_cur_monitor: 24; + /// circuit control parameter, in sleep mode + pub pd_cur_slp, set_pd_cur_slp: 25; + /// power down VDDSDIO regulator + pub vddsdio_pd_en, set_vddsdio_pd_en: 26; + /// keep main XTAL powered up in sleep + pub xtal_fpu, set_xtal_fpu: 27; + /// keep rtc regulator powered up in sleep + pub rtc_regulator_fpu, set_rtc_regulator_fpu: 28; + /// enable deep sleep reject + pub deep_slp_reject, set_deep_slp_reject: 29; + /// enable light sleep reject + pub light_slp_reject, set_light_slp_reject: 30; +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_rtc_slowmem_pd_en(true); + cfg.set_rtc_fastmem_pd_en(true); + cfg + } +} + +fn rtc_sleep_pu(val: bool) { + // Note: Called rtc_sleep_pd in idf, but makes more sense like this with the + // single boolean argument + let rtc_cntl = LPWR::regs(); + let syscon = unsafe { &*esp32s2::SYSCON::ptr() }; + let bb = unsafe { &*esp32s2::BB::ptr() }; + let i2s = unsafe { &*esp32s2::I2S0::ptr() }; + let nrx = unsafe { &*esp32s2::NRX::ptr() }; + let fe = unsafe { &*esp32s2::FE::ptr() }; + let fe2 = unsafe { &*esp32s2::FE2::ptr() }; + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().bit(val)); + + rtc_cntl + .pwc() + .modify(|_, w| w.slowmem_force_lpu().bit(val).fastmem_force_lpu().bit(val)); + + i2s.pd_conf() + .write(|w| w.plc_mem_force_pu().bit(val).fifo_force_pu().bit(val)); + + syscon.front_end_mem_pd().modify(|_r, w| { + w.dc_mem_force_pu() + .bit(val) + .pbus_mem_force_pu() + .bit(val) + .agc_mem_force_pu() + .bit(val) + }); + + bb.bbpd_ctrl() + .modify(|_r, w| w.fft_force_pu().bit(val).dc_est_force_pu().bit(val)); + + nrx.nrxpd_ctrl().modify(|_, w| { + w.rx_rot_force_pu() + .bit(val) + .vit_force_pu() + .bit(val) + .demap_force_pu() + .bit(val) + }); + + fe.gen_ctrl().modify(|_, w| w.iq_est_force_pu().bit(val)); + + fe2.tx_interp_ctrl() + .modify(|_, w| w.tx_inf_force_pu().bit(val)); +} + +impl RtcSleepConfig { + /// Configures the RTC for deep sleep mode. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + let mut cfg = Self::default(); + + cfg.set_lslp_mem_inf_fpu(false); + cfg.set_rtc_mem_inf_follow_cpu(true); // ? + cfg.set_rtc_fastmem_pd_en(true); + cfg.set_rtc_slowmem_pd_en(true); + cfg.set_rtc_peri_pd_en(true); + cfg.set_wifi_pd_en(true); + cfg.set_int_8m_pd_en(true); + + // Because of force_flags + cfg.set_vddsdio_pd_en(true); + + // because of dig_peri_pd_en + cfg.set_dig_dbias_slp(0); + + cfg.set_deep_slp(true); + cfg.set_wdt_flashboot_mod_en(false); + cfg.set_vddsdio_pd_en(true); + cfg.set_xtal_fpu(false); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + + // because of RTC_SLEEP_PD_DIG + // NOTE: Might be the a different case for RTC_SLEEP_PD_DIG in + // rtc_sleep_get_default_config + cfg.set_rtc_regulator_fpu(false); + cfg.set_dbg_atten_slp(RTC_CNTL_DBG_ATTEN_DEEPSLEEP_DEFAULT); + cfg.set_rtc_dbias_slp(0); + + // because of xtal_fpu + cfg.set_xtal_fpu(false); + cfg.set_bias_sleep_monitor(true); + cfg.set_pd_cur_monitor(true); + cfg.set_bias_sleep_slp(true); + cfg.set_pd_cur_slp(true); + + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + // settings derived from esp_clk_init -> rtc_init + unsafe { + let rtc_cntl = LPWR::regs(); + let extmem = EXTMEM::regs(); + let system = SYSTEM::regs(); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().clear_bit()); + rtc_cntl + .dig_iso() + .modify(|_, w| w.wifi_force_iso().clear_bit()); + + rtc_cntl.ana_conf().modify(|_, w| w.pvtmon_pu().clear_bit()); + + rtc_cntl.timer1().modify(|_, w| { + w.pll_buf_wait().bits(RTC_CNTL_PLL_BUF_WAIT_DEFAULT); + w.ck8m_wait().bits(RTC_CNTL_CK8M_WAIT_DEFAULT) + }); + + // idf: "Moved from rtc sleep to rtc init to save sleep function running time + // set shortest possible sleep time limit" + + rtc_cntl + .timer5() + .modify(|_, w| w.min_slp_val().bits(RTC_CNTL_MIN_SLP_VAL_MIN)); + + rtc_cntl.timer3().modify(|_, w| { + // set wifi timer + w.wifi_powerup_timer().bits(WIFI_POWERUP_CYCLES); + w.wifi_wait_timer().bits(WIFI_WAIT_CYCLES) + }); + + rtc_cntl.timer4().modify(|_, w| { + // set rtc peri timer + w.powerup_timer().bits(RTC_POWERUP_CYCLES); + w.wait_timer().bits(RTC_WAIT_CYCLES); + // set digital wrap timer + w.dg_wrap_powerup_timer().bits(DG_WRAP_POWERUP_CYCLES); + w.dg_wrap_wait_timer().bits(DG_WRAP_WAIT_CYCLES) + }); + + rtc_cntl.timer5().modify(|_, w| { + w.rtcmem_powerup_timer().bits(RTC_MEM_POWERUP_CYCLES); + w.rtcmem_wait_timer().bits(RTC_MEM_WAIT_CYCLES) + }); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dec_heartbeat_width().set_bit(); + w.inc_heartbeat_period().set_bit() + }); + + // Reset RTC bias to default value (needed if waking up from deep sleep) + rtc_cntl.reg().modify(|_, w| { + w.dbias_wak().bits(RTC_CNTL_DBIAS_1V10); + w.dbias_slp().bits(RTC_CNTL_DBIAS_1V10) + }); + + // Set the wait time to the default value. + rtc_cntl.timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT) + }); + + // clkctl_init + { + // clear CMMU clock force on + extmem + .pro_cache_mmu_power_ctrl() + .modify(|_, w| w.pro_cache_mmu_mem_force_on().clear_bit()); + + // clear tag clock force on + extmem + .pro_dcache_tag_power_ctrl() + .modify(|_, w| w.pro_dcache_tag_mem_force_on().clear_bit()); + + extmem + .pro_icache_tag_power_ctrl() + .modify(|_, w| w.pro_icache_tag_mem_force_on().clear_bit()); + + system.rom_ctrl_0().modify(|_, w| w.rom_fo().bits(0)); + system.sram_ctrl_0().modify(|_, w| w.sram_fo().bits(0)); + + // clear register clock force on + SPI0::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + SPI1::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + } + + // pwrctl_init + { + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().clear_bit()); + + // CLEAR APLL close + rtc_cntl.ana_conf().modify(|_, w| { + w.plla_force_pu().clear_bit(); + w.plla_force_pd().set_bit() + }); + + // cancel bbpll force pu if setting no force power up + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().clear_bit(); + w.bbpll_i2c_force_pu().clear_bit(); + w.bb_i2c_force_pu().clear_bit() + }); + + // cancel RTC REG force PU + + rtc_cntl.pwc().modify(|_, w| w.force_pu().clear_bit()); + rtc_cntl.reg().modify(|_, w| { + w.regulator_force_pu().clear_bit(); + w.dboost_force_pu().clear_bit() + }); + + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_force_pu().clear_bit(); + w.fastmem_force_pu().clear_bit(); + w.slowmem_force_noiso().clear_bit(); + w.fastmem_force_noiso().clear_bit() + }); + + rtc_cntl.reg().modify(|_, w| w.dboost_force_pd().set_bit()); + + // cancel sar i2c pd force + rtc_cntl + .ana_conf() + .modify(|_, w| w.sar_i2c_force_pd().clear_bit()); + // cancel digital pu force + // NOTE: duplicate from idf + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_force_pu().clear_bit(); + w.fastmem_force_pu().clear_bit() + }); + + // If this mask is enabled, all soc memories cannot enter power down mode + // We should control soc memory power down mode from RTC, so we will not touch + // this register any more + + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + // If this pd_cfg is set to 1, all memory won't enter low power mode during + // light sleep If this pd_cfg is set to 0, all memory will enter low + // power mode during light sleep + rtc_sleep_pu(false); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.dg_wrap_force_pu().clear_bit(); + w.wifi_force_pu().clear_bit() + }); + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso().clear_bit(); + // NOTE: not present in idf. + w.dg_wrap_force_iso().clear_bit() + }); + + rtc_cntl.dig_iso().modify(|_, w| { + w.wifi_force_noiso().clear_bit(); + // NOTE: not present in idf. + w.wifi_force_iso().clear_bit() + }); + + rtc_cntl.pwc().modify(|_, w| w.force_noiso().clear_bit()); + + // cancel digital PADS force no iso + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); + + // if DPORT_CPU_WAIT_MODE_FORCE_ON == 0, + // the cpu clk will be closed when cpu enter WAITI mode + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold().clear_bit(); + w.dg_pad_force_noiso().clear_bit() + }); + } + + // force power down wifi and bt power domain + rtc_cntl + .dig_iso() + .modify(|_, w| w.wifi_force_iso().set_bit()); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().set_bit()); + + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + } + } + + pub(crate) fn apply(&self) { + // like esp-idf rtc_sleep_init() and deep_sleep_start() + let rtc_cntl = LPWR::regs(); + + if self.deep_slp() { + // "Due to hardware limitations, on S2 the brownout detector + // sometimes trigger during deep sleep to circumvent + // this we disable the brownout detector before sleeping' - from + // idf's deep_sleep_start() + unsafe { + // brownout_hal_config(brownlout_hal_config_t{0}) + rtc_cntl.brown_out().modify(|_, w| { + w.int_wait().bits(2); + w.close_flash_ena().clear_bit(); + w.pd_rf_ena().clear_bit(); + w.cnt_clr().set_bit() + }); + rtc_cntl.brown_out().modify(|_, w| { + // Set followed by clear in idf + w.cnt_clr().clear_bit(); + w.rst_wait().bits(0x3fff); + w.rst_ena().clear_bit(); + w.brown_out2_ena().set_bit(); + w.rst_sel().set_bit() + }); + regi2c::I2C_BOD_REG_THRESHOLD.write_field(0); + rtc_cntl.brown_out().modify(|_, w| w.ena().clear_bit()); + rtc_cntl.int_ena().modify(|_, w| w.brown_out().clear_bit()); + // NOTE: rtc_isr_deregister? + } + } + + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + let mem_folw_cpu = self.rtc_mem_inf_follow_cpu(); + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_folw_cpu().bit(mem_folw_cpu); + w.fastmem_folw_cpu().bit(mem_folw_cpu) + }); + + let rtc_fastmem_pd_en = self.rtc_fastmem_pd_en(); + rtc_cntl.pwc().modify(|_, w| { + w.fastmem_pd_en().bit(rtc_fastmem_pd_en); + w.fastmem_force_pu().bit(!rtc_fastmem_pd_en); + w.fastmem_force_noiso().bit(!rtc_fastmem_pd_en) + }); + + let rtc_slowmem_pd_en = self.rtc_slowmem_pd_en(); + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_pd_en().bit(rtc_slowmem_pd_en); + w.slowmem_force_pu().bit(!rtc_slowmem_pd_en); + w.slowmem_force_noiso().bit(!rtc_slowmem_pd_en) + }); + + let rtc_peri_pd_en = self.rtc_peri_pd_en(); + rtc_cntl.pwc().modify(|_, w| w.pd_en().bit(rtc_peri_pd_en)); + + if self.wifi_pd_en() { + rtc_cntl + .dig_iso() + .modify(|_, w| w.wifi_force_noiso().clear_bit()); + + rtc_cntl.dig_pwc().modify(|_, w| { + w.wifi_force_pu().clear_bit(); + w.wifi_pd_en().set_bit() + }); + } else { + rtc_cntl.dig_pwc().modify(|_, w| w.wifi_pd_en().clear_bit()); + } + + unsafe { + rtc_cntl.reg().modify(|_, w| { + w.dbias_slp().bits(self.rtc_dbias_slp()); + w.dig_reg_dbias_slp().bits(self.dig_dbias_slp()) + }); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_monitor() + .bits(RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT); + w.bias_sleep_monitor().bit(self.bias_sleep_monitor()); + w.bias_sleep_deep_slp().bit(self.bias_sleep_slp()); + w.pd_cur_monitor().bit(self.pd_cur_monitor()); + w.pd_cur_deep_slp().bit(self.pd_cur_slp()); + w.dbg_atten_deep_slp().bits(self.dbg_atten_slp()) + }); + + if self.deep_slp() { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().set_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w.ckgen_i2c_pu().clear_bit(); + w.pll_i2c_pu().clear_bit(); + w.rfrx_pbus_pu().clear_bit(); + w.txrf_i2c_pu().clear_bit() + }); + + rtc_cntl + .options0() + .modify(|_, w| w.bb_i2c_force_pu().clear_bit()); + } else { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().clear_bit()); + } + + let rtc_regulator_fpu = self.rtc_regulator_fpu(); + rtc_cntl + .reg() + .modify(|_, w| w.regulator_force_pu().bit(rtc_regulator_fpu)); + + let int_8m_pd_en = self.int_8m_pd_en(); + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().bit(!int_8m_pd_en)); + + // enable VDDSDIO control by state machine + rtc_cntl.sdio_conf().modify(|_, w| { + w.sdio_force().clear_bit(); + w.sdio_reg_pd_en().bit(self.vddsdio_pd_en()) + }); + + rtc_cntl.slp_reject_conf().modify(|_, w| { + w.deep_slp_reject_en().bit(self.deep_slp_reject()); + w.light_slp_reject_en().bit(self.light_slp_reject()) + }); + + // Set wait cycle for touch or COCPU after deep sleep and light + // sleep. + + rtc_cntl.timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP) + }); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(self.xtal_fpu())); + } + } + + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + // TODO: Add reject triggers + unsafe { + LPWR::regs() + .reset_state() + .modify(|_, w| w.procpu_stat_vector_sel().set_bit()); + + // set bits for what can wake us up + LPWR::regs() + .wakeup_state() + .modify(|_, w| w.wakeup_ena().bits(wakeup_triggers.0.into())); + + // WARN: slp_wakeup is not set in esp-idf + LPWR::regs().state0().write(|w| { + w.sleep_en().set_bit(); + w.slp_wakeup().set_bit() + }); + } + } + + pub(crate) fn finish_sleep(&self) { + // In deep sleep mode, we never get here + unsafe { + LPWR::regs().int_clr().write(|w| { + w.slp_reject() + .clear_bit_by_one() + .slp_wakeup() + .clear_bit_by_one() + }); + + // restore config if it is a light sleep + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + // Recover default wait cycle for touch or COCPU after wakeup. + + LPWR::regs().timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT) + }); + } + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs new file mode 100644 index 00000000000..7127a351ca2 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs @@ -0,0 +1,850 @@ +use super::{ + Ext0WakeupSource, + Ext1WakeupSource, + TimerWakeupSource, + WakeSource, + WakeTriggers, + WakeupLevel, +}; +use crate::{ + gpio::{RtcFunction, RtcPin}, + peripherals::{APB_CTRL, EXTMEM, LPWR, RTC_IO, SPI0, SPI1, SYSTEM}, + rtc_cntl::{Rtc, sleep::RtcioWakeupSource}, + soc::regi2c, +}; + +// Approximate mapping of voltages to RTC_CNTL_DBIAS_WAK, RTC_CNTL_DBIAS_SLP, +// RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DIG_DBIAS_SLP values. +// Valid if RTC_CNTL_DBG_ATTEN is 0. +/// Digital bias setting for 0.90V. +pub const RTC_CNTL_DBIAS_0V90: u8 = 13; +/// Digital bias setting for 0.95V. +pub const RTC_CNTL_DBIAS_0V95: u8 = 16; +/// Digital bias setting for 1.00V. +pub const RTC_CNTL_DBIAS_1V00: u8 = 18; +/// Digital bias setting for 1.05V. +pub const RTC_CNTL_DBIAS_1V05: u8 = 20; +/// Digital bias setting for 1.10V. +pub const RTC_CNTL_DBIAS_1V10: u8 = 23; +/// Digital bias setting for 1.15V. +pub const RTC_CNTL_DBIAS_1V15: u8 = 25; +/// Digital bias setting for 1.20V. +pub const RTC_CNTL_DBIAS_1V20: u8 = 28; +/// Digital bias setting for 1.25V. +pub const RTC_CNTL_DBIAS_1V25: u8 = 30; +/// Digital bias setting for 1.30V. Voltage is approximately 1.34V in practice. +pub const RTC_CNTL_DBIAS_1V30: u8 = 31; +/// Default monitor debug attenuation value. +pub const RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT: u8 = 0; +/// ULP co-processor touch start wait time during sleep, set to maximum. +pub const RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP: u16 = 0xFF; +/// ULP co-processor touch start wait time default value. +pub const RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT: u16 = 0x10; +/// Default wait time for PLL buffer during startup. +pub const RTC_CNTL_PLL_BUF_WAIT_DEFAULT: u8 = 20; +/// Default wait time for CK8M during startup. +pub const RTC_CNTL_CK8M_WAIT_DEFAULT: u8 = 20; +/// Minimum sleep value. +pub const RTC_CNTL_MIN_SLP_VAL_MIN: u8 = 2; +/// Deep sleep debug attenuation setting for ultra-low power mode. +pub const RTC_CNTL_DBG_ATTEN_DEEPSLEEP_ULTRA_LOW: u8 = 15; +/// Power-up setting for other blocks. +pub const OTHER_BLOCKS_POWERUP: u8 = 1; +/// Wait cycles for other blocks. +pub const OTHER_BLOCKS_WAIT: u16 = 1; +/// WiFi power-up cycles. +pub const WIFI_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// WiFi wait cycles. +pub const WIFI_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// Bluetooth power-up cycles. +pub const BT_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// Bluetooth wait cycles. +pub const BT_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// RTC power-up cycles. +pub const RTC_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// RTC wait cycles. +pub const RTC_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// CPU top power-up cycles. +pub const CPU_TOP_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// CPU top wait cycles. +pub const CPU_TOP_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// DG wrap power-up cycles. +pub const DG_WRAP_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// DG wrap wait cycles. +pub const DG_WRAP_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// DG peripheral power-up cycles. +pub const DG_PERI_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// DG peripheral wait cycles. +pub const DG_PERI_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; +/// RTC memory power-up cycles. +pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; +/// RTC memory wait cycles. +pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; + +impl WakeSource for TimerWakeupSource { + fn apply( + &self, + rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_timer(true); + let rtc_cntl = LPWR::regs(); + // TODO: maybe add check to prevent overflow? + let ticks = crate::clock::us_to_rtc_ticks(self.duration.as_micros() as u64); + // "alarm" time in slow rtc ticks + let now = rtc.time_since_boot_raw(); + let time_in_ticks = now + ticks; + unsafe { + rtc_cntl + .slp_timer0() + .write(|w| w.slp_val_lo().bits((time_in_ticks & 0xffffffff) as u32)); + + rtc_cntl + .int_clr() + .write(|w| w.main_timer().clear_bit_by_one()); + + rtc_cntl.slp_timer1().write(|w| { + w.slp_val_hi().bits(((time_in_ticks >> 32) & 0xffff) as u16); + w.main_timer_alarm_en().set_bit() + }); + } + } +} + +impl WakeSource for Ext0WakeupSource

    { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_ext0(true); + + // set pin to RTC function + self.pin + .borrow_mut() + .rtc_set_config(true, true, RtcFunction::Rtc); + + unsafe { + let rtc_io = RTC_IO::regs(); + // set pin register field + rtc_io + .ext_wakeup0() + .modify(|_, w| w.sel().bits(self.pin.borrow().rtc_number())); + // set level register field + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup0_lv().bit(self.level == WakeupLevel::High)); + } + } +} + +impl Drop for Ext0WakeupSource

    { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + self.pin + .borrow_mut() + .rtc_set_config(true, false, RtcFunction::Rtc); + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_ext1(true); + + // set pins to RTC function + let mut pins = self.pins.borrow_mut(); + let mut bits = 0u32; + for pin in pins.iter_mut() { + pin.rtc_set_config(true, true, RtcFunction::Rtc); + bits |= 1 << pin.rtc_number(); + } + + unsafe { + let rtc_cntl = LPWR::regs(); + // clear previous wakeup status + rtc_cntl + .ext_wakeup1() + .modify(|_, w| w.ext_wakeup1_status_clr().set_bit()); + // set pin register field + rtc_cntl + .ext_wakeup1() + .modify(|_, w| w.ext_wakeup1_sel().bits(bits)); + // set level register field + rtc_cntl + .ext_wakeup_conf() + .modify(|_r, w| w.ext_wakeup1_lv().bit(self.level == WakeupLevel::High)); + } + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for pin in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +impl RtcioWakeupSource<'_, '_> { + fn apply_pin(&self, pin: &mut dyn RtcPin, level: WakeupLevel) { + let rtcio = RTC_IO::regs(); + + pin.rtc_set_config(true, true, RtcFunction::Rtc); + + rtcio.pin(pin.number() as usize).modify(|_, w| unsafe { + w.wakeup_enable().set_bit().int_type().bits(match level { + WakeupLevel::Low => 4, + WakeupLevel::High => 5, + }) + }); + } +} + +impl WakeSource for RtcioWakeupSource<'_, '_> { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + sleep_config: &mut RtcSleepConfig, + ) { + let mut pins = self.pins.borrow_mut(); + + if pins.is_empty() { + return; + } + + // don't power down RTC peripherals + sleep_config.set_rtc_peri_pd_en(false); + triggers.set_gpio(true); + + // Since we only use RTCIO pins, we can keep deep sleep enabled. + let sens = crate::peripherals::SENS::regs(); + + // TODO: disable clock when not in use + sens.sar_peri_clk_gate_conf() + .modify(|_, w| w.iomux_clk_en().set_bit()); + + for (pin, level) in pins.iter_mut() { + self.apply_pin(*pin, *level); + } + } +} + +impl Drop for RtcioWakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for (pin, _level) in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +bitfield::bitfield! { + /// Configuration for the RTC sleep behavior. + #[derive(Clone, Copy)] + pub struct RtcSleepConfig(u64); + impl Debug; + /// force normal voltage in sleep mode (digital domain memory) + pub lslp_mem_inf_fpu, set_lslp_mem_inf_fpu: 0; + /// keep low voltage in sleep mode (even if ULP/touch is used) + pub rtc_mem_inf_follow_cpu, set_rtc_mem_inf_follow_cpu: 1; + /// power down RTC fast memory + pub rtc_fastmem_pd_en, set_rtc_fastmem_pd_en: 2; + /// power down RTC slow memory + pub rtc_slowmem_pd_en, set_rtc_slowmem_pd_en: 3; + /// power down RTC peripherals + pub rtc_peri_pd_en, set_rtc_peri_pd_en: 4; + /// power down Modem(wifi and ble) + pub modem_pd_en, set_modem_pd_en: 5; + /// power down CPU, but not restart when lightsleep. + pub cpu_pd_en, set_cpu_pd_en: 6; + /// Power down Internal 8M oscillator + pub int_8m_pd_en, set_int_8m_pd_en: 7; + /// power down digital peripherals + pub dig_peri_pd_en, set_dig_peri_pd_en: 8; + /// power down digital domain + pub deep_slp, set_deep_slp: 9; + /// enable WDT flashboot mode + pub wdt_flashboot_mod_en, set_wdt_flashboot_mod_en: 10; + /// set bias for digital domain, in sleep mode + pub u8, dig_dbias_slp, set_dig_dbias_slp: 15, 11; + /// set bias for RTC domain, in sleep mode + pub u8, rtc_dbias_slp, set_rtc_dbias_slp: 20, 16; + /// circuit control parameter, in monitor mode + pub bias_sleep_monitor, set_bias_sleep_monitor: 21; + /// voltage parameter, in sleep mode + pub u8, dbg_atten_slp, set_dbg_atten_slp: 25, 22; + /// circuit control parameter, in sleep mode + pub bias_sleep_slp, set_bias_sleep_slp: 26; + /// circuit control parameter, in monitor mode + pub pd_cur_monitor, set_pd_cur_monitor: 27; + /// circuit control parameter, in sleep mode + pub pd_cur_slp, set_pd_cur_slp: 28; + /// power down VDDSDIO regulator + pub vddsdio_pd_en, set_vddsdio_pd_en: 29; + /// keep main XTAL powered up in sleep + pub xtal_fpu, set_xtal_fpu: 30; + /// keep rtc regulator powered up in sleep + pub rtc_regulator_fpu, set_rtc_regulator_fpu: 31; + /// enable deep sleep reject + pub deep_slp_reject, set_deep_slp_reject: 32; + /// enable light sleep reject + pub light_slp_reject, set_light_slp_reject: 33; +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + let mut cfg = Self(Default::default()); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg.set_dig_dbias_slp(RTC_CNTL_DBIAS_1V10); + cfg + } +} + +const SYSCON_SRAM_POWER_UP: u16 = 0x7FF; +const SYSCON_ROM_POWER_UP: u8 = 0x7; + +fn rtc_sleep_pu(val: bool) { + let rtc_cntl = LPWR::regs(); + let syscon = unsafe { &*esp32s3::APB_CTRL::ptr() }; + let bb = unsafe { &*esp32s3::BB::ptr() }; + let nrx = unsafe { &*esp32s3::NRX::ptr() }; + let fe = unsafe { &*esp32s3::FE::ptr() }; + let fe2 = unsafe { &*esp32s3::FE2::ptr() }; + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().bit(val)); + + rtc_cntl + .pwc() + .modify(|_, w| w.slowmem_force_lpu().bit(val).fastmem_force_lpu().bit(val)); + + syscon.front_end_mem_pd().modify(|_r, w| { + w.dc_mem_force_pu() + .bit(val) + .pbus_mem_force_pu() + .bit(val) + .agc_mem_force_pu() + .bit(val) + }); + + bb.bbpd_ctrl() + .modify(|_r, w| w.fft_force_pu().bit(val).dc_est_force_pu().bit(val)); + + nrx.nrxpd_ctrl().modify(|_, w| { + w.rx_rot_force_pu() + .bit(val) + .vit_force_pu() + .bit(val) + .demap_force_pu() + .bit(val) + }); + + fe.gen_ctrl().modify(|_, w| w.iq_est_force_pu().bit(val)); + + fe2.tx_interp_ctrl() + .modify(|_, w| w.tx_inf_force_pu().bit(val)); + + syscon.mem_power_up().modify(|_r, w| unsafe { + w.sram_power_up() + .bits(if val { SYSCON_SRAM_POWER_UP } else { 0 }) + .rom_power_up() + .bits(if val { SYSCON_ROM_POWER_UP } else { 0 }) + }); +} + +impl RtcSleepConfig { + /// Configures the RTC for deep sleep mode. + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + let mut cfg = Self::default(); + + cfg.set_lslp_mem_inf_fpu(false); + cfg.set_rtc_mem_inf_follow_cpu(true); // ? + cfg.set_rtc_fastmem_pd_en(true); + cfg.set_rtc_slowmem_pd_en(true); + cfg.set_rtc_peri_pd_en(true); + cfg.set_modem_pd_en(true); + cfg.set_cpu_pd_en(true); + cfg.set_int_8m_pd_en(true); + + cfg.set_dig_peri_pd_en(true); + cfg.set_dig_dbias_slp(0); // because of dig_peri_pd_en + + cfg.set_deep_slp(true); + cfg.set_wdt_flashboot_mod_en(false); + cfg.set_vddsdio_pd_en(true); + cfg.set_xtal_fpu(false); + cfg.set_deep_slp_reject(true); + cfg.set_light_slp_reject(true); + cfg.set_rtc_dbias_slp(RTC_CNTL_DBIAS_1V10); + + // because of dig_peri_pd_en + cfg.set_rtc_regulator_fpu(false); + cfg.set_dbg_atten_slp(RTC_CNTL_DBG_ATTEN_DEEPSLEEP_ULTRA_LOW); + + // because of xtal_fpu + cfg.set_bias_sleep_monitor(true); + cfg.set_pd_cur_monitor(true); + cfg.set_bias_sleep_slp(true); + cfg.set_pd_cur_slp(true); + + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc<'_>) { + // settings derived from esp_clk_init -> rtc_init + unsafe { + let rtc_cntl = LPWR::regs(); + let syscon = APB_CTRL::regs(); + let extmem = EXTMEM::regs(); + let system = SYSTEM::regs(); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().clear_bit()); + + regi2c::I2C_DIG_REG_XPD_RTC_REG.write_field(0); + regi2c::I2C_DIG_REG_XPD_DIG_REG.write_field(0); + + rtc_cntl.ana_conf().modify(|_, w| w.pvtmon_pu().clear_bit()); + + rtc_cntl.timer1().modify(|_, w| { + w.pll_buf_wait() + .bits(RTC_CNTL_PLL_BUF_WAIT_DEFAULT) + .ck8m_wait() + .bits(RTC_CNTL_CK8M_WAIT_DEFAULT) + }); + + // Moved from rtc sleep to rtc init to save sleep function running time + // set shortest possible sleep time limit + + rtc_cntl + .timer5() + .modify(|_, w| w.min_slp_val().bits(RTC_CNTL_MIN_SLP_VAL_MIN)); + + rtc_cntl.timer3().modify(|_, w| { + // set wifi timer + w.wifi_powerup_timer().bits(WIFI_POWERUP_CYCLES); + // set bt timer + w.wifi_wait_timer().bits(WIFI_WAIT_CYCLES); + w.bt_powerup_timer().bits(BT_POWERUP_CYCLES); + w.bt_wait_timer().bits(BT_WAIT_CYCLES) + }); + + rtc_cntl.timer6().modify(|_, w| { + w.cpu_top_powerup_timer().bits(CPU_TOP_POWERUP_CYCLES); + w.cpu_top_wait_timer().bits(CPU_TOP_WAIT_CYCLES) + }); + + rtc_cntl.timer4().modify(|_, w| { + // set rtc peri timer + w.powerup_timer().bits(RTC_POWERUP_CYCLES); + // set digital wrap timer + w.wait_timer().bits(RTC_WAIT_CYCLES); + w.dg_wrap_powerup_timer().bits(DG_WRAP_POWERUP_CYCLES); + w.dg_wrap_wait_timer().bits(DG_WRAP_WAIT_CYCLES) + }); + + rtc_cntl.timer6().modify(|_, w| { + w.dg_peri_powerup_timer().bits(DG_PERI_POWERUP_CYCLES); + w.dg_peri_wait_timer().bits(DG_PERI_WAIT_CYCLES) + }); + + // Reset RTC bias to default value (needed if waking up from deep sleep) + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(RTC_CNTL_DBIAS_1V10); + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(RTC_CNTL_DBIAS_1V10); + + // Set the wait time to the default value. + + rtc_cntl.timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT) + }); + + // LDO dbias initialization + // TODO: this modifies g_rtc_dbias_pvt_non_240m and g_dig_dbias_pvt_non_240m. + // We're using a high enough default but we should read from the efuse. + // rtc_set_stored_dbias(); + + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(RTC_CNTL_DBIAS_1V25); + regi2c::I2C_DIG_REG_EXT_DIG_DREG.write_field(RTC_CNTL_DBIAS_1V25); + + // clear CMMU clock force on + + extmem + .cache_mmu_power_ctrl() + .modify(|_, w| w.cache_mmu_mem_force_on().clear_bit()); + + // clear clkgate force on + syscon.clkgate_force_on().write(|w| w.bits(0)); + + // clear tag clock force on + + extmem + .dcache_tag_power_ctrl() + .modify(|_, w| w.dcache_tag_mem_force_on().clear_bit()); + + extmem + .icache_tag_power_ctrl() + .modify(|_, w| w.icache_tag_mem_force_on().clear_bit()); + + // clear register clock force on + SPI0::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + SPI1::regs() + .clock_gate() + .modify(|_, w| w.clk_en().clear_bit()); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().clear_bit()); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().clear_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w + // open sar_i2c protect function to avoid sar_i2c reset when rtc_ldo is low. + // clear i2c_reset_protect pd force, need tested in low temperature. + // NOTE: this bit is written again in esp-idf, but it's not clear why. + .i2c_reset_por_force_pd() + .clear_bit() + }); + + // cancel bbpll force pu if setting no force power up + + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().clear_bit(); + w.bbpll_i2c_force_pu().clear_bit(); + w.bb_i2c_force_pu().clear_bit() + }); + + // cancel RTC REG force PU + + rtc_cntl.pwc().modify(|_, w| w.force_pu().clear_bit()); + + rtc_cntl.rtc().modify(|_, w| { + w.regulator_force_pu().clear_bit(); + w.dboost_force_pu().clear_bit() + }); + + rtc_cntl.pwc().modify(|_, w| { + w.slowmem_force_noiso().clear_bit(); + w.fastmem_force_noiso().clear_bit() + }); + + rtc_cntl.rtc().modify(|_, w| w.dboost_force_pd().set_bit()); + + // If this mask is enabled, all soc memories cannot enter power down mode + // We should control soc memory power down mode from RTC, so we will not touch + // this register any more + + system + .mem_pd_mask() + .modify(|_, w| w.lslp_mem_pd_mask().clear_bit()); + + // If this pd_cfg is set to 1, all memory won't enter low power mode during + // light sleep If this pd_cfg is set to 0, all memory will enter low + // power mode during light sleep + rtc_sleep_pu(false); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_force_pu().clear_bit()); + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_wrap_force_noiso().clear_bit(); + w.dg_wrap_force_iso().clear_bit() + }); + + rtc_cntl.dig_iso().modify(|_, w| { + w.wifi_force_noiso().clear_bit(); + w.wifi_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pu().clear_bit()); + + rtc_cntl + .dig_iso() + .modify(|_, w| w.bt_force_noiso().clear_bit().bt_force_iso().clear_bit()); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.bt_force_pu().clear_bit()); + + rtc_cntl.dig_iso().modify(|_, w| { + w.cpu_top_force_noiso().clear_bit(); + w.cpu_top_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.cpu_top_force_pu().clear_bit()); + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_peri_force_noiso().clear_bit(); + w.dg_peri_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_peri_force_pu().clear_bit()); + + rtc_cntl.pwc().modify(|_, w| { + w.force_noiso().clear_bit(); + w.force_iso().clear_bit(); + w.force_pu().clear_bit() + }); + + // if SYSTEM_CPU_WAIT_MODE_FORCE_ON == 0, + // the cpu clk will be closed when cpu enter WAITI mode + + system + .cpu_per_conf() + .modify(|_, w| w.cpu_wait_mode_force_on().clear_bit()); + + // cancel digital PADS force no iso + + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_pad_force_unhold().clear_bit(); + w.dg_pad_force_noiso().clear_bit() + }); + + // force power down modem(wifi and ble) power domain + + rtc_cntl + .dig_iso() + .modify(|_, w| w.wifi_force_iso().set_bit()); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pd().set_bit()); + + rtc_cntl.int_ena().write(|w| w.bits(0)); + rtc_cntl.int_clr().write(|w| w.bits(u32::MAX)); + } + } + + pub(crate) fn apply(&self) { + // like esp-idf rtc_sleep_init() + let rtc_cntl = LPWR::regs(); + + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + if self.modem_pd_en() { + rtc_cntl.dig_iso().modify(|_, w| { + w.wifi_force_noiso().clear_bit(); + w.wifi_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pu().clear_bit().wifi_pd_en().set_bit()); + } else { + rtc_cntl.options0().modify(|_, w| { + w.bbpll_force_pu().set_bit(); + w.bbpll_i2c_force_pu().set_bit(); + w.bb_i2c_force_pu().set_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.wifi_force_pu().set_bit().wifi_pd_en().clear_bit()); + } + + if self.cpu_pd_en() { + rtc_cntl.dig_iso().modify(|_, w| { + w.cpu_top_force_noiso().clear_bit(); + w.cpu_top_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.cpu_top_force_pu().clear_bit().cpu_top_pd_en().set_bit()); + } else { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.cpu_top_pd_en().clear_bit()); + } + + if self.dig_peri_pd_en() { + rtc_cntl.dig_iso().modify(|_, w| { + w.dg_peri_force_noiso().clear_bit(); + w.dg_peri_force_iso().clear_bit() + }); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_peri_force_pu().clear_bit().dg_peri_pd_en().set_bit()); + } else { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_peri_pd_en().clear_bit()); + } + + if self.rtc_peri_pd_en() { + rtc_cntl.pwc().modify(|_, w| { + w.force_noiso().clear_bit(); + w.force_iso().clear_bit(); + w.force_pu().clear_bit(); + w.pd_en().set_bit() + }); + } else { + rtc_cntl.pwc().modify(|_, w| w.pd_en().clear_bit()); + } + + unsafe { + regi2c::I2C_DIG_REG_EXT_RTC_DREG_SLEEP.write_field(self.rtc_dbias_slp()); + regi2c::I2C_DIG_REG_EXT_DIG_DREG_SLEEP.write_field(self.dig_dbias_slp()); + + rtc_cntl.bias_conf().modify(|_, w| { + w.dbg_atten_deep_slp().bits(self.dbg_atten_slp()); + w.bias_sleep_deep_slp().bit(self.bias_sleep_slp()); + w.pd_cur_deep_slp().bit(self.pd_cur_slp()); + w.dbg_atten_monitor() + .bits(RTC_CNTL_DBG_ATTEN_MONITOR_DEFAULT); + w.bias_sleep_monitor().bit(self.bias_sleep_monitor()); + w.pd_cur_monitor().bit(self.pd_cur_monitor()) + }); + + if self.deep_slp() { + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().set_bit()); + + rtc_cntl.ana_conf().modify(|_, w| { + w.ckgen_i2c_pu().clear_bit(); + w.pll_i2c_pu().clear_bit(); + w.rfrx_pbus_pu().clear_bit(); + w.txrf_i2c_pu().clear_bit() + }); + + rtc_cntl + .options0() + .modify(|_, w| w.bb_i2c_force_pu().clear_bit()); + } else { + rtc_cntl + .regulator_drv_ctrl() + .modify(|_, w| w.dg_vdd_drv_b_slp().bits(0xF)); + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.dg_wrap_pd_en().clear_bit()); + } + + // mem force pu + + rtc_cntl + .dig_pwc() + .modify(|_, w| w.lslp_mem_force_pu().set_bit()); + + rtc_cntl + .rtc() + .modify(|_, w| w.regulator_force_pu().bit(self.rtc_regulator_fpu())); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.ck8m_force_pu().bit(!self.int_8m_pd_en())); + + // enable VDDSDIO control by state machine + + rtc_cntl.sdio_conf().modify(|_, w| { + w.sdio_force().clear_bit(); + w.sdio_reg_pd_en().bit(self.vddsdio_pd_en()) + }); + + rtc_cntl.slp_reject_conf().modify(|_, w| { + w.deep_slp_reject_en().bit(self.deep_slp_reject()); + w.light_slp_reject_en().bit(self.light_slp_reject()) + }); + + // Set wait cycle for touch or COCPU after deep sleep and light + // sleep. + + rtc_cntl.timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_IN_SLEEP) + }); + + rtc_cntl + .options0() + .modify(|_, w| w.xtl_force_pu().bit(self.xtal_fpu())); + + rtc_cntl + .clk_conf() + .modify(|_, w| w.xtal_global_force_nogating().bit(self.xtal_fpu())); + } + } + + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + unsafe { + LPWR::regs() + .reset_state() + .modify(|_, w| w.procpu_stat_vector_sel().set_bit()); + + // set bits for what can wake us up + LPWR::regs() + .wakeup_state() + .modify(|_, w| w.wakeup_ena().bits(wakeup_triggers.0.into())); + + LPWR::regs() + .state0() + .write(|w| w.sleep_en().set_bit().slp_wakeup().set_bit()); + } + } + + pub(crate) fn finish_sleep(&self) { + // In deep sleep mode, we never get here + unsafe { + LPWR::regs().int_clr().write(|w| { + w.slp_reject() + .clear_bit_by_one() + .slp_wakeup() + .clear_bit_by_one() + }); + + // restore config if it is a light sleep + if self.lslp_mem_inf_fpu() { + rtc_sleep_pu(true); + } + + // Recover default wait cycle for touch or COCPU after wakeup. + + LPWR::regs().timer2().modify(|_, w| { + w.ulpcp_touch_start_wait() + .bits(RTC_CNTL_ULPCP_TOUCH_START_WAIT_DEFAULT) + }); + } + } +} diff --git a/esp-hal/src/rtc_cntl/sleep/mod.rs b/esp-hal/src/rtc_cntl/sleep/mod.rs new file mode 100644 index 00000000000..fc8c8efe049 --- /dev/null +++ b/esp-hal/src/rtc_cntl/sleep/mod.rs @@ -0,0 +1,553 @@ +//! # RTC Control Sleep Module +//! +//! ## Overview +//! The `sleep` module allows configuring various wakeup sources and setting up +//! the sleep behavior based on those sources. The supported wakeup sources +//! include: +//! * `GPIO` pins - light sleep only +//! * timers +//! * `SDIO (Secure Digital Input/Output) - light sleep only` +//! * `MAC (Media Access Control)` wake - light sleep only +//! * `UART0` - light sleep only +//! * `UART1` - light sleep only +//! * `touch` +//! * `ULP (Ultra-Low Power)` wake +//! * `BT (Bluetooth) wake` - light sleep only + +use core::cell::RefCell; +#[cfg(any(esp32, esp32c3, esp32s2, esp32s3, esp32c6, esp32c2, esp32h2))] +use core::time::Duration; + +#[cfg(any(esp32, esp32s2, esp32s3))] +use crate::gpio::RtcPin as RtcIoWakeupPinType; +#[cfg(any(esp32c3, esp32c6, esp32c2, esp32h2))] +use crate::gpio::RtcPinWithResistors as RtcIoWakeupPinType; +use crate::rtc_cntl::Rtc; + +#[cfg_attr(esp32, path = "esp32.rs")] +#[cfg_attr(esp32s2, path = "esp32s2.rs")] +#[cfg_attr(esp32s3, path = "esp32s3.rs")] +#[cfg_attr(esp32c3, path = "esp32c3.rs")] +#[cfg_attr(esp32c6, path = "esp32c6.rs")] +#[cfg_attr(esp32c2, path = "esp32c2.rs")] +#[cfg_attr(esp32h2, path = "esp32h2.rs")] +mod sleep_impl; + +pub use sleep_impl::*; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +/// Level at which a wake-up event is triggered +pub enum WakeupLevel { + /// The wake-up event is triggered when the pin is low. + Low, + #[default] + /// The wake-up event is triggered when the pin is high. + High, +} + +#[procmacros::doc_replace] +/// Represents a timer wake-up source, triggering an event after a specified +/// duration. +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use core::time::Duration; +/// # use esp_hal::delay::Delay; +/// # use esp_hal::rtc_cntl::{reset_reason, sleep::TimerWakeupSource, wakeup_cause, Rtc, SocResetReason}; +/// # use esp_hal::system::Cpu; +/// +/// let delay = Delay::new(); +/// let mut rtc = Rtc::new(peripherals.LPWR); +/// +/// let reason = reset_reason(Cpu::ProCpu); +/// let wake_reason = wakeup_cause(); +/// +/// println!("{:?} {?}", reason, wake_reason); +/// +/// let timer = TimerWakeupSource::new(Duration::from_secs(5)); +/// delay.delay_millis(100); +/// rtc.sleep_deep(&[&timer]); +/// +/// # {after_snippet} +/// ``` +#[derive(Debug, Default, Clone, Copy)] +#[cfg(any(esp32, esp32c3, esp32s2, esp32s3, esp32c6, esp32c2, esp32h2))] +pub struct TimerWakeupSource { + /// The duration after which the wake-up event is triggered. + duration: Duration, +} + +#[cfg(any(esp32, esp32c3, esp32s2, esp32s3, esp32c6, esp32c2, esp32h2))] +impl TimerWakeupSource { + /// Creates a new timer wake-up source with the specified duration. + pub fn new(duration: Duration) -> Self { + Self { duration } + } +} + +/// Errors that can occur when configuring RTC wake-up sources. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The selected pin is not a valid RTC pin. + NotRtcPin, + /// The maximum number of wake-up sources has been exceeded. + TooManyWakeupSources, +} + +#[procmacros::doc_replace] +/// External wake-up source (Ext0). +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use core::time::Duration; +/// # use esp_hal::delay::Delay; +/// # use esp_hal::rtc_cntl::{reset_reason, sleep::{Ext0WakeupSource, TimerWakeupSource, WakeupLevel}, wakeup_cause, Rtc, SocResetReason}; +/// # use esp_hal::system::Cpu; +/// # use esp_hal::gpio::{Input, InputConfig, Pull}; +/// +/// let delay = Delay::new(); +/// let mut rtc = Rtc::new(peripherals.LPWR); +/// +/// let config = InputConfig::default().with_pull(Pull::None); +/// let mut pin_4 = peripherals.GPIO4; +/// let pin_4_input = Input::new(pin_4.reborrow(), config); +/// +/// let reason = reset_reason(Cpu::ProCpu); +/// let wake_reason = wakeup_cause(); +/// +/// println!("{:?} {?}", reason, wake_reason); +/// +/// let timer = TimerWakeupSource::new(Duration::from_secs(30)); +/// +/// core::mem::drop(pin_4_input); +/// let ext0 = Ext0WakeupSource::new(pin_4, WakeupLevel::High); +/// +/// delay.delay_millis(100); +/// rtc.sleep_deep(&[&timer, &ext0]); +/// +/// # } +/// ``` +#[cfg(any(esp32, esp32s2, esp32s3))] +pub struct Ext0WakeupSource { + /// The pin used as the wake-up source. + pin: RefCell

    , + /// The level at which the wake-up event is triggered. + level: WakeupLevel, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl Ext0WakeupSource

    { + /// Creates a new external wake-up source (Ext0``) with the specified pin + /// and wake-up level. + pub fn new(pin: P, level: WakeupLevel) -> Self { + Self { + pin: RefCell::new(pin), + level, + } + } +} + +#[procmacros::doc_replace] +/// External wake-up source (Ext1). +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use core::time::Duration; +/// # use esp_hal::delay::Delay; +/// # use esp_hal::rtc_cntl::{reset_reason, sleep::{Ext1WakeupSource, TimerWakeupSource, WakeupLevel}, wakeup_cause, Rtc, SocResetReason}; +/// # use esp_hal::system::Cpu; +/// # use esp_hal::gpio::{Input, InputConfig, Pull, RtcPin}; +/// +/// let delay = Delay::new(); +/// let mut rtc = Rtc::new(peripherals.LPWR); +/// +/// let config = InputConfig::default().with_pull(Pull::None); +/// let mut pin_2 = peripherals.GPIO2; +/// let mut pin_4 = peripherals.GPIO4; +/// let pin_4_driver = Input::new(pin_4.reborrow(), config); +/// +/// let reason = reset_reason(Cpu::ProCpu); +/// let wake_reason = wakeup_cause(); +/// +/// println!("{:?} {?}", reason, wake_reason); +/// +/// let timer = TimerWakeupSource::new(Duration::from_secs(30)); +/// +/// // Drop the driver to access `pin_4` +/// core::mem::drop(pin_4_driver); +/// +/// let mut wakeup_pins: [&mut dyn RtcPin; 2] = [&mut pin_4, &mut pin_2]; +/// +/// let ext1 = Ext1WakeupSource::new(&mut wakeup_pins, WakeupLevel::High); +/// +/// delay.delay_millis(100); +/// rtc.sleep_deep(&[&timer, &ext1]); +/// +/// # } +/// ``` +#[cfg(any(esp32, esp32s2, esp32s3))] +pub struct Ext1WakeupSource<'a, 'b> { + /// A collection of pins used as wake-up sources. + pins: RefCell<&'a mut [&'b mut dyn RtcIoWakeupPinType]>, + /// The level at which the wake-up event is triggered across all pins. + level: WakeupLevel, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl<'a, 'b> Ext1WakeupSource<'a, 'b> { + /// Creates a new external wake-up source (Ext1) with the specified pins and + /// wake-up level. + pub fn new(pins: &'a mut [&'b mut dyn RtcIoWakeupPinType], level: WakeupLevel) -> Self { + Self { + pins: RefCell::new(pins), + level, + } + } +} + +#[procmacros::doc_replace( + "pin_low" => { + cfg(esp32c6) => "GPIO2", + cfg(esp32h2) => "GPIO9", + }, + "pin_high" => { + cfg(esp32c6) => "GPIO3", + cfg(esp32h2) => "GPIO10" + }, +)] +/// External wake-up source (Ext1). +/// ```rust, no_run +/// # {before_snippet} +/// # use core::time::Duration; +/// # use esp_hal::delay::Delay; +/// # use esp_hal::rtc_cntl::{reset_reason, sleep::{Ext1WakeupSource, TimerWakeupSource, WakeupLevel}, wakeup_cause, Rtc, SocResetReason}; +/// # use esp_hal::system::Cpu; +/// # use esp_hal::gpio::{Input, InputConfig, Pull, RtcPinWithResistors}; +/// # +/// let delay = Delay::new(); +/// let mut rtc = Rtc::new(peripherals.LPWR); +/// +/// let config = InputConfig::default().with_pull(Pull::None); +/// let mut pin_low_input = Input::new(peripherals.__pin_low__.reborrow(), config); +/// +/// let reason = reset_reason(Cpu::ProCpu); +/// let wake_reason = wakeup_cause(); +/// +/// println!("{:?} {?}", reason, wake_reason); +/// +/// let timer = TimerWakeupSource::new(Duration::from_secs(30)); +/// +/// core::mem::drop(pin_low_input); +/// +/// let wakeup_pins: &mut [(&mut dyn RtcPinWithResistors, WakeupLevel)] = +/// &mut [ +/// (&mut peripherals.__pin_low__, WakeupLevel::Low), +/// (&mut peripherals.__pin_high__, WakeupLevel::High), +/// ]; +/// +/// let ext1 = Ext1WakeupSource::new(wakeup_pins); +/// +/// delay.delay_millis(100); +/// rtc.sleep_deep(&[&timer, &ext1]); +/// +/// # } +/// ``` +#[cfg(any(esp32c6, esp32h2))] +pub struct Ext1WakeupSource<'a, 'b> { + pins: RefCell<&'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]>, +} + +#[cfg(any(esp32c6, esp32h2))] +impl<'a, 'b> Ext1WakeupSource<'a, 'b> { + /// Creates a new external wake-up source (Ext1) with the specified pins and + /// wake-up level. + pub fn new(pins: &'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]) -> Self { + Self { + pins: RefCell::new(pins), + } + } +} + +#[procmacros::doc_replace( + "pin0" => { + cfg(any(esp32c3, esp32c2)) => "GPIO2", + cfg(any(esp32s2, esp32s3)) => "GPIO17" + }, + "pin1" => { + cfg(any(esp32c3, esp32c2)) => "GPIO3", + cfg(any(esp32s2, esp32s3)) => "GPIO18" + }, + "rtc_pin_trait" => { + cfg(any(esp32c3, esp32c2)) => "gpio::RtcPinWithResistors", + cfg(any(esp32s2, esp32s3)) => "gpio::RtcPin" + }, +)] +/// RTC_IO wakeup source +/// +/// RTC_IO wakeup allows configuring any combination of RTC_IO pins with +/// arbitrary wakeup levels to wake up the chip from sleep. This wakeup source +/// can be used to wake up from both light and deep sleep. +/// +/// ```rust, no_run +/// # {before_snippet} +/// # use core::time::Duration; +/// # use esp_hal::delay::Delay; +/// # use esp_hal::gpio::{self, Input, InputConfig, Pull}; +/// # use esp_hal::rtc_cntl::{reset_reason, +/// # sleep::{RtcioWakeupSource, TimerWakeupSource, WakeupLevel}, +/// # wakeup_cause, Rtc, SocResetReason +/// # }; +/// # use esp_hal::system::Cpu; +/// +/// let mut rtc = Rtc::new(peripherals.LPWR); +/// +/// let reason = reset_reason(Cpu::ProCpu); +/// let wake_reason = wakeup_cause(); +/// +/// println!("{:?} {?}", reason, wake_reason); +/// +/// let delay = Delay::new(); +/// let timer = TimerWakeupSource::new(Duration::from_secs(10)); +/// let wakeup_pins: &mut [(&mut dyn __rtc_pin_trait__, WakeupLevel)] = &mut [ +/// (&mut peripherals.__pin0__, WakeupLevel::Low), +/// (&mut peripherals.__pin1__, WakeupLevel::High), +/// ]; +/// +/// let rtcio = RtcioWakeupSource::new(wakeup_pins); +/// delay.delay_millis(100); +/// rtc.sleep_deep(&[&timer, &rtcio]); +/// +/// # {after_snippet} +/// ``` +#[cfg(any(esp32c3, esp32s2, esp32s3, esp32c2))] +pub struct RtcioWakeupSource<'a, 'b> { + pins: RefCell<&'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]>, +} + +#[cfg(any(esp32c3, esp32s2, esp32s3, esp32c2))] +impl<'a, 'b> RtcioWakeupSource<'a, 'b> { + /// Creates a new external GPIO wake-up source. + pub fn new(pins: &'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]) -> Self { + Self { + pins: RefCell::new(pins), + } + } +} + +/// LP Core wakeup source +/// +/// Wake up from LP core. This wakeup source +/// can be used to wake up from both light and deep sleep. +#[cfg(esp32c6)] +pub struct WakeFromLpCoreWakeupSource {} + +#[cfg(esp32c6)] +impl WakeFromLpCoreWakeupSource { + /// Create a new instance of `WakeFromLpCoreWakeupSource` + pub fn new() -> Self { + Self {} + } +} + +#[cfg(esp32c6)] +impl Default for WakeFromLpCoreWakeupSource { + fn default() -> Self { + Self::new() + } +} + +/// GPIO wakeup source +/// +/// Wake up from GPIO high or low level. Any pin can be used with this wake up +/// source. Configure the pin for wake up via +/// [crate::gpio::Input::wakeup_enable]. +/// +/// This wakeup source can be used to wake up from light sleep only. +pub struct GpioWakeupSource {} + +impl GpioWakeupSource { + /// Create a new instance of [GpioWakeupSource] + pub fn new() -> Self { + Self {} + } +} + +impl Default for GpioWakeupSource { + fn default() -> Self { + Self::new() + } +} + +impl WakeSource for GpioWakeupSource { + fn apply( + &self, + _rtc: &Rtc<'_>, + triggers: &mut WakeTriggers, + _sleep_config: &mut RtcSleepConfig, + ) { + triggers.set_gpio(true); + } +} + +macro_rules! uart_wakeup_impl { + ($num:literal) => { + paste::paste! { + #[doc = concat!("UART", $num, " wakeup source")] + /// + /// The chip can be woken up by reverting RXD for multiple cycles until the + /// number of rising edges is equal to or greater than the given value. + /// + /// Note that the character which triggers wakeup (and any characters before + /// it) will not be received by the UART after wakeup. This means that the + /// external device typically needs to send an extra character to trigger + /// wakeup before sending the data. + /// + /// After waking-up from UART, you should send some extra data through the UART + /// port in Active mode, so that the internal wakeup indication signal can be + /// cleared. Otherwise, the next UART wake-up would trigger with two less + /// rising edges than the configured threshold value. + /// + /// Wakeup from light sleep takes some time, so not every character sent to the + /// UART can be received by the application. + /// + /// This wakeup source can be used to wake up from light sleep only. + pub struct [< Uart $num WakeupSource >] { + threshold: u16, + } + + impl [< Uart $num WakeupSource >] { + #[doc = concat!("Create a new instance of UART", $num, " wakeup source>") ] + /// + /// # Panics + /// + /// Panics if `threshold` is out of bounds. + pub fn new(threshold: u16) -> Self { + if threshold > 1023 { + panic!("Invalid threshold"); + } + Self { threshold } + } + } + + impl WakeSource for [< Uart $num WakeupSource >] { + fn apply(&self, _rtc: &Rtc<'_>, triggers: &mut WakeTriggers, _sleep_config: &mut RtcSleepConfig) { + triggers.[< set_uart $num >](true); + let uart = crate::peripherals::[< UART $num >]::regs(); + + #[cfg(any(esp32, esp32s2, esp32s3, esp32c2, esp32c3))] + uart.sleep_conf() + .modify(|_, w| unsafe { w.active_threshold().bits(self.threshold) }); + + #[cfg(not(any(esp32, esp32s2, esp32s3, esp32c2, esp32c3)))] + uart.sleep_conf2().modify(|_, w| unsafe { + w.wk_mode_sel() + .bits(0) + .active_threshold() + .bits(self.threshold) + }); + } + } + } + }; +} + +uart_wakeup_impl!(0); +uart_wakeup_impl!(1); + +#[cfg(esp32s2)] +bitfield::bitfield! { + /// Represents the wakeup triggers. + #[derive(Default, Clone, Copy)] + pub struct WakeTriggers(u16); + impl Debug; + /// EXT0 GPIO wakeup + pub ext0, set_ext0: 0; + /// EXT1 GPIO wakeup + pub ext1, set_ext1: 1; + /// GPIO wakeup (l5ght sleep only) + pub gpio, set_gpio: 2; + /// Timer wakeup + pub timer, set_timer: 3; + /// WiFi SoC wakeup + pub wifi_soc, set_wifi_soc: 5; + /// UART0 wakeup (light sleep only) + pub uart0, set_uart0: 6; + /// UART1 wakeup (light sleep only) + pub uart1, set_uart1: 7; + /// Touch wakeup + pub touch, set_touch: 8; + /// ULP-FSM wakeup + pub ulp, set_ulp: 11; + /// USB wakeup + pub usb, set_usb: 15; +} + +#[cfg(any(esp32, esp32c2, esp32c3, esp32s3))] +bitfield::bitfield! { + /// Represents the wakeup triggers. + #[derive(Default, Clone, Copy)] + pub struct WakeTriggers(u16); + impl Debug; + /// EXT0 GPIO wakeup + pub ext0, set_ext0: 0; + /// EXT1 GPIO wakeup + pub ext1, set_ext1: 1; + /// GPIO wakeup (light sleep only) + pub gpio, set_gpio: 2; + /// Timer wakeup + pub timer, set_timer: 3; + /// SDIO wakeup (light sleep only) + pub sdio, set_sdio: 4; + /// MAC wakeup (light sleep only) + pub mac, set_mac: 5; + /// UART0 wakeup (light sleep only) + pub uart0, set_uart0: 6; + /// UART1 wakeup (light sleep only) + pub uart1, set_uart1: 7; + /// Touch wakeup + pub touch, set_touch: 8; + /// ULP wakeup + pub ulp, set_ulp: 9; + /// BT wakeup (light sleep only) + pub bt, set_bt: 10; +} + +#[cfg(soc_has_pmu)] +bitfield::bitfield! { + /// Represents the wakeup triggers. + #[derive(Default, Clone, Copy)] + pub struct WakeTriggers(u16); + impl Debug; + + /// EXT0 GPIO wakeup + pub ext0, set_ext0: 0; + /// EXT1 GPIO wakeup + pub ext1, set_ext1: 1; + /// GPIO wakeup + pub gpio, set_gpio: 2; + /// WiFi beacon wakeup + pub wifi_beacon, set_wifi_beacon: 3; + /// Timer wakeup + pub timer, set_timer: 4; + /// WiFi SoC wakeup + pub wifi_soc, set_wifi_soc: 5; + /// UART0 wakeup + pub uart0, set_uart0: 6; + /// UART1 wakeup + pub uart1, set_uart1: 7; + /// SDIO wakeup + pub sdio, set_sdio: 8; + /// BT wakeup + pub bt, set_bt: 10; + /// LP core wakeup + pub lp_core, set_lp_core: 11; + /// USB wakeup + pub usb, set_usb: 14; +} + +/// Trait representing a wakeup source. +pub trait WakeSource { + /// Configures the RTC and applies the wakeup triggers. + fn apply(&self, rtc: &Rtc<'_>, triggers: &mut WakeTriggers, sleep_config: &mut RtcSleepConfig); +} diff --git a/esp-hal/src/sha.rs b/esp-hal/src/sha.rs new file mode 100644 index 00000000000..2b517f9125d --- /dev/null +++ b/esp-hal/src/sha.rs @@ -0,0 +1,1434 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Secure Hash Algorithm (SHA) Accelerator +//! +//! ## Overview +//! This SHA accelerator is a hardware device that speeds up the SHA algorithm +//! significantly, compared to a SHA algorithm implemented solely in software +//! +//! ## Configuration +//! This driver allows you to perform cryptographic hash operations using +//! various hash algorithms supported by the SHA peripheral, such as: +//! * SHA-1 +//! * SHA-224 +//! * SHA-256 +//! * SHA-384 +//! * SHA-512 +//! +//! The driver supports two working modes: +//! * Typical SHA (CPU-driven) +//! * DMA-SHA (not supported yet) +//! +//! It provides functions to update the hash calculation with input data, finish +//! the hash calculation and retrieve the resulting hash value. The SHA +//! peripheral on ESP chips can handle large data streams efficiently, making it +//! suitable for cryptographic applications that require secure hashing. +//! +//! To use the SHA Peripheral Driver, you need to initialize it with the desired +//! SHA mode and the corresponding SHA peripheral. Once initialized, you can +//! update the hash calculation by providing input data, finish the calculation +//! to retrieve the hash value and repeat the process for a new hash calculation +//! if needed. +//! +//! ## Examples +//! +//! ### Using the `Sha` driver +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::sha::Sha; +//! # use esp_hal::sha::Sha256; +//! # use nb::block; +//! let mut source_data = "HELLO, ESPRESSIF!".as_bytes(); +//! let mut sha = Sha::new(peripherals.SHA); +//! let mut hasher = sha.start::(); +//! // Short hashes can be created by decreasing the output buffer to the +//! // desired length +//! let mut output = [0u8; 32]; +//! +//! while !source_data.is_empty() { +//! // All the HW Sha functions are infallible so unwrap is fine to use if +//! // you use block! +//! source_data = block!(hasher.update(source_data))?; +//! } +//! +//! // Finish can be called as many times as desired to get multiple copies of +//! // the output. +//! block!(hasher.finish(output.as_mut_slice()))?; +//! +//! # {after_snippet} +//! ``` +//! +//! ### Using the `ShaBackend` driver +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::sha::{Sha1Context, ShaBackend}; +//! # +//! let mut sha = ShaBackend::new(peripherals.SHA); +//! // Start the backend, which allows processing SHA operations. +//! let _backend = sha.start(); +//! +//! // Create a new context to hash data with SHA-1. +//! let mut sha1_ctx = Sha1Context::new(); +//! +//! // SHA-1 outputs a 20-byte digest. +//! let mut digest: [u8; 20] = [0; 20]; +//! +//! // Process data. The `update` function returns a handle which can be used to wait +//! // for the operation to finish. +//! sha1_ctx.update(b"input data").wait_blocking(); +//! sha1_ctx.update(b"input data").wait_blocking(); +//! sha1_ctx.update(b"input data").wait_blocking(); +//! +//! // Extract the final hash. This resets the context. +//! sha1_ctx.finalize(&mut digest).wait_blocking(); +//! +//! // digest now contains the SHA-1 hash of the input. +//! # +//! # {after_snippet} +//! ``` + +#![allow(deprecated, reason = "generic_array 0.14 has been deprecated")] + +use core::{ + borrow::BorrowMut, + convert::Infallible, + marker::PhantomData, + mem::size_of, + ptr::NonNull, +}; + +/// Re-export digest for convenience +pub use digest::Digest; + +use crate::{ + peripherals::SHA, + reg_access::{AlignmentHelper, SocDependentEndianess}, + system::GenericPeripheralGuard, + work_queue::{Handle, Poll, Status, VTable, WorkQueue, WorkQueueDriver, WorkQueueFrontend}, +}; + +// ESP32 quirks: +// - Big endian text register (what about hash?) +// - Text and hash is in the same register -> needs an additional load operation to place the hash +// in the text registers +// - Each algorithm has its own register cluster +// - No support for interleaved operation + +/// The SHA Accelerator driver instance +pub struct Sha<'d> { + sha: SHA<'d>, + _guard: GenericPeripheralGuard<{ crate::system::Peripheral::Sha as u8 }>, +} + +impl<'d> Sha<'d> { + /// Create a new instance of the SHA Accelerator driver. + pub fn new(sha: SHA<'d>) -> Self { + let guard = GenericPeripheralGuard::new(); + + Self { sha, _guard: guard } + } + + /// Start a new digest. + pub fn start<'a, A: ShaAlgorithm>(&'a mut self) -> ShaDigest<'d, A, &'a mut Self> { + ShaDigest::new(self) + } + + /// Start a new digest and take ownership of the driver. + /// This is useful for storage outside a function body. i.e. in static or + /// struct. + pub fn start_owned(self) -> ShaDigest<'d, A, Self> { + ShaDigest::new(self) + } + + /// Returns true if the hardware is processing the next message. + fn is_busy(&self, algo: ShaAlgorithmKind) -> bool { + algo.is_busy(&self.sha) + } + + fn process_buffer(&self, state: &mut DigestState) { + if state.first_run { + state.algorithm.start(&self.sha); + state.first_run = false; + } else { + state.algorithm.r#continue(&self.sha); + } + } + + fn write_data<'a>( + &self, + state: &mut DigestState, + incoming: &'a [u8], + ) -> nb::Result<&'a [u8], Infallible> { + if state.message_buffer_is_full { + if self.is_busy(state.algorithm) { + // The message buffer is full and the hardware is still processing the previous + // message. There's nothing to be done besides wait for the hardware. + return Err(nb::Error::WouldBlock); + } else { + // Submit the full buffer. + self.process_buffer(state); + // The buffer is now free for filling. + state.message_buffer_is_full = false; + } + } + + let chunk_len = state.algorithm.chunk_length(); + let mod_cursor = state.cursor % chunk_len; + + let (remaining, bound_reached) = state.alignment_helper.aligned_volatile_copy( + m_mem(&self.sha, 0), + incoming, + chunk_len, + mod_cursor, + ); + + state.cursor += incoming.len() - remaining.len(); + + if bound_reached { + // Message is full now. We don't have to wait for the result, just start the processing + // or set the flag. + _ = self.process_buffer_or_wait(state); + } + + Ok(remaining) + } + + fn process_buffer_or_wait(&self, state: &mut DigestState) -> nb::Result<(), Infallible> { + if self.is_busy(state.algorithm) { + // The message buffer is full and the hardware is still processing the + // previous message. There's nothing to be done besides wait for the + // hardware. + state.message_buffer_is_full = true; + return Err(nb::Error::WouldBlock); + } + + // Send the full buffer. + self.process_buffer(state); + + Ok(()) + } + + fn finish(&self, state: &mut DigestState, output: &mut [u8]) -> nb::Result<(), Infallible> { + if state.message_buffer_is_full { + // Wait for the hardware to become idle. + if self.is_busy(state.algorithm) { + return Err(nb::Error::WouldBlock); + } + + // Start processing so that we can continue writing into SHA memory. + self.process_buffer(state); + state.message_buffer_is_full = false; + } + + let chunk_len = state.algorithm.chunk_length(); + if state.finalize_state == FinalizeState::NotStarted { + let cursor = state.cursor; + self.update(state, &[0x80])?; // Append "1" bit + state.finished_message_size = cursor; + + state.finalize_state = FinalizeState::FlushAlignBuffer; + } + + if state.finalize_state == FinalizeState::FlushAlignBuffer { + let flushed = state + .alignment_helper + .flush_to(m_mem(&self.sha, 0), state.cursor % chunk_len); + + state.finalize_state = FinalizeState::ZeroPadAlmostFull; + if flushed > 0 { + state.cursor += flushed; + if state.cursor.is_multiple_of(chunk_len) { + self.process_buffer_or_wait(state)?; + } + } + } + + let mut mod_cursor = state.cursor % chunk_len; + if state.finalize_state == FinalizeState::ZeroPadAlmostFull { + // Zero out remaining data if buffer is almost full (>=448/896), and process + // buffer. + // + // In either case, we'll continue to the next state. + state.finalize_state = FinalizeState::WriteMessageLength; + let pad_len = chunk_len - mod_cursor; + if pad_len < state.algorithm.message_length_bytes() { + state.alignment_helper.volatile_write( + m_mem(&self.sha, 0), + 0_u8, + pad_len, + mod_cursor, + ); + state.cursor += pad_len; + + self.process_buffer_or_wait(state)?; + mod_cursor = 0; + } + } + + if state.finalize_state == FinalizeState::WriteMessageLength { + // In this state, we pad the remainder of the message block with 0s and append the + // message length to the very end. + // FIXME: this u64 should be u128 for 1024-bit block algos. Since cursor is only usize + // (u32), this makes no difference currently, but may limit maximum message length in + // the future. + let message_len_bytes = size_of::(); + + let pad_len = chunk_len - mod_cursor - message_len_bytes; + // Fill remaining space with zeros + state + .alignment_helper + .volatile_write(m_mem(&self.sha, 0), 0, pad_len, mod_cursor); + + // Write message length + let length = state.finished_message_size as u64 * 8; + state.alignment_helper.aligned_volatile_copy( + m_mem(&self.sha, 0), + &length.to_be_bytes(), + chunk_len, + chunk_len - message_len_bytes, + ); + + // Set up last state, start processing + state.finalize_state = FinalizeState::ReadResult; + self.process_buffer_or_wait(state)?; + } + + if state.finalize_state == FinalizeState::ReadResult { + if state.algorithm.is_busy(&self.sha) { + return Err(nb::Error::WouldBlock); + } + if state.algorithm.load(&self.sha) { + // Spin wait for result, 8-20 clock cycles according to manual + while self.is_busy(state.algorithm) {} + } + + state.alignment_helper.volatile_read_regset( + h_mem(&self.sha, 0), + output, + core::cmp::min(output.len(), 32), + ); + + state.first_run = true; + state.cursor = 0; + state.alignment_helper.reset(); + state.finalize_state = FinalizeState::NotStarted; + + return Ok(()); + } + + Err(nb::Error::WouldBlock) + } + + fn update<'a>( + &self, + state: &mut DigestState, + incoming: &'a [u8], + ) -> nb::Result<&'a [u8], Infallible> { + state.finalize_state = FinalizeState::default(); + self.write_data(state, incoming) + } +} + +impl crate::private::Sealed for Sha<'_> {} + +#[cfg(sha_dma)] +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Sha<'_> { + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.sha.disable_peri_interrupt_on_all_cores(); + self.sha.bind_peri_interrupt(handler); + } +} + +// A few notes on this implementation with regards to 'memcpy', +// - The registers are *not* cleared after processing, so padding needs to be written out +// - Registers need to be written one u32 at a time, no u8 access +// - This means that we need to buffer bytes coming in up to 4 u8's in order to create a full u32 + +/// An active digest +/// +/// This implementation might fail after u32::MAX/8 bytes, to increase please +/// see ::finish() length/self.cursor usage +pub struct ShaDigest<'d, A, S: BorrowMut>> { + sha: S, + state: DigestState, + phantom: PhantomData<(&'d (), A)>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Default)] +enum FinalizeState { + #[default] + NotStarted, + FlushAlignBuffer, + ZeroPadAlmostFull, + WriteMessageLength, + ReadResult, +} + +#[derive(Clone, Debug)] +struct DigestState { + algorithm: ShaAlgorithmKind, + alignment_helper: AlignmentHelper, + cursor: usize, + first_run: bool, + finished_message_size: usize, + message_buffer_is_full: bool, + finalize_state: FinalizeState, +} + +impl DigestState { + fn new(algorithm: ShaAlgorithmKind) -> Self { + Self { + algorithm, + alignment_helper: AlignmentHelper::default(), + cursor: 0, + first_run: true, + finished_message_size: 0, + message_buffer_is_full: false, + finalize_state: FinalizeState::default(), + } + } +} + +impl<'d, A: ShaAlgorithm, S: BorrowMut>> ShaDigest<'d, A, S> { + /// Creates a new digest + #[allow(unused_mut)] + pub fn new(mut sha: S) -> Self { + #[cfg(not(esp32))] + // Setup SHA Mode. + sha.borrow_mut() + .sha + .register_block() + .mode() + .write(|w| unsafe { w.mode().bits(A::ALGORITHM_KIND.mode_bits()) }); + + Self { + sha, + state: DigestState::new(A::ALGORITHM_KIND), + phantom: PhantomData, + } + } + + /// Restores a previously saved digest. + #[cfg(not(esp32))] + pub fn restore(mut sha: S, ctx: &mut Context) -> Self { + // Setup SHA Mode. + sha.borrow_mut() + .sha + .register_block() + .mode() + .write(|w| unsafe { w.mode().bits(A::ALGORITHM_KIND.mode_bits()) }); + + // Restore the message buffer + unsafe { + core::ptr::copy_nonoverlapping( + ctx.buffer.as_ptr(), + m_mem(&sha.borrow_mut().sha, 0), + 32, + ); + } + + // Restore previously saved hash + ctx.state.alignment_helper.volatile_write_regset( + h_mem(&sha.borrow_mut().sha, 0), + &ctx.saved_digest, + 64, + ); + + Self { + sha, + state: ctx.state.clone(), + phantom: PhantomData, + } + } + + /// Returns true if the hardware is processing the next message. + pub fn is_busy(&self) -> bool { + A::ALGORITHM_KIND.is_busy(&self.sha.borrow().sha) + } + + /// Updates the SHA digest with the provided data buffer. + pub fn update<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> { + self.sha.borrow_mut().update(&mut self.state, incoming) + } + + /// Finish of the calculation (if not already) and copy result to output + /// After `finish()` is called `update()`s will contribute to a new hash + /// which can be calculated again with `finish()`. + /// + /// Typically, output is expected to be the size of + /// [ShaAlgorithm::DIGEST_LENGTH], but smaller inputs can be given to + /// get a "short hash" + pub fn finish(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> { + self.sha.borrow_mut().finish(&mut self.state, output) + } + + /// Save the current state of the digest for later continuation. + #[cfg(not(esp32))] + pub fn save(&mut self, context: &mut Context) -> nb::Result<(), Infallible> { + if self.is_busy() { + return Err(nb::Error::WouldBlock); + } + + context.state = self.state.clone(); + + // Save the content of the current hash. + self.state.alignment_helper.volatile_read_regset( + h_mem(&self.sha.borrow_mut().sha, 0), + &mut context.saved_digest, + 64, + ); + + // Save the content of the current (probably partially written) message. + unsafe { + core::ptr::copy_nonoverlapping( + m_mem(&self.sha.borrow_mut().sha, 0), + context.buffer.as_mut_ptr(), + 32, + ); + } + + Ok(()) + } + + /// Discard the current digest and return the peripheral. + pub fn cancel(self) -> S { + self.sha + } +} + +#[cfg(not(esp32))] +/// Context for a SHA Accelerator driver instance +#[derive(Debug, Clone)] +pub struct Context { + state: DigestState, + /// Buffered bytes (SHA_M_n_REG) to be processed. + buffer: [u32; 32], + /// Saved digest (SHA_H_n_REG) for interleaving operation + saved_digest: [u8; 64], + phantom: PhantomData, +} + +#[cfg(not(esp32))] +impl Context { + /// Create a new empty context + pub fn new() -> Self { + Self { + state: DigestState::new(A::ALGORITHM_KIND), + buffer: [0; 32], + saved_digest: [0; 64], + phantom: PhantomData, + } + } + + /// Indicates if the SHA context is in the first run. + /// + /// Returns `true` if this is the first time processing data with the SHA + /// instance, otherwise returns `false`. + pub fn first_run(&self) -> bool { + self.state.first_run + } +} + +#[cfg(not(esp32))] +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +/// This trait encapsulates the configuration for a specific SHA algorithm. +pub trait ShaAlgorithm: crate::private::Sealed { + /// Constant containing the name of the algorithm as a string. + const ALGORITHM: &'static str; + + /// Constant containing the kind of the algorithm. + const ALGORITHM_KIND: ShaAlgorithmKind; + + /// The length of the chunk that the algorithm processes at a time. + /// + /// For example, in SHA-256, this would typically be 64 bytes. + const CHUNK_LENGTH: usize; + + /// The length of the resulting digest produced by the algorithm. + /// + /// For example, in SHA-256, this would be 32 bytes. + const DIGEST_LENGTH: usize; + + #[doc(hidden)] + type DigestOutputSize: digest::generic_array::ArrayLength + 'static; +} + +/// Note: digest has a blanket trait implementation for [digest::Digest] for any +/// element that implements FixedOutput + Default + Update + HashMarker +impl<'d, A: ShaAlgorithm, S: BorrowMut>> digest::HashMarker for ShaDigest<'d, A, S> {} + +impl<'d, A: ShaAlgorithm, S: BorrowMut>> digest::OutputSizeUser for ShaDigest<'d, A, S> { + type OutputSize = A::DigestOutputSize; +} + +impl<'d, A: ShaAlgorithm, S: BorrowMut>> digest::Update for ShaDigest<'d, A, S> { + fn update(&mut self, mut remaining: &[u8]) { + while !remaining.is_empty() { + remaining = nb::block!(Self::update(self, remaining)).unwrap(); + } + } +} + +impl<'d, A: ShaAlgorithm, S: BorrowMut>> digest::FixedOutput for ShaDigest<'d, A, S> { + fn finalize_into(mut self, out: &mut digest::Output) { + nb::block!(self.finish(out)).unwrap(); + } +} + +for_each_sha_algorithm! { + (algos $( ( $name:ident, $full_name:literal $sizes:tt $security:tt, $mode_bits:literal ) ),*) => { + + /// Specifies particular SHA algorithm. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[non_exhaustive] + pub enum ShaAlgorithmKind { + $( + #[doc = concat!("The ", $full_name, " algorithm.")] + $name, + )* + } + }; +} + +for_each_sha_algorithm! { + (algos $( ( $name:ident, $full_name:literal (sizes: $block_size:literal, $digest_len:literal, $message_length_bytes:literal) $security:tt, $mode_bits:literal ) ),*) => { + impl ShaAlgorithmKind { + #[cfg(not(esp32))] + const fn mode_bits(self) -> u8 { + match self { + $(ShaAlgorithmKind::$name => $mode_bits,)* + } + } + + const fn chunk_length(self) -> usize { + match self { + $(ShaAlgorithmKind::$name => $block_size,)* + } + } + + /// Bytes needed to represent the length of the longest possible message. + const fn message_length_bytes(self) -> usize { + match self { + $(ShaAlgorithmKind::$name => $message_length_bytes,)* + } + } + + const fn digest_length(self) -> usize { + match self { + $(ShaAlgorithmKind::$name => $digest_len,)* + } + } + } + }; +} + +impl ShaAlgorithmKind { + fn start(self, sha: &crate::peripherals::SHA<'_>) { + let regs = sha.register_block(); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + match self { + ShaAlgorithmKind::Sha1 => regs.sha1_start().write(|w| w.sha1_start().set_bit()), + ShaAlgorithmKind::Sha256 => regs.sha256_start().write(|w| w.sha256_start().set_bit()), + ShaAlgorithmKind::Sha384 => regs.sha384_start().write(|w| w.sha384_start().set_bit()), + ShaAlgorithmKind::Sha512 => regs.sha512_start().write(|w| w.sha512_start().set_bit()), + }; + } else { + regs.start().write(|w| w.start().set_bit()); + } + } + } + + fn r#continue(self, sha: &crate::peripherals::SHA<'_>) { + let regs = sha.register_block(); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + match self { + ShaAlgorithmKind::Sha1 => regs.sha1_continue().write(|w| w.sha1_continue().set_bit()), + ShaAlgorithmKind::Sha256 => regs.sha256_continue().write(|w| w.sha256_continue().set_bit()), + ShaAlgorithmKind::Sha384 => regs.sha384_continue().write(|w| w.sha384_continue().set_bit()), + ShaAlgorithmKind::Sha512 => regs.sha512_continue().write(|w| w.sha512_continue().set_bit()), + }; + } else { + regs.continue_().write(|w| w.continue_().set_bit()); + } + } + } + + /// Starts loading the hash into the output registers. + /// + /// Returns whether the caller needs to wait for the hash to be loaded. + fn load(self, _sha: &crate::peripherals::SHA<'_>) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let regs = _sha.register_block(); + match self { + ShaAlgorithmKind::Sha1 => regs.sha1_load().write(|w| w.sha1_load().set_bit()), + ShaAlgorithmKind::Sha256 => regs.sha256_load().write(|w| w.sha256_load().set_bit()), + ShaAlgorithmKind::Sha384 => regs.sha384_load().write(|w| w.sha384_load().set_bit()), + ShaAlgorithmKind::Sha512 => regs.sha512_load().write(|w| w.sha512_load().set_bit()), + }; + + true + } else { + // Return that no waiting is necessary + false + } + } + } + + fn is_busy(self, sha: &crate::peripherals::SHA<'_>) -> bool { + let regs = sha.register_block(); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let bit = match self { + ShaAlgorithmKind::Sha1 => regs.sha1_busy().read().sha1_busy(), + ShaAlgorithmKind::Sha256 => regs.sha256_busy().read().sha256_busy(), + ShaAlgorithmKind::Sha384 => regs.sha384_busy().read().sha384_busy(), + ShaAlgorithmKind::Sha512 => regs.sha512_busy().read().sha512_busy(), + }; + } else { + let bit = regs.busy().read().state(); + } + } + + bit.bit_is_set() + } +} + +// This macro call creates Sha algorithm structs that implement the ShaAlgorithm trait. +for_each_sha_algorithm! { + ( $name:ident, $full_name:literal (sizes: $block_size:literal, $digest_len:literal, $message_length_bytes:literal) (insecure_against: $($attack_kind:literal),*), $mode_bits:literal ) => { + #[doc = concat!("Hardware-accelerated ", $full_name, " implementation")] + /// + /// This struct manages the context and state required for processing data using the selected hashing algorithm. + + /// + /// The struct provides various functionalities such as initializing the hashing + /// process, updating the internal state with new data, and finalizing the + /// hashing operation to generate the final digest. + $( + #[doc = ""] + #[doc = concat!(" > ⚠️ Note that this algorithm is known to be insecure against ", $attack_kind, " attacks.")] + )* + #[non_exhaustive] + pub struct $name; + + impl crate::private::Sealed for $name {} + + impl ShaAlgorithm for $name { + const ALGORITHM: &'static str = stringify!($name); + const ALGORITHM_KIND: ShaAlgorithmKind = ShaAlgorithmKind::$name; + + const CHUNK_LENGTH: usize = Self::ALGORITHM_KIND.chunk_length(); + const DIGEST_LENGTH: usize = Self::ALGORITHM_KIND.digest_length(); + + type DigestOutputSize = paste::paste!(digest::consts::[< U $digest_len >]); + } + }; +} + +fn h_mem(sha: &crate::peripherals::SHA<'_>, index: usize) -> *mut u32 { + let sha = sha.register_block(); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + sha.text(index).as_ptr() + } else { + sha.h_mem(index).as_ptr() + } + } +} + +fn m_mem(sha: &crate::peripherals::SHA<'_>, index: usize) -> *mut u32 { + let sha = sha.register_block(); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + sha.text(index).as_ptr() + } else { + sha.m_mem(index).as_ptr() + } + } +} + +#[derive(Clone)] +struct ShaOperation { + operation: ShaOperationKind, + // Buffer containing pieced-together message bytes, not necessarily a complete block. Not a fat + // pointer because the driver will update the number of buffered bytes. + buffer: NonNull, + buffered_bytes: u8, + message: NonNull<[u8]>, + // Intermediate hash value. For SHA-512/t, this may also contain the initial hash (unverified + // claim) + #[cfg(not(esp32))] + hw_state: NonNull<[u32]>, + // TODO: we don't really need DigestState, especially not the alignment helper, but the driver + // is formulated like this and so reusing what we have is simpler. + state: DigestState, +} + +impl ShaOperation { + fn reset(&mut self) { + self.state = DigestState::new(self.state.algorithm); + self.buffered_bytes = 0; + } +} + +// Safety: ShaOperation is safe to share between threads, in the context of a WorkQueue. The +// WorkQueue ensures that only a single location can access the data. All the internals, except +// for the pointers, are Sync. The pointers are safe to share because they point at data that the +// SHA driver ensures can be accessed safely and soundly. +unsafe impl Sync for ShaOperation {} +// Safety: we will not hold on to the pointers when the work item leaves the queue. +unsafe impl Send for ShaOperation {} + +static SHA_WORK_QUEUE: WorkQueue = WorkQueue::new(); +const BLOCKING_SHA_VTABLE: VTable = VTable { + post: |driver, item| { + let driver = unsafe { ShaBackend::from_raw(driver) }; + + // Ensure driver is initialized + if let DriverState::Uninitialized(sha) = &driver.driver { + driver.driver = DriverState::Initialized(Sha::new(unsafe { sha.clone_unchecked() })); + }; + + Some(driver.process(item)) + }, + poll: |driver, item| { + let driver = unsafe { ShaBackend::from_raw(driver) }; + driver.process(item) + }, + cancel: |_driver, _item| { + // To achieve a decent performance in Typical SHA mode, we run the operations in a blocking + // manner and so they can't be cancelled. + }, + stop: |driver| { + // Drop the SHA driver to conserve power when there is nothing to do (or when the driver was + // stopped). + let driver = unsafe { ShaBackend::from_raw(driver) }; + driver.deinitialize() + }, +}; + +/// Contains information about processing the current work item. +struct ProcessingState { + message_partially_processed: bool, + message_bytes_processed: usize, +} + +enum DriverState<'d> { + Uninitialized(SHA<'d>), + Initialized(Sha<'d>), +} + +#[derive(Clone, Copy, PartialEq)] +enum ShaOperationKind { + Update, + Finalize, +} + +#[procmacros::doc_replace] +/// CPU-driven SHA processing backend. +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::sha::{Sha1Context, ShaBackend}; +/// # +/// let mut sha = ShaBackend::new(peripherals.SHA); +/// // Start the backend, which allows processing SHA operations. +/// let _backend = sha.start(); +/// +/// // Create a new context to hash data with SHA-1. +/// let mut sha1_ctx = Sha1Context::new(); +/// +/// // SHA-1 outputs a 20-byte digest. +/// let mut digest: [u8; 20] = [0; 20]; +/// +/// // Process data. The `update` function returns a handle which can be used to wait +/// // for the operation to finish. +/// sha1_ctx.update(b"input data").wait_blocking(); +/// sha1_ctx.update(b"input data").wait_blocking(); +/// sha1_ctx.update(b"input data").wait_blocking(); +/// +/// // Extract the final hash. This resets the context. +/// sha1_ctx.finalize(&mut digest).wait_blocking(); +/// +/// // digest now contains the SHA-1 hash of the input. +/// # +/// # {after_snippet} +/// ``` +pub struct ShaBackend<'d> { + driver: DriverState<'d>, + processing_state: ProcessingState, +} +impl<'d> ShaBackend<'d> { + /// Creates a new SHA backend. + /// + /// The backend needs to be [`start`][Self::start]ed before it can execute SHA operations. + pub fn new(sha: SHA<'d>) -> Self { + Self { + driver: DriverState::Uninitialized(sha), + processing_state: ProcessingState { + message_partially_processed: false, + message_bytes_processed: 0, + }, + } + } + + /// Registers the CPU-driven SHA driver to process SHA operations. + /// + /// The driver stops operating when the returned object is dropped. + pub fn start(&mut self) -> ShaWorkQueueDriver<'_, 'd> { + ShaWorkQueueDriver { + inner: WorkQueueDriver::new(self, BLOCKING_SHA_VTABLE, &SHA_WORK_QUEUE), + } + } + + // WorkQueue callbacks. They may run in any context. + + unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self { + unsafe { ptr.cast::>().as_mut() } + } + + fn process(&mut self, item: &mut ShaOperation) -> Poll { + // The hardware allows us to write the next message block while it's still processing the + // current one. We can only do this if the current message block belongs to the same message + // as the previous one. This is unfortunately not a safe operation in the work queue, + // because we can't inspect the next work item during the processing of the current + // one. + // However, we can receive a message with multiple blocks and we can apply the optimization + // while processing the message bytes here. + // Another optimization we can do is skipping restoring the intermediate hash value if the + // next operation is a continuation of the current one. We still need to save it between + // operations, though. + + // In this function we process a number of message blocks. + // There are two possible cases we need to handle: + // - We receive message bytes that, including the buffered bytes, are enough for at least + // one complete SHA block. We need to copy whole blocks into the hardware, do the + // computation, save the state, and save the remaining data in the buffer. + // - We receive message bytes that, including the buffered bytes, are not enough for a + // complete SHA block. This case marks the end of the message. We will need to apply + // padding, then finalize the computation. + + if item.operation == ShaOperationKind::Update { + self.process_update(item) + } else { + self.process_finalize(item) + } + } + + #[cfg(not(esp32))] + fn restore_state(driver: &mut Sha<'_>, item: &ShaOperation) { + driver + .sha + .register_block() + .mode() + .write(|w| unsafe { w.mode().bits(item.state.algorithm.mode_bits()) }); + + // Restore previously saved hash. Don't bother on first_run, the start operation will + // use a hard-coded initial hash. + if !item.state.first_run { + for (i, reg) in driver.sha.register_block().h_mem_iter().enumerate() { + reg.write(|w| unsafe { w.bits(item.hw_state.as_ref()[i]) }); + } + } + } + + fn process_update(&mut self, item: &mut ShaOperation) -> Poll { + let driver = if let DriverState::Initialized(sha) = &mut self.driver { + sha + } else { + unreachable!() + }; + + // The message can be longer than a SHA block, so we track the processed bytes. + if !self.processing_state.message_partially_processed { + self.processing_state = ProcessingState { + message_partially_processed: true, + message_bytes_processed: 0, + }; + + #[cfg(not(esp32))] + Self::restore_state(driver, item); + + let buffered = unsafe { + core::slice::from_raw_parts(item.buffer.as_ptr(), item.buffered_bytes as usize) + }; + debug!( + "update: restored state with {} buffered bytes", + buffered.len() + ); + + // This is never supposed to block or even start processing, we're writing an incomplete + // block into idle hardware. + debug_assert!(buffered.len() < item.state.algorithm.chunk_length()); + nb::block!(driver.write_data(&mut item.state, buffered)).unwrap(); + } + + let remaining_message = + unsafe { &item.message.as_ref()[self.processing_state.message_bytes_processed..] }; + + // Even if we don't have a full chunk's worth of message, this function was called because + // with the buffered data we have enough. If `message_processed == 0`, we have written a + // partial block loaded in the hardware and will need to fill it out with message bytes. + if remaining_message.len() >= item.state.algorithm.chunk_length() + || self.processing_state.message_bytes_processed == 0 + { + if let Ok(remaining) = driver.write_data(&mut item.state, remaining_message) { + let total_consumed = item.message.len() - remaining.len(); + self.processing_state.message_bytes_processed = total_consumed; + } + + // Request recall, SHA doesn't have an interrupt. + return Poll::Pending(true); + } else { + // It's not necessary to wait here in the first call - no operation can be running when + // we only restored a partial buffer. + + // Unfortunately we don't see the future - we can't tell if the next operation can be + // overlapped with this one, so we need to wait for processing to complete so that we + // can save the context - ESP32 is an exception, as only one context can use the + // hardware at a time. + #[cfg(not(esp32))] + if driver.is_busy(item.state.algorithm) { + // Request recall, SHA doesn't have an interrupt. + return Poll::Pending(true); + } + } + + // Save hash. If `!first_run`, we did not start an operation so there is no hash to save. + #[cfg(not(esp32))] + if !item.state.first_run { + for (i, reg) in driver.sha.register_block().h_mem_iter().enumerate() { + unsafe { item.hw_state.as_mut()[i] = reg.read().bits() }; + } + } + + // We can only process complete blocks before finalization. Write back the unprocessed bytes + // to the item's buffer. + if !remaining_message.is_empty() { + debug!( + "Writing back {} unprocessed bytes to buffer", + remaining_message.len() + ); + unsafe { + // Safety: the frontend ensures that the buffer is large enough to hold the + // remaining message. + core::ptr::copy_nonoverlapping( + remaining_message.as_ptr(), + item.buffer.as_ptr(), + remaining_message.len(), + ); + } + } + item.buffered_bytes = remaining_message.len() as u8; + self.processing_state.message_partially_processed = false; + + Poll::Ready(Status::Completed) + } + + fn process_finalize(&mut self, item: &mut ShaOperation) -> Poll { + let driver = if let DriverState::Initialized(sha) = &mut self.driver { + sha + } else { + unreachable!() + }; + + if !self.processing_state.message_partially_processed { + // We don't need to track the byte count here, just that we've restored the hash and + // written the buffered data. `process_finalize` ignores `message_bytes_processed`. + self.processing_state.message_partially_processed = true; + + #[cfg(not(esp32))] + Self::restore_state(driver, item); + + let buffered = unsafe { item.message.as_ref() }; + debug!( + "finalize: restored state with {} buffered bytes", + buffered.len() + ); + + // This is never supposed to block or even start processing, we're writing an incomplete + // block into idle hardware. + debug_assert!(buffered.len() < item.state.algorithm.chunk_length()); + + nb::block!(driver.write_data(&mut item.state, buffered)).unwrap(); + } + + // Safety: caller must ensure that result buffer is large enough. + let result = unsafe { + core::slice::from_raw_parts_mut( + item.buffer.as_ptr(), + item.state.algorithm.digest_length(), + ) + }; + if driver.finish(&mut item.state, result).is_err() { + return Poll::Pending(true); + } + + self.processing_state.message_partially_processed = false; + + item.reset(); + + Poll::Ready(Status::Completed) + } + + fn deinitialize(&mut self) { + if let DriverState::Initialized(ref sha) = self.driver { + self.driver = DriverState::Uninitialized(unsafe { sha.sha.clone_unchecked() }); + } + } +} + +/// An active work queue driver. +/// +/// This object must be kept around, otherwise SHA operations will never complete. +pub struct ShaWorkQueueDriver<'t, 'd> { + inner: WorkQueueDriver<'t, ShaBackend<'d>, ShaOperation>, +} + +impl<'t, 'd> ShaWorkQueueDriver<'t, 'd> { + /// Finishes processing the current work queue item, then stops the driver. + pub fn stop(self) -> impl Future { + self.inner.stop() + } +} + +#[cfg(esp32)] +enum SoftwareHasher { + Sha1(sha1::Sha1), + Sha256(sha2::Sha256), + Sha384(sha2::Sha384), + Sha512(sha2::Sha512), +} + +// Common implementation, to be hidden behind algo-dependent contexts. +#[cfg_attr(not(esp32), derive(Clone))] +struct ShaContext { + frontend: WorkQueueFrontend, + buffer: [u8; CHUNK_BYTES], + + #[cfg(not(esp32))] // Saved H_MEM registers + state: [u32; 16], + + // ESP32 can't save (or rather, restore) the hash context, so we need to fall back to software + // if a context already uses the hardware. + #[cfg(esp32)] + use_software: Option, +} + +// ESP32 can't save the context, so it can't support interleaved operation. To support the digest +// API, we need to limit the number of concurrent contexts - to 1. Thankfully ESP32 has no DSA/HMAC, +// so we don't need to worry about figuring THAT edge case out. +// Currently we fall back to a software implementation if multiple contexts are created. Instead, we +// could save the hash like we do on other MCUs, and continue with software if another context has +// been created in the mean time. This would prevent a case where a context could indefinitely +// reserve the hardware. +#[cfg(esp32)] +use portable_atomic::{AtomicBool, Ordering}; +#[cfg(esp32)] +static ACCELERATOR_IN_USE: AtomicBool = AtomicBool::new(false); + +impl ShaContext { + fn new(algorithm: ShaAlgorithmKind) -> Self { + #[cfg(esp32)] + let use_software = if ACCELERATOR_IN_USE.swap(true, Ordering::SeqCst) { + let hasher = match algorithm { + ShaAlgorithmKind::Sha1 => SoftwareHasher::Sha1(sha1::Sha1::new()), + ShaAlgorithmKind::Sha256 => SoftwareHasher::Sha256(sha2::Sha256::new()), + ShaAlgorithmKind::Sha384 => SoftwareHasher::Sha384(sha2::Sha384::new()), + ShaAlgorithmKind::Sha512 => SoftwareHasher::Sha512(sha2::Sha512::new()), + }; + Some(hasher) + } else { + None + }; + + Self { + frontend: WorkQueueFrontend::new(ShaOperation { + operation: ShaOperationKind::Update, + buffer: NonNull::dangling(), + message: NonNull::from(&mut []), + #[cfg(not(esp32))] + hw_state: NonNull::from(&mut []), + buffered_bytes: 0, + state: DigestState::new(algorithm), + }), + buffer: [0; CHUNK_BYTES], + #[cfg(not(esp32))] + state: [0; 16], + + #[cfg(esp32)] + use_software, + } + } + + fn update<'t>(&'t mut self, data: &'t [u8]) -> ShaHandle<'t> { + debug!( + "Update {:?} with {} bytes", + self.frontend.data_mut().state.algorithm, + data.len() + ); + #[cfg(esp32)] + if let Some(hasher) = self.use_software.as_mut() { + Self::update_using_software(hasher, data); + return ShaHandle(self.frontend.post_completed(&SHA_WORK_QUEUE)); + } + + let op_data = self.frontend.data_mut(); + + // We want to pass complete blocks to the worker. If we pass an incomplete block, it must be + // the last one. Because update can take any number of bytes, we need to buffer a block's + // worth, truncate data to the nearest block size, then save the rest. + // Buffering data here, and handling head/data/tail processing is way too annoying. + // Therefore, we implement the following strategy: + // - If we can buffer the data, we do. + // - If we can't, we post a work item to the queue. The work item includes the buffer, the + // data, and the state pointers. + // - The worker will load and process data as it sees fit. The worker will put the remaining + // data into the buffer, and update the state and buffer length. + // - The worker is responsible for updating the buffer and state. It will set `first` to + // false if it saves an intermediate result into `state`. + // This means chunking and remainder handling is the responsibility of the worker. + let buffered = op_data.buffered_bytes as usize; + if data.len() + buffered < CHUNK_BYTES { + op_data.buffered_bytes += data.len() as u8; + op_data.message = NonNull::from(data); // Ensure message.len() returns the consumed length + + self.buffer[buffered..][..data.len()].copy_from_slice(data); + return ShaHandle(self.frontend.post_completed(&SHA_WORK_QUEUE)); + } + + // If a block is complete, we send it to the hardware. If the data we send is a whole number + // of complete blocks, the driver will leave `buffer` empty. This ensures that we + // never start a finalize operation with a complete block. + + op_data.operation = ShaOperationKind::Update; + op_data.message = NonNull::from(data); + op_data.buffer = NonNull::from(&mut self.buffer).cast(); + #[cfg(not(esp32))] + { + op_data.hw_state = NonNull::from(&mut self.state); + } + + ShaHandle(self.frontend.post(&SHA_WORK_QUEUE)) + } + + fn finalize<'t>(&'t mut self, result: &mut [u8]) -> ShaHandle<'t> { + debug!( + "Finalize {:?} into buffer of {} bytes", + self.frontend.data_mut().state.algorithm, + result.len() + ); + #[cfg(esp32)] + if let Some(hasher) = self.use_software.as_mut() { + Self::finalize_using_software(hasher, result); + return ShaHandle(self.frontend.post_completed(&SHA_WORK_QUEUE)); + } + + let op_data = self.frontend.data_mut(); + + debug_assert!((op_data.buffered_bytes as usize) < op_data.state.algorithm.chunk_length()); + + op_data.operation = ShaOperationKind::Finalize; + op_data.message = NonNull::from(&mut self.buffer[..op_data.buffered_bytes as usize]); + op_data.buffer = NonNull::from(result).cast(); + #[cfg(not(esp32))] + { + op_data.hw_state = NonNull::from(&mut self.state); + } + + ShaHandle(self.frontend.post(&SHA_WORK_QUEUE)) + } + + #[cfg(esp32)] + fn update_using_software(hasher: &mut SoftwareHasher, data: &[u8]) { + match hasher { + SoftwareHasher::Sha1(sha) => sha.update(data), + SoftwareHasher::Sha256(sha) => sha.update(data), + SoftwareHasher::Sha384(sha) => sha.update(data), + SoftwareHasher::Sha512(sha) => sha.update(data), + } + } + + #[cfg(esp32)] + fn finalize_using_software(hasher: &mut SoftwareHasher, result: &mut [u8]) { + match hasher { + SoftwareHasher::Sha1(sha) => { + let output = sha.finalize_reset(); + result.copy_from_slice(output.as_slice()) + } + SoftwareHasher::Sha256(sha) => { + let output = sha.finalize_reset(); + result.copy_from_slice(output.as_slice()) + } + SoftwareHasher::Sha384(sha) => { + let output = sha.finalize_reset(); + result.copy_from_slice(output.as_slice()) + } + SoftwareHasher::Sha512(sha) => { + let output = sha.finalize_reset(); + result.copy_from_slice(output.as_slice()) + } + } + } +} + +#[cfg(esp32)] +impl Drop + for ShaContext +{ + fn drop(&mut self) { + ACCELERATOR_IN_USE.store(false, Ordering::Release); + } +} + +/// A handle for an in-progress operation, returned by [`update`](Sha1Context::update) or +/// [`finalize`](Sha1Context::finalize). +pub struct ShaHandle<'t>(Handle<'t, ShaOperation>); + +impl ShaHandle<'_> { + /// Polls the status of the work item. + /// + /// This function returns `true` if the item has been processed. + #[inline] + pub fn poll(&mut self) -> bool { + self.0.poll() + } + + /// Polls the work item to completion, by busy-looping. + /// + /// This function returns immediately if `poll` returns `true`. + #[inline] + pub fn wait_blocking(self) -> Status { + self.0.wait_blocking() + } + + /// Waits until the work item is completed. + #[inline] + pub fn wait(&mut self) -> impl Future { + self.0.wait() + } + + /// Cancels the work item and asynchronously waits until it is removed from the work queue. + #[inline] + pub fn cancel(&mut self) -> impl Future { + self.0.cancel() + } +} + +/// Error type returned by [`finalize_into_slice`](Sha1Context::finalize_into_slice). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum FinalizeError { + /// The provided buffer is smaller than the digest output of the algorithm. + BufferTooSmall, +} + +// Now implement the actual public types. +// Helper macro to limit the scope of `paste` +macro_rules! impl_worker_context { + ($name:ident, $full_name:literal, $algo:expr, $digest_len:literal, $block_size:literal ) => { + #[doc = concat!("A ", $full_name, " context.")] + #[cfg_attr(not(esp32), derive(Clone))] + pub struct $name(ShaContext<{ $algo.chunk_length() }, { $algo.digest_length() / 4 }>); + + impl $name { + /// Creates a new context. + /// + /// The context represents the in-progress processing of a single message. You need to + /// feed message bytes to [`Self::update`], then finalize the process using + /// [`Self::finalize`]. + /// + /// Any number of contexts can be created, to hash any number of messages concurrently. + pub fn new() -> Self { + Self(ShaContext::new($algo)) + } + + /// Hashes `data`. + pub fn update<'t>(&'t mut self, data: &'t [u8]) -> ShaHandle<'t> { + self.0.update(data) + } + + /// Finishes the hashing process, writes the final hash to `result`. + /// + /// Resets the context to an empty state. + pub fn finalize<'t>( + &'t mut self, + result: &mut [u8; { $algo.digest_length() }], + ) -> ShaHandle<'t> { + self.0.finalize(result) + } + + /// Finishes the hashing process, writes the final hash to `result`. + /// + /// Resets the context to an empty state. + /// + /// Returns [`FinalizeError::BufferTooSmall`] if `result` is not large enough to hold + /// the final hash. + pub fn finalize_into_slice<'t>( + &'t mut self, + result: &mut [u8], + ) -> Result, FinalizeError> { + if result.len() < $algo.digest_length() { + return Err(FinalizeError::BufferTooSmall); + } + + Ok(self.0.finalize(result)) + } + } + + impl Default for $name { + fn default() -> Self { + Self::new() + } + } + + // Implementing these implies Digest, too + impl digest::HashMarker for $name {} + + impl digest::OutputSizeUser for $name { + type OutputSize = paste::paste!(digest::consts::[< U $digest_len >]); + } + + impl digest::Update for $name { + fn update(&mut self, data: &[u8]) { + Self::update(self, data).wait_blocking(); + } + } + + impl digest::FixedOutput for $name { + fn finalize_into(mut self, out: &mut digest::Output) { + Self::finalize(&mut self, out.as_mut()).wait_blocking(); + } + } + + impl digest::core_api::BlockSizeUser for $name { + type BlockSize = paste::paste!(digest::consts::[< U $block_size >]); + + fn block_size() -> usize { + $block_size + } + } + }; +} + +for_each_sha_algorithm! { + ( $name:ident, $full_name:literal (sizes: $block_size:literal, $digest_len:literal, $message_length_bytes:literal) $security:tt, $mode_bits:literal ) => { + paste::paste! { + impl_worker_context!( [<$name Context>], $full_name, ShaAlgorithmKind::$name, $digest_len, $block_size ); + } + }; +} diff --git a/esp-hal/src/soc/esp32/clocks.rs b/esp-hal/src/soc/esp32/clocks.rs new file mode 100644 index 00000000000..77c613f4382 --- /dev/null +++ b/esp-hal/src/soc/esp32/clocks.rs @@ -0,0 +1,839 @@ +//! Clock tree definitions and implementations for ESP32. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (150k RC_SLOW and 8M RC_FAST) are not calibrated here, this system can +//! only give a rough estimate of their frequency. They can be calibrated separately using a known +//! crystal frequency. +//! - Some of the SOC capabilities are not implemented: using external 32K oscillator instead of a +//! crystal, divider for CLK8M and possibly more. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom}; + +use crate::{ + clock::Clocks, + efuse::VOL_LEVEL_HP_INV, + peripherals::{APB_CTRL, LPWR, RTC_IO, SYSTEM, TIMG0, TIMG1, UART0, UART1, UART2}, + rtc_cntl::Rtc, + soc::regi2c, + time::Rate, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, + + /// 240 MHz CPU clock + _240MHz = 240, +} + +impl CpuClock { + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_320), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_4), + syscon_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_320), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_2), + syscon_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; + const PRESET_240: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_480), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_2), + syscon_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + CpuClock::_240MHz => CpuClock::PRESET_240, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + v if v == CpuClock::PRESET_240 => Some(CpuClock::_240MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + // Switch CPU to XTAL before reconfiguring PLL. The bootloader may have left the CPU + // running on PLL, and changing PLL parameters while it is the active clock source would + // cause instability. + ClockTree::with(|clocks| { + configure_xtal_clk(clocks, XtalClkConfig::_40); + configure_syscon_pre_div(clocks, SysconPreDivConfig::new(0)); + configure_cpu_clk(clocks, CpuClkConfig::Xtal); + }); + + // Detect XTAL if unset. + // FIXME: this doesn't support running from RC_FAST_CLK. We should rework detection to + // only run when requesting XTAL. + if self.xtal_clk.is_none() { + let xtal = ClockTree::with(detect_xtal_freq); + debug!("Auto-detected XTAL frequency: {}", xtal.value()); + self.xtal_clk = Some(xtal); + } + + self.apply(); + } +} + +fn detect_xtal_freq(clocks: &mut ClockTree) -> XtalClkConfig { + // Estimate XTAL frequency using RC_FAST/256 as the calibration clock. RC_SLOW is too + // imprecise for reliable detection. + const CALIBRATION_CYCLES: u32 = 10; + + // The digital path for RC_FAST_D256 must be enabled for TIMG calibration to work. + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_clk8m_d256_en().set_bit()); + + let (xtal_cycles, calibration_clock_frequency) = Clocks::measure_rtc_clock( + clocks, + Timg0CalibrationClockConfig::RcFastDivClk, + CALIBRATION_CYCLES, + ); + + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_clk8m_d256_en().clear_bit()); + + let mhz = (calibration_clock_frequency * xtal_cycles / CALIBRATION_CYCLES).as_mhz(); + + if mhz.abs_diff(40) < mhz.abs_diff(26) { + XtalClkConfig::_40 + } else { + XtalClkConfig::_26 + } +} + +const BBPLL_IR_CAL_DELAY_VAL: u8 = 0x18; +const BBPLL_IR_CAL_EXT_CAP_VAL: u8 = 0x20; +const BBPLL_OC_ENB_FCAL_VAL: u8 = 0x9a; +const BBPLL_OC_ENB_VCON_VAL: u8 = 0x00; +const BBPLL_BBADC_CAL_7_0_VAL: u8 = 0x00; + +const RTC_CNTL_DBIAS_1V10: u8 = 4; +const RTC_CNTL_DBIAS_1V25: u8 = 7; + +const BBPLL_ENDIV5_VAL_320M: u8 = 0x43; +const BBPLL_BBADC_DSMP_VAL_320M: u8 = 0x84; +const BBPLL_ENDIV5_VAL_480M: u8 = 0xc3; +const BBPLL_BBADC_DSMP_VAL_480M: u8 = 0x74; + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. We save the value in a register + // similar to ESP-IDF, just in case something relies on that, or, if we can in the future read + // back the value instead of wasting RAM on it. + + // Used by `rtc_clk_xtal_freq_get` patched ROM function. + let freq_mhz = config.value() / 1_000_000; + LPWR::regs().store4().modify(|r, w| unsafe { + // The data is stored in two copies of 16-bit values. The first bit overwrites the LSB of + // the frequency value with DISABLE_ROM_LOG. + + // Copy the DISABLE_ROM_LOG bit + let disable_rom_log_bit = r.bits() & Rtc::RTC_DISABLE_ROM_LOG; + let half = (freq_mhz & (0xFFFF & !Rtc::RTC_DISABLE_ROM_LOG)) | disable_rom_log_bit; + w.data().bits(half | (half << 16)) + }); +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + // TODO: this should probably be refcounted with APLL, but ESP32 TRM states APLL_CLK is sourced + // from PLL. Currently it's unclear how these relate to each other - but the internal I2C bus is + // required for both of them. + LPWR::regs().options0().modify(|_, w| { + let power_down = !en; + w.bias_i2c_force_pd().bit(power_down); + w.bb_i2c_force_pd().bit(power_down); + w.bbpll_force_pd().bit(power_down); + w.bbpll_i2c_force_pd().bit(power_down) + }); + + if !en { + return; + } + + // Reset BBPLL calibration configuration. + regi2c::I2C_BBPLL_IR_CAL_DELAY.write_reg(BBPLL_IR_CAL_DELAY_VAL); + regi2c::I2C_BBPLL_IR_CAL_EXT_CAP.write_reg(BBPLL_IR_CAL_EXT_CAP_VAL); + regi2c::I2C_BBPLL_OC_ENB_FCAL.write_reg(BBPLL_OC_ENB_FCAL_VAL); + regi2c::I2C_BBPLL_OC_ENB_VCON.write_reg(BBPLL_OC_ENB_VCON_VAL); + regi2c::I2C_BBPLL_BBADC_CAL_REG.write_reg(BBPLL_BBADC_CAL_7_0_VAL); + + let xtal_cfg = unwrap!(clocks.xtal_clk); + let pll_cfg = unwrap!(clocks.pll_clk); + + // This classification is arbitrary based on variable names and what (PLL output or XTAL + // input) affects the values. + struct PllParams { + // The only parameter I could infer, it divides the reference clock (XTAL). We'll either + // use a reference of 40MHz or 2MHz (or maybe 20/1MHz). The rest of the parameters + // somehow ensure this divided reference clock is multiplied to the PLL target frequency. + div_ref: u8, + div10_8: u8, + lref: u8, + dcur: u8, + bw: u8, + div7_0: u8, + } + impl PllParams { + fn apply(&self) { + regi2c::I2C_BBPLL_OC_LREF + .write_reg((self.lref << 7) | (self.div10_8 << 4) | self.div_ref); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(self.div7_0); + regi2c::I2C_BBPLL_OC_DCUR.write_reg((self.bw << 6) | self.dcur); + } + } + + struct VoltageParams { + endiv5: u8, + bbadc_dsmp: u8, + dbias: u8, + } + impl VoltageParams { + fn apply(&self) { + LPWR::regs() + .reg() + .modify(|_, w| unsafe { w.dig_dbias_wak().bits(self.dbias) }); + + regi2c::I2C_BBPLL_ENDIV5.write_reg(self.endiv5); + regi2c::I2C_BBPLL_BBADC_DSMP.write_reg(self.bbadc_dsmp); + } + } + + let voltage_params = match pll_cfg { + PllClkConfig::_320 => VoltageParams { + dbias: RTC_CNTL_DBIAS_1V10, + endiv5: BBPLL_ENDIV5_VAL_320M, + bbadc_dsmp: BBPLL_BBADC_DSMP_VAL_320M, + }, + PllClkConfig::_480 => VoltageParams { + dbias: RTC_CNTL_DBIAS_1V25 - crate::efuse::read_field_le::(VOL_LEVEL_HP_INV), + endiv5: BBPLL_ENDIV5_VAL_480M, + bbadc_dsmp: BBPLL_BBADC_DSMP_VAL_480M, + }, + }; + + let pll_params = match xtal_cfg { + XtalClkConfig::_40 => PllParams { + div_ref: 0, // divided ref ~~ 40MHz + lref: 0, + dcur: 6, + bw: 3, + div10_8: 0, + div7_0: match pll_cfg { + PllClkConfig::_320 => 32, + PllClkConfig::_480 => 28, + }, + }, + + XtalClkConfig::_26 => PllParams { + div_ref: 12, // divided ref ~~ 2MHz + lref: 1, + dcur: 0, + bw: 1, + div10_8: 4, + div7_0: match pll_cfg { + PllClkConfig::_320 => 224, + PllClkConfig::_480 => 144, + }, + }, + }; + + // Apply voltage first, then PLL params. For 480MHz, the higher voltage needs time to + // stabilize before configuring PLL parameters. + voltage_params.apply(); + if matches!(pll_cfg, PllClkConfig::_480) { + ets_delay_us(3); + } + pll_params.apply(); + + // Wait for PLL to lock. Use the slower of the two possible wait times (80us for RC_SLOW, + // 160us for 32K XTAL). + const SOC_DELAY_PLL_ENABLE_WITH_32K: u32 = 160; + ets_delay_us(SOC_DELAY_PLL_ENABLE_WITH_32K); +} + +fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { + // Nothing to do. The PLL may still be powered down. We'll configure it in + // `enable_pll_clk_impl`. +} + +// APLL_CLK + +fn enable_apll_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs().ana_conf().modify(|_, w| { + w.plla_force_pd().bit(!en); + w.plla_force_pu().bit(en) + }); + if en { + todo!( + "Implement APLL configuration and calibration here. See esp-idf `clk_ll_apll_set_config`" + ); + } +} + +fn configure_apll_clk_impl(_clocks: &mut ClockTree, _config: ApllClkConfig) { + // Nothing to do. The APLL may still be powered down. We'll configure it in + // `enable_apll_clk_impl`. +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + const RTC_CNTL_CK8M_DFREQ_DEFAULT: u8 = 172; + LPWR::regs().clk_conf().modify(|_, w| { + // Set divider = 0 so that RC_FAST_CLK is 8MHz. + unsafe { + w.ck8m_div_sel().bits(0); + // CK8M_DFREQ value controls tuning of 8M clock. + w.ck8m_dfreq().bits(RTC_CNTL_CK8M_DFREQ_DEFAULT); + } + + let power_down = !en; + w.ck8m_force_pd().bit(power_down); + w.ck8m_force_pu().bit(!power_down) + }); +} + +// PLL_F160M_CLK + +fn enable_pll_f160m_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// CPU_PLL_DIV_IN + +// Not an actual MUX, used to allow configuring the DIVA divider as one block. +// Related to CPU clock source configuration. +fn enable_cpu_pll_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CpuPllDivInConfig, +) { + // Nothing to do. +} + +// CPU_PLL_DIV + +fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { + // Nothing to do. +} + +// SYSCON_PRE_DIV_IN + +// Not an actual MUX, used to allow configuring the DIVB divider as one block. +// Related to CPU clock source configuration. +fn enable_syscon_pre_div_in_impl(_: &mut ClockTree, _: bool) { + // Nothing to do. +} + +fn configure_syscon_pre_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: SysconPreDivInConfig, +) { + // Nothing to do. +} + +// SYSCON_PRE_DIV + +fn enable_syscon_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_syscon_pre_div_impl(_clocks: &mut ClockTree, new_config: SysconPreDivConfig) { + APB_CTRL::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) }); +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: ApbClkConfig, +) { + // Nothing to do. +} + +// REF_TICK + +fn enable_ref_tick_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: RefTickConfig, +) { + // Nothing to do. +} + +// REF_TICK_XTAL + +fn enable_ref_tick_xtal_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_xtal_impl(_clocks: &mut ClockTree, new_config: RefTickXtalConfig) { + APB_CTRL::regs() + .xtal_tick_conf() + .write(|w| unsafe { w.xtal_tick_num().bits(new_config.divisor() as u8) }); +} + +// REF_TICK_FOSC + +fn enable_ref_tick_fosc_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_fosc_impl(_clocks: &mut ClockTree, new_config: RefTickFoscConfig) { + APB_CTRL::regs() + .ck8m_tick_conf() + .write(|w| unsafe { w.ck8m_tick_num().bits(new_config.divisor() as u8) }); +} + +// REF_TICK_APLL + +fn enable_ref_tick_apll_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_apll_impl(_clocks: &mut ClockTree, new_config: RefTickApllConfig) { + APB_CTRL::regs() + .apll_tick_conf() + .write(|w| unsafe { w.apll_tick_num().bits(new_config.divisor() as u8) }); +} + +// REF_TICK_PLL + +fn enable_ref_tick_pll_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_pll_impl(_clocks: &mut ClockTree, new_config: RefTickPllConfig) { + APB_CTRL::regs() + .pll_tick_conf() + .write(|w| unsafe { w.pll_tick_num().bits(new_config.divisor() as u8) }); +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CpuClkConfig, +) { + // Based on TRM Table 7.2-2 + + // Configure CPUPERIOD divider(?) to output(?) 80, 160 or 240MHz, although because APLL is + // freely configurable the final clock can be different. + let pll_selector = clocks.cpu_pll_div_in; + let bbpll_config = clocks.pll_clk.map(|pll| pll.value()).unwrap_or_default(); + let is_240mhz = pll_selector == Some(CpuPllDivInConfig::Pll) && bbpll_config == 480_000_000; + let clock_source_sel1_bit = match clocks.cpu_pll_div { + Some(CpuPllDivConfig::_2) => { + if is_240mhz { + 2 + } else { + 1 + } + } + _ => 0, // divide-by-4 or don't care + }; + + SYSTEM::regs() + .cpu_per_conf() + .modify(|_, w| unsafe { w.cpuperiod_sel().bits(clock_source_sel1_bit) }); + + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + CpuClkConfig::Xtal => w.soc_clk_sel().xtal(), + CpuClkConfig::RcFast => w.soc_clk_sel().ck8m(), + CpuClkConfig::Apll => w.soc_clk_sel().apll(), + CpuClkConfig::Pll => w.soc_clk_sel().pll(), + }); + + // Store frequencies in expected places. + let cpu_freq = Rate::from_hz(cpu_clk_frequency(clocks)); + ets_update_cpu_frequency_rom(cpu_freq.as_mhz()); + + let apb_freq = Rate::from_hz(apb_clk_frequency(clocks)); + update_apb_frequency(apb_freq); +} + +fn update_apb_frequency(freq: Rate) { + let freq_shifted = (freq.as_hz() >> 12) & 0xFFFF; + let value = freq_shifted | (freq_shifted << 16); + LPWR::regs() + .store5() + .modify(|_, w| unsafe { w.data().bits(value) }); +} + +// APB_CLK_CPU_DIV2 + +fn enable_apb_clk_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// APB_CLK_80M + +fn enable_apb_clk_80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + // RTCIO could be configured to allow an external oscillator to be used. We could model this + // with a MUX, probably, but this is omitted for now for simplicity. + + // TODO: implement esp-idf's CONFIG_RTC_EXT_CRYST_ADDIT_CURRENT options. + const CLK_LL_XTAL_32K_DAC_VAL: u8 = 1; + const CLK_LL_XTAL_32K_DRES_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DBIAS_VAL: u8 = 0; + RTC_IO::regs().xtal_32k_pad().modify(|_, w| unsafe { + w.dac_xtal_32k().bits(CLK_LL_XTAL_32K_DAC_VAL); + w.dres_xtal_32k().bits(CLK_LL_XTAL_32K_DRES_VAL); + w.dbias_xtal_32k().bits(CLK_LL_XTAL_32K_DBIAS_VAL); + + if en { + w.x32n_rde().clear_bit(); + w.x32n_rue().clear_bit(); + w.x32n_fun_ie().clear_bit(); + + w.x32p_rde().clear_bit(); + w.x32p_rue().clear_bit(); + w.x32p_fun_ie().clear_bit(); + } + + w.x32n_mux_sel().bit(en); + w.x32p_mux_sel().bit(en); + w.xpd_xtal_32k().bit(en) + }); + + // Enable for digital part + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_xtal32k_en().bit(en)); +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of 150k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255; + LPWR::regs() + .reg() + .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) }); + } +} + +// RC_FAST_DIV_CLK + +fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs().clk_conf().modify(|_, w| { + // Active-low: clear to enable, set to disable. + w.enb_ck8m_div().bit(!en); + w.ck8m_div().div256() + }); +} + +// XTAL_DIV_CLK + +fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RTC_SLOW_CLK + +fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcSlowClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + RtcSlowClkConfig::RcSlow => w.ana_clk_rtc_sel().slow_ck(), + RtcSlowClkConfig::Xtal32k => w.ana_clk_rtc_sel().ck_xtal_32k(), + RtcSlowClkConfig::RcFast => w.ana_clk_rtc_sel().ck8m_d256_out(), + }); +} + +// RTC_FAST_CLK + +fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcFastClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + RtcFastClkConfig::Xtal => w.fast_clk_rtc_sel().xtal_div_4(), + RtcFastClkConfig::Rc => w.fast_clk_rtc_sel().ck8m(), + }); +} + +// UART_MEM_CLK + +fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: these functions (peripheral bus clock control) should be generated, + // replacing current PeripheralClockControl code. + // Enabling clock should probably not reset the peripheral. + let regs = SYSTEM::regs(); + + if en { + regs.perip_rst_en() + .modify(|_, w| w.uart_mem_rst().bit(true)); + regs.perip_rst_en() + .modify(|_, w| w.uart_mem_rst().bit(false)); + } + + regs.perip_clk_en() + .modify(|_, w| w.uart_mem_clk_en().bit(en)); +} + +// MCPWM0_FUNCTION_CLOCK + +fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mcpwm0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Mcpwm0FunctionClockConfig, +) { + // Nothing to do. +} + +// MCPWM1_FUNCTION_CLOCK + +fn enable_mcpwm1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mcpwm1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Mcpwm0FunctionClockConfig, +) { + // Nothing to do. +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +impl Timg0CalibrationClockConfig { + fn cali_clk_sel_bits(self) -> u8 { + match self { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + } + } +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs() + .rtccalicfg() + .modify(|_, w| unsafe { w.rtc_cali_clk_sel().bits(new_selector.cali_clk_sel_bits()) }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs() + .rtccalicfg() + .modify(|_, w| unsafe { w.rtc_cali_clk_sel().bits(new_selector.cali_clk_sel_bits()) }); +} + +// UART0_MEM_CLOCK + +fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart0_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART0::regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(new_selector == Uart0FunctionClockConfig::Apb) + }); +} + +// UART1_MEM_CLOCK + +fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart1_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART1::regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(new_selector == Uart0FunctionClockConfig::Apb) + }); +} + +// UART2_MEM_CLOCK + +fn enable_uart2_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart2_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART2_FUNCTION_CLOCK + +fn enable_uart2_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_uart2_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART2::regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(new_selector == Uart0FunctionClockConfig::Apb) + }); +} diff --git a/esp-hal/src/soc/esp32/cpu_control.rs b/esp-hal/src/soc/esp32/cpu_control.rs new file mode 100644 index 00000000000..483903402b4 --- /dev/null +++ b/esp-hal/src/soc/esp32/cpu_control.rs @@ -0,0 +1,213 @@ +//! # Control CPU Cores (ESP32) +//! +//! ## Overview +//! +//! This module provides essential functionality for controlling +//! and managing the APP (second) CPU core on the `ESP32` chip. It is used to +//! start and stop program execution on the APP core. + +use core::sync::atomic::Ordering; + +use crate::{ + peripherals::{DPORT, LPWR, SPI0}, + system::{Cpu, multi_core::*}, +}; + +pub(crate) unsafe fn internal_park_core(core: Cpu, park: bool) { + let c1_value = if park { 0x21 } else { 0 }; + let c0_value = if park { 0x02 } else { 0 }; + match core { + Cpu::ProCpu => { + LPWR::regs() + .sw_cpu_stall() + .modify(|_, w| unsafe { w.sw_stall_procpu_c1().bits(c1_value) }); + LPWR::regs() + .options0() + .modify(|_, w| unsafe { w.sw_stall_procpu_c0().bits(c0_value) }); + } + Cpu::AppCpu => { + LPWR::regs() + .sw_cpu_stall() + .modify(|_, w| unsafe { w.sw_stall_appcpu_c1().bits(c1_value) }); + LPWR::regs() + .options0() + .modify(|_, w| unsafe { w.sw_stall_appcpu_c0().bits(c0_value) }); + } + } +} + +/// Returns `true` if the specified core is currently running (not stalled). +#[instability::unstable] +pub fn is_running(core: Cpu) -> bool { + if core == Cpu::AppCpu { + let dport = DPORT::regs(); + // DPORT_APPCPU_CLKGATE_EN in APPCPU_CTRL_B bit 0 -> needs to be 1 to even be enabled + // DPORT_APPCPU_RUNSTALL in APPCPU_CTRL_C bit 0 -> needs to be 0 to not stall + if dport + .appcpu_ctrl_b() + .read() + .appcpu_clkgate_en() + .bit_is_clear() + || dport.appcpu_ctrl_c().read().appcpu_runstall().bit_is_set() + { + // If the core is not enabled or is stallled, we can take this shortcut + return false; + } + } + + // sw_stall_appcpu_c1[5:0], sw_stall_appcpu_c0[1:0]} == 0x86 will stall APP CPU + // sw_stall_procpu_c1[5:0], reg_sw_stall_procpu_c0[1:0]} == 0x86 will stall PRO CPU + let is_stalled = match core { + Cpu::ProCpu => { + let c1 = LPWR::regs() + .sw_cpu_stall() + .read() + .sw_stall_procpu_c1() + .bits(); + let c0 = LPWR::regs().options0().read().sw_stall_procpu_c0().bits(); + (c1 << 2) | c0 + } + Cpu::AppCpu => { + let c1 = LPWR::regs() + .sw_cpu_stall() + .read() + .sw_stall_appcpu_c1() + .bits(); + let c0 = LPWR::regs().options0().read().sw_stall_appcpu_c0().bits(); + (c1 << 2) | c0 + } + }; + + is_stalled != 0x86 +} + +fn flush_cache(core: Cpu) { + let dport_control = DPORT::regs(); + + match core { + Cpu::ProCpu => { + dport_control + .pro_cache_ctrl() + .modify(|_, w| w.pro_cache_flush_ena().clear_bit()); + dport_control + .pro_cache_ctrl() + .modify(|_, w| w.pro_cache_flush_ena().set_bit()); + while dport_control + .pro_cache_ctrl() + .read() + .pro_cache_flush_done() + .bit_is_clear() + {} + + dport_control + .pro_cache_ctrl() + .modify(|_, w| w.pro_cache_flush_ena().clear_bit()); + } + Cpu::AppCpu => { + dport_control + .app_cache_ctrl() + .modify(|_, w| w.app_cache_flush_ena().clear_bit()); + dport_control + .app_cache_ctrl() + .modify(|_, w| w.app_cache_flush_ena().set_bit()); + while dport_control + .app_cache_ctrl() + .read() + .app_cache_flush_done() + .bit_is_clear() + {} + dport_control + .app_cache_ctrl() + .modify(|_, w| w.app_cache_flush_ena().clear_bit()); + } + }; +} + +fn enable_cache(core: Cpu) { + let spi0 = SPI0::regs(); + let dport_control = DPORT::regs(); + + match core { + Cpu::ProCpu => { + spi0.cache_fctrl().modify(|_, w| w.cache_req_en().set_bit()); + dport_control + .pro_cache_ctrl() + .modify(|_, w| w.pro_cache_enable().set_bit()); + } + Cpu::AppCpu => { + spi0.cache_fctrl().modify(|_, w| w.cache_req_en().set_bit()); + dport_control + .app_cache_ctrl() + .modify(|_, w| w.app_cache_enable().set_bit()); + } + }; +} + +pub(crate) fn start_core1(entry_point: *const u32) { + let dport_control = DPORT::regs(); + + flush_cache(Cpu::AppCpu); + enable_cache(Cpu::AppCpu); + + dport_control + .appcpu_ctrl_d() + .write(|w| unsafe { w.appcpu_boot_addr().bits(entry_point as u32) }); + + dport_control + .appcpu_ctrl_b() + .modify(|_, w| w.appcpu_clkgate_en().set_bit()); + dport_control + .appcpu_ctrl_c() + .modify(|_, w| w.appcpu_runstall().clear_bit()); + dport_control + .appcpu_ctrl_a() + .modify(|_, w| w.appcpu_resetting().set_bit()); + dport_control + .appcpu_ctrl_a() + .modify(|_, w| w.appcpu_resetting().clear_bit()); +} + +pub(crate) fn start_core1_init() -> ! +where + F: FnOnce(), +{ + // disables interrupts + unsafe { + xtensa_lx::interrupt::set_mask(0); + } + + // reset cycle compare registers + xtensa_lx::timer::set_ccompare0(0); + xtensa_lx::timer::set_ccompare1(0); + xtensa_lx::timer::set_ccompare2(0); + + unsafe extern "C" { + static mut _init_start: u32; + } + + // set vector table and stack pointer + unsafe { + xtensa_lx::set_vecbase(&raw const _init_start); + xtensa_lx::set_stack_pointer(APP_CORE_STACK_TOP.load(Ordering::Acquire)); + + #[cfg(all(feature = "rt", stack_guard_monitoring))] + { + let stack_guard = APP_CORE_STACK_GUARD.load(Ordering::Acquire); + stack_guard.write_volatile(esp_config::esp_config_int!( + u32, + "ESP_HAL_CONFIG_STACK_GUARD_VALUE" + )); + // setting 0 effectively disables the functionality + crate::debugger::set_stack_watchpoint(stack_guard as usize); + } + } + + // Do not call setup_interrupts as that would disable peripheral interrupts, too. + unsafe { crate::interrupt::init_vectoring() }; + + // Trampoline to run from the new stack. + // start_core1_run should _NEVER_ be inlined + // as we rely on the function call to use + // the new stack. + unsafe { CpuControl::start_core1_run::() } +} diff --git a/esp-hal/src/soc/esp32/gpio.rs b/esp-hal/src/soc/esp32/gpio.rs new file mode 100644 index 00000000000..a4f7a2b93f3 --- /dev/null +++ b/esp-hal/src/soc/esp32/gpio.rs @@ -0,0 +1,326 @@ +//! # GPIO configuration module (ESP32) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `errata36(pin_num: u8, pull_up: bool, pull_down: bool)`: +//! * Handles the configuration of pull-up and pull-down resistors for specific GPIO pins +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +macro_rules! rtcio_analog { + ($pin_peri:ident, $rtc_pin:expr, $pin_reg:expr, $prefix:pat, $hold:ident) => { + paste::paste! { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for $crate::peripherals::$pin_peri<'_> { + fn rtc_number(&self) -> u8 { + $rtc_pin + } + + /// Set the RTC properties of the pin. If `mux` is true then then pin is + /// routed to RTC, when false it is routed to IO_MUX. + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: $crate::gpio::RtcFunction) { + // disable input + $crate::peripherals::RTC_IO::regs() + .$pin_reg.modify(|_,w| unsafe { + w.[<$prefix fun_ie>]().bit(input_enable); + w.[<$prefix mux_sel>]().bit(mux); + w.[<$prefix fun_sel>]().bits(func as u8) + }); + } + + fn rtcio_pad_hold(&self, enable: bool) { + $crate::peripherals::LPWR::regs() + .hold_force() + .modify(|_, w| w.$hold().bit(enable)); + } + } + + for_each_gpio! { + // Implement RtcPinWithResistors if $pin_peri is an output pin + ($n:tt, $pin_peri $in_afs:tt $out_afs:tt ($input:tt [Output])) => { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPinWithResistors for $crate::peripherals::$pin_peri<'_> { + fn rtcio_pullup(&self, enable: bool) { + $crate::peripherals::RTC_IO::regs() + .$pin_reg.modify(|_, w| w.[< $prefix rue >]().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + $crate::peripherals::RTC_IO::regs() + .$pin_reg.modify(|_, w| w.[< $prefix rde >]().bit(enable)); + } + } + }; + } + + impl $crate::peripherals::$pin_peri<'_> { + /// Configures the pin for analog mode. + #[cfg(feature = "unstable")] + pub(crate) fn set_analog_impl(&self) { + use $crate::gpio::RtcPin; + let rtcio = $crate::peripherals::RTC_IO::regs(); + + // disable output + rtcio.enable_w1tc().write(|w| unsafe { w.enable_w1tc().bits(1 << self.rtc_number()) }); + + // disable open drain + rtcio.pin(self.rtc_number() as usize).modify(|_,w| w.pad_driver().bit(false)); + + rtcio.$pin_reg.modify(|_,w| { + w.[<$prefix fun_ie>]().clear_bit(); + + // Connect pin to analog / RTC module instead of standard GPIO + w.[<$prefix mux_sel>]().set_bit(); + + // Select function "RTC function 1" (GPIO) for analog use + unsafe { w.[<$prefix fun_sel>]().bits(0b00) }; + + for_each_gpio! { + // Disable pull-up and pull-down resistors on the pin, if it has them + ($n:tt, $pin_peri $in_afs:tt $out_afs:tt ($input:tt [Output])) => { + w.[<$prefix rue>]().bit(false); + w.[<$prefix rde>]().bit(false); + }; + } + + w + }); + } + } + } + }; + + ( + $( ( $pin_peri:ident, $rtc_pin:expr, $pin_reg:expr, $prefix:pat, $hold:ident $(, $rue:literal )? ) )+ + ) => { + $( + rtcio_analog!($pin_peri, $rtc_pin, $pin_reg, $prefix, $hold $(, $rue )?); + )+ + }; +} + +#[rustfmt::skip] +macro_rules! touch_out_reg { + ($regs:expr, 0) => { $regs.sar_touch_out1() }; + ($regs:expr, 1) => { $regs.sar_touch_out1() }; + ($regs:expr, 2) => { $regs.sar_touch_out2() }; + ($regs:expr, 3) => { $regs.sar_touch_out2() }; + ($regs:expr, 4) => { $regs.sar_touch_out3() }; + ($regs:expr, 5) => { $regs.sar_touch_out3() }; + ($regs:expr, 6) => { $regs.sar_touch_out4() }; + ($regs:expr, 7) => { $regs.sar_touch_out4() }; + ($regs:expr, 8) => { $regs.sar_touch_out5() }; + ($regs:expr, 9) => { $regs.sar_touch_out5() }; +} + +#[rustfmt::skip] +macro_rules! touch_thres_reg { + ($regs:expr, 0) => { $regs.sar_touch_thres1() }; + ($regs:expr, 1) => { $regs.sar_touch_thres1() }; + ($regs:expr, 2) => { $regs.sar_touch_thres2() }; + ($regs:expr, 3) => { $regs.sar_touch_thres2() }; + ($regs:expr, 4) => { $regs.sar_touch_thres3() }; + ($regs:expr, 5) => { $regs.sar_touch_thres3() }; + ($regs:expr, 6) => { $regs.sar_touch_thres4() }; + ($regs:expr, 7) => { $regs.sar_touch_thres4() }; + ($regs:expr, 8) => { $regs.sar_touch_thres5() }; + ($regs:expr, 9) => { $regs.sar_touch_thres5() }; +} + +#[rustfmt::skip] +macro_rules! touch_pad_reg { + ($regs:expr, 0) => { $regs.touch_pad0() }; + ($regs:expr, 1) => { $regs.touch_pad1() }; + ($regs:expr, 2) => { $regs.touch_pad2() }; + ($regs:expr, 3) => { $regs.touch_pad3() }; + ($regs:expr, 4) => { $regs.touch_pad4() }; + ($regs:expr, 5) => { $regs.touch_pad5() }; + ($regs:expr, 6) => { $regs.touch_pad6() }; + ($regs:expr, 7) => { $regs.touch_pad7() }; + ($regs:expr, 8) => { $regs.touch_pad8() }; + ($regs:expr, 9) => { $regs.touch_pad9() }; +} + +#[rustfmt::skip] +macro_rules! touch_out_th_field { + ($accessor:expr, 0) => { $accessor.touch_out_th0() }; + ($accessor:expr, 1) => { $accessor.touch_out_th1() }; + ($accessor:expr, 2) => { $accessor.touch_out_th0() }; + ($accessor:expr, 3) => { $accessor.touch_out_th1() }; + ($accessor:expr, 4) => { $accessor.touch_out_th0() }; + ($accessor:expr, 5) => { $accessor.touch_out_th1() }; + ($accessor:expr, 6) => { $accessor.touch_out_th0() }; + ($accessor:expr, 7) => { $accessor.touch_out_th1() }; + ($accessor:expr, 8) => { $accessor.touch_out_th0() }; + ($accessor:expr, 9) => { $accessor.touch_out_th1() }; +} + +#[rustfmt::skip] +macro_rules! touch_meas_out_field { + ($accessor:expr, 0) => { $accessor.touch_meas_out0() }; + ($accessor:expr, 1) => { $accessor.touch_meas_out1() }; + ($accessor:expr, 2) => { $accessor.touch_meas_out0() }; + ($accessor:expr, 3) => { $accessor.touch_meas_out1() }; + ($accessor:expr, 4) => { $accessor.touch_meas_out0() }; + ($accessor:expr, 5) => { $accessor.touch_meas_out1() }; + ($accessor:expr, 6) => { $accessor.touch_meas_out0() }; + ($accessor:expr, 7) => { $accessor.touch_meas_out1() }; + ($accessor:expr, 8) => { $accessor.touch_meas_out0() }; + ($accessor:expr, 9) => { $accessor.touch_meas_out1() }; +} + +/// Common functionality for all touch pads +macro_rules! touch { + ( + $( + ($touch_num:tt, $pin_peri:ident $(, $pad_register_has_all_fields:literal)?) + )+ + ) => { + $( + impl $crate::gpio::TouchPin for $crate::peripherals::$pin_peri<'_> { + fn set_touch(&self, _: $crate::private::Internal) { + use $crate::peripherals::{GPIO, RTC_IO, SENS}; + use $crate::gpio::RtcPin; + + let gpio = GPIO::regs(); + let rtcio = RTC_IO::regs(); + let sens = SENS::regs(); + + // Pad to normal mode (not open-drain) + gpio.pin(self.rtc_number() as usize).write(|w| w.pad_driver().clear_bit()); + + // clear output + rtcio + .enable_w1tc() + .write(|w| unsafe { w.enable_w1tc().bits(1 << self.rtc_number()) }); + + touch_thres_reg!(sens, $touch_num).write(|w| unsafe { + touch_out_th_field!(w, $touch_num).bits( + 0b0 // Default: 0 for esp32 gets overridden later anyway. + ) + }); + + touch_pad_reg!(RTC_IO::regs(), $touch_num).write( + #[allow(unused_unsafe)] + |w| unsafe { + w.xpd().set_bit(); + w.tie_opt().clear_bit(); + + // touch_pad8 and 9 are missing a few fields + $( + crate::ignore!($pad_register_has_all_fields); + + // clear input_enable + w.fun_ie().clear_bit(); + // Connect pin to analog / RTC module instead of standard GPIO + w.mux_sel().set_bit(); + // Disable pull-up and pull-down resistors on the pin + w.rue().clear_bit(); + w.rde().clear_bit(); + // Select function "RTC function 1" (GPIO) for analog use + w.fun_sel().bits(0b00); + )? + + w + } + ); + + // enable the pin + sens.sar_touch_enable().modify(|r, w| unsafe { + w.touch_pad_worken().bits( + r.touch_pad_worken().bits() | ( 1 << $touch_num ) + ) + }); + } + + fn touch_measurement(&self, _: $crate::private::Internal) -> u16 { + let regs = $crate::peripherals::SENS::regs(); + let reg = touch_out_reg!(regs, $touch_num).read(); + touch_meas_out_field!(reg, $touch_num).bits() + } + + fn touch_nr(&self, _: $crate::private::Internal) -> u8 { + $touch_num + } + + fn set_threshold(&self, threshold: u16, _: $crate::private::Internal) { + let sens = $crate::peripherals::SENS::regs(); + touch_thres_reg!(sens, $touch_num).write(|w| unsafe { + touch_out_th_field!(w, $touch_num).bits(threshold) + }); + } + })+ + }; +} + +pub(crate) fn errata36(pin: crate::gpio::AnyPin<'_>, pull_up: bool, pull_down: bool) { + use crate::gpio::{Pin, RtcPinWithResistors}; + + for_each_lp_function! { + (all_expanded $( (($_sig:ident, RTC_GPIOn, $_n:literal), $gpio:ident) ),* ) => { + const RTC_IO_PINS: &[u8] = &[ $( $crate::peripherals::$gpio::NUMBER ),* ]; + }; + }; + + if RTC_IO_PINS.contains(&pin.number()) && pin.is_output() { + pin.rtcio_pullup(pull_up); + pin.rtcio_pulldown(pull_down); + } +} + +rtcio_analog! { + (GPIO36, 0, sensor_pads(), sense1_, sense1 ) + (GPIO37, 1, sensor_pads(), sense2_, sense2 ) + (GPIO38, 2, sensor_pads(), sense3_, sense3 ) + (GPIO39, 3, sensor_pads(), sense4_, sense4 ) + (GPIO34, 4, adc_pad(), adc1_, adc1 ) + (GPIO35, 5, adc_pad(), adc2_, adc2 ) + (GPIO25, 6, pad_dac1(), "", pdac1 ) + (GPIO26, 7, pad_dac2(), "", pdac2 ) + (GPIO33, 8, xtal_32k_pad(), x32n_, x32n ) + (GPIO32, 9, xtal_32k_pad(), x32p_, x32p ) + (GPIO4, 10, touch_pad0(), "", touch_pad0) + (GPIO0, 11, touch_pad1(), "", touch_pad1) + (GPIO2, 12, touch_pad2(), "", touch_pad2) + (GPIO15, 13, touch_pad3(), "", touch_pad3) + (GPIO13, 14, touch_pad4(), "", touch_pad4) + (GPIO12, 15, touch_pad5(), "", touch_pad5) + (GPIO14, 16, touch_pad6(), "", touch_pad6) + (GPIO27, 17, touch_pad7(), "", touch_pad7) +} + +touch! { + // touch_nr, pin_nr, normal_pin + (0, GPIO4, true) + (1, GPIO0, true) + (2, GPIO2, true) + (3, GPIO15, true) + (4, GPIO13, true) + (5, GPIO12, true) + (6, GPIO14, true) + (7, GPIO27, true) + // --- touch_pad8 and 9 are missing a few fields + (8, GPIO33) + (9, GPIO32) +} diff --git a/esp-hal/src/soc/esp32/mod.rs b/esp-hal/src/soc/esp32/mod.rs new file mode 100644 index 00000000000..a839c2bb881 --- /dev/null +++ b/esp-hal/src/soc/esp32/mod.rs @@ -0,0 +1,29 @@ +//! # SOC (System-on-Chip) module (ESP32) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32` chip. + +crate::unstable_module! { + pub mod clocks; + pub mod trng; +} +#[cfg(feature = "unstable")] +pub mod cpu_control; +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// The base clock frequency for the I2S peripheral (Hertz). + pub const I2S_SCLK: u32 = 160_000_000; + /// The default clock source for I2S operations. + pub const I2S_DEFAULT_CLK_SRC: u32 = 2; +} + +pub(crate) unsafe fn configure_cpu_caches() {} + +pub(crate) fn pre_init() {} diff --git a/esp-hal/src/soc/esp32/regi2c.rs b/esp-hal/src/soc/esp32/regi2c.rs new file mode 100644 index 00000000000..bed60e4658b --- /dev/null +++ b/esp-hal/src/soc/esp32/regi2c.rs @@ -0,0 +1,30 @@ +use crate::rom::regi2c::{RegI2cMaster, RegI2cRegister, define_regi2c}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 4) { + reg: I2C_BBPLL_IR_CAL_DELAY(0) {} + reg: I2C_BBPLL_IR_CAL_EXT_CAP(1) {} + reg: I2C_BBPLL_OC_LREF(2) {} + reg: I2C_BBPLL_OC_DIV_REG(3) {} + reg: I2C_BBPLL_OC_ENB_FCAL(4) {} + reg: I2C_BBPLL_OC_DCUR(5) {} + reg: I2C_BBPLL_BBADC_DSMP(9) {} + reg: I2C_BBPLL_OC_ENB_VCON(10) {} + reg: I2C_BBPLL_ENDIV5(11) {} + reg: I2C_BBPLL_BBADC_CAL_REG(12) {} + } +} + +pub(crate) fn regi2c_read(block: u8, host_id: u8, reg_add: u8) -> u8 { + unsafe extern "C" { + pub(crate) fn esp_rom_regi2c_read(block: u8, block_hostid: u8, reg_add: u8) -> u8; + } + unsafe { esp_rom_regi2c_read(block, host_id, reg_add) } +} + +pub(crate) fn regi2c_write(block: u8, host_id: u8, reg_add: u8, data: u8) { + unsafe extern "C" { + pub(crate) fn rom_i2c_writeReg(block: u8, block_hostid: u8, reg_add: u8, indata: u8); + } + unsafe { rom_i2c_writeReg(block, host_id, reg_add, data) }; +} diff --git a/esp-hal/src/soc/esp32/trng.rs b/esp-hal/src/soc/esp32/trng.rs new file mode 100644 index 00000000000..eab954a6be0 --- /dev/null +++ b/esp-hal/src/soc/esp32/trng.rs @@ -0,0 +1,158 @@ +//! Helper functions for TRNG functionality + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + let rtc_cntl = crate::peripherals::LPWR::regs(); + let sens = crate::peripherals::SENS::regs(); + let dport = crate::peripherals::DPORT::regs(); + let apb_ctrl = crate::peripherals::APB_CTRL::regs(); + let i2s0 = crate::peripherals::I2S0::regs(); + + unsafe { + rtc_cntl.test_mux().modify(|_, w| w.dtest_rtc().bits(2)); + + rtc_cntl.test_mux().modify(|_, w| w.ent_rtc().set_bit()); + + sens.sar_start_force() + .modify(|_, w| w.sar2_en_test().set_bit()); + + // periph_module_enable(PERIPH_I2S0_MODULE); + dport + .perip_clk_en() + .modify(|_, w| w.i2c_ext0_clk_en().set_bit()); + + dport + .perip_rst_en() + .modify(|_, w| w.i2c_ext0_rst().clear_bit()); + + sens.sar_start_force() + .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); + + sens.sar_start_force() + .modify(|_, w| w.ulp_cp_start_top().clear_bit()); + + // Test pattern configuration byte 0xAD: + //--[7:4] channel_sel: 10-->en_test + //--[3:2] bit_width : 3-->12bit + //--[1:0] atten : 1-->3dB attenuation + apb_ctrl + .apb_saradc_sar2_patt_tab1() + .write(|w| w.bits(0xADADADAD)); + apb_ctrl + .apb_saradc_sar2_patt_tab2() + .write(|w| w.bits(0xADADADAD)); + apb_ctrl + .apb_saradc_sar2_patt_tab3() + .write(|w| w.bits(0xADADADAD)); + apb_ctrl + .apb_saradc_sar2_patt_tab4() + .write(|w| w.bits(0xADADADAD)); + + sens.sar_meas_wait2() + .modify(|_, w| w.force_xpd_sar().bits(3)); + + sens.sar_read_ctrl() + .modify(|_, w| w.sar1_dig_force().set_bit()); + + sens.sar_read_ctrl2() + .modify(|_, w| w.sar2_dig_force().set_bit()); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_sar2_mux().set_bit()); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_sar_clk_div().bits(4)); + + apb_ctrl + .apb_saradc_fsm() + .modify(|_, w| w.saradc_rstb_wait().bits(8)); + + apb_ctrl + .apb_saradc_fsm() + .modify(|_, w| w.saradc_start_wait().bits(10)); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_work_mode().bits(0)); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_sar_sel().set_bit()); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_data_sar_sel().clear_bit()); + + i2s0.sample_rate_conf() + .modify(|_, w| w.rx_bck_div_num().bits(20)); + + apb_ctrl + .apb_saradc_ctrl() + .modify(|_, w| w.saradc_data_to_i2s().set_bit()); + + i2s0.conf2().modify(|_, w| w.camera_en().set_bit()); + + i2s0.conf2().modify(|_, w| w.lcd_en().set_bit()); + + i2s0.conf2().modify(|_, w| w.data_enable().set_bit()); + + i2s0.conf2() + .modify(|_, w| w.data_enable_test_en().set_bit()); + + i2s0.conf().modify(|_, w| w.rx_start().set_bit()); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + let sens = crate::peripherals::SENS::regs(); + let i2s0 = crate::peripherals::I2S0::regs(); + let apb_ctrl = crate::peripherals::APB_CTRL::regs(); + + unsafe { + i2s0.conf().modify(|_, w| w.rx_start().clear_bit()); + + i2s0.conf().modify(|_, w| w.rx_reset().set_bit()); + + i2s0.conf().modify(|_, w| w.rx_reset().clear_bit()); + + i2s0.conf2().modify(|_, w| { + w.camera_en() + .clear_bit() + .lcd_en() + .clear_bit() + .data_enable_test_en() + .clear_bit() + .data_enable() + .clear_bit() + }); + + sens.sar_read_ctrl() + .modify(|_, w| w.sar1_dig_force().clear_bit()); + + sens.sar_read_ctrl2() + .modify(|_, w| w.sar2_dig_force().clear_bit()); + + sens.sar_start_force() + .modify(|_, w| w.sar2_en_test().clear_bit()); + + apb_ctrl.apb_saradc_ctrl().modify(|_, w| { + w.saradc_sar2_mux() + .clear_bit() + .saradc_sar_sel() + .clear_bit() + .saradc_data_to_i2s() + .clear_bit() + }); + + sens.sar_meas_wait2() + .modify(|_, w| w.force_xpd_sar().bits(0)); + + apb_ctrl + .apb_saradc_fsm() + .modify(|_, w| w.saradc_start_wait().bits(8)); + } +} diff --git a/esp-hal/src/soc/esp32c2/clocks.rs b/esp-hal/src/soc/esp32c2/clocks.rs new file mode 100644 index 00000000000..8d6245b1cd9 --- /dev/null +++ b/esp-hal/src/soc/esp32c2/clocks.rs @@ -0,0 +1,718 @@ +//! Clock tree definitions and implementations for ESP32-C2. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (136k RC_SLOW and 17.5M RC_FAST) are not calibrated here, this system +//! can only give a rough estimate of their frequency. They can be calibrated separately using a +//! known crystal frequency. +//! - Some of the SOC capabilities are not implemented: using external 32K oscillator, divider for +//! RC_FAST and possibly more. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom}; + +use crate::{ + clock::Clocks, + peripherals::{I2C_ANA_MST, LPWR, SYSTEM, TIMG0, UART0, UART1}, + rtc_cntl::Rtc, + soc::regi2c, + time::Rate, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 120 MHz CPU clock + _120MHz = 120, +} + +impl CpuClock { + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + cpu_pll_div: Some(CpuPllDivConfig::_6), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; + const PRESET_120: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + cpu_pll_div: Some(CpuPllDivConfig::_4), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_120MHz => CpuClock::PRESET_120, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_120 => Some(CpuClock::_120MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + // Switch CPU to XTAL before reconfiguring PLL. + ClockTree::with(|clocks| { + configure_xtal_clk(clocks, XtalClkConfig::_40); + configure_system_pre_div(clocks, SystemPreDivConfig::new(0)); + configure_cpu_clk(clocks, CpuClkConfig::Xtal); + }); + + // Detect XTAL if unset. + // FIXME: this doesn't support running from RC_FAST_CLK. We should rework detection to + // only run when requesting XTAL. + if self.xtal_clk.is_none() { + // While the bootloader stores a crystal frequency in a retention register, + // that comes from a constant that we should not trust. If the user did not + // provide a crystal frequency, we should detect it. + let xtal = ClockTree::with(detect_xtal_freq); + debug!("Auto-detected XTAL frequency: {}", xtal.value()); + self.xtal_clk = Some(xtal); + } + + self.apply(); + } +} + +fn detect_xtal_freq(clocks: &mut ClockTree) -> XtalClkConfig { + // Estimate XTAL frequency using RC_FAST/256 as the calibration clock. RC_SLOW is too + // imprecise for reliable detection. + const CALIBRATION_CYCLES: u32 = 10; + + // The digital path for RC_FAST_D256 must be enabled for TIMG calibration to work. + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_clk8m_d256_en().set_bit()); + + let (xtal_cycles, calibration_clock_frequency) = Clocks::measure_rtc_clock( + clocks, + Timg0CalibrationClockConfig::RcFastDivClk, + Timg0FunctionClockConfig::XtalClk, + CALIBRATION_CYCLES, + ); + + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_clk8m_d256_en().clear_bit()); + + let mhz = (calibration_clock_frequency * xtal_cycles / CALIBRATION_CYCLES).as_mhz(); + + if mhz.abs_diff(40) < mhz.abs_diff(26) { + XtalClkConfig::_40 + } else { + XtalClkConfig::_26 + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. We save the value in a register + // similar to ESP-IDF, just in case something relies on that, or, if we can in the future read + // back the value instead of wasting RAM on it. + + // Used by `rtc_clk_xtal_freq_get` patched ROM function. + let freq_mhz = config.value() / 1_000_000; + LPWR::regs().store4().modify(|r, w| unsafe { + // The data is stored in two copies of 16-bit values. The first bit overwrites the LSB of + // the frequency value with DISABLE_ROM_LOG. + + // Copy the DISABLE_ROM_LOG bit + let disable_rom_log_bit = r.bits() & Rtc::RTC_DISABLE_ROM_LOG; + let half = (freq_mhz & (0xFFFF & !Rtc::RTC_DISABLE_ROM_LOG)) | disable_rom_log_bit; + w.data().bits(half | (half << 16)) + }); +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; + + // regi2c_ctrl_ll_i2c_bbpll_enable + I2C_ANA_MST::regs() + .ana_config() + .modify(|_, w| w.bbpll_pd().bit(!en)); + + LPWR::regs().options0().modify(|_, w| { + let power_down = !en; + w.bb_i2c_force_pd().bit(power_down); + w.bbpll_force_pd().bit(power_down); + w.bbpll_i2c_force_pd().bit(power_down) + }); + + if !en { + return; + } + + // Digital part + + SYSTEM::regs() + .cpu_per_conf() + .modify(|_, w| w.pll_freq_sel().set_bit()); // Undocumented, selects 480MHz PLL. + + // Analog part + + // Start BBPLL self-calibration + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + let div_ref: u8; + let div7_0: u8; + let dr1: u8; + let dr3: u8; + let dchgp: u8; + let dcur: u8; + let dbias: u8; + // Configure 480M PLL + match unwrap!(clocks.xtal_clk) { + XtalClkConfig::_26 => { + // Divide by 13 -> reference = 2MHz + div_ref = 12; + // Multiply by 236 + 4 -> PLL output = 480MHz + div7_0 = 236; + dr1 = 4; + dr3 = 4; + dchgp = 0; + dcur = 0; + dbias = 2; + } + XtalClkConfig::_40 => { + // Divide by 1 -> reference = 40MHz + div_ref = 0; + // Multiply by 8 + 4 -> PLL output = 480MHz + div7_0 = 8; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 3; + dbias = 2; + } + } + + regi2c::I2C_BBPLL_REG4.write_reg(0x6b); + + let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; + let i2c_bbpll_dcur = + (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; + + regi2c::I2C_BBPLL_OC_REF.write_reg(i2c_bbpll_lref); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(div7_0); + regi2c::I2C_BBPLL_OC_DR1.write_field(dr1); + regi2c::I2C_BBPLL_OC_DR3.write_field(dr3); + regi2c::I2C_BBPLL_REG6.write_reg(i2c_bbpll_dcur); + regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(dbias); + + while I2C_ANA_MST::regs() + .ana_conf0() + .read() + .bbpll_cal_done() + .bit_is_clear() + {} + + ets_delay_us(10); + + // Stop BBPLL self-calibration + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().set_bit(); + w.bbpll_stop_force_low().clear_bit() + }); +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + // XPD_RC_OSCILLATOR exists but we'll manage that separately + const RTC_CNTL_FOSC_DFREQ_DEFAULT: u8 = 172; + LPWR::regs().clk_conf().modify(|_, w| { + // Confusing CK8M naming inherited from ESP32? + + // CK8M_DFREQ value controls tuning of 8M clock. + unsafe { w.ck8m_dfreq().bits(RTC_CNTL_FOSC_DFREQ_DEFAULT) }; + + w.enb_ck8m().bit(!en); + w.dig_clk8m_en().bit(en); // digital system clock gate + // Do not force the clock either way. + w.ck8m_force_pd().clear_bit(); + w.ck8m_force_pu().clear_bit() + }); + LPWR::regs() + .timer1() + .modify(|_, w| unsafe { w.ck8m_wait().bits(if en { 5 } else { 20 }) }); +} + +// OSC_SLOW_CLK + +fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if !en { + return; + } + + // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255; + LPWR::regs() + .rtc_cntl() + .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) }); + + // Also configure the divider here to its usual value of 1. + + // Updating the divider should be part of the RC_SLOW_CLK divider config: + let slow_clk_conf = LPWR::regs().slow_clk_conf(); + // Invalidate + let new_value = slow_clk_conf.modify(|_, w| w.ana_clk_div_vld().clear_bit()); + // Update divider + let new_value = slow_clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ana_clk_div().bits(0) + }); + // Re-synchronize + slow_clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ana_clk_div_vld().set_bit() + }); +} + +// RC_FAST_DIV_CLK + +fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs() + .clk_conf() + .modify(|_, w| w.enb_ck8m_div().bit(!en)); +} + +// SYSTEM_PRE_DIV_IN + +// Not an actual MUX, used to allow configuring the DIV divider as one block. +// Related to CPU clock source configuration. +fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: SystemPreDivInConfig, +) { + // Nothing to do. +} + +// SYSTEM_PRE_DIV + +fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_impl(_clocks: &mut ClockTree, new_config: SystemPreDivConfig) { + SYSTEM::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) }); +} + +// CPU_PLL_DIV + +fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { + // Nothing to do. +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: ApbClkConfig, +) { + // Nothing to do. +} + +// CRYPTO_CLK + +fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_crypto_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CryptoClkConfig, +) { + // Nothing to do, determined by CPU clock. +} + +// MSPI_CLK + +fn enable_mspi_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mspi_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: MspiClkConfig, +) { + // Nothing to do, determined by CPU clock. +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CpuClkConfig, +) { + // Based on TRM Table 6.2-2 + if new_selector == CpuClkConfig::Pll { + SYSTEM::regs().cpu_per_conf().modify(|_, w| unsafe { + w.cpuperiod_sel().bits(match unwrap!(clocks.cpu_pll_div) { + CpuPllDivConfig::_4 => 1, + CpuPllDivConfig::_6 => 0, + }) + }); + } + + SYSTEM::regs().sysclk_conf().modify(|_, w| unsafe { + w.soc_clk_sel().bits(match new_selector { + CpuClkConfig::Xtal => 0, + CpuClkConfig::RcFast => 2, + CpuClkConfig::Pll => 1, + }) + }); + + let apb_freq = Rate::from_hz(apb_clk_frequency(clocks)); + update_apb_frequency(apb_freq); + + let cpu_freq = Rate::from_hz(cpu_clk_frequency(clocks)); + ets_update_cpu_frequency_rom(cpu_freq.as_mhz()); +} + +fn update_apb_frequency(freq: Rate) { + let freq_shifted = (freq.as_hz() >> 12) & 0xFFFF; + let value = freq_shifted | (freq_shifted << 16); + LPWR::regs() + .store5() + .modify(|_, w| unsafe { w.data().bits(value) }); +} + +// PLL_40M + +fn enable_pll_40m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_60M + +fn enable_pll_60m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_80M + +fn enable_pll_80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// CPU_DIV2 + +fn enable_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RC_FAST_CLK_DIV_N + +fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, new_config: RcFastClkDivNConfig) { + let clk_conf = LPWR::regs().clk_conf(); + // Invalidate because we may be changing the divider from some other value + let new_value = clk_conf.modify(|_, w| w.ck8m_div_sel_vld().clear_bit()); + // Update divider + let new_value = clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ck8m_div_sel().bits(new_config.divisor() as u8) + }); + // Re-synchronize + clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ck8m_div_sel_vld().set_bit() + }); +} + +// XTAL_DIV_CLK + +fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RTC_SLOW_CLK + +fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcSlowClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| unsafe { + // TODO: variants should be in PAC + w.ana_clk_rtc_sel().bits(match new_selector { + RtcSlowClkConfig::OscSlow => 1, + RtcSlowClkConfig::RcSlow => 0, + RtcSlowClkConfig::RcFast => 2, + }) + }); + + ets_delay_us(300); +} + +// RTC_FAST_CLK + +fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcFastClkConfig, +) { + // TODO: variants should be fixed in PAC + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + RtcFastClkConfig::Xtal => w.fast_clk_rtc_sel().clear_bit(), + RtcFastClkConfig::Rc => w.fast_clk_rtc_sel().set_bit(), + }); + ets_delay_us(3); +} + +// LOW_POWER_CLK + +fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing in esp-idf does this - is this managed by hardware, or the radio blobs? + // SYSTEM::regs() + // .bt_lpck_div_frac() + // .modify(|_, w| w.lpclk_rtc_en().bit(en)); +} + +fn configure_low_power_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LowPowerClkConfig, +) { + SYSTEM::regs().bt_lpck_div_frac().modify(|_, w| { + w.lpclk_sel_8m() + .bit(new_selector == LowPowerClkConfig::RcFast); + w.lpclk_sel_rtc_slow() + .bit(new_selector == LowPowerClkConfig::RtcSlow); + w.lpclk_sel_xtal() + .bit(new_selector == LowPowerClkConfig::Xtal); + w.lpclk_sel_xtal32k() + .bit(new_selector == LowPowerClkConfig::OscSlow) + }); +} + +// UART_MEM_CLK + +fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: these functions (peripheral bus clock control) should be generated, + // replacing current PeripheralClockControl code. + // Enabling clock should probably not reset the peripheral. + let regs = SYSTEM::regs(); + + if en { + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(true)); + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(false)); + } + + regs.perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(en)); +} + +// TIMG0_FUNCTION_CLOCK + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: should we model T0_DIVIDER, too? + TIMG0::regs() + .regclk() + .modify(|_, w| w.timer_clk_is_active().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG0::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Osc32kClk => 2, + }) + }); +} + +// TIMG0_WDT_CLOCK + +fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // No separate clock control enable bit. +} + +fn configure_timg0_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + TIMG0::regs().wdtconfig0().modify(|_, w| { + w.wdt_use_xtal() + .bit(new_selector == Timg0WdtClockConfig::XtalClk) + }); +} + +// UART0_MEM_CLOCK + +fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart0_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART0::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART0::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF40m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART1_MEM_CLOCK + +fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart1_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART1::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART1::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF40m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} diff --git a/esp-hal/src/soc/esp32c2/gpio.rs b/esp-hal/src/soc/esp32c2/gpio.rs new file mode 100644 index 00000000000..04bdbe52f34 --- /dev/null +++ b/esp-hal/src/soc/esp32c2/gpio.rs @@ -0,0 +1,77 @@ +//! # GPIO configuration module (ESP32-C2) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-C2` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +macro_rules! rtc_pins { + ( $( $pin_num:expr )+ ) => { + $( + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for paste::paste!($crate::peripherals::[]<'_>) { + unsafe fn apply_wakeup(&self, wakeup: bool, level: u8) { + let gpio_wakeup = $crate::peripherals::LPWR::regs().cntl_gpio_wakeup(); + unsafe { + paste::paste! { + gpio_wakeup.modify(|_, w| w.[< gpio_pin $pin_num _wakeup_enable >]().bit(wakeup)); + gpio_wakeup.modify(|_, w| w.[< gpio_pin $pin_num _int_type >]().bits(level)); + } + } + } + + fn rtcio_pad_hold(&self, enable: bool) { + paste::paste! { + $crate::peripherals::LPWR::regs() + .pad_hold().modify(|_, w| w.[< gpio_pin $pin_num _hold >]().bit(enable)); + } + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl crate::gpio::RtcPinWithResistors for paste::paste!($crate::peripherals::[]<'_>) { + fn rtcio_pullup(&self, enable: bool) { + $crate::peripherals::IO_MUX::regs() + .gpio($pin_num) + .modify(|_, w| w.fun_wpu().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + $crate::peripherals::IO_MUX::regs() + .gpio($pin_num) + .modify(|_, w| w.fun_wpd().bit(enable)); + } + } + )+ + }; +} + +rtc_pins! { + 0 + 1 + 2 + 3 + 4 + 5 +} diff --git a/esp-hal/src/soc/esp32c2/mod.rs b/esp-hal/src/soc/esp32c2/mod.rs new file mode 100644 index 00000000000..4f8f3a7a433 --- /dev/null +++ b/esp-hal/src/soc/esp32c2/mod.rs @@ -0,0 +1,17 @@ +//! # SOC (System-on-Chip) module (ESP32-C2) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-C2` chip. + +crate::unstable_module! { + pub mod clocks; + pub mod trng; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32c2 as pac; + +pub(crate) fn pre_init() {} diff --git a/esp-hal/src/soc/esp32c2/regi2c.rs b/esp-hal/src/soc/esp32c2/regi2c.rs new file mode 100644 index 00000000000..9dd3f28f93a --- /dev/null +++ b/esp-hal/src/soc/esp32c2/regi2c.rs @@ -0,0 +1,158 @@ +use crate::rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 0) { + reg: I2C_BBPLL_IR_CAL(0) { + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4), + field: I2C_BBPLL_IR_CAL_DELAY(3..0) + } + reg: I2C_BBPLL_IR_CAL_EXT_REG(1) { + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0) + } + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_ENB_FCAL(7..7), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_OC_TSCHGP(7..7), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_RSTB_DIV_ADC(0..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_EN_USB(7..7), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_OC_DR1(2..0) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DCUR(2..0) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_LOCK(7..7), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_CAP(3..0) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DREF(7..6), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_OC_VCO_DBIAS(1..0) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_ENT_ADC(7..6), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_PLL_MSB(3..3), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_BBADC_DCUR(1..0) + } + } + master: REGI2C_BIAS(0x6a, 0) { + reg: I2C_BIAS_DREG(1) { + field: I2C_BIAS_DREG_1P1_PVT(3..0) + } + } + master: REGI2C_DIG_REG(0x6d, 0) { + reg: I2C_DIG_REG4(4) { + field: I2C_DIG_REG_ENX_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG(4..0) + } + reg: I2C_DIG_REG5(5) { + field: I2C_DIG_REG_ENIF_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG6(6) { + field: I2C_DIG_REG_ENX_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG(4..0) + } + reg: I2C_DIG_REG_ENIF_DIG(7) { + field: I2C_DIG_REG_ENIF_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG9(9) { + field: I2C_DIG_REG_OR_EN_CONT_CAL(7..7) + } + reg: I2C_DIG_REG_XPD(13) { + field: I2C_DIG_REG_XPD_DIG_REG(3..3), + field: I2C_DIG_REG_XPD_RTC_REG(2..2) + } + reg: I2C_DIG_REG_SCK_DCAP(14) {} + } + master: REGI2C_ULP_CAL(0x61, 0) { + reg: I2C_ULP_CAL_IR(0) { + field: I2C_ULP_IR_DISABLE_WATCHDOG_CK(6..6), + field: I2C_ULP_IR_FORCE_XPD_IPH(4..4), + field: I2C_ULP_IR_FORCE_XPD_CK(2..2), + field: I2C_ULP_IR_RESETB(0..0) + } + reg: I2C_ULP_CAL_O(3) { + field: I2C_ULP_BG_O_DONE_FLAG(3..3), + field: I2C_ULP_O_DONE_FLAG(0..0) + } + reg: I2C_ULP_CAL_OCODE(4) {} + reg: I2C_ULP_IR_FORCE(5) { + field: I2C_ULP_IR_FORCE_CODE(6..6), + field: I2C_BOD_THRESHOLD(2..0) + } + reg: I2C_ULP_EXT_CODE(6) {} + } + master: REGI2C_SAR_I2C(0x69, 0) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) {} + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } +} + +pub(crate) fn regi2c_read(block: u8, host_id: u8, reg_add: u8) -> u8 { + unsafe extern "C" { + pub(crate) fn esp_rom_regi2c_read(block: u8, block_hostid: u8, reg_add: u8) -> u8; + } + unsafe { esp_rom_regi2c_read(block, host_id, reg_add) } +} + +pub(crate) fn regi2c_write(block: u8, host_id: u8, reg_add: u8, data: u8) { + unsafe extern "C" { + pub(crate) fn rom_i2c_writeReg(block: u8, block_hostid: u8, reg_add: u8, indata: u8); + } + unsafe { rom_i2c_writeReg(block, host_id, reg_add, data) }; +} diff --git a/esp-hal/src/soc/esp32c2/trng.rs b/esp-hal/src/soc/esp32c2/trng.rs new file mode 100644 index 00000000000..923775af8ba --- /dev/null +++ b/esp-hal/src/soc/esp32c2/trng.rs @@ -0,0 +1,114 @@ +use crate::{ + peripherals::{APB_SARADC, LPWR, SYSTEM}, + soc::regi2c, +}; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + let rtc_cntl = LPWR::regs(); + let system = SYSTEM::regs(); + let apb_saradc = APB_SARADC::regs(); + + unsafe { + // RNG module is always clock enabled + rtc_cntl + .cntl_sensor_ctrl() + .modify(|_, w| w.force_xpd_sar().bits(3)); + + rtc_cntl.ana_conf().modify(|_, w| w.sar_i2c_pu().set_bit()); + + // Bridging sar2 internal reference voltage + // Cannot replace with PAC-based functions + regi2c::ADC_SAR2_ENCAL_REF.write_field(1); + regi2c::ADC_SAR_DTEST_RTC.write_field(0); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + regi2c::ADC_SAR_ENT_TSENS.write_field(0); + + // Enable SAR ADC2 internal channel to read adc2 ref voltage for additional + // entropy + system + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().set_bit()); + + system + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().clear_bit()); + + apb_saradc.clkm_conf().modify(|_, w| w.clk_sel().bits(2)); + apb_saradc.clkm_conf().modify(|_, w| w.clk_en().set_bit()); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_gated().set_bit()); + apb_saradc.ctrl().modify(|_, w| w.xpd_sar_force().bits(3)); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(1)); + apb_saradc.fsm_wait().modify(|_, w| w.rstb_wait().bits(8)); + apb_saradc.fsm_wait().modify(|_, w| w.xpd_wait().bits(5)); + + apb_saradc + .fsm_wait() + .modify(|_, w| w.standby_wait().bits(100)); + + apb_saradc + .ctrl() + .modify(|_, w| w.sar_patt_p_clear().set_bit()); + + apb_saradc + .ctrl() + .modify(|_, w| w.sar_patt_p_clear().clear_bit()); + + apb_saradc.ctrl().modify(|_, w| w.sar_patt_len().bits(0)); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.sar_patt_tab1().bits(0x9cffff)); + + apb_saradc + .sar_patt_tab2() + .modify(|_, w| w.sar_patt_tab2().bits(0x9cffff)); + + apb_saradc.ctrl2().modify(|_, w| w.timer_target().bits(100)); + + apb_saradc + .clkm_conf() + .modify(|_, w| w.clkm_div_num().bits(15)); + + apb_saradc + .ctrl2() + .modify(|_, w| w.meas_num_limit().clear_bit()); + + apb_saradc.dma_conf().modify(|_, w| w.adc_trans().set_bit()); + + apb_saradc.ctrl2().modify(|_, w| w.timer_en().set_bit()); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + let apb_saradc = APB_SARADC::regs(); + let rtc_cntl = LPWR::regs(); + + unsafe { + regi2c::ADC_SAR2_ENCAL_REF.write_field(0); + + apb_saradc.ctrl2().modify(|_, w| w.timer_en().clear_bit()); + + apb_saradc + .dma_conf() + .modify(|_, w| w.adc_trans().clear_bit()); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.sar_patt_tab1().bits(0xffffff)); + + apb_saradc + .sar_patt_tab2() + .modify(|_, w| w.sar_patt_tab2().bits(0xffffff)); + + apb_saradc.clkm_conf().modify(|_, w| w.clk_en().clear_bit()); + + apb_saradc.ctrl().modify(|_, w| w.xpd_sar_force().bits(0)); + + rtc_cntl + .cntl_sensor_ctrl() + .modify(|_, w| w.force_xpd_sar().bits(0)); + } +} diff --git a/esp-hal/src/soc/esp32c3/clocks.rs b/esp-hal/src/soc/esp32c3/clocks.rs new file mode 100644 index 00000000000..4c9b71fa991 --- /dev/null +++ b/esp-hal/src/soc/esp32c3/clocks.rs @@ -0,0 +1,752 @@ +//! Clock tree definitions and implementations for ESP32-C3. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (136k RC_SLOW and 17.5M RC_FAST) are not calibrated here, this system +//! can only give a rough estimate of their frequency. They can be calibrated separately using a +//! known crystal frequency. +//! - Some of the SOC capabilities are not implemented. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom}; + +use crate::{ + peripherals::{APB_CTRL, I2C_ANA_MST, LPWR, SYSTEM, TIMG0, TIMG1, UART0, UART1}, + soc::regi2c, + time::Rate, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, +} + +impl CpuClock { + // The presets use 480MHz PLL by default, because that is the default value the chip boots + // with, and changing it breaks USB Serial/JTAG. + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + pll_clk: Some(PllClkConfig::_480), + cpu_pll_div_out: Some(CpuPllDivOutConfig::_80), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + pll_clk: Some(PllClkConfig::_480), + cpu_pll_div_out: Some(CpuPllDivOutConfig::_160), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + if self.xtal_clk.is_none() { + // TODO: support multiple crystal frequencies (esp-idf supports 32M). + self.xtal_clk = Some(XtalClkConfig::_40); + } + + // Switch CPU to XTAL before reconfiguring PLL. + ClockTree::with(|clocks| { + configure_xtal_clk(clocks, XtalClkConfig::_40); + configure_system_pre_div(clocks, SystemPreDivConfig::new(0)); + configure_cpu_clk(clocks, CpuClkConfig::Xtal); + }); + + self.apply(); + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + // regi2c_ctrl_ll_i2c_bbpll_enable + I2C_ANA_MST::regs() + .ana_config() + .modify(|_, w| w.bbpll_pd().bit(!en)); + + LPWR::regs().options0().modify(|_, w| { + let power_down = !en; + w.bb_i2c_force_pd().bit(power_down); + w.bbpll_force_pd().bit(power_down); + w.bbpll_i2c_force_pd().bit(power_down) + }); + + if !en { + return; + } + + // Digital part + let pll_freq = unwrap!(clocks.pll_clk); + let xtal_freq = unwrap!(clocks.xtal_clk); + SYSTEM::regs() + .cpu_per_conf() + .modify(|_, w| w.pll_freq_sel().bit(pll_freq == PllClkConfig::_480)); + + // Analog part + + // Start BBPLL self-calibration + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + let div_ref: u8; + let div7_0: u8; + let dr1: u8; + let dr3: u8; + let dchgp: u8; + let dcur: u8; + let dbias: u8; + match pll_freq { + PllClkConfig::_480 => { + // Configure 480M PLL + match xtal_freq { + XtalClkConfig::_40 => { + div_ref = 0; + // Will multiply by 8 + 4 = 12 + div7_0 = 8; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 3; + dbias = 2; + } + } + + // Set the MODE_HF bit + regi2c::I2C_BBPLL_REG4.write_reg(0x6b); + } + PllClkConfig::_320 => { + // Configure 320M PLL + match xtal_freq { + XtalClkConfig::_40 => { + div_ref = 0; + // Will multiply by 4 + 4 = 8 + div7_0 = 4; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 3; + dbias = 2; + } + } + + // Clear the MODE_HF bit + regi2c::I2C_BBPLL_REG4.write_reg(0x69); + } + } + + const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; + + let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; + + // Weird, that the last two writes flip these values... + let i2c_bbpll_dcur = + (2 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (1 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; + + regi2c::I2C_BBPLL_OC_REF.write_reg(i2c_bbpll_lref); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(div7_0); + regi2c::I2C_BBPLL_OC_DR1.write_field(dr1); + regi2c::I2C_BBPLL_OC_DR3.write_field(dr3); + regi2c::I2C_BBPLL_REG6.write_reg(i2c_bbpll_dcur); + regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(dbias); + regi2c::I2C_BBPLL_OC_DHREF_SEL.write_field(2); + regi2c::I2C_BBPLL_OC_DLREF_SEL.write_field(1); +} + +fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { + // Nothing to do. The PLL may still be powered down. We'll configure it in + // `enable_pll_clk_impl`. +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + // XPD_RC_OSCILLATOR exists but we'll manage that separately + const RTC_CNTL_FOSC_DFREQ_DEFAULT: u8 = 172; + LPWR::regs().clk_conf().modify(|_, w| { + // Confusing CK8M naming inherited from ESP32? + + // CK8M_DFREQ value controls tuning of 8M clock. + unsafe { w.ck8m_dfreq().bits(RTC_CNTL_FOSC_DFREQ_DEFAULT) }; + + w.enb_ck8m().bit(!en); + w.dig_clk8m_en().bit(en); // digital system clock gate + // Do not force the clock either way. + w.ck8m_force_pd().clear_bit(); + w.ck8m_force_pu().clear_bit() + }); + LPWR::regs() + .timer1() + .modify(|_, w| unsafe { w.ck8m_wait().bits(if en { 5 } else { 20 }) }); +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + // RTCIO could be configured to allow an external oscillator to be used. We could model this + // with a MUX, probably, but this is omitted for now for simplicity. + + const CLK_LL_XTAL_32K_DAC_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DRES_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DGM_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DBUF_VAL: bool = true; // differential buffer + LPWR::regs().ext_xtl_conf().modify(|_, w| unsafe { + w.xtal32k_gpio_sel().bit(false); + + w.dac_xtal_32k().bits(CLK_LL_XTAL_32K_DAC_VAL); + w.dres_xtal_32k().bits(CLK_LL_XTAL_32K_DRES_VAL); + w.dgm_xtal_32k().bits(CLK_LL_XTAL_32K_DGM_VAL); + w.dbuf_xtal_32k().bit(CLK_LL_XTAL_32K_DBUF_VAL); + + w.xpd_xtal_32k().bit(en) + }); + + // Enable for digital part + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_xtal32k_en().bit(en)); +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255; + LPWR::regs() + .rtc_cntl() + .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) }); + + // Also configure the divider here to its usual value of 1. + + // Updating the divider should be part of the RC_SLOW_CLK divider config: + let slow_clk_conf = LPWR::regs().slow_clk_conf(); + // Invalidate + let new_value = slow_clk_conf.modify(|_, w| w.ana_clk_div_vld().clear_bit()); + // Update divider + let new_value = slow_clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ana_clk_div().bits(0) + }); + // Re-synchronize + slow_clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ana_clk_div_vld().set_bit() + }); + } +} + +// RC_FAST_DIV_CLK + +fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs() + .clk_conf() + .modify(|_, w| w.enb_ck8m_div().bit(!en)); +} + +// SYSTEM_PRE_DIV_IN + +fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: SystemPreDivInConfig, +) { + // Nothing to do. +} + +// SYSTEM_PRE_DIV + +fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_impl(_clocks: &mut ClockTree, new_config: SystemPreDivConfig) { + APB_CTRL::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) }); +} + +// CPU_PLL_DIV_OUT + +fn enable_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _config: CpuPllDivOutConfig) { + // Nothing to do. +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: ApbClkConfig, +) { + // Nothing to do. +} + +// CRYPTO_CLK + +fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_crypto_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CryptoClkConfig, +) { + // Nothing to do. +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CpuClkConfig, +) { + // Based on TRM Table 6.2-2 + if new_selector == CpuClkConfig::Pll { + SYSTEM::regs().cpu_per_conf().modify(|_, w| unsafe { + w.cpuperiod_sel() + .bits(match unwrap!(clocks.cpu_pll_div_out) { + CpuPllDivOutConfig::_80 => 0, + CpuPllDivOutConfig::_160 => 1, + }) + }); + } + + SYSTEM::regs().sysclk_conf().modify(|_, w| unsafe { + w.pre_div_cnt().bits(0); + w.soc_clk_sel().bits(match new_selector { + CpuClkConfig::Xtal => 0, + CpuClkConfig::RcFast => 2, + CpuClkConfig::Pll => 1, + }) + }); + + let apb_freq = Rate::from_hz(apb_clk_frequency(clocks)); + update_apb_frequency(apb_freq); + + let cpu_freq = Rate::from_hz(cpu_clk_frequency(clocks)); + ets_update_cpu_frequency_rom(cpu_freq.as_mhz()); +} + +fn update_apb_frequency(freq: Rate) { + let freq_shifted = (freq.as_hz() >> 12) & 0xFFFF; + let value = freq_shifted | (freq_shifted << 16); + LPWR::regs() + .store5() + .modify(|_, w| unsafe { w.data().bits(value) }); +} + +// PLL_80M + +fn enable_pll_80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_160M + +fn enable_pll_160m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RC_FAST_CLK_DIV_N + +fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, new_config: RcFastClkDivNConfig) { + let clk_conf = LPWR::regs().clk_conf(); + // Invalidate because we may be changing the divider from some other value + let new_value = clk_conf.modify(|_, w| w.ck8m_div_sel_vld().clear_bit()); + // Update divider + let new_value = clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ck8m_div_sel().bits(new_config.divisor() as u8) + }); + // Re-synchronize + clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ck8m_div_sel_vld().set_bit() + }); +} + +// XTAL_DIV_CLK + +fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RTC_SLOW_CLK + +fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcSlowClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| unsafe { + // TODO: variants should be in PAC + w.ana_clk_rtc_sel().bits(match new_selector { + RtcSlowClkConfig::Xtal32k => 1, + RtcSlowClkConfig::RcSlow => 0, + RtcSlowClkConfig::RcFast => 2, + }) + }); + ets_delay_us(300); +} + +// RTC_FAST_CLK + +fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcFastClkConfig, +) { + // TODO: variants should be fixed in PAC + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + RtcFastClkConfig::Xtal => w.fast_clk_rtc_sel().clear_bit(), + RtcFastClkConfig::Rc => w.fast_clk_rtc_sel().set_bit(), + }); + ets_delay_us(3); +} + +// LOW_POWER_CLK + +fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing in esp-idf does this - is this managed by hardware, or the radio blobs? + // SYSTEM::regs() + // .bt_lpck_div_frac() + // .modify(|_, w| w.lpclk_rtc_en().bit(en)); +} + +fn configure_low_power_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LowPowerClkConfig, +) { + SYSTEM::regs().bt_lpck_div_frac().modify(|_, w| { + w.lpclk_sel_8m() + .bit(new_selector == LowPowerClkConfig::RcFast); + w.lpclk_sel_rtc_slow() + .bit(new_selector == LowPowerClkConfig::RtcSlow); + w.lpclk_sel_xtal() + .bit(new_selector == LowPowerClkConfig::Xtal); + w.lpclk_sel_xtal32k() + .bit(new_selector == LowPowerClkConfig::Xtal32k) + }); +} + +// UART_MEM_CLK + +fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: these functions (peripheral bus clock control) should be generated, + // replacing current PeripheralClockControl code. + // Enabling clock should probably not reset the peripheral. + let regs = SYSTEM::regs(); + + if en { + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(true)); + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(false)); + } + + regs.perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(en)); +} + +// RMT_SCLK + +fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, en: bool) { + crate::peripherals::RMT::regs() + .sys_conf() + .modify(|_, w| w.sclk_active().bit(en)); +} + +fn configure_rmt_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RmtSclkConfig, +) { + crate::peripherals::RMT::regs() + .sys_conf() + .modify(|_, w| unsafe { + w.clk_en().clear_bit(); + w.sclk_sel().bits(match new_selector { + RmtSclkConfig::ApbClk => 1, + RmtSclkConfig::RcFastClk => 2, + RmtSclkConfig::XtalClk => 3, + }) + }); +} + +// TIMG0_FUNCTION_CLOCK + +// Note that the function clock is a pre-requisite of the timer, but does not enable the counter. + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: should we model T0_DIVIDER, too? + TIMG0::regs().regclk().modify(|_, w| { + w.timer_clk_is_active().bit(en); + w.clk_en().bit(en) + }); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG0::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG0_WDT_CLOCK + +fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // No separate clock control enable bit. +} + +fn configure_timg0_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + TIMG0::regs().wdtconfig0().modify(|_, w| { + w.wdt_use_xtal() + .bit(new_selector == Timg0WdtClockConfig::XtalClk) + }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + TIMG1::regs().regclk().modify(|_, w| { + w.timer_clk_is_active().bit(en); + w.clk_en().bit(en) + }); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG1::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG1_WDT_CLOCK + +fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // No separate clock control enable bit. +} + +fn configure_timg1_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + TIMG1::regs().wdtconfig0().modify(|_, w| { + w.wdt_use_xtal() + .bit(new_selector == Timg0WdtClockConfig::XtalClk) + }); +} + +// UART0_MEM_CLOCK + +fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart0_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART0::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART0::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Apb => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART1_MEM_CLOCK + +fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart1_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART1::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART1::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Apb => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} diff --git a/esp-hal/src/soc/esp32c3/gpio.rs b/esp-hal/src/soc/esp32c3/gpio.rs new file mode 100644 index 00000000000..ce31f42e8fe --- /dev/null +++ b/esp-hal/src/soc/esp32c3/gpio.rs @@ -0,0 +1,78 @@ +//! # GPIO configuration module (ESP32-C3) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-C3` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +macro_rules! rtc_pins { + ( $( $pin_num:expr )+ ) => { + $( + paste::paste! { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for $crate::peripherals::[]<'_> { + unsafe fn apply_wakeup(&self, wakeup: bool, level: u8) { + let rtc_cntl = $crate::peripherals::LPWR::regs(); + let gpio_wakeup = rtc_cntl.gpio_wakeup(); + + unsafe { + gpio_wakeup.modify(|_, w| w.[< gpio_pin $pin_num _wakeup_enable >]().bit(wakeup)); + gpio_wakeup.modify(|_, w| w.[< gpio_pin $pin_num _int_type >]().bits(level)); + } + } + + fn rtcio_pad_hold(&self, enable: bool) { + $crate::peripherals::LPWR::regs() + .pad_hold().modify(|_, w| w.[< gpio_pin $pin_num _hold >]().bit(enable)); + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl crate::gpio::RtcPinWithResistors for $crate::peripherals::[]<'_> { + fn rtcio_pullup(&self, enable: bool) { + $crate::peripherals::IO_MUX::regs() + .gpio($pin_num) + .modify(|_, w| w.fun_wpu().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + $crate::peripherals::IO_MUX::regs() + .gpio($pin_num) + .modify(|_, w| w.fun_wpd().bit(enable)); + } + } + } + )+ + }; +} + +// RTC pins 0 through 5 (inclusive) support GPIO wakeup +rtc_pins! { + 0 + 1 + 2 + 3 + 4 + 5 +} diff --git a/esp-hal/src/soc/esp32c3/mod.rs b/esp-hal/src/soc/esp32c3/mod.rs new file mode 100644 index 00000000000..ebee53e0f2a --- /dev/null +++ b/esp-hal/src/soc/esp32c3/mod.rs @@ -0,0 +1,29 @@ +//! # SOC (System-on-Chip) module (ESP32-C3) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-C3` chip. +//! +//! Also few constants are defined in this module for `ESP32-C3` chip: +//! * I2S_SCLK: 160_000_000 - I2S clock frequency +//! * I2S_DEFAULT_CLK_SRC: 2 - I2S clock source + +crate::unstable_module! { + pub mod clocks; + pub mod trng; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32c3 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// The base clock frequency for the I2S peripheral (Hertz). + pub const I2S_SCLK: u32 = 160_000_000; + /// The default clock source for I2S operations. + pub const I2S_DEFAULT_CLK_SRC: u8 = 2; +} + +pub(crate) fn pre_init() {} diff --git a/esp-hal/src/soc/esp32c3/regi2c.rs b/esp-hal/src/soc/esp32c3/regi2c.rs new file mode 100644 index 00000000000..9dd3f28f93a --- /dev/null +++ b/esp-hal/src/soc/esp32c3/regi2c.rs @@ -0,0 +1,158 @@ +use crate::rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 0) { + reg: I2C_BBPLL_IR_CAL(0) { + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4), + field: I2C_BBPLL_IR_CAL_DELAY(3..0) + } + reg: I2C_BBPLL_IR_CAL_EXT_REG(1) { + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0) + } + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_ENB_FCAL(7..7), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_OC_TSCHGP(7..7), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_RSTB_DIV_ADC(0..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_EN_USB(7..7), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_OC_DR1(2..0) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DCUR(2..0) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_LOCK(7..7), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_CAP(3..0) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DREF(7..6), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_OC_VCO_DBIAS(1..0) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_ENT_ADC(7..6), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_PLL_MSB(3..3), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_BBADC_DCUR(1..0) + } + } + master: REGI2C_BIAS(0x6a, 0) { + reg: I2C_BIAS_DREG(1) { + field: I2C_BIAS_DREG_1P1_PVT(3..0) + } + } + master: REGI2C_DIG_REG(0x6d, 0) { + reg: I2C_DIG_REG4(4) { + field: I2C_DIG_REG_ENX_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG(4..0) + } + reg: I2C_DIG_REG5(5) { + field: I2C_DIG_REG_ENIF_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG6(6) { + field: I2C_DIG_REG_ENX_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG(4..0) + } + reg: I2C_DIG_REG_ENIF_DIG(7) { + field: I2C_DIG_REG_ENIF_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG9(9) { + field: I2C_DIG_REG_OR_EN_CONT_CAL(7..7) + } + reg: I2C_DIG_REG_XPD(13) { + field: I2C_DIG_REG_XPD_DIG_REG(3..3), + field: I2C_DIG_REG_XPD_RTC_REG(2..2) + } + reg: I2C_DIG_REG_SCK_DCAP(14) {} + } + master: REGI2C_ULP_CAL(0x61, 0) { + reg: I2C_ULP_CAL_IR(0) { + field: I2C_ULP_IR_DISABLE_WATCHDOG_CK(6..6), + field: I2C_ULP_IR_FORCE_XPD_IPH(4..4), + field: I2C_ULP_IR_FORCE_XPD_CK(2..2), + field: I2C_ULP_IR_RESETB(0..0) + } + reg: I2C_ULP_CAL_O(3) { + field: I2C_ULP_BG_O_DONE_FLAG(3..3), + field: I2C_ULP_O_DONE_FLAG(0..0) + } + reg: I2C_ULP_CAL_OCODE(4) {} + reg: I2C_ULP_IR_FORCE(5) { + field: I2C_ULP_IR_FORCE_CODE(6..6), + field: I2C_BOD_THRESHOLD(2..0) + } + reg: I2C_ULP_EXT_CODE(6) {} + } + master: REGI2C_SAR_I2C(0x69, 0) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) {} + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } +} + +pub(crate) fn regi2c_read(block: u8, host_id: u8, reg_add: u8) -> u8 { + unsafe extern "C" { + pub(crate) fn esp_rom_regi2c_read(block: u8, block_hostid: u8, reg_add: u8) -> u8; + } + unsafe { esp_rom_regi2c_read(block, host_id, reg_add) } +} + +pub(crate) fn regi2c_write(block: u8, host_id: u8, reg_add: u8, data: u8) { + unsafe extern "C" { + pub(crate) fn rom_i2c_writeReg(block: u8, block_hostid: u8, reg_add: u8, indata: u8); + } + unsafe { rom_i2c_writeReg(block, host_id, reg_add, data) }; +} diff --git a/esp-hal/src/soc/esp32c3/trng.rs b/esp-hal/src/soc/esp32c3/trng.rs new file mode 100644 index 00000000000..8ad1d975045 --- /dev/null +++ b/esp-hal/src/soc/esp32c3/trng.rs @@ -0,0 +1,114 @@ +use crate::{ + peripherals::{APB_SARADC, LPWR, SYSTEM}, + soc::regi2c, +}; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + let system = SYSTEM::regs(); + let rtc_cntl = LPWR::regs(); + let apb_saradc = APB_SARADC::regs(); + + unsafe { + // RNG module is always clock enabled + rtc_cntl + .sensor_ctrl() + .modify(|_, w| w.force_xpd_sar().bits(3)); + + rtc_cntl.ana_conf().modify(|_, w| w.sar_i2c_pu().set_bit()); + + // Bridging sar2 internal reference voltage + // Cannot replace with PAC-based functions + regi2c::ADC_SAR2_ENCAL_REF.write_field(1); + regi2c::ADC_SAR_DTEST_RTC.write_field(0); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + regi2c::ADC_SAR_ENT_TSENS.write_field(0); + + // Enable SAR ADC2 internal channel to read adc2 ref voltage for additional + // entropy + system + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().set_bit()); + + system + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().clear_bit()); + + apb_saradc.clkm_conf().modify(|_, w| w.clk_sel().bits(2)); + apb_saradc.clkm_conf().modify(|_, w| w.clk_en().set_bit()); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_gated().set_bit()); + apb_saradc.ctrl().modify(|_, w| w.xpd_sar_force().bits(3)); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(1)); + apb_saradc.fsm_wait().modify(|_, w| w.rstb_wait().bits(8)); + apb_saradc.fsm_wait().modify(|_, w| w.xpd_wait().bits(5)); + + apb_saradc + .fsm_wait() + .modify(|_, w| w.standby_wait().bits(100)); + + apb_saradc + .ctrl() + .modify(|_, w| w.sar_patt_p_clear().set_bit()); + + apb_saradc + .ctrl() + .modify(|_, w| w.sar_patt_p_clear().clear_bit()); + + apb_saradc.ctrl().modify(|_, w| w.sar_patt_len().bits(0)); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.sar_patt_tab1().bits(0x9cffff)); + + apb_saradc + .sar_patt_tab2() + .modify(|_, w| w.sar_patt_tab2().bits(0x9cffff)); + + apb_saradc.ctrl2().modify(|_, w| w.timer_target().bits(100)); + + apb_saradc + .clkm_conf() + .modify(|_, w| w.clkm_div_num().bits(15)); + + apb_saradc + .ctrl2() + .modify(|_, w| w.meas_num_limit().clear_bit()); + + apb_saradc.dma_conf().modify(|_, w| w.adc_trans().set_bit()); + + apb_saradc.ctrl2().modify(|_, w| w.timer_en().set_bit()); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + let rtc_cntl = LPWR::regs(); + let apb_saradc = APB_SARADC::regs(); + + unsafe { + regi2c::ADC_SAR2_ENCAL_REF.write_field(0); + + apb_saradc.ctrl2().modify(|_, w| w.timer_en().clear_bit()); + + apb_saradc + .dma_conf() + .modify(|_, w| w.adc_trans().clear_bit()); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.sar_patt_tab1().bits(0xffffff)); + + apb_saradc + .sar_patt_tab2() + .modify(|_, w| w.sar_patt_tab2().bits(0xffffff)); + + apb_saradc.clkm_conf().modify(|_, w| w.clk_en().clear_bit()); + + apb_saradc.ctrl().modify(|_, w| w.xpd_sar_force().bits(0)); + + rtc_cntl + .sensor_ctrl() + .modify(|_, w| w.force_xpd_sar().bits(0)); + } +} diff --git a/esp-hal/src/soc/esp32c5/clocks.rs b/esp-hal/src/soc/esp32c5/clocks.rs new file mode 100644 index 00000000000..b70d4f3b4ba --- /dev/null +++ b/esp-hal/src/soc/esp32c5/clocks.rs @@ -0,0 +1,697 @@ +//! Clock tree definitions and implementations for ESP32-C5. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (32K OSC_SLOW, 130k RC_SLOW and 20M RC_FAST) are not calibrated here, +//! this system can only give a rough estimate of their frequency. They can be calibrated +//! separately using a known crystal frequency. +//! - Some of the SOC capabilities are not implemented: I2S external pad clock source, external 32k +//! oscillator, others. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use crate::{ + peripherals::{I2C_ANA_MST, LP_CLKRST, MODEM_LPCON, PCR, PMU}, + soc::regi2c, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, + + /// 240 MHz CPU clock + _240MHz = 240, +} + +impl CpuClock { + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + hp_root_clk: Some(HpRootClkConfig::PllF160m), + cpu_clk: Some(CpuClkConfig::new(1)), + ahb_clk: Some(AhbClkConfig::new(3)), // 40MHz - cannot exceed XTAL_CLK + apb_clk: Some(ApbClkConfig::new(0)), + lp_fast_clk: Some(LpFastClkConfig::RcFast), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + crypto_clk: Some(CryptoClkConfig::PllF480m), + timg_calibration_clock: None, + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + hp_root_clk: Some(HpRootClkConfig::PllF160m), + cpu_clk: Some(CpuClkConfig::new(0)), + ahb_clk: Some(AhbClkConfig::new(3)), // 40MHz - cannot exceed XTAL_CLK + apb_clk: Some(ApbClkConfig::new(0)), + lp_fast_clk: Some(LpFastClkConfig::RcFast), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + crypto_clk: Some(CryptoClkConfig::PllF480m), + timg_calibration_clock: None, + }; + const PRESET_240: ClockConfig = ClockConfig { + xtal_clk: None, + hp_root_clk: Some(HpRootClkConfig::PllF240m), + cpu_clk: Some(CpuClkConfig::new(0)), + ahb_clk: Some(AhbClkConfig::new(5)), // 40MHz - cannot exceed XTAL_CLK + apb_clk: Some(ApbClkConfig::new(0)), + lp_fast_clk: Some(LpFastClkConfig::RcFast), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + crypto_clk: Some(CryptoClkConfig::PllF480m), + timg_calibration_clock: None, + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + CpuClock::_240MHz => CpuClock::PRESET_240, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + v if v == CpuClock::PRESET_240 => Some(CpuClock::_240MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + // FIXME: we ignore user XTAL configuration, but we shouldn't offer it in the first place. + // PCR_CLK_XTAL_FREQ updates its value based on EFUSE_XTAL_48M_SEL. + self.xtal_clk = if PCR::regs().sysclk_conf().read().clk_xtal_freq().bits() == 40 { + Some(XtalClkConfig::_40) + } else { + Some(XtalClkConfig::_48) + }; + + self.apply(); + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // Nothing to do here. +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + if en { + PMU::regs().imm_hp_ck_power().write(|w| { + w.tie_high_xpd_bb_i2c().set_bit(); + w.tie_high_xpd_bbpll().set_bit(); + w.tie_high_xpd_bbpll_i2c().set_bit() + }); + PMU::regs() + .imm_hp_ck_power() + .write(|w| w.tie_high_global_bbpll_icg().set_bit()); + } else { + PMU::regs() + .imm_hp_ck_power() + .write(|w| w.tie_low_global_bbpll_icg().set_bit()); + PMU::regs().imm_hp_ck_power().write(|w| { + w.tie_low_xpd_bb_i2c().set_bit(); + w.tie_low_xpd_bbpll().set_bit(); + w.tie_low_xpd_bbpll_i2c().set_bit() + }); + + return; + } + + // Digital part - nothing to do here, PLL always runs at 480MHz + + // Analog part + // TODO: reference count I2C_ANA_MST clock (also applies to other chips) + let old_clk_conf = MODEM_LPCON::regs().clk_conf().read(); + MODEM_LPCON::regs().clk_conf().write(|w| { + unsafe { w.bits(old_clk_conf.bits()) }; + w.clk_i2c_mst_en().set_bit() + }); + + // BBPLL CALIBRATION START + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + let div7_0: u8; + let dr1: u8; + let dr3: u8; + match unwrap!(clocks.xtal_clk()) { + XtalClkConfig::_40 => { + div7_0 = 12; + dr1 = 0; + dr3 = 0; + } + XtalClkConfig::_48 => { + div7_0 = 10; + dr1 = 1; + dr3 = 1; + } + } + + const DIV_REF: u8 = 1; // Do not divide reference clock + const DCHGP: u8 = 5; + const DBIAS: u8 = 3; + const HREF: u8 = 3; + const LREF: u8 = 1; + + const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; + + const I2C_BBPLL_LREF: u8 = (DCHGP << I2C_BBPLL_OC_DCHGP_LSB) | DIV_REF; + + regi2c::I2C_BBPLL_OC_REF.write_reg(I2C_BBPLL_LREF); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(div7_0); + regi2c::I2C_BBPLL_OC_DR1.write_field(dr1); + regi2c::I2C_BBPLL_OC_DR3.write_field(dr3); + regi2c::I2C_BBPLL_OC_DLREF_SEL.write_field(LREF); + regi2c::I2C_BBPLL_OC_DHREF_SEL.write_field(HREF); + regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(DBIAS); + + // WAIT CALIBRATION DONE + while I2C_ANA_MST::regs() + .ana_conf0() + .read() + .cal_done() + .bit_is_clear() + {} + crate::rom::ets_delay_us(10); + + // BBPLL CALIBRATION STOP + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().set_bit(); + w.bbpll_stop_force_low().clear_bit() + }); + + MODEM_LPCON::regs() + .clk_conf() + .write(|w| unsafe { w.bits(old_clk_conf.bits()) }); +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_fosc().bit(en)); +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_xtal32k().bit(en)); +} + +// OSC_SLOW_CLK + +fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_osc32k().bit(en)); +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_sosc().bit(en)); +} + +// PLL_F12M + +fn enable_pll_f12m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_12m_clk_en().bit(en)); +} + +// PLL_F20M + +fn enable_pll_f20m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_20m_clk_en().bit(en)); +} + +// PLL_F40M + +fn enable_pll_f40m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_40m_clk_en().bit(en)); +} + +// PLL_F48M + +fn enable_pll_f48m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_48m_clk_en().bit(en)); +} + +// PLL_F60M + +fn enable_pll_f60m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_60m_clk_en().bit(en)); +} + +// PLL_F80M + +fn enable_pll_f80m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_80m_clk_en().bit(en)); +} + +// PLL_F120M + +fn enable_pll_f120m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_120m_clk_en().bit(en)); +} + +// PLL_F160M + +fn enable_pll_f160m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_160m_clk_en().bit(en)); +} + +// PLL_F240M + +fn enable_pll_f240m_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pll_div_clk_en() + .modify(|_, w| w.pll_240m_clk_en().bit(en)); +} + +// HP_ROOT_CLK + +fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_hp_root_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: HpRootClkConfig, +) { + // TODO: Limit AHB to ensure it's running at <= XTAL_CLK && CPU_CLK must be an integer multiple + // of AHB + PCR::regs().sysclk_conf().modify(|_, w| unsafe { + w.soc_clk_sel().bits(match new_selector { + HpRootClkConfig::Xtal => 0, + HpRootClkConfig::RcFast => 1, + HpRootClkConfig::PllF160m => 2, + HpRootClkConfig::PllF240m => 3, + }) + }); +} + +// CPU_CLK + +fn enable_cpu_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_cpu_clk_impl(_clocks: &mut ClockTree, new_config: CpuClkConfig) { + PCR::regs() + .cpu_freq_conf() + .modify(|_, w| unsafe { w.cpu_div_num().bits(new_config.divisor() as u8) }); + + PCR::regs() + .bus_clk_update() + .write(|w| w.bus_clock_update().set_bit()); +} + +// AHB_CLK + +fn enable_ahb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_ahb_clk_impl(_clocks: &mut ClockTree, new_config: AhbClkConfig) { + PCR::regs() + .ahb_freq_conf() + .modify(|_, w| unsafe { w.ahb_div_num().bits(new_config.divisor() as u8) }); + + PCR::regs() + .bus_clk_update() + .write(|w| w.bus_clock_update().set_bit()); +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_apb_clk_impl(_clocks: &mut ClockTree, new_config: ApbClkConfig) { + PCR::regs() + .apb_freq_conf() + .modify(|_, w| unsafe { w.apb_div_num().bits(new_config.divisor() as u8) }); +} + +// XTAL_D2_CLK + +fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +// LP_FAST_CLK + +fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_lp_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpFastClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe { + w.fast_clk_sel().bits(match new_selector { + LpFastClkConfig::RcFast => 0, + LpFastClkConfig::XtalD2 => 1, + LpFastClkConfig::Xtal => 2, + }) + }); +} + +// LP_SLOW_CLK + +fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_lp_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpSlowClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe { + w.slow_clk_sel().bits(match new_selector { + LpSlowClkConfig::RcSlow => 0, + LpSlowClkConfig::Xtal32k => 1, + // LpSlowClkConfig::Ext32k => 2, + LpSlowClkConfig::OscSlow => 3, + }) + }); +} + +// CRYPTO_CLK + +fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_crypto_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CryptoClkConfig, +) { + PCR::regs().sec_conf().modify(|_, w| unsafe { + w.sec_clk_sel().bits(match new_selector { + CryptoClkConfig::Xtal => 0, + CryptoClkConfig::Fosc => 1, + CryptoClkConfig::PllF480m => 2, + }) + }); +} + +// TIMG_CALIBRATION_CLOCK + +fn enable_timg_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do here. +} + +fn configure_timg_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: TimgCalibrationClockConfig, +) { + PCR::regs().ctrl_32k_conf().modify(|_, w| unsafe { + w._32k_sel().bits(match new_selector { + TimgCalibrationClockConfig::OscSlowClk => 0, + TimgCalibrationClockConfig::Xtal32kClk => 1, + // TimgCalibrationClockConfig::Ext32kClk => 2, + TimgCalibrationClockConfig::RcSlowClk => 3, + TimgCalibrationClockConfig::RcFastDivClk => 4, + }) + }); +} + +// PARLIO_RX_CLOCK + +fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| w.parl_clk_rx_en().bit(en)); +} + +fn configure_parlio_rx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioRxClockConfig, +) { + PCR::regs().parl_clk_rx_conf().modify(|_, w| unsafe { + w.parl_clk_rx_sel().bits(match new_selector { + ParlioRxClockConfig::XtalClk => 0, + ParlioRxClockConfig::RcFastClk => 1, + ParlioRxClockConfig::PllF240m => 2, + }) + }); +} + +// PARLIO_TX_CLOCK + +fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| w.parl_clk_tx_en().bit(en)); +} + +fn configure_parlio_tx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioTxClockConfig, +) { + PCR::regs().parl_clk_tx_conf().modify(|_, w| unsafe { + w.parl_clk_tx_sel().bits(match new_selector { + ParlioTxClockConfig::XtalClk => 0, + ParlioTxClockConfig::RcFastClk => 1, + ParlioTxClockConfig::PllF240m => 2, + }) + }); +} + +// RMT_SCLK + +fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs().rmt_pd_ctrl().modify(|_, w| { + w.rmt_mem_force_pu().bit(en); + w.rmt_mem_force_pd().bit(!en) + }); + + PCR::regs() + .rmt_sclk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_rmt_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RmtSclkConfig, +) { + PCR::regs().rmt_sclk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + RmtSclkConfig::XtalClk => 0, + RmtSclkConfig::RcFastClk => 1, + RmtSclkConfig::PllF80m => 2, + }) + }); +} + +// TIMG0_FUNCTION_CLOCK + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg0_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 1, + Timg0FunctionClockConfig::PllF80m => 2, + }) + }); +} + +// TIMG0_WDT_CLOCK + +fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| w.tg0_wdt_clk_en().bit(en)); +} + +fn configure_timg0_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg0_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::RcFastClk => 1, + Timg0WdtClockConfig::PllF80m => 2, + }) + }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(en)); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg1_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 1, + Timg0FunctionClockConfig::PllF80m => 2, + }) + }); +} + +// TIMG1_WDT_CLOCK + +fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| w.tg1_wdt_clk_en().bit(en)); +} + +fn configure_timg1_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg1_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::RcFastClk => 1, + Timg0WdtClockConfig::PllF80m => 2, + }) + }); +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Disabling this prevents the device from booting + // PCR::regs() + // .uart(0) + // .clk_conf() + // .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(0).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Xtal => 0, + Uart0FunctionClockConfig::RcFast => 1, + Uart0FunctionClockConfig::PllF80m => 2, + }) + }); +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .uart(1) + .clk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(1).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Xtal => 0, + Uart0FunctionClockConfig::RcFast => 1, + Uart0FunctionClockConfig::PllF80m => 2, + }) + }); +} diff --git a/esp-hal/src/soc/esp32c5/gpio.rs b/esp-hal/src/soc/esp32c5/gpio.rs new file mode 100644 index 00000000000..43cb5ccaff1 --- /dev/null +++ b/esp-hal/src/soc/esp32c5/gpio.rs @@ -0,0 +1,37 @@ +//! # GPIO configuration module (ESP32-C5) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-C5` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +// crate::gpio::lp_io::lp_gpio! { +// 0 +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +// } diff --git a/esp-hal/src/soc/esp32c5/mod.rs b/esp-hal/src/soc/esp32c5/mod.rs new file mode 100644 index 00000000000..a4547719162 --- /dev/null +++ b/esp-hal/src/soc/esp32c5/mod.rs @@ -0,0 +1,29 @@ +//! # SOC (System-on-Chip) module (ESP32-C5) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-C5` chip. +//! +//! Also few constants are defined in this module for `ESP32-C5` chip: + +crate::unstable_module! { + pub mod clocks; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32c5 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants {} + +pub(crate) fn pre_init() { + // Reset TEE security modes. This allows unrestricted access to TEE masters, including DMA. + // FIXME: this is a temporary workaround until we have a proper solution for TEE security modes. + for m in crate::peripherals::TEE::regs().m_mode_ctrl_iter() { + m.reset(); + } + // this is hacky, but for some reason we must reset the output enable register manually + crate::peripherals::GPIO::regs().enable().reset(); +} diff --git a/esp-hal/src/soc/esp32c5/regi2c.rs b/esp-hal/src/soc/esp32c5/regi2c.rs new file mode 100644 index 00000000000..7cfd036c6d0 --- /dev/null +++ b/esp-hal/src/soc/esp32c5/regi2c.rs @@ -0,0 +1,206 @@ +use crate::{ + peripherals::{I2C_ANA_MST, MODEM_LPCON}, + rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}, +}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 0) { + reg: I2C_BBPLL_IR_CAL(0) { + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4), + field: I2C_BBPLL_IR_CAL_DELAY(3..0) + } + reg: I2C_BBPLL_IR_CAL_EXT_REG(1) { + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0) + } + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_ENB_FCAL(7..7), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_OC_TSCHGP(7..7), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_RSTB_DIV_ADC(0..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_EN_USB(7..7), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_OC_DR1(2..0) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DCUR(2..0) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_LOCK(7..7), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_CAP(3..0) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DREF(7..6), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_OC_VCO_DBIAS(1..0) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_ENT_ADC(7..6), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_PLL_MSB(3..3), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_BBADC_DCUR(1..0) + } + } + master: REGI2C_BIAS(0x6a, 0) { + reg: I2C_BIAS_DREG(1) { + field: I2C_BIAS_DREG_1P1_PVT(3..0) + } + } + master: REGI2C_DIG_REG(0x6d, 0) { + reg: I2C_DIG_REG4(4) { + field: I2C_DIG_REG_ENX_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG(4..0) + } + reg: I2C_DIG_REG5(5) { + field: I2C_DIG_REG_ENIF_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG6(6) { + field: I2C_DIG_REG_ENX_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG(4..0) + } + reg: I2C_DIG_REG_ENIF_DIG(7) { + field: I2C_DIG_REG_ENIF_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG9(9) { + field: I2C_DIG_REG_OR_EN_CONT_CAL(7..7) + } + reg: I2C_DIG_REG_XPD(13) { + field: I2C_DIG_REG_XPD_DIG_REG(3..3), + field: I2C_DIG_REG_XPD_RTC_REG(2..2) + } + reg: I2C_DIG_REG_SCK_DCAP(14) {} + } + master: REGI2C_ULP_CAL(0x61, 0) { + reg: I2C_ULP_CAL_IR(0) { + field: I2C_ULP_IR_DISABLE_WATCHDOG_CK(6..6), + field: I2C_ULP_IR_FORCE_XPD_IPH(4..4), + field: I2C_ULP_IR_FORCE_XPD_CK(2..2), + field: I2C_ULP_IR_RESETB(0..0) + } + reg: I2C_ULP_CAL_O(3) { + field: I2C_ULP_BG_O_DONE_FLAG(3..3), + field: I2C_ULP_O_DONE_FLAG(0..0) + } + reg: I2C_ULP_CAL_OCODE(4) {} + reg: I2C_ULP_IR_FORCE(5) { + field: I2C_ULP_IR_FORCE_CODE(6..6), + field: I2C_BOD_THRESHOLD(2..0) + } + reg: I2C_ULP_EXT_CODE(6) {} + } + master: REGI2C_SAR_I2C(0x69, 0) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) {} + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } +} + +fn regi2c_enable_block(block: u8) -> usize { + MODEM_LPCON::regs() + .clk_conf() + .modify(|_, w| w.clk_i2c_mst_en().set_bit()); + + // Before config I2C register, enable corresponding slave. + let i2c_sel_bits = I2C_ANA_MST::regs().ana_conf2().read(); + let i2c_sel = match block { + v if v == REGI2C_BBPLL.master => i2c_sel_bits.bbpll_mst_sel().bit_is_set(), + v if v == REGI2C_BIAS.master => i2c_sel_bits.bias_mst_sel().bit_is_set(), + v if v == REGI2C_DIG_REG.master => i2c_sel_bits.dig_reg_mst_sel().bit_is_set(), + v if v == REGI2C_ULP_CAL.master => i2c_sel_bits.ulp_cal_mst_sel().bit_is_set(), + v if v == REGI2C_SAR_I2C.master => i2c_sel_bits.sar_i2c_mst_sel().bit_is_set(), + _ => unreachable!(), + }; + I2C_ANA_MST::regs().ana_conf1().write(|w| unsafe { + const I2C_MST_ANA_CONF1_M: u32 = 0x00FFFFFF; + w.bits(I2C_MST_ANA_CONF1_M); + match block { + v if v == REGI2C_BBPLL.master => w.bbpll_rd().clear_bit(), + v if v == REGI2C_BIAS.master => w.bias_rd().clear_bit(), + v if v == REGI2C_DIG_REG.master => w.dig_reg_rd().clear_bit(), + v if v == REGI2C_ULP_CAL.master => w.ulp_cal_rd().clear_bit(), + v if v == REGI2C_SAR_I2C.master => w.sar_i2c_rd().clear_bit(), + _ => unreachable!(), + } + }); + + if i2c_sel { 0 } else { 1 } +} + +pub(crate) fn regi2c_read(block: u8, _host_id: u8, reg_add: u8) -> u8 { + let master = regi2c_enable_block(block); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).read().data().bits() +} + +pub(crate) fn regi2c_write(block: u8, _host_id: u8, reg_add: u8, data: u8) { + let master = regi2c_enable_block(block); + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add); + w.read_write().set_bit(); + w.data().bits(data) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} +} diff --git a/esp-hal/src/soc/esp32c6/clocks.rs b/esp-hal/src/soc/esp32c6/clocks.rs new file mode 100644 index 00000000000..977150b2416 --- /dev/null +++ b/esp-hal/src/soc/esp32c6/clocks.rs @@ -0,0 +1,781 @@ +//! Clock tree definitions and implementations for ESP32-C6. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (136k RC_SLOW and 17.5M RC_FAST) are not calibrated here, this system +//! can only give a rough estimate of their frequency. They can be calibrated separately using a +//! known crystal frequency. +//! - Some of the SOC capabilities are not implemented: I2S external pad clock source, external 32k +//! oscillator, LP_DYN_FAST_CLK, APB_DECREASE_DIV (which seems unnecessary to model), +//! PCR_CPU_HS_120M_FORCE. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use crate::{ + peripherals::{I2C_ANA_MST, LP_CLKRST, MODEM_LPCON, PCR, PMU, TIMG0, TIMG1}, + soc::regi2c, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, +} + +impl CpuClock { + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + soc_root_clk: Some(SocRootClkConfig::Pll), + cpu_hs_div: Some(CpuHsDivConfig::_1), + cpu_ls_div: None, // Unused when root clock is PLL + ahb_hs_div: Some(AhbHsDivConfig::_3), + ahb_ls_div: None, // Unused when root clock is PLL + // Configures 80MHz MSPI clock + mspi_fast_hs_clk: Some(MspiFastHsClkConfig::_5), + mspi_fast_ls_clk: None, // Unused when root clock is PLL + apb_clk: Some(ApbClkConfig::new(0)), + ledc_sclk: Some(LedcSclkConfig::PllF80m), + lp_fast_clk: Some(LpFastClkConfig::RcFastClk), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + soc_root_clk: Some(SocRootClkConfig::Pll), + cpu_hs_div: Some(CpuHsDivConfig::_0), + cpu_ls_div: None, // Unused when root clock is PLL + ahb_hs_div: Some(AhbHsDivConfig::_3), + ahb_ls_div: None, // Unused when root clock is PLL + // Configures 80MHz MSPI clock + mspi_fast_hs_clk: Some(MspiFastHsClkConfig::_5), + mspi_fast_ls_clk: None, // Unused when root clock is PLL + apb_clk: Some(ApbClkConfig::new(0)), + ledc_sclk: Some(LedcSclkConfig::PllF80m), + lp_fast_clk: Some(LpFastClkConfig::RcFastClk), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + if self.xtal_clk.is_none() { + self.xtal_clk = Some(XtalClkConfig::_40); + } + + // On ESP32C6, MSPI source clock's default HS divider leads to 120MHz, which is unusable + // before calibration. Therefore, before switching SOC_ROOT_CLK to HS, we need to set + // MSPI source clock HS divider to make it run at 80MHz after the switch. + // PLL = 480MHz, so divider is 6. + ClockTree::with(|clocks| configure_mspi_fast_hs_clk(clocks, MspiFastHsClkConfig::_5)); + + self.apply(); + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. +} + +// PLL_CLK + +fn enable_pll_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // TODO: these are WT fields, PAC should be fixed accordingly + PMU::regs().imm_hp_ck_power().modify(|_, w| { + w.tie_high_xpd_bb_i2c().set_bit(); + w.tie_high_xpd_bbpll().set_bit(); + w.tie_high_xpd_bbpll_i2c().set_bit() + }); + PMU::regs() + .imm_hp_ck_power() + .modify(|_, w| w.tie_high_global_bbpll_icg().set_bit()); + } else { + PMU::regs() + .imm_hp_ck_power() + .modify(|_, w| w.tie_low_global_bbpll_icg().set_bit()); + PMU::regs().imm_hp_ck_power().modify(|_, w| { + w.tie_low_xpd_bb_i2c().set_bit(); + w.tie_low_xpd_bbpll().set_bit(); + w.tie_low_xpd_bbpll_i2c().set_bit() + }); + + return; + } + + // enable i2c mst clk by temporarily forcing on + let old_clk_conf = MODEM_LPCON::regs().clk_conf().read(); + MODEM_LPCON::regs().clk_conf().write(|w| { + unsafe { w.bits(old_clk_conf.bits()) }; + w.clk_i2c_mst_en().set_bit() + }); + + MODEM_LPCON::regs() + .i2c_mst_clk_conf() + .modify(|_, w| w.clk_i2c_mst_sel_160m().set_bit()); + + // BBPLL CALIBRATION START + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + const DIV_REF: u8 = 0; // Do not divide reference clock + const DCHGP: u8 = 5; + const DCUR: u8 = 3; + + const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; + + const I2C_BBPLL_LREF: u8 = (DCHGP << I2C_BBPLL_OC_DCHGP_LSB) | DIV_REF; + const I2C_BBPLL_DCUR: u8 = + (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | DCUR; + + regi2c::I2C_BBPLL_OC_REF.write_reg(I2C_BBPLL_LREF); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(8); // multiply 40MHz XTAL by 8+4 + regi2c::I2C_BBPLL_OC_DR1.write_field(0); + regi2c::I2C_BBPLL_OC_DR3.write_field(0); + regi2c::I2C_BBPLL_REG6.write_reg(I2C_BBPLL_DCUR); + regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(2); + + // WAIT CALIBRATION DONE + while I2C_ANA_MST::regs() + .ana_conf0() + .read() + .cal_done() + .bit_is_clear() + {} + + // workaround bbpll calibration might stop early + crate::rom::ets_delay_us(10); + + // BBPLL CALIBRATION STOP + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().set_bit(); + w.bbpll_stop_force_low().clear_bit() + }); + + MODEM_LPCON::regs() + .clk_conf() + .write(|w| unsafe { w.bits(old_clk_conf.bits()) }); +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_fosc_clk().bit(en)); + // TODO: Should the digital clock gate be a different clock node? + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_fosc().bit(en)); + crate::rom::ets_delay_us(5); +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + LP_CLKRST::regs().xtal32k().write(|w| unsafe { + w.dac_xtal32k().bits(3); + w.dres_xtal32k().bits(3); + w.dgm_xtal32k().bits(3); + w.dbuf_xtal32k().bit(true) + }); + + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_xtal32k().bit(en)); + + // Enable for digital part + // TODO: Should the digital clock gate be a different clock node? + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_xtal32k().bit(en)); +} + +// OSC_SLOW_CLK + +fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // TODO: + // gpio_ll_input_enable(&GPIO, SOC_EXT_OSC_SLOW_GPIO_NUM); + // REG_SET_BIT(LP_AON_GPIO_HOLD0_REG, BIT(SOC_EXT_OSC_SLOW_GPIO_NUM)); + todo!(); + + // No need to configure anything else for OSC_SLOW_CLK +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 128; + crate::soc::regi2c::I2C_DIG_REG_SCK_DCAP.write_reg(RTC_CNTL_SCK_DCAP_DEFAULT); + } + + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_rc32k().bit(en)); + + // Enable for digital part + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_osc32k().bit(en)); +} + +// HP_ROOT_CLK + +fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, managed by hardware. +} + +fn configure_hp_root_clk_impl(_clocks: &mut ClockTree, _new_config: HpRootClkConfig) { + // Nothing to do, managed by hardware. +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CpuClkConfig, +) { + // Nothing to do, automatically managed by hardware. +} + +// AHB_CLK + +fn configure_ahb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: AhbClkConfig, +) { + // Nothing to do, automatically managed by hardware. +} + +// MSPI_FAST_CLK + +fn enable_mspi_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mspi_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: MspiFastClkConfig, +) { + // Nothing to do, automatically managed by hardware. +} + +// SOC_ROOT_CLK + +fn enable_soc_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_soc_root_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: SocRootClkConfig, +) { + PCR::regs().sysclk_conf().modify(|_, w| unsafe { + w.soc_clk_sel().bits(match new_selector { + SocRootClkConfig::Xtal => 0, + SocRootClkConfig::Pll => 1, + SocRootClkConfig::RcFast => 2, + }) + }); +} + +// CPU_HS_DIV + +fn enable_cpu_hs_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_hs_div_impl(_clocks: &mut ClockTree, new_config: CpuHsDivConfig) { + PCR::regs() + .cpu_freq_conf() + .modify(|_, w| unsafe { w.cpu_hs_div_num().bits(new_config.divisor() as u8) }); +} + +// CPU_LS_DIV + +fn enable_cpu_ls_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_ls_div_impl(_clocks: &mut ClockTree, new_config: CpuLsDivConfig) { + PCR::regs() + .cpu_freq_conf() + .modify(|_, w| unsafe { w.cpu_ls_div_num().bits(new_config.divisor() as u8) }); +} + +// AHB_HS_DIV + +fn enable_ahb_hs_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ahb_hs_div_impl(_clocks: &mut ClockTree, new_config: AhbHsDivConfig) { + PCR::regs() + .ahb_freq_conf() + .modify(|_, w| unsafe { w.ahb_hs_div_num().bits(new_config.divisor() as u8) }); +} + +// AHB_LS_DIV + +fn enable_ahb_ls_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ahb_ls_div_impl(_clocks: &mut ClockTree, new_config: AhbLsDivConfig) { + PCR::regs() + .ahb_freq_conf() + .modify(|_, w| unsafe { w.ahb_ls_div_num().bits(new_config.divisor() as u8) }); +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl(_clocks: &mut ClockTree, new_config: ApbClkConfig) { + PCR::regs() + .apb_freq_conf() + .modify(|_, w| unsafe { w.apb_div_num().bits(new_config.divisor() as u8) }); +} + +// MSPI_FAST_HS_CLK + +fn enable_mspi_fast_hs_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, automatically managed by hardware. +} + +fn configure_mspi_fast_hs_clk_impl(_clocks: &mut ClockTree, new_config: MspiFastHsClkConfig) { + PCR::regs() + .mspi_clk_conf() + .modify(|_, w| unsafe { w.mspi_fast_hs_div_num().bits(new_config.divisor() as u8) }); +} + +// MSPI_FAST_LS_CLK + +fn enable_mspi_fast_ls_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, automatically managed by hardware. +} + +fn configure_mspi_fast_ls_clk_impl(_clocks: &mut ClockTree, new_config: MspiFastLsClkConfig) { + PCR::regs() + .mspi_clk_conf() + .modify(|_, w| unsafe { w.mspi_fast_ls_div_num().bits(new_config.divisor() as u8) }); +} + +// PLL_F48M + +fn enable_pll_f48m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_F80M + +fn enable_pll_f80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_F160M + +fn enable_pll_f160m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_F240M + +fn enable_pll_f240m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// LEDC_SCLK + +fn enable_ledc_sclk_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .ledc_sclk_conf() + .modify(|_, w| w.ledc_sclk_en().bit(en)); +} + +fn configure_ledc_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LedcSclkConfig, +) { + PCR::regs().ledc_sclk_conf().modify(|_, w| unsafe { + w.ledc_sclk_sel().bits(match new_selector { + LedcSclkConfig::PllF80m => 1, + LedcSclkConfig::RcFastClk => 2, + LedcSclkConfig::XtalClk => 3, + }) + }); +} + +// PARLIO_RX_CLOCK + +fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| w.parl_clk_rx_en().bit(en)); +} + +fn configure_parlio_rx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioRxClockConfig, +) { + PCR::regs().parl_clk_rx_conf().modify(|_, w| unsafe { + w.parl_clk_rx_sel().bits(match new_selector { + ParlioRxClockConfig::XtalClk => 0, + ParlioRxClockConfig::RcFastClk => 2, + ParlioRxClockConfig::PllF240m => 1, + }) + }); +} + +// PARLIO_TX_CLOCK + +fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| w.parl_clk_tx_en().bit(en)); +} + +fn configure_parlio_tx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioTxClockConfig, +) { + PCR::regs().parl_clk_tx_conf().modify(|_, w| unsafe { + w.parl_clk_tx_sel().bits(match new_selector { + ParlioTxClockConfig::XtalClk => 0, + ParlioTxClockConfig::RcFastClk => 2, + ParlioTxClockConfig::PllF240m => 1, + }) + }); +} + +// RMT_SCLK + +fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .rmt_sclk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_rmt_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RmtSclkConfig, +) { + PCR::regs().rmt_sclk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + RmtSclkConfig::PllF80m => 1, + RmtSclkConfig::RcFastClk => 2, + RmtSclkConfig::XtalClk => 3, + }) + }); +} + +// XTAL_D2_CLK + +fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, the divider is always on. +} + +// LP_FAST_CLK + +fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_lp_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpFastClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| { + w.fast_clk_sel().bit(match new_selector { + LpFastClkConfig::RcFastClk => false, + LpFastClkConfig::XtalD2Clk => true, + }) + }); +} + +// LP_SLOW_CLK + +fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_lp_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpSlowClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe { + w.slow_clk_sel().bits(match new_selector { + LpSlowClkConfig::Xtal32k => 1, + LpSlowClkConfig::RcSlow => 0, + LpSlowClkConfig::OscSlow => 2, + }) + }); +} + +// MCPWM0_FUNCTION_CLOCK + +fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pwm_clk_conf() + .modify(|_, w| w.pwm_clkm_en().bit(en)); +} + +fn configure_mcpwm0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Mcpwm0FunctionClockConfig, +) { + PCR::regs().pwm_clk_conf().modify(|_, w| unsafe { + w.pwm_clkm_sel().bits(match new_selector { + Mcpwm0FunctionClockConfig::PllF160m => 1, + Mcpwm0FunctionClockConfig::XtalClk => 2, + Mcpwm0FunctionClockConfig::RcFastClk => 3, + }) + }); +} + +// TIMG0_FUNCTION_CLOCK + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg0_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 2, + Timg0FunctionClockConfig::PllF80m => 1, + }) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG0_WDT_CLOCK + +fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| w.tg0_wdt_clk_en().bit(en)); +} + +fn configure_timg0_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg0_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::PllF80m => 1, + Timg0WdtClockConfig::RcFastClk => 2, + }) + }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(en)); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg1_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 2, + Timg0FunctionClockConfig::PllF80m => 1, + }) + }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG1_WDT_CLOCK + +fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| w.tg1_wdt_clk_en().bit(en)); +} + +fn configure_timg1_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg1_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::PllF80m => 1, + Timg0WdtClockConfig::RcFastClk => 2, + }) + }); +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .uart(0) + .clk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(0).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF80m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .uart(1) + .clk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(1).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF80m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} diff --git a/esp-hal/src/soc/esp32c6/gpio.rs b/esp-hal/src/soc/esp32c6/gpio.rs new file mode 100644 index 00000000000..57e9a778c0b --- /dev/null +++ b/esp-hal/src/soc/esp32c6/gpio.rs @@ -0,0 +1,38 @@ +//! # GPIO configuration module (ESP32-C6) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-C6` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +crate::gpio::lp_io::lp_gpio! { + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 +} diff --git a/esp-hal/src/soc/esp32c6/lp_core.rs b/esp-hal/src/soc/esp32c6/lp_core.rs new file mode 100644 index 00000000000..c0595827d15 --- /dev/null +++ b/esp-hal/src/soc/esp32c6/lp_core.rs @@ -0,0 +1,137 @@ +//! # Control the LP core +//! +//! ## Overview +//! The `LP_CORE` driver provides an interface for controlling and managing the +//! low power core of `ESP` chips, allowing efficient low power operation and +//! wakeup from sleep based on configurable sources. The low power core is +//! responsible for executing low power tasks while the high power core is in +//! sleep mode. +//! +//! The `LpCore` struct provides methods to stop and run the low power core. +//! +//! The `stop` method stops the low power core, putting it into a sleep state. +//! +//! The `run` method starts the low power core and specifies the wakeup source. +//! +//! ⚠️: The examples for LP Core are quite extensive, so for a more +//! detailed study of how to use this LP Core please visit [the repository +//! with corresponding example]. +//! +//! [the repository with corresponding example]: https://github.com/esp-rs/esp-hal/blob/main/examples/peripheral/lp_core/lp_blinky/src/main.rs + +use crate::peripherals::{LP_AON, LP_CORE, LP_PERI, LPWR, PMU}; + +/// Represents the possible wakeup sources for the LP (Low Power) core. +#[derive(Debug, Clone, Copy)] +pub enum LpCoreWakeupSource { + /// Wakeup source from the HP (High Performance) CPU. + HpCpu, +} + +/// Clock sources for the LP core +#[derive(Debug, Clone, Copy)] +pub enum LpCoreClockSource { + /// 17.5 MHz clock + /// + /// Might not be very accurate + RcFastClk, + /// 20 MHz clock + XtalD2Clk, +} + +/// Represents the Low Power (LP) core peripheral. +pub struct LpCore<'d> { + _lp_core: LP_CORE<'d>, +} + +impl<'d> LpCore<'d> { + /// Create a new instance using [LpCoreClockSource::RcFastClk] + pub fn new(lp_core: LP_CORE<'d>) -> Self { + LpCore::new_with_clock(lp_core, LpCoreClockSource::RcFastClk) + } + + /// Create a new instance using the given clock + pub fn new_with_clock(lp_core: LP_CORE<'d>, clk_src: LpCoreClockSource) -> Self { + match clk_src { + LpCoreClockSource::RcFastClk => LPWR::regs() + .lp_clk_conf() + .modify(|_, w| w.fast_clk_sel().clear_bit()), + LpCoreClockSource::XtalD2Clk => LPWR::regs() + .lp_clk_conf() + .modify(|_, w| w.fast_clk_sel().set_bit()), + }; + + let mut this = Self { _lp_core: lp_core }; + this.stop(); + + // clear all of LP_RAM - this makes sure .bss is cleared without relying + let lp_ram = + unsafe { core::slice::from_raw_parts_mut(0x5000_0000 as *mut u32, 16 * 1024 / 4) }; + lp_ram.fill(0u32); + + this + } + + /// Stop the LP core + pub fn stop(&mut self) { + ulp_lp_core_stop(); + } + + /// Start the LP core + pub fn run(&mut self, wakeup_src: LpCoreWakeupSource) { + ulp_lp_core_run(wakeup_src); + } +} + +fn ulp_lp_core_stop() { + PMU::regs() + .lp_cpu_pwr1() + .modify(|_, w| unsafe { w.lp_cpu_wakeup_en().bits(0) }); + PMU::regs() + .lp_cpu_pwr1() + .modify(|_, w| w.lp_cpu_sleep_req().set_bit()); +} + +fn ulp_lp_core_run(wakeup_src: LpCoreWakeupSource) { + let lp_aon = LP_AON::regs(); + let pmu = PMU::regs(); + let lp_peri = LP_PERI::regs(); + + // Enable LP-Core + lp_aon.lpcore().modify(|_, w| w.disable().clear_bit()); + + // Allow LP core to access LP memory during sleep + lp_aon + .lpbus() + .modify(|_, w| w.fast_mem_mux_sel().clear_bit()); + lp_aon + .lpbus() + .modify(|_, w| w.fast_mem_mux_sel_update().set_bit()); + + // Enable stall at sleep request + pmu.lp_cpu_pwr0() + .modify(|_, w| w.lp_cpu_slp_stall_en().set_bit()); + + // Enable reset after wake-up + pmu.lp_cpu_pwr0() + .modify(|_, w| w.lp_cpu_slp_reset_en().set_bit()); + + // Set wake-up sources + let src = match wakeup_src { + LpCoreWakeupSource::HpCpu => 0x01, + }; + pmu.lp_cpu_pwr1() + .modify(|_, w| unsafe { w.lp_cpu_wakeup_en().bits(src) }); + + // Enable JTAG debugging + lp_peri + .cpu() + .modify(|_, w| w.lpcore_dbgm_unavaliable().clear_bit()); + + // wake up + match wakeup_src { + LpCoreWakeupSource::HpCpu => { + pmu.hp_lp_cpu_comm().write(|w| w.hp_trigger_lp().set_bit()); + } + } +} diff --git a/esp-hal/src/soc/esp32c6/mod.rs b/esp-hal/src/soc/esp32c6/mod.rs new file mode 100644 index 00000000000..227ce96342c --- /dev/null +++ b/esp-hal/src/soc/esp32c6/mod.rs @@ -0,0 +1,49 @@ +//! # SOC (System-on-Chip) module (ESP32-C6) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-C6` chip. +//! +//! Also few constants are defined in this module for `ESP32-C6` chip: +//! * TIMG_DEFAULT_CLK_SRC: 1 - Timer clock source +//! * I2S_DEFAULT_CLK_SRC: 2 - I2S clock source +//! * I2S_SCLK: 160_000_000 - I2S clock frequency + +crate::unstable_module! { + pub mod clocks; + pub mod lp_core; + pub mod trng; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32c6 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// The clock frequency for the I2S peripheral in Hertz. + pub const I2S_SCLK: u32 = 160_000_000; + /// The default clock source for the I2S peripheral. + pub const I2S_DEFAULT_CLK_SRC: u8 = 2; +} + +pub(crate) fn pre_init() { + // By default, these access path filters are enable and allow the access to + // masters only if they are in TEE mode. + // + // Since all masters except HP CPU boot in REE mode, default setting of these + // filters will deny the access by all masters except HP CPU. + // + // So, disabling these filters early. + + crate::peripherals::LP_APM::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); + crate::peripherals::LP_APM0::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); + crate::peripherals::HP_APM::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); +} diff --git a/esp-hal/src/soc/esp32c6/regi2c.rs b/esp-hal/src/soc/esp32c6/regi2c.rs new file mode 100644 index 00000000000..7cfd036c6d0 --- /dev/null +++ b/esp-hal/src/soc/esp32c6/regi2c.rs @@ -0,0 +1,206 @@ +use crate::{ + peripherals::{I2C_ANA_MST, MODEM_LPCON}, + rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}, +}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 0) { + reg: I2C_BBPLL_IR_CAL(0) { + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4), + field: I2C_BBPLL_IR_CAL_DELAY(3..0) + } + reg: I2C_BBPLL_IR_CAL_EXT_REG(1) { + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0) + } + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_ENB_FCAL(7..7), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_OC_TSCHGP(7..7), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_RSTB_DIV_ADC(0..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_EN_USB(7..7), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_OC_DR1(2..0) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DCUR(2..0) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_LOCK(7..7), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_CAP(3..0) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DREF(7..6), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_OC_VCO_DBIAS(1..0) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_ENT_ADC(7..6), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_PLL_MSB(3..3), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_BBADC_DCUR(1..0) + } + } + master: REGI2C_BIAS(0x6a, 0) { + reg: I2C_BIAS_DREG(1) { + field: I2C_BIAS_DREG_1P1_PVT(3..0) + } + } + master: REGI2C_DIG_REG(0x6d, 0) { + reg: I2C_DIG_REG4(4) { + field: I2C_DIG_REG_ENX_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG(4..0) + } + reg: I2C_DIG_REG5(5) { + field: I2C_DIG_REG_ENIF_RTC_DREG(7..7), + field: I2C_DIG_REG_EXT_RTC_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG6(6) { + field: I2C_DIG_REG_ENX_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG(4..0) + } + reg: I2C_DIG_REG_ENIF_DIG(7) { + field: I2C_DIG_REG_ENIF_DIG_DREG(7..7), + field: I2C_DIG_REG_EXT_DIG_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG9(9) { + field: I2C_DIG_REG_OR_EN_CONT_CAL(7..7) + } + reg: I2C_DIG_REG_XPD(13) { + field: I2C_DIG_REG_XPD_DIG_REG(3..3), + field: I2C_DIG_REG_XPD_RTC_REG(2..2) + } + reg: I2C_DIG_REG_SCK_DCAP(14) {} + } + master: REGI2C_ULP_CAL(0x61, 0) { + reg: I2C_ULP_CAL_IR(0) { + field: I2C_ULP_IR_DISABLE_WATCHDOG_CK(6..6), + field: I2C_ULP_IR_FORCE_XPD_IPH(4..4), + field: I2C_ULP_IR_FORCE_XPD_CK(2..2), + field: I2C_ULP_IR_RESETB(0..0) + } + reg: I2C_ULP_CAL_O(3) { + field: I2C_ULP_BG_O_DONE_FLAG(3..3), + field: I2C_ULP_O_DONE_FLAG(0..0) + } + reg: I2C_ULP_CAL_OCODE(4) {} + reg: I2C_ULP_IR_FORCE(5) { + field: I2C_ULP_IR_FORCE_CODE(6..6), + field: I2C_BOD_THRESHOLD(2..0) + } + reg: I2C_ULP_EXT_CODE(6) {} + } + master: REGI2C_SAR_I2C(0x69, 0) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) {} + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } +} + +fn regi2c_enable_block(block: u8) -> usize { + MODEM_LPCON::regs() + .clk_conf() + .modify(|_, w| w.clk_i2c_mst_en().set_bit()); + + // Before config I2C register, enable corresponding slave. + let i2c_sel_bits = I2C_ANA_MST::regs().ana_conf2().read(); + let i2c_sel = match block { + v if v == REGI2C_BBPLL.master => i2c_sel_bits.bbpll_mst_sel().bit_is_set(), + v if v == REGI2C_BIAS.master => i2c_sel_bits.bias_mst_sel().bit_is_set(), + v if v == REGI2C_DIG_REG.master => i2c_sel_bits.dig_reg_mst_sel().bit_is_set(), + v if v == REGI2C_ULP_CAL.master => i2c_sel_bits.ulp_cal_mst_sel().bit_is_set(), + v if v == REGI2C_SAR_I2C.master => i2c_sel_bits.sar_i2c_mst_sel().bit_is_set(), + _ => unreachable!(), + }; + I2C_ANA_MST::regs().ana_conf1().write(|w| unsafe { + const I2C_MST_ANA_CONF1_M: u32 = 0x00FFFFFF; + w.bits(I2C_MST_ANA_CONF1_M); + match block { + v if v == REGI2C_BBPLL.master => w.bbpll_rd().clear_bit(), + v if v == REGI2C_BIAS.master => w.bias_rd().clear_bit(), + v if v == REGI2C_DIG_REG.master => w.dig_reg_rd().clear_bit(), + v if v == REGI2C_ULP_CAL.master => w.ulp_cal_rd().clear_bit(), + v if v == REGI2C_SAR_I2C.master => w.sar_i2c_rd().clear_bit(), + _ => unreachable!(), + } + }); + + if i2c_sel { 0 } else { 1 } +} + +pub(crate) fn regi2c_read(block: u8, _host_id: u8, reg_add: u8) -> u8 { + let master = regi2c_enable_block(block); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).read().data().bits() +} + +pub(crate) fn regi2c_write(block: u8, _host_id: u8, reg_add: u8, data: u8) { + let master = regi2c_enable_block(block); + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add); + w.read_write().set_bit(); + w.data().bits(data) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} +} diff --git a/esp-hal/src/soc/esp32c6/trng.rs b/esp-hal/src/soc/esp32c6/trng.rs new file mode 100644 index 00000000000..de9ef0524e8 --- /dev/null +++ b/esp-hal/src/soc/esp32c6/trng.rs @@ -0,0 +1,109 @@ +use crate::{ + peripherals::{APB_SARADC, PCR, PMU}, + soc::regi2c, +}; + +const SAR2_CHANNEL: u32 = 9; +const SAR2_ATTEN: u32 = 1; +const SAR1_ATTEN: u32 = 1; +const PATTERN_BIT_WIDTH: u32 = 6; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + let pcr = PCR::regs(); + let pmu = PMU::regs(); + let apb_saradc = APB_SARADC::regs(); + + unsafe { + // Pull SAR ADC out of reset + pcr.saradc_conf().modify(|_, w| w.saradc_rst_en().set_bit()); + + pcr.saradc_conf() + .modify(|_, w| w.saradc_rst_en().clear_bit()); + + // Enable SAR ADC APB clock + pcr.saradc_conf() + .modify(|_, w| w.saradc_reg_clk_en().set_bit()); + + // Enable ADC_CTRL_CLK (SAR ADC function clock) + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_en().set_bit()); + + // Select XTAL clock (40 MHz) source for ADC_CTRL_CLK + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_sel().bits(0)); + + // Set the clock divider for ADC_CTRL_CLK to default value (in case it has been + // changed) + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_div_num().bits(0)); + + // some ADC sensor registers are in power group PERIF_I2C and need to be enabled + // via PMU + pmu.rf_pwc().modify(|_, w| w.perif_i2c_rstb().set_bit()); + + pmu.rf_pwc().modify(|_, w| w.xpd_perif_i2c().set_bit()); + + // Config ADC circuit (Analog part) with I2C(HOST ID 0x69) and chose internal + // voltage as sampling source + regi2c::ADC_SAR_DTEST_RTC.write_field(2); + regi2c::ADC_SAR_ENT_RTC.write_field(1); + regi2c::ADC_SAR1_ENCAL_REF.write_field(1); + regi2c::ADC_SAR2_ENCAL_REF.write_field(1); + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(0x08); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(0x66); + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(0x08); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(0x66); + + // create patterns and set them in pattern table + let pattern_one: u32 = (SAR2_CHANNEL << 2) | SAR2_ATTEN; // we want channel 9 with max attenuation + let pattern_two: u32 = SAR1_ATTEN; // we want channel 0 with max attenuation, channel doesn't really matter here + let pattern_table: u32 = + (pattern_two << (3 * PATTERN_BIT_WIDTH)) | (pattern_one << (2 * PATTERN_BIT_WIDTH)); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.bits(pattern_table)); + + // set pattern length to 2 (APB_SARADC_SAR_PATT_LEN counts from 0) + apb_saradc.ctrl().modify(|_, w| w.sar_patt_len().bits(1)); + + // Same as in C3 + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(15)); + + // set timer expiry (timer is ADC_CTRL_CLK) + apb_saradc.ctrl2().modify(|_, w| w.timer_target().bits(200)); + + // enable timer + apb_saradc.ctrl2().modify(|_, w| w.timer_en().set_bit()); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + unsafe { + APB_SARADC::regs() + .ctrl2() + .modify(|_, w| w.timer_en().clear_bit()); + + APB_SARADC::regs() + .sar_patt_tab1() + .modify(|_, w| w.bits(0xFFFFFF)); + + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(0x60); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(0); + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(0x60); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(0); + regi2c::ADC_SAR_DTEST_RTC.write_field(0); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + regi2c::ADC_SAR1_ENCAL_REF.write_field(0); + regi2c::ADC_SAR2_ENCAL_REF.write_field(0); + + PCR::regs() + .saradc_clkm_conf() + .modify(|_, w| w.bits(0x00404000)); + + PCR::regs().saradc_conf().modify(|_, w| w.bits(0x5)); + } +} diff --git a/esp-hal/src/soc/esp32h2/clocks.rs b/esp-hal/src/soc/esp32h2/clocks.rs new file mode 100644 index 00000000000..4778ad54335 --- /dev/null +++ b/esp-hal/src/soc/esp32h2/clocks.rs @@ -0,0 +1,619 @@ +//! Clock tree definitions and implementations for ESP32-H2. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (130k RC_SLOW and 8M RC_FAST) are not calibrated here, this system can +//! only give a rough estimate of their frequency. They can be calibrated separately using a known +//! crystal frequency. +//! - Some of the SOC capabilities are not implemented. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use crate::{ + peripherals::{I2C_ANA_MST, LP_CLKRST, MODEM_LPCON, PCR, PMU, TIMG0, TIMG1}, + soc::regi2c, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 96 MHz CPU clock + #[default] + _96MHz = 96, +} + +impl CpuClock { + const PRESET_96: ClockConfig = ClockConfig { + xtal_clk: None, + hp_root_clk: Some(HpRootClkConfig::Pll96), + cpu_clk: Some(CpuClkConfig::new(0)), + ahb_clk: Some(AhbClkConfig::new(0)), + apb_clk: Some(ApbClkConfig::new(0)), + lp_fast_clk: Some(LpFastClkConfig::RcFastClk), + lp_slow_clk: Some(LpSlowClkConfig::RcSlow), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_96MHz => CpuClock::PRESET_96, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_96 => Some(CpuClock::_96MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + if self.xtal_clk.is_none() { + self.xtal_clk = Some(XtalClkConfig::_32); + } + + self.apply(); + } +} + +fn clk_ll_bus_update() { + PCR::regs() + .bus_clk_update() + .modify(|_, w| w.bus_clock_update().bit(true)); + + // reg_get_bit + while PCR::regs() + .bus_clk_update() + .read() + .bus_clock_update() + .bit_is_set() + {} +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. +} + +// PLL_F96M_CLK + +fn enable_pll_f96m_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + PMU::regs().imm_hp_ck_power().modify(|_, w| { + w.tie_high_xpd_bb_i2c().set_bit(); + w.tie_high_xpd_bbpll().set_bit(); + w.tie_high_xpd_bbpll_i2c().set_bit() + }); + PMU::regs() + .imm_hp_ck_power() + .modify(|_, w| w.tie_high_global_bbpll_icg().set_bit()); + } else { + PMU::regs() + .imm_hp_ck_power() + .modify(|_, w| w.tie_low_global_bbpll_icg().set_bit()); + PMU::regs().imm_hp_ck_power().modify(|_, w| { + w.tie_low_xpd_bbpll().set_bit(); + w.tie_low_xpd_bbpll_i2c().set_bit() + }); + + return; + } + + // Enable I2C master clock + MODEM_LPCON::regs() + .clk_conf_force_on() + .modify(|_, w| w.clk_i2c_mst_fo().set_bit()); + + // Set I2C clock to 96MHz + const MODEM_LPCON_CLK_I2C_SEL_96M: u32 = 1 << 0; // FIXME add to PAC + MODEM_LPCON::regs() + .clk_conf() + .modify(|r, w| unsafe { w.bits(r.bits() | MODEM_LPCON_CLK_I2C_SEL_96M) }); + + // BPPLL calibration start + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + regi2c::I2C_BBPLL_OC_REF_DIV.write_field(0); + regi2c::I2C_BBPLL_OC_DIV.write_field(1); + regi2c::I2C_BBPLL_OC_DHREF_SEL.write_field(3); + regi2c::I2C_BBPLL_OC_DLREF_SEL.write_field(1); + + // WAIT CALIBRATION DONE + while I2C_ANA_MST::regs() + .ana_conf0() + .read() + .cal_done() + .bit_is_clear() + {} + + // workaround bbpll calibration might stop early + crate::rom::ets_delay_us(10); + + // BBPLL CALIBRATION STOP + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().set_bit(); + w.bbpll_stop_force_low().clear_bit() + }); +} + +// PLL_F64M_CLK - called Flash PLL in esp-idf + +fn enable_pll_f64m_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_F48M_CLK + +fn enable_pll_f48m_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_fosc_clk().bit(en)); + // Enable for digital part + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_fosc().bit(en)); +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_xtal32k().bit(en)); + + // Enable for digital part + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_xtal32k().bit(en)); +} + +// OSC_SLOW_CLK + +fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + unimplemented!() +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 85; + crate::soc::regi2c::I2C_PMU_OC_SCK_DCAP.write_reg(RTC_CNTL_SCK_DCAP_DEFAULT); + } + + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_rc32k().bit(en)); + + // Enable for digital part + LP_CLKRST::regs() + .clk_to_hp() + .modify(|_, w| w.icg_hp_osc32k().bit(en)); +} + +// PLL_LP_CLK + +fn enable_pll_lp_clk_impl(_clocks: &mut ClockTree, en: bool) { + PMU::regs() + .hp_sleep_lp_ck_power() + .modify(|_, w| w.hp_sleep_xpd_lppll().bit(en)); +} + +// HP_ROOT_CLK + +fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_hp_root_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: HpRootClkConfig, +) { + PCR::regs().sysclk_conf().modify(|_, w| unsafe { + w.soc_clk_sel().bits(match new_selector { + HpRootClkConfig::Pll96 => 1, + HpRootClkConfig::Pll64 => 3, + HpRootClkConfig::Xtal => 0, + HpRootClkConfig::RcFast => 2, + }) + }); + + // "When selecting the clock source of HP_ROOT_CLK, or configuring the clock divisor for CPU_CLK + // and AHB_CLK ..." + clk_ll_bus_update(); +} + +// CPU_CLK + +fn enable_cpu_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_cpu_clk_impl(_clocks: &mut ClockTree, new_config: CpuClkConfig) { + PCR::regs() + .cpu_freq_conf() + .modify(|_, w| unsafe { w.cpu_div_num().bits(new_config.divisor() as u8) }); + + // "When selecting the clock source of HP_ROOT_CLK, or configuring the clock divisor for CPU_CLK + // and AHB_CLK ..." + clk_ll_bus_update(); +} + +// AHB_CLK + +fn enable_ahb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_ahb_clk_impl(_clocks: &mut ClockTree, new_config: AhbClkConfig) { + PCR::regs() + .ahb_freq_conf() + .modify(|_, w| unsafe { w.ahb_div_num().bits(new_config.divisor() as u8) }); + + // "When selecting the clock source of HP_ROOT_CLK, or configuring the clock divisor for CPU_CLK + // and AHB_CLK ..." + clk_ll_bus_update(); +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl(_clocks: &mut ClockTree, new_config: ApbClkConfig) { + PCR::regs() + .apb_freq_conf() + .modify(|_, w| unsafe { w.apb_div_num().bits(new_config.divisor() as u8) }); +} + +// XTAL_D2_CLK + +fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// LP_FAST_CLK + +fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_lp_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpFastClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe { + w.fast_clk_sel().bits(match new_selector { + LpFastClkConfig::RcFastClk => 0, + LpFastClkConfig::XtalD2Clk => 1, + LpFastClkConfig::PllLpClk => 2, + }) + }); +} + +// LP_SLOW_CLK + +fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_lp_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LpSlowClkConfig, +) { + LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe { + w.slow_clk_sel().bits(match new_selector { + LpSlowClkConfig::RcSlow => 0, + LpSlowClkConfig::Xtal32k => 1, + LpSlowClkConfig::OscSlow => 2, + }) + }); +} + +// MCPWM0_FUNCTION_CLOCK + +fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .pwm_clk_conf() + .modify(|_, w| w.pwm_clkm_en().bit(en)); +} + +fn configure_mcpwm0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Mcpwm0FunctionClockConfig, +) { + PCR::regs().pwm_clk_conf().modify(|_, w| unsafe { + w.pwm_clkm_sel().bits(match new_selector { + Mcpwm0FunctionClockConfig::XtalClk => 0, + Mcpwm0FunctionClockConfig::RcFastClk => 1, + Mcpwm0FunctionClockConfig::PllF96m => 2, + }) + }); +} + +// TIMG0_FUNCTION_CLOCK + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg0_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 1, + Timg0FunctionClockConfig::PllF48m => 2, + }) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG0_WDT_CLOCK + +fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| w.tg0_wdt_clk_en().bit(en)); +} + +fn configure_timg0_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup0_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg0_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::RcFastClk => 1, + Timg0WdtClockConfig::PllF48m => 2, + }) + }); +} + +// PARLIO_RX_CLOCK + +fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_rx_conf() + .modify(|_, w| w.parl_clk_rx_en().bit(en)); +} + +fn configure_parlio_rx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioRxClockConfig, +) { + PCR::regs().parl_clk_rx_conf().modify(|_, w| unsafe { + w.parl_clk_rx_sel().bits(match new_selector { + ParlioRxClockConfig::XtalClk => 0, + ParlioRxClockConfig::RcFastClk => 2, + ParlioRxClockConfig::PllF96m => 1, + }) + }); +} + +// PARLIO_TX_CLOCK + +fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .parl_clk_tx_conf() + .modify(|_, w| w.parl_clk_tx_en().bit(en)); +} + +fn configure_parlio_tx_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: ParlioTxClockConfig, +) { + PCR::regs().parl_clk_tx_conf().modify(|_, w| unsafe { + w.parl_clk_tx_sel().bits(match new_selector { + ParlioTxClockConfig::XtalClk => 0, + ParlioTxClockConfig::RcFastClk => 2, + ParlioTxClockConfig::PllF96m => 1, + }) + }); +} + +// RMT_SCLK + +fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .rmt_sclk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_rmt_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RmtSclkConfig, +) { + PCR::regs().rmt_sclk_conf().modify(|_, w| { + w.sclk_sel().bit(match new_selector { + RmtSclkConfig::XtalClk => false, + RmtSclkConfig::RcFastClk => true, + }) + }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(en)); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + // TODO: add variants to PAC + PCR::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| unsafe { + w.tg1_timer_clk_sel().bits(match new_selector { + Timg0FunctionClockConfig::XtalClk => 0, + Timg0FunctionClockConfig::RcFastClk => 1, + Timg0FunctionClockConfig::PllF48m => 2, + }) + }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG1_WDT_CLOCK + +fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| w.tg1_wdt_clk_en().bit(en)); +} + +fn configure_timg1_wdt_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0WdtClockConfig, +) { + PCR::regs() + .timergroup1_wdt_clk_conf() + .modify(|_, w| unsafe { + w.tg1_wdt_clk_sel().bits(match new_selector { + Timg0WdtClockConfig::XtalClk => 0, + Timg0WdtClockConfig::RcFastClk => 1, + Timg0WdtClockConfig::PllF48m => 2, + }) + }); +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // At least on revision 0.1 switching SCLK off causes the chip to no longer boot. +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(0).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF48m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .uart(1) + .clk_conf() + .modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + PCR::regs().uart(1).clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::PllF48m => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} diff --git a/esp-hal/src/soc/esp32h2/gpio.rs b/esp-hal/src/soc/esp32h2/gpio.rs new file mode 100644 index 00000000000..94937c10729 --- /dev/null +++ b/esp-hal/src/soc/esp32h2/gpio.rs @@ -0,0 +1,55 @@ +//! # GPIO configuration module (ESP32-H2) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-H2` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +for_each_lp_function! { + (($_rtc:ident, LP_GPIOn, $n:literal), $gpio:ident) => { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for $crate::peripherals::$gpio<'_> { + fn rtc_number(&self) -> u8 { + $n + } + + fn rtcio_pad_hold(&self, enable: bool) { + let mask = 1 << $n; + unsafe { + let lp_aon = $crate::peripherals::LP_AON::regs(); + + lp_aon.gpio_hold0().modify(|r, w| { + if enable { + w.gpio_hold0().bits(r.gpio_hold0().bits() | mask) + } else { + w.gpio_hold0().bits(r.gpio_hold0().bits() & !mask) + } + }); + } + } + } + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPinWithResistors for $crate::peripherals::$gpio<'_> {} + }; +} diff --git a/esp-hal/src/soc/esp32h2/mod.rs b/esp-hal/src/soc/esp32h2/mod.rs new file mode 100644 index 00000000000..82d7c190cb4 --- /dev/null +++ b/esp-hal/src/soc/esp32h2/mod.rs @@ -0,0 +1,48 @@ +//! # SOC (System-on-Chip) module (ESP32-H2) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-H2` chip. +//! +//! Also few constants are defined in this module for `ESP32-H2` chip: +//! * TIMG_DEFAULT_CLK_SRC: 2 - Timer clock source +//! * I2S_DEFAULT_CLK_SRC: 1 - I2S clock source +//! * I2S_SCLK: 96_000_000 - I2S clock frequency + +crate::unstable_module! { + pub mod clocks; + pub mod trng; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32h2 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// Default clock source for the I2S peripheral. + pub const I2S_DEFAULT_CLK_SRC: u8 = 1; + /// Clock frequency for the I2S peripheral, in Hertz. + pub const I2S_SCLK: u32 = 96_000_000; +} + +pub(crate) fn pre_init() { + // By default, these access path filters are enable and allow the access to + // masters only if they are in TEE mode. + // + // Since all masters except HP CPU boot in REE mode, default setting of these + // filters will deny the access by all masters except HP CPU. + // + // So, disabling these filters early. + + crate::peripherals::LP_APM::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); + crate::peripherals::LP_APM0::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); + crate::peripherals::HP_APM::regs() + .func_ctrl() + .write(|w| unsafe { w.bits(0x0) }); +} diff --git a/esp-hal/src/soc/esp32h2/regi2c.rs b/esp-hal/src/soc/esp32h2/regi2c.rs new file mode 100644 index 00000000000..bcddc36f492 --- /dev/null +++ b/esp-hal/src/soc/esp32h2/regi2c.rs @@ -0,0 +1,142 @@ +use crate::{ + peripherals::{I2C_ANA_MST, MODEM_LPCON}, + rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}, +}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 0) { + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(5..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4) + } + } + master: REGI2C_BIAS(0x6a, 0) { + reg: I2C_BIAS_DREG0(0) { + field: I2C_BIAS_DREG_0P8(7..4) + } + reg: I2C_BIAS_DREG1(1) { + field: I2C_BIAS_DREG_1P1_PVT(3..0) + } + } + master: REGI2C_PMU_REG(0x6d, 0) { + reg: I2C_PMU_REG8(8) { + field: I2C_PMU_EN_I2C_DIG_DREG_SLP(3..3), + field: I2C_PMU_EN_I2C_RTC_DREG_SLP(2..2), + field: I2C_PMU_EN_I2C_DIG_DREG(1..1), + field: I2C_PMU_EN_I2C_RTC_DREG(0..0) + } + reg: I2C_PMU_XPD(9) { + field: I2C_PMU_OR_XPD_DIG_REG(6..6), + field: I2C_PMU_OR_XPD_RTC_REG(4..4) + } + reg: I2C_PMU_OC_SCK_DCAP(14) {} + reg: I2C_PMU_REG15(15) { + field: I2C_PMU_OR_XPD_TRX(2..2) + } + reg: I2C_PMU_REG21(21) { + field: I2C_PMU_SEL_PLL8M_REF(6..6) + } + } + master: REGI2C_ULP_CAL(0x61, 0) { + reg: I2C_ULP_CAL_IR(0) { + field: I2C_ULP_IR_RESETB(0..0) + } + } + master: REGI2C_SAR_I2C(0x69, 0) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4), + field: ADC_SAR1_SAMPLE_CYCLE(2..0) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) { + field: ADC_SARADC_TSENS_DAC(3..0) + } + reg: I2C_SAR_REG7(7) { + field: ADC_SARADC_EN_TOUT_SAR1_BUS(5..5), + field: ADC_SARADC_ENT_SAR(3..1), + field: ADC_SARADC_DTEST(1..0) + } + reg: I2C_SAR_REG8(8) { + field: ADC_SAR1_ENCAL_GND(1..1) + } + } +} + +fn regi2c_enable_block(block: u8) -> usize { + MODEM_LPCON::regs() + .clk_conf() + .modify(|_, w| w.clk_i2c_mst_en().set_bit()); + + // Before config I2C register, enable corresponding slave. + let i2c_sel_bits = I2C_ANA_MST::regs().ana_conf2().read(); + let i2c_sel = match block { + v if v == REGI2C_BBPLL.master => i2c_sel_bits.bbpll_mst_sel().bit_is_set(), + v if v == REGI2C_BIAS.master => i2c_sel_bits.bias_mst_sel().bit_is_set(), + v if v == REGI2C_PMU_REG.master => i2c_sel_bits.dig_reg_mst_sel().bit_is_set(), + v if v == REGI2C_ULP_CAL.master => i2c_sel_bits.ulp_cal_mst_sel().bit_is_set(), + v if v == REGI2C_SAR_I2C.master => i2c_sel_bits.sar_i2c_mst_sel().bit_is_set(), + _ => unreachable!(), + }; + I2C_ANA_MST::regs().ana_conf1().write(|w| unsafe { + const I2C_MST_ANA_CONF1_M: u32 = 0x00FFFFFF; + w.bits(I2C_MST_ANA_CONF1_M); + match block { + v if v == REGI2C_BBPLL.master => w.bbpll_rd().clear_bit(), + v if v == REGI2C_BIAS.master => w.bias_rd().clear_bit(), + v if v == REGI2C_PMU_REG.master => w.dig_reg_rd().clear_bit(), + v if v == REGI2C_ULP_CAL.master => w.ulp_cal_rd().clear_bit(), + v if v == REGI2C_SAR_I2C.master => w.sar_i2c_rd().clear_bit(), + _ => unreachable!(), + } + }); + + if i2c_sel { 0 } else { 1 } +} + +pub(crate) fn regi2c_read(block: u8, _host_id: u8, reg_add: u8) -> u8 { + let master = regi2c_enable_block(block); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} + + I2C_ANA_MST::regs().i2c_ctrl(master).read().data().bits() +} + +pub(crate) fn regi2c_write(block: u8, _host_id: u8, reg_add: u8, data: u8) { + let master = regi2c_enable_block(block); + + I2C_ANA_MST::regs().i2c_ctrl(master).write(|w| unsafe { + w.slave_addr().bits(block); + w.slave_reg_addr().bits(reg_add); + w.read_write().set_bit(); + w.data().bits(data) + }); + + while I2C_ANA_MST::regs().i2c_ctrl(master).read().busy().bit() {} +} diff --git a/esp-hal/src/soc/esp32h2/trng.rs b/esp-hal/src/soc/esp32h2/trng.rs new file mode 100644 index 00000000000..59647947fd2 --- /dev/null +++ b/esp-hal/src/soc/esp32h2/trng.rs @@ -0,0 +1,108 @@ +use crate::{ + peripherals::{APB_SARADC, PCR, PMU}, + soc::regi2c, +}; + +const SAR2_CHANNEL: u32 = 9; +const SAR2_ATTEN: u32 = 1; +const SAR1_ATTEN: u32 = 1; +const PATTERN_BIT_WIDTH: u32 = 6; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + let pcr = PCR::regs(); + let pmu = PMU::regs(); + let apb_saradc = APB_SARADC::regs(); + + unsafe { + // Pull SAR ADC out of reset + pcr.saradc_conf().modify(|_, w| w.saradc_rst_en().set_bit()); + + pcr.saradc_conf() + .modify(|_, w| w.saradc_rst_en().clear_bit()); + + // Enable SAR ADC APB clock + pcr.saradc_conf() + .modify(|_, w| w.saradc_reg_clk_en().set_bit()); + + // Enable ADC_CTRL_CLK (SAR ADC function clock) + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_en().set_bit()); + + // Select XTAL clock (40 MHz) source for ADC_CTRL_CLK + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_sel().bits(0)); + + // Set the clock divider for ADC_CTRL_CLK to default value (in case it has been + // changed) + pcr.saradc_clkm_conf() + .modify(|_, w| w.saradc_clkm_div_num().bits(0)); + + // some ADC sensor registers are in power group PERIF_I2C and need to be enabled + // via PMU + pmu.rf_pwc().modify(|_, w| w.xpd_perif_i2c().set_bit()); + + // Config ADC circuit (Analog part) with I2C(HOST ID 0x69) and chose internal + // voltage as sampling source + regi2c::ADC_SARADC_DTEST.write_field(2); + regi2c::ADC_SARADC_ENT_SAR.write_field(1); + regi2c::ADC_SARADC_EN_TOUT_SAR1_BUS.write_field(1); + + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(0x08); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(0x66); + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(0x08); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(0x66); + + // create patterns and set them in pattern table + let pattern_one: u32 = (SAR2_CHANNEL << 2) | SAR2_ATTEN; // we want channel 9 with max attenuation + let pattern_two: u32 = SAR1_ATTEN; // we want channel 0 with max attenuation, channel doesn't really matter here + let pattern_table: u32 = + (pattern_two << (3 * PATTERN_BIT_WIDTH)) | (pattern_one << (2 * PATTERN_BIT_WIDTH)); + + apb_saradc + .sar_patt_tab1() + .modify(|_, w| w.bits(pattern_table)); + + // set pattern length to 2 (APB_SARADC_SAR_PATT_LEN counts from 0) + apb_saradc.ctrl().modify(|_, w| w.sar_patt_len().bits(0)); + + // Same as in C3 + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(15)); + + // set timer expiry (timer is ADC_CTRL_CLK) + apb_saradc.ctrl2().modify(|_, w| w.timer_target().bits(200)); + + // enable timer + apb_saradc.ctrl2().modify(|_, w| w.timer_en().set_bit()); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + unsafe { + APB_SARADC::regs() + .ctrl2() + .modify(|_, w| w.timer_en().clear_bit()); + + APB_SARADC::regs() + .sar_patt_tab1() + .modify(|_, w| w.bits(0xFFFFFF)); + + // Revert ADC I2C configuration and initial voltage source setting + regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(0x60); + regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(0); + regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(0x60); + regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(0); + regi2c::ADC_SARADC_DTEST.write_field(0); + regi2c::ADC_SARADC_ENT_SAR.write_field(0); + regi2c::ADC_SARADC_EN_TOUT_SAR1_BUS.write_field(0); + // disable ADC_CTRL_CLK (SAR ADC function clock) + PCR::regs() + .saradc_clkm_conf() + .modify(|_, w| w.bits(0x00404000)); + + // Set PCR_SARADC_CONF_REG to initial state + PCR::regs().saradc_conf().modify(|_, w| w.bits(0x5)); + } +} diff --git a/esp-hal/src/soc/esp32s2/clocks.rs b/esp-hal/src/soc/esp32s2/clocks.rs new file mode 100644 index 00000000000..3154e17ebe0 --- /dev/null +++ b/esp-hal/src/soc/esp32s2/clocks.rs @@ -0,0 +1,751 @@ +//! Clock tree definitions and implementations for ESP32-S2. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (90k RC_SLOW and 8M RC_FAST) are not calibrated here, this system can +//! only give a rough estimate of their frequency. They can be calibrated separately using a known +//! crystal frequency. +//! - Some of the SOC capabilities are not implemented: using external 32K oscillator instead of a +//! crystal, divider for CLK8M and possibly more. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom}; + +use crate::{ + peripherals::{I2C_ANA_MST, LPWR, SYSCON, SYSTEM, TIMG0, TIMG1, UART0, UART1}, + soc::regi2c, + time::Rate, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, + + /// 240 MHz CPU clock + _240MHz = 240, +} + +impl CpuClock { + // Crypto peripherals don't like 320MHz PLL. + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_480), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_6), + system_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_480), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_3), + system_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; + const PRESET_240: ClockConfig = ClockConfig { + xtal_clk: None, + pll_clk: Some(PllClkConfig::_480), + apll_clk: None, + cpu_pll_div: Some(CpuPllDivConfig::_2), + system_pre_div: None, + cpu_clk: Some(CpuClkConfig::Pll), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + CpuClock::_240MHz => CpuClock::PRESET_240, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + v if v == CpuClock::PRESET_240 => Some(CpuClock::_240MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + if self.xtal_clk.is_none() { + self.xtal_clk = Some(XtalClkConfig::_40); + } + + // Switch CPU to XTAL before reconfiguring PLL. + ClockTree::with(|clocks| { + configure_xtal_clk(clocks, XtalClkConfig::_40); + configure_system_pre_div(clocks, SystemPreDivConfig::new(0)); + configure_cpu_clk(clocks, CpuClkConfig::Xtal); + }); + + self.apply(); + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + let power_down = !en; + + // regi2c_ctrl_ll_i2c_bbpll_enable + // regi2c_ctrl_ll_i2c_apll_enable + I2C_ANA_MST::regs().config1().modify(|_, w| { + w.bbpll().bit(power_down); + w.apll().bit(power_down) + }); + + LPWR::regs().options0().modify(|_, w| { + w.bb_i2c_force_pd().bit(power_down); + w.bbpll_force_pd().bit(power_down); + w.bbpll_i2c_force_pd().bit(power_down) + }); + + if !en { + return; + } + + ensure_voltage_raised(clocks); + + // Analog part + let div_ref: u8; + let div7_0: u8; + let dr1: u8; + let dr3: u8; + let dchgp: u8; + let dcur: u8; + let mode_hf: u8; + + match unwrap!(clocks.pll_clk) { + PllClkConfig::_480 => { + div_ref = 0; + div7_0 = 8; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 4; + mode_hf = 0x6B; + } + PllClkConfig::_320 => { + div_ref = 0; + div7_0 = 4; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 5; + mode_hf = 0x69; + } + } + + const I2C_BBPLL_OC_DCHGP_LSB: u8 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u8 = 6; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u8 = 4; + + regi2c::I2C_BBPLL_REG4.write_reg(mode_hf); + let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | (div_ref); + let i2c_bbpll_dcur = + (2 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (1 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; + regi2c::I2C_BBPLL_REG2.write_reg(i2c_bbpll_lref); + regi2c::I2C_BBPLL_REG3.write_reg(div7_0); + + regi2c::I2C_BBPLL_OC_DR1.write_field(dr1); + regi2c::I2C_BBPLL_OC_DR3.write_field(dr3); + regi2c::I2C_BBPLL_REG6.write_reg(i2c_bbpll_dcur); + + regi2c::I2C_BBPLL_IR_CAL_ENX_CAP.write_field(1); + + let mut success = false; + for ext_cap in 0..16 { + regi2c::I2C_BBPLL_IR_CAL_EXT_CAP.write_field(ext_cap); + + if regi2c::I2C_BBPLL_OR_CAL_CAP.read() == 0 { + success |= true; + break; + } + } + + assert!(success, "BBPLL calibration failed"); + + ensure_voltage_minimal(clocks); +} + +fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { + // Nothing to do. The PLL may still be powered down. We'll configure it in + // `enable_pll_clk_impl`. +} + +// APLL_CLK + +fn enable_apll_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs().ana_conf().modify(|_, w| { + w.plla_force_pd().bit(!en); + w.plla_force_pu().bit(en) + }); + if en { + todo!( + "Implement APLL configuration and calibration here. See esp-idf `clk_ll_apll_set_config`" + ); + } +} + +fn configure_apll_clk_impl(_clocks: &mut ClockTree, _config: ApllClkConfig) { + // Nothing to do. The APLL may still be powered down. We'll configure it in + // `enable_apll_clk_impl`. +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + const CLK_LL_RC_FAST_ENABLE_WAIT_DEFAULT: u8 = 5; + const CLK_LL_RC_FAST_WAIT_DEFAULT: u8 = 20; + LPWR::regs().clk_conf().modify(|_, w| w.enb_ck8m().bit(!en)); + LPWR::regs().timer1().modify(|_, w| unsafe { + w.ck8m_wait().bits(if en { + CLK_LL_RC_FAST_ENABLE_WAIT_DEFAULT + } else { + CLK_LL_RC_FAST_WAIT_DEFAULT + }) + }); + if en { + ets_delay_us(50); + } +} + +// CPU_PLL_DIV_IN + +fn enable_cpu_pll_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CpuPllDivInConfig, +) { + // Nothing to do. +} + +// CPU_PLL_DIV + +fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { + // Nothing to do. +} + +// SYSTEM_PRE_DIV_IN + +fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: SystemPreDivInConfig, +) { + // Nothing to do. +} + +// SYSTEM_PRE_DIV + +fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_impl(_clocks: &mut ClockTree, new_config: SystemPreDivConfig) { + SYSTEM::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) }); +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: ApbClkConfig, +) { + // Nothing to do. +} + +// REF_TICK + +fn enable_ref_tick_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: RefTickConfig, +) { + // Nothing to do. +} + +// REF_TICK_XTAL + +fn enable_ref_tick_xtal_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_xtal_impl(_clocks: &mut ClockTree, new_config: RefTickXtalConfig) { + SYSCON::regs() + .tick_conf() + .modify(|_, w| unsafe { w.xtal_tick_num().bits(new_config.divisor() as u8) }); +} + +// REF_TICK_CK8M + +fn enable_ref_tick_ck8m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_ref_tick_ck8m_impl(_clocks: &mut ClockTree, new_config: RefTickCk8mConfig) { + SYSCON::regs() + .tick_conf() + .modify(|_, w| unsafe { w.ck8m_tick_num().bits(new_config.divisor() as u8) }); +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CpuClkConfig, +) { + // Based on TRM Table 6.2-2 + let clock_source_sel0_bit = match new_selector { + CpuClkConfig::Xtal => 0, + CpuClkConfig::RcFast => 2, + CpuClkConfig::Apll => 3, + CpuClkConfig::Pll => 1, + }; + let clock_source_sel1_bit = clocks.pll_clk == Some(PllClkConfig::_480); + let clock_source_sel2_bit = match (clocks.pll_clk, clocks.cpu_pll_div) { + (Some(PllClkConfig::_480), Some(CpuPllDivConfig::_6)) => 0, + (Some(PllClkConfig::_480), Some(CpuPllDivConfig::_3)) => 1, + (Some(PllClkConfig::_480), Some(CpuPllDivConfig::_2)) => 2, + + // 320 MHz or APLL + (_, Some(CpuPllDivConfig::_4)) => 0, + (_, Some(CpuPllDivConfig::_2)) => 1, + + // don't care + _ => 0, + }; + + ensure_voltage_raised(clocks); + + if new_selector == CpuClkConfig::Pll { + SYSTEM::regs().cpu_per_conf().modify(|_, w| { + unsafe { w.cpuperiod_sel().bits(clock_source_sel2_bit) }; + w.pll_freq_sel().bit(clock_source_sel1_bit) + }); + } + + SYSTEM::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.soc_clk_sel().bits(clock_source_sel0_bit) }); + + // Store frequencies in expected places. + let cpu_freq = Rate::from_hz(cpu_clk_frequency(clocks)); + ets_update_cpu_frequency_rom(cpu_freq.as_mhz()); + + let apb_freq = Rate::from_hz(apb_clk_frequency(clocks)); + update_apb_frequency(apb_freq); + + ensure_voltage_minimal(clocks); +} + +fn update_apb_frequency(freq: Rate) { + let freq_shifted = (freq.as_hz() >> 12) & 0xFFFF; + let value = freq_shifted | (freq_shifted << 16); + LPWR::regs() + .store5() + .modify(|_, w| unsafe { w.data().bits(value) }); +} + +fn uses_80mhz_flash() -> bool { + unsafe { + crate::soc::pac::SPI0::steal() + .clock() + .read() + .clk_equ_sysclk() + .bit_is_set() + } +} +fn is_max_cpu_speed(clocks: &mut ClockTree) -> bool { + clocks.cpu_clk == Some(CpuClkConfig::Pll) + && (clocks.pll_clk, clocks.cpu_pll_div) + == (Some(PllClkConfig::_480), Some(CpuPllDivConfig::_2)) +} + +const RTC_CNTL_DBIAS_1V10: u8 = 4; +const RTC_CNTL_DBIAS_1V25: u8 = 7; + +fn ensure_voltage_raised(clocks: &mut ClockTree) { + if is_max_cpu_speed(clocks) || uses_80mhz_flash() { + LPWR::regs().reg().modify(|_, w| unsafe { + w.dig_reg_dbias_wak().bits(RTC_CNTL_DBIAS_1V25); + w.dbias_wak().bits(RTC_CNTL_DBIAS_1V25) + }); + + ets_delay_us(40); + } +} + +fn ensure_voltage_minimal(clocks: &mut ClockTree) { + if !is_max_cpu_speed(clocks) { + LPWR::regs().reg().modify(|_, w| unsafe { + if uses_80mhz_flash() { + w.dig_reg_dbias_wak().bits(RTC_CNTL_DBIAS_1V25); + } else { + w.dig_reg_dbias_wak().bits(RTC_CNTL_DBIAS_1V10); + } + w.dbias_wak().bits(RTC_CNTL_DBIAS_1V10) + }); + ets_delay_us(40); + } +} + +// APB_CLK_CPU_DIV2 + +fn enable_apb_clk_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// APB_CLK_80M + +fn enable_apb_clk_80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + // This bit only enables the clock for the digital core, what about RTC? Should we split the + // clock node in two? + LPWR::regs().ext_xtl_conf().modify(|_, w| unsafe { + w.dac_xtal_32k().bits(3); + w.dres_xtal_32k().bits(3); + w.dgm_xtal_32k().bits(3); + w.dbuf_xtal_32k().bit(true); + w.xpd_xtal_32k().bit(en); + w.xtal32k_xpd_force().bit(!en) + }); + + // TODO: external oscillator may need different settings + + // Enable for digital part + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_xtal32k_en().bit(en)); +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of the 90k clock. The higher the value of DCAP, the lower + // the frequency. There is no separate enable bit, just make sure the calibration + // value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255; + LPWR::regs() + .reg() + .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) }); + + // Also configure the divider here to its usual value of 1. + + // Updating the divider should be part of the RC_SLOW_CLK divider config: + let slow_clk_conf = LPWR::regs().slow_clk_conf(); + // Invalidate + let new_value = slow_clk_conf.modify(|_, w| w.ana_clk_div_vld().clear_bit()); + // Update divider + let new_value = slow_clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ana_clk_div().bits(0) + }); + // Re-synchronize + slow_clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ana_clk_div_vld().set_bit() + }); + } +} + +// RC_FAST_DIV_CLK + +fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs().clk_conf().modify(|_, w| { + // Active-low: clear to enable, set to disable. + w.enb_ck8m_div().bit(!en); + unsafe { w.ck8m_div().bits(1) } // divide by 256 + }); +} + +// XTAL_DIV_CLK + +fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RTC_SLOW_CLK + +fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcSlowClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| unsafe { + w.ana_clk_rtc_sel().bits(match new_selector { + RtcSlowClkConfig::RcSlow => 0, + RtcSlowClkConfig::Xtal32k => 1, + RtcSlowClkConfig::RcFast => 2, + }) + }); + + ets_delay_us(300); +} + +// RTC_FAST_CLK + +fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcFastClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| { + w.fast_clk_rtc_sel() + .bit(new_selector == RtcFastClkConfig::Rc) + }); + + ets_delay_us(3); +} + +// UART_MEM_CLK + +fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: these functions (peripheral bus clock control) should be generated, + // replacing current PeripheralClockControl code. + // Enabling clock should probably not reset the peripheral. + let regs = SYSTEM::regs(); + + if en { + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(true)); + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(false)); + } + + regs.perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(en)); +} + +// TIMG0_FUNCTION_CLOCK + +// Note that the function clock is a pre-requisite of the timer, but does not enable the counter. + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + TIMG0::regs().regclk().modify(|_, w| w.clk_en().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG0::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); + TIMG0::regs().t(1).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +impl Timg0CalibrationClockConfig { + fn cali_clk_sel_bits(self) -> u8 { + match self { + Timg0CalibrationClockConfig::RtcClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + } + } +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs() + .rtccalicfg() + .modify(|_, w| unsafe { w.rtc_cali_clk_sel().bits(new_selector.cali_clk_sel_bits()) }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + TIMG1::regs().regclk().modify(|_, w| w.clk_en().bit(en)); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG1::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); + TIMG1::regs().t(1).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs() + .rtccalicfg() + .modify(|_, w| unsafe { w.rtc_cali_clk_sel().bits(new_selector.cali_clk_sel_bits()) }); +} + +// UART0_MEM_CLOCK + +fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart0_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART0::regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(new_selector == Uart0FunctionClockConfig::Apb) + }); +} + +// UART1_MEM_CLOCK + +fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart1_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART1::regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(new_selector == Uart0FunctionClockConfig::Apb) + }); +} diff --git a/esp-hal/src/soc/esp32s2/gpio.rs b/esp-hal/src/soc/esp32s2/gpio.rs new file mode 100644 index 00000000000..d21518077eb --- /dev/null +++ b/esp-hal/src/soc/esp32s2/gpio.rs @@ -0,0 +1,174 @@ +//! # GPIO configuration module (ESP32-S2) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-S2` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `impl_get_rtc_pad`: +//! * This macro_rule generates a function to get a specific RTC pad. It takes a single +//! argument `$pad_name`, which is an identifier representing the name of the pad. Returns a +//! reference to the corresponding RTC pad. +//! - `impl_get_rtc_pad_indexed`: +//! * This macro_rule generates a function similar to the previous one but for indexed RTC +//! pads. It takes two arguments: `$pad_name`, which represents the name of the pad, and +//! `$idx`, which is the index of the specific pad. Returns a reference to the indexed RTC +//! pad. +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +#[rustfmt::skip] +macro_rules! pin_reg { + ($rtcio:ident, GPIO0 ) => { $rtcio.touch_pad(0) }; + ($rtcio:ident, GPIO1 ) => { $rtcio.touch_pad(1) }; + ($rtcio:ident, GPIO2 ) => { $rtcio.touch_pad(2) }; + ($rtcio:ident, GPIO3 ) => { $rtcio.touch_pad(3) }; + ($rtcio:ident, GPIO4 ) => { $rtcio.touch_pad(4) }; + ($rtcio:ident, GPIO5 ) => { $rtcio.touch_pad(5) }; + ($rtcio:ident, GPIO6 ) => { $rtcio.touch_pad(6) }; + ($rtcio:ident, GPIO7 ) => { $rtcio.touch_pad(7) }; + ($rtcio:ident, GPIO8 ) => { $rtcio.touch_pad(8) }; + ($rtcio:ident, GPIO9 ) => { $rtcio.touch_pad(9) }; + ($rtcio:ident, GPIO10) => { $rtcio.touch_pad(10) }; + ($rtcio:ident, GPIO11) => { $rtcio.touch_pad(11) }; + ($rtcio:ident, GPIO12) => { $rtcio.touch_pad(12) }; + ($rtcio:ident, GPIO13) => { $rtcio.touch_pad(13) }; + ($rtcio:ident, GPIO14) => { $rtcio.touch_pad(14) }; + ($rtcio:ident, GPIO15) => { $rtcio.xtal_32p_pad() }; + ($rtcio:ident, GPIO16) => { $rtcio.xtal_32n_pad() }; + ($rtcio:ident, GPIO17) => { $rtcio.pad_dac1() }; + ($rtcio:ident, GPIO18) => { $rtcio.pad_dac2() }; + ($rtcio:ident, GPIO19) => { $rtcio.rtc_pad19() }; + ($rtcio:ident, GPIO20) => { $rtcio.rtc_pad20() }; + ($rtcio:ident, GPIO21) => { $rtcio.rtc_pad21() }; +} + +#[rustfmt::skip] +macro_rules! hold_field { + ($reg:ident, GPIO0 ) => { $reg.touch_pad0() }; + ($reg:ident, GPIO1 ) => { $reg.touch_pad1() }; + ($reg:ident, GPIO2 ) => { $reg.touch_pad2() }; + ($reg:ident, GPIO3 ) => { $reg.touch_pad3() }; + ($reg:ident, GPIO4 ) => { $reg.touch_pad4() }; + ($reg:ident, GPIO5 ) => { $reg.touch_pad5() }; + ($reg:ident, GPIO6 ) => { $reg.touch_pad6() }; + ($reg:ident, GPIO7 ) => { $reg.touch_pad7() }; + ($reg:ident, GPIO8 ) => { $reg.touch_pad8() }; + ($reg:ident, GPIO9 ) => { $reg.touch_pad9() }; + ($reg:ident, GPIO10) => { $reg.touch_pad10() }; + ($reg:ident, GPIO11) => { $reg.touch_pad11() }; + ($reg:ident, GPIO12) => { $reg.touch_pad12() }; + ($reg:ident, GPIO13) => { $reg.touch_pad13() }; + ($reg:ident, GPIO14) => { $reg.touch_pad14() }; + ($reg:ident, GPIO15) => { $reg.x32p() }; + ($reg:ident, GPIO16) => { $reg.x32n() }; + ($reg:ident, GPIO17) => { $reg.pdac1() }; + ($reg:ident, GPIO18) => { $reg.pdac2() }; + ($reg:ident, GPIO19) => { $reg.pad19() }; + ($reg:ident, GPIO20) => { $reg.pad20() }; + ($reg:ident, GPIO21) => { $reg.pad21() }; +} + +for_each_lp_function! { + (($_rtc:ident, RTC_GPIOn, $n:literal), $gpio:ident) => { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl crate::gpio::RtcPin for crate::peripherals::$gpio<'_> { + fn rtc_number(&self) -> u8 { + $n + } + + /// Set the RTC properties of the pin. If `mux` is true then then pin is + /// routed to RTC, when false it is routed to IO_MUX. + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: crate::gpio::RtcFunction) { + enable_iomux_clk_gate(); + + let rtcio = crate::peripherals::RTC_IO::regs(); + pin_reg!(rtcio, $gpio) + .modify(|_, w| unsafe { + w.fun_ie().bit(input_enable); + w.mux_sel().bit(mux); + w.fun_sel().bits(func as u8) + }); + } + + fn rtcio_pad_hold(&self, enable: bool) { + crate::peripherals::LPWR::regs() + .pad_hold() + .modify(|_, w| hold_field!(w, $gpio).bit(enable)); + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl crate::gpio::RtcPinWithResistors for crate::peripherals::$gpio<'_> { + fn rtcio_pullup(&self, enable: bool) { + let rtcio = crate::peripherals::RTC_IO::regs(); + pin_reg!(rtcio, $gpio).modify(|_, w| w.rue().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + let rtcio = crate::peripherals::RTC_IO::regs(); + pin_reg!(rtcio, $gpio).modify(|_, w| w.rde().bit(enable)); + } + } + }; +} + +for_each_analog_function! { + (($_ch:ident, ADCn_CHm, $_n:literal, $_m:literal), $gpio:ident) => { + impl crate::peripherals::$gpio<'_> { + #[cfg(feature = "unstable")] + pub(crate) fn set_analog_impl(&self) { + use crate::gpio::RtcPin; + enable_iomux_clk_gate(); + + let rtcio = crate::peripherals::RTC_IO::regs(); + + // disable output + rtcio.enable_w1tc().write(|w| unsafe { w.enable_w1tc().bits(1 << self.rtc_number()) }); + + // disable open drain + rtcio.pin(self.rtc_number() as usize).modify(|_,w| w.pad_driver().bit(false)); + + pin_reg!(rtcio, $gpio).modify(|_,w| { + w.fun_ie().clear_bit(); + + // Connect pin to analog / RTC module instead of standard GPIO + w.mux_sel().set_bit(); + + // Select function "RTC function 1" (GPIO) for analog use + unsafe { w.fun_sel().bits(0b00) }; + + // Disable pull-up and pull-down resistors on the pin + w.rue().bit(false); + w.rde().bit(false); + + w + }); + } + } + }; +} + +fn enable_iomux_clk_gate() { + crate::peripherals::SENS::regs() + .sar_io_mux_conf() + .modify(|_, w| w.iomux_clk_gate_en().set_bit()); +} diff --git a/esp-hal/src/soc/esp32s2/mod.rs b/esp-hal/src/soc/esp32s2/mod.rs new file mode 100644 index 00000000000..2c3cd2b74a5 --- /dev/null +++ b/esp-hal/src/soc/esp32s2/mod.rs @@ -0,0 +1,66 @@ +//! # SOC (System-on-Chip) module (ESP32-S2) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-S2` chip. +//! +//! Also few constants are defined in this module for `ESP32-S2` chip: +//! * I2S_SCLK: 160_000_000 - I2S clock frequency +//! * I2S_DEFAULT_CLK_SRC: 2 - I2S clock source + +crate::unstable_module! { + pub mod clocks; + pub mod trng; + pub mod ulp_core; +} +pub mod gpio; +pub(crate) mod regi2c; + +pub(crate) use esp32s2 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// System clock frequency for the I2S peripheral, in Hertz. + pub const I2S_SCLK: u32 = 160_000_000; + /// Default clock source for the I2S peripheral. + pub const I2S_DEFAULT_CLK_SRC: u32 = 2; +} + +/// Write back a specific range of data in the cache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_writeback_addr(addr: u32, size: u32) { + unsafe extern "C" { + fn Cache_WriteBack_Addr(addr: u32, size: u32); + } + unsafe { + Cache_WriteBack_Addr(addr, size); + } +} + +/// Invalidate a specific range of addresses in the cache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { + unsafe extern "C" { + fn Cache_Invalidate_Addr(addr: u32, size: u32); + } + unsafe { + Cache_Invalidate_Addr(addr, size); + } +} + +/// Get the size of a cache line in the DCache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_get_dcache_line_size() -> u32 { + unsafe extern "C" { + fn Cache_Get_DCache_Line_Size() -> u32; + } + unsafe { Cache_Get_DCache_Line_Size() } +} + +pub(crate) unsafe fn configure_cpu_caches() {} + +pub(crate) fn pre_init() {} diff --git a/esp-hal/src/soc/esp32s2/regi2c.rs b/esp-hal/src/soc/esp32s2/regi2c.rs new file mode 100644 index 00000000000..3e52eb001da --- /dev/null +++ b/esp-hal/src/soc/esp32s2/regi2c.rs @@ -0,0 +1,203 @@ +use crate::{ + peripherals::{I2C_ANA_MST, SYSCON}, + rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}, +}; + +define_regi2c! { + master: REGI2C_SAR(0x69, 1) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4), + field: ADC_SAR1_SAMPLE_CYCLE(2..0) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) { + field: I2C_SARADC_TSENS_DAC(3..0) + } + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), // inferred, esp-idf doesn't have this + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } + master: REGI2C_BOD(0x61, 1) { + reg: I2C_BOD_REG5(5) { + field: I2C_BOD_REG_THRESHOLD(2..0) + } + } + master: REGI2C_BBPLL(0x66, 1) { + reg: I2C_BBPLL_REG0(0) { + field: I2C_BBPLL_IR_CAL_DELAY(3..0), + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4) + } + reg: I2C_BBPLL_REG1(1) { + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7) + } + reg: I2C_BBPLL_REG2(2) { + field: I2C_BBPLL_OC_REF_DIV(3..0), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_ENB_FCAL(7..7) + } + reg: I2C_BBPLL_REG3(3) { + field: I2C_BBPLL_OC_DIV_7_0(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_RSTB_DIV_ADC(0..0), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_OC_TSCHGP(7..7) + } + reg: I2C_BBPLL_REG5(5) { + field: I2C_BBPLL_OC_DR1(2..0), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_EN_USB(7..7) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DCUR(2..0), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_OC_DLREF_SEL(7..6) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_CAL_CAP(3..0), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_LOCK(7..7) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DREF(7..6) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_BBADC_DCUR(1..0), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_ENT_PLL(3..3), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_ADC(7..6) + } + } + master: REGI2C_APLL(0x6D, 1) { + reg: I2C_APLL_REG0(0) { + field: I2C_APLL_IR_CAL_DELAY(3..0), + field: I2C_APLL_IR_CAL_RSTB(4..4), + field: I2C_APLL_IR_CAL_START(5..5), + field: I2C_APLL_IR_CAL_UNSTOP(6..6), + field: I2C_APLL_OC_ENB_FCAL(7..7) + } + reg: I2C_APLL_REG1(1) { + field: I2C_APLL_IR_CAL_EXT_CAP(4..0), + field: I2C_APLL_IR_CAL_ENX_CAP(5..5), + field: I2C_APLL_OC_LBW(6..6) + } + reg: I2C_APLL_REG2(2) { + field: I2C_APLL_IR_CAL_CK_DIV(3..0), + field: I2C_APLL_OC_DCHGP(6..4), + field: I2C_APLL_OC_ENB_VCON(7..7) + } + reg: I2C_APLL_REG3(3) { + field: I2C_APLL_OR_CAL_CAP(4..0), + field: I2C_APLL_OR_CAL_UDF(5..5), + field: I2C_APLL_OR_CAL_OVF(6..6), + field: I2C_APLL_OR_CAL_END(7..7) + } + reg: I2C_APLL_REG4(4) { + field: I2C_APLL_OR_OUTPUT_DIV(4..0), + field: I2C_APLL_OC_TSCHGP(6..6), + field: I2C_APLL_EN_FAST_CAL(7..7) + } + reg: I2C_APLL_REG5(5) { + field: I2C_APLL_OC_DHREF_SEL(1..0), + field: I2C_APLL_OC_DLREF_SEL(3..2), + field: I2C_APLL_SDM_DITHER(4..4), + field: I2C_APLL_SDM_STOP(5..5), + field: I2C_APLL_SDM_RSTB(6..6) + } + reg: I2C_APLL_REG6(6) { + field: I2C_APLL_OC_DVDD(4..0) + } + reg: I2C_APLL_REG7(7) { + field: I2C_APLL_DSDM2(5..0) + } + reg: I2C_APLL_REG8(8) { + field: I2C_APLL_DSDM1(7..0) + } + reg: I2C_APLL_REG9(9) { + field: I2C_APLL_DSDM0(7..0) + } + } +} + +pub(crate) fn i2c_rtc_enable_block(block: u8) { + I2C_ANA_MST::regs().config0().modify(|_, w| unsafe { + const MAGIC_DEFAULT: u16 = 0x1c40; + w.magic_ctrl().bits(MAGIC_DEFAULT) + }); + I2C_ANA_MST::regs().config1().modify(|_, w| unsafe { + const ALL_MASK_V: u16 = 0x7FFF; + w.all_mask().bits(ALL_MASK_V) + }); + + SYSCON::regs() + .wifi_clk_en() + .modify(|_, w| w.mac_clk_en().set_bit()); + + I2C_ANA_MST::regs().config1().modify(|_, w| match block { + v if v == REGI2C_APLL.master => w.apll().clear_bit(), + v if v == REGI2C_BBPLL.master => w.bbpll().clear_bit(), + v if v == REGI2C_SAR.master => w.sar().clear_bit(), + v if v == REGI2C_BOD.master => w.bod().clear_bit(), + _ => unreachable!(), + }); +} + +pub(crate) fn regi2c_read(block: u8, _host_id: u8, reg_add: u8) -> u8 { + i2c_rtc_enable_block(block); + + I2C_ANA_MST::regs().config2().write(|w| unsafe { + w.slave_id().bits(block); + w.addr().bits(reg_add) + }); + + while I2C_ANA_MST::regs().config2().read().busy().bit_is_set() {} + + I2C_ANA_MST::regs().config2().read().data().bits() +} + +pub(crate) fn regi2c_write(block: u8, _host_id: u8, reg_add: u8, data: u8) { + i2c_rtc_enable_block(block); + + I2C_ANA_MST::regs().config2().write(|w| unsafe { + w.slave_id().bits(block); + w.addr().bits(reg_add); + w.wr_cntl().bit(true); + w.data().bits(data) + }); + + while I2C_ANA_MST::regs().config2().read().busy().bit_is_set() {} +} diff --git a/esp-hal/src/soc/esp32s2/trng.rs b/esp-hal/src/soc/esp32s2/trng.rs new file mode 100644 index 00000000000..6de8fc5fa3d --- /dev/null +++ b/esp-hal/src/soc/esp32s2/trng.rs @@ -0,0 +1,114 @@ +use crate::{ + peripherals::{APB_SARADC, I2C_ANA_MST, LPWR, SENS, SYSTEM}, + soc::regi2c, +}; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + // Enable 8M clock source for RNG (this is actually enough to produce strong + // random results, but enabling the SAR ADC as well adds some insurance.) + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_clk8m_en().set_bit()); + + // Enable SAR ADC to read a disconnected input for additional entropy + SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().set_bit()); + + APB_SARADC::regs() + .clkm_conf() + .modify(|_, w| unsafe { w.clk_sel().bits(2) }); + + LPWR::regs().ana_conf().modify(|_, w| { + w.sar_i2c_force_pd().clear_bit(); + w.sar_i2c_force_pu().set_bit() + }); + + I2C_ANA_MST::regs() + .config1() + .modify(|_, w| w.sar().clear_bit()); + I2C_ANA_MST::regs() + .config0() // ANA_CONFIG2_REG in esp-idf + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 16)) }); + + regi2c::ADC_SAR_DTEST_RTC.write_field(0); + regi2c::ADC_SAR_ENT_TSENS.write_field(1); + regi2c::ADC_SAR1_ENCAL_REF.write_field(1); + regi2c::ADC_SAR1_DREF.write_field(4); + regi2c::ADC_SAR2_DREF.write_field(4); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + + APB_SARADC::regs() + .ctrl() + .modify(|_, w| unsafe { w.sar1_patt_len().bits(0) }); + + APB_SARADC::regs() + .sar1_patt_tab1() + .modify(|_, w| unsafe { w.bits(0xafffffff) }); + + APB_SARADC::regs() + .ctrl() + .modify(|_, w| unsafe { w.sar2_patt_len().bits(0) }); + + APB_SARADC::regs() + .sar2_patt_tab1() + .modify(|_, w| unsafe { w.bits(0xafffffff) }); + + SENS::regs() + .sar_meas1_mux() + .modify(|_, w| w.sar1_dig_force().set_bit()); + + APB_SARADC::regs() + .ctrl() + .modify(|_, w| unsafe { w.work_mode().bits(1) }); + + APB_SARADC::regs() + .ctrl2() + .modify(|_, w| w.meas_num_limit().clear_bit()); + + SENS::regs() + .sar_power_xpd_sar() + .modify(|_, w| unsafe { w.force_xpd_sar().bits(3) }); + + APB_SARADC::regs().ctrl2().modify(|_, w| unsafe { + w.timer_sel().set_bit(); + w.timer_target().bits(100) + }); + APB_SARADC::regs() + .ctrl() + .modify(|_, w| w.start_force().clear_bit()); + APB_SARADC::regs() + .ctrl2() + .modify(|_, w| w.timer_en().set_bit()); +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + unsafe { + // Restore internal I2C bus state + regi2c::ADC_SAR1_DREF.write_field(1); + regi2c::ADC_SAR2_DREF.write_field(1); + regi2c::ADC_SAR1_ENCAL_REF.write_field(0); + regi2c::ADC_SAR_ENT_TSENS.write_field(0); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + + // Restore SARADC to default mode + SENS::regs() + .sar_meas1_mux() + .modify(|_, w| w.sar1_dig_force().clear_bit()); + + SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().set_bit()); + + SENS::regs() + .sar_power_xpd_sar() + .modify(|_, w| w.force_xpd_sar().bits(0)); + + APB_SARADC::regs() + .ctrl2() + .modify(|_, w| w.timer_en().clear_bit()); + } +} diff --git a/esp-hal/src/soc/esp32s2/ulp_core.rs b/esp-hal/src/soc/esp32s2/ulp_core.rs new file mode 100644 index 00000000000..4a04dc795c7 --- /dev/null +++ b/esp-hal/src/soc/esp32s2/ulp_core.rs @@ -0,0 +1,150 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Control the ULP core +//! +//! ## Overview +//! +//! The `ULP CORE` peripheral allows control over the `Ultra-Low Power +//! (ULP) core` in `ESP` chips. The ULP core is a low-power processor +//! designed for executing tasks in deep sleep mode, enabling efficient power +//! management in ESP systems. +//! +//! The `UlpCore` struct provides an interface to interact with the `ULP` +//! peripheral. It allows starting and configuring the ULP core for operation. +//! The `UlpCore` struct is initialized with a peripheral reference to the `ULP +//! CORE` instance. +//! +//! ## Examples +//! ```rust, no_run +//! # {before_snippet} +//! const CODE: &[u8] = &[ +//! 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x05, 0x01, 0x81, 0x45, 0x85, 0x05, 0x0c, 0xc1, 0xf5, +//! 0xbf, 0x00, 0x00, 0x00, 0x00, +//! ]; +//! let mut ulp_core = esp_hal::ulp_core::UlpCore::new(peripherals.ULP_RISCV_CORE); +//! // ulp_core.stop(); currently not implemented +//! +//! // copy code to RTC ram +//! let lp_ram = 0x5000_0000 as *mut u8; +//! unsafe { +//! core::ptr::copy_nonoverlapping(CODE as *const _ as *const u8, lp_ram, CODE.len()); +//! } +//! +//! // start ULP core +//! ulp_core.run(esp_hal::ulp_core::UlpCoreWakeupSource::HpCpu); +//! +//! unsafe { +//! let data = 0x5000_0010 as *mut u32; +//! loop {} +//! } +//! # } +//! ``` + +use crate::peripherals::LPWR; + +/// Enum representing the possible wakeup sources for the ULP core. +#[derive(Debug, Clone, Copy)] +pub enum UlpCoreWakeupSource { + /// Wakeup source from the HP (High Performance) CPU. + HpCpu, +} + +/// Structure representing the ULP (Ultra-Low Power) core. +pub struct UlpCore<'d> { + _lp_core: crate::peripherals::ULP_RISCV_CORE<'d>, +} + +impl<'d> UlpCore<'d> { + /// Creates a new instance of the `UlpCore` struct. + pub fn new(lp_core: crate::peripherals::ULP_RISCV_CORE<'d>) -> Self { + // clear all of RTC_SLOW_RAM - this makes sure .bss is cleared without relying + let lp_ram = + unsafe { core::slice::from_raw_parts_mut(0x5000_0000 as *mut u32, 8 * 1024 / 4) }; + lp_ram.fill(0u32); + + Self { _lp_core: lp_core } + } + + // currently stopping the ULP doesn't work (while following the procedures + // outlines in the TRM) - so don't offer this function for now + // + // pub fn stop(&mut self) { + // ulp_stop(); + // } + + /// Runs the ULP core with the specified wakeup source. + pub fn run(&mut self, wakeup_src: UlpCoreWakeupSource) { + ulp_run(wakeup_src); + } +} + +#[allow(unused)] // TODO: remove cfg when implementation is corrected +fn ulp_stop() { + LPWR::regs() + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); + + // suspends the ulp operation + LPWR::regs() + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done().set_bit()); + + // Resets the processor + LPWR::regs() + .cocpu_ctrl() + .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); +} + +fn ulp_run(wakeup_src: UlpCoreWakeupSource) { + let rtc_cntl = LPWR::regs(); + + // Reset COCPU when power on + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); + + // Disable ULP timer + rtc_cntl + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); + + // wait for at least 1 RTC_SLOW_CLK cycle + crate::rom::ets_delay_us(20); + + // Select ULP-RISC-V to send the DONE signal + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done_force().set_bit()); + + ulp_config_wakeup_source(wakeup_src); + + // Select RISC-V as the ULP_TIMER trigger target + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_sel().clear_bit()); + + // Clear any spurious wakeup trigger interrupts upon ULP startup + crate::rom::ets_delay_us(20); + + rtc_cntl.int_clr().write(|w| { + w.cocpu() + .clear_bit_by_one() + .cocpu_trap() + .clear_bit_by_one() + .ulp_cp() + .clear_bit_by_one() + }); +} + +fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { + match wakeup_src { + UlpCoreWakeupSource::HpCpu => { + // use timer to wake up + LPWR::regs() + .ulp_cp_ctrl() + .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); + LPWR::regs() + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); + } + } +} diff --git a/esp-hal/src/soc/esp32s3/clocks.rs b/esp-hal/src/soc/esp32s3/clocks.rs new file mode 100644 index 00000000000..9e8dc9afefc --- /dev/null +++ b/esp-hal/src/soc/esp32s3/clocks.rs @@ -0,0 +1,972 @@ +//! Clock tree definitions and implementations for ESP32-S3. +//! +//! Remarks: +//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need +//! to be configured. +//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock +//! configuration. +//! - Internal RC oscillators (136k RC_SLOW and 17.5M RC_FAST) are not calibrated here, this system +//! can only give a rough estimate of their frequency. They can be calibrated separately using a +//! known crystal frequency. +//! - Some of the SOC capabilities are not implemented. +#![allow(dead_code, reason = "Some of this is bound to be unused")] +#![allow(missing_docs, reason = "Experimental")] + +// TODO: This is a temporary place for this, should probably be moved into clocks_ll. + +use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom}; + +use crate::{ + peripherals::{I2C_ANA_MST, LPWR, SYSTEM, TIMG0, TIMG1, UART0, UART1, UART2}, + soc::regi2c, + time::Rate, +}; + +define_clock_tree_types!(); + +/// Clock configuration options. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow( + clippy::enum_variant_names, + reason = "MHz suffix indicates physical unit." +)] +#[non_exhaustive] +pub enum CpuClock { + /// 80 MHz CPU clock + #[default] + _80MHz = 80, + + /// 160 MHz CPU clock + _160MHz = 160, + + /// 240 MHz CPU clock + _240MHz = 240, +} + +impl CpuClock { + // The presets use 480MHz PLL by default, because that is the default value the chip boots + // with, and changing it breaks USB Serial/JTAG. + const PRESET_80: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + pll_clk: Some(PllClkConfig::_480), + cpu_pll_div_out: Some(CpuPllDivOutConfig::_80), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; + const PRESET_160: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + pll_clk: Some(PllClkConfig::_480), + cpu_pll_div_out: Some(CpuPllDivOutConfig::_160), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; + const PRESET_240: ClockConfig = ClockConfig { + xtal_clk: None, + system_pre_div: None, + pll_clk: Some(PllClkConfig::_480), + cpu_pll_div_out: Some(CpuPllDivOutConfig::_240), + cpu_clk: Some(CpuClkConfig::Pll), + rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)), + rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow), + rtc_fast_clk: Some(RtcFastClkConfig::Rc), + low_power_clk: Some(LowPowerClkConfig::RtcSlow), + }; +} + +impl From for ClockConfig { + fn from(value: CpuClock) -> ClockConfig { + match value { + CpuClock::_80MHz => CpuClock::PRESET_80, + CpuClock::_160MHz => CpuClock::PRESET_160, + CpuClock::_240MHz => CpuClock::PRESET_240, + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::from(CpuClock::default()) + } +} + +impl ClockConfig { + pub(crate) fn try_get_preset(self) -> Option { + match self { + v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz), + v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz), + v if v == CpuClock::PRESET_240 => Some(CpuClock::_240MHz), + _ => None, + } + } + + pub(crate) fn configure(mut self) { + if self.xtal_clk.is_none() { + // TODO: support multiple crystal frequencies (esp-idf supports 32M). + self.xtal_clk = Some(XtalClkConfig::_40); + } + + // Switch CPU to XTAL before reconfiguring PLL. + ClockTree::with(|clocks| { + configure_xtal_clk(clocks, XtalClkConfig::_40); + configure_system_pre_div(clocks, SystemPreDivConfig::new(0)); + configure_cpu_clk(clocks, CpuClkConfig::Xtal); + }); + + self.apply(); + } +} + +// XTAL_CLK + +fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { + // The stored configuration affects PLL settings instead. +} + +// PLL_CLK + +fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) { + // regi2c_ctrl_ll_i2c_bbpll_enable + I2C_ANA_MST::regs() + .ana_config() + .modify(|_, w| w.bbpll_pd().bit(!en)); + + LPWR::regs().options0().modify(|_, w| { + let power_down = !en; + w.bb_i2c_force_pd().bit(power_down); + w.bbpll_force_pd().bit(power_down); + w.bbpll_i2c_force_pd().bit(power_down) + }); + + if !en { + return; + } + + ensure_voltage_raised(clocks); + + // Analog part + let pll_freq = unwrap!(clocks.pll_clk); + let xtal_freq = unwrap!(clocks.xtal_clk); + + let div_ref: u8; + let div7_0: u8; + let dr1: u8; + let dr3: u8; + let dchgp: u8; + let dcur: u8; + let dbias = 3; + match pll_freq { + PllClkConfig::_480 => { + // Configure 480M PLL + match xtal_freq { + XtalClkConfig::_40 => { + div_ref = 0; + // Will multiply by 8 + 4 = 12 + div7_0 = 8; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 3; + } + } + + // Set the MODE_HF bit + regi2c::I2C_BBPLL_REG4.write_reg(0x6b); + } + PllClkConfig::_320 => { + // Configure 320M PLL + match xtal_freq { + XtalClkConfig::_40 => { + div_ref = 0; + // Will multiply by 4 + 4 = 8 + div7_0 = 4; + dr1 = 0; + dr3 = 0; + dchgp = 5; + dcur = 3; + } + } + + // Clear the MODE_HF bit + regi2c::I2C_BBPLL_REG4.write_reg(0x69); + } + } + + const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4; + const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6; + const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4; + + let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref; + + let i2c_bbpll_dcur = + (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur; + + regi2c::I2C_BBPLL_OC_REF.write_reg(i2c_bbpll_lref); + regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(div7_0); + regi2c::I2C_BBPLL_OC_DR1.write_field(dr1); + regi2c::I2C_BBPLL_OC_DR3.write_field(dr3); + regi2c::I2C_BBPLL_REG6.write_reg(i2c_bbpll_dcur); + regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(dbias); + + // Start BBPLL self-calibration + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().clear_bit(); + w.bbpll_stop_force_low().set_bit() + }); + + // WAIT CALIBRATION DONE + while I2C_ANA_MST::regs() + .ana_conf0() + .read() + .bbpll_cal_done() + .bit_is_clear() + {} + + // workaround bbpll calibration might stop early + crate::rom::ets_delay_us(10); + + // BBPLL CALIBRATION STOP + I2C_ANA_MST::regs().ana_conf0().modify(|_, w| { + w.bbpll_stop_force_high().set_bit(); + w.bbpll_stop_force_low().clear_bit() + }); + + ensure_voltage_minimal(clocks); +} + +fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { + // Nothing to do. The PLL may still be powered down. We'll configure it in + // `enable_pll_clk_impl`. +} + +// RC_FAST_CLK + +fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) { + // XPD_RC_OSCILLATOR exists but we'll manage that separately + const RTC_CNTL_FOSC_DFREQ_DEFAULT: u8 = 100; + LPWR::regs().clk_conf().modify(|_, w| { + // Confusing CK8M naming inherited from ESP32? + + // CK8M_DFREQ value controls tuning of 8M clock. + unsafe { w.ck8m_dfreq().bits(RTC_CNTL_FOSC_DFREQ_DEFAULT) }; + + w.enb_ck8m().bit(!en); + w.dig_clk8m_en().bit(en); // digital system clock gate + // Do not force the clock either way. + w.ck8m_force_pd().clear_bit(); + w.ck8m_force_pu().clear_bit() + }); + LPWR::regs() + .timer1() + .modify(|_, w| unsafe { w.ck8m_wait().bits(if en { 5 } else { 20 }) }); +} + +// XTAL32K_CLK + +fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) { + // RTCIO could be configured to allow an external oscillator to be used. We could model this + // with a MUX, probably, but this is omitted for now for simplicity. + + const CLK_LL_XTAL_32K_DAC_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DRES_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DGM_VAL: u8 = 3; + const CLK_LL_XTAL_32K_DBUF_VAL: bool = true; // differential buffer + LPWR::regs().ext_xtl_conf().modify(|_, w| unsafe { + w.xtal32k_gpio_sel().bit(false); + + w.dac_xtal_32k().bits(CLK_LL_XTAL_32K_DAC_VAL); + w.dres_xtal_32k().bits(CLK_LL_XTAL_32K_DRES_VAL); + w.dgm_xtal_32k().bits(CLK_LL_XTAL_32K_DGM_VAL); + w.dbuf_xtal_32k().bit(CLK_LL_XTAL_32K_DBUF_VAL); + + w.xpd_xtal_32k().bit(en) + }); + LPWR::regs() + .clk_conf() + .modify(|_, w| w.dig_xtal32k_en().bit(en)); +} + +// RC_SLOW_CLK + +fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) { + if en { + // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the + // frequency. There is no separate enable bit, just make sure the calibration value is set. + const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255; + LPWR::regs() + .rtc() + .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) }); + + // Also configure the divider here to its usual value of 1. + + // Updating the divider should be part of the RC_SLOW_CLK divider config: + let slow_clk_conf = LPWR::regs().slow_clk_conf(); + // Invalidate + let new_value = slow_clk_conf.modify(|_, w| w.ana_clk_div_vld().clear_bit()); + // Update divider + let new_value = slow_clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ana_clk_div().bits(0) + }); + // Re-synchronize + slow_clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ana_clk_div_vld().set_bit() + }); + } +} + +// RC_FAST_DIV_CLK + +fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) { + LPWR::regs() + .clk_conf() + .modify(|_, w| w.enb_ck8m_div().bit(!en)); +} + +// SYSTEM_PRE_DIV_IN + +fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_in_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: SystemPreDivInConfig, +) { + // Nothing to do. +} + +// SYSTEM_PRE_DIV + +fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_system_pre_div_impl(_clocks: &mut ClockTree, new_config: SystemPreDivConfig) { + SYSTEM::regs() + .sysclk_conf() + .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) }); +} + +// CPU_PLL_DIV_OUT + +fn enable_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _config: CpuPllDivOutConfig) { + // Nothing to do. +} + +// APB_CLK + +fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_apb_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: ApbClkConfig, +) { + // Nothing to do. +} + +// CRYPTO_PWM_CLK + +fn enable_crypto_pwm_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_crypto_pwm_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: CryptoPwmClkConfig, +) { + // Nothing to do. +} + +// CPU_CLK + +fn configure_cpu_clk_impl( + clocks: &mut ClockTree, + _old_selector: Option, + new_selector: CpuClkConfig, +) { + // Based on TRM Table 7.2-2 + let clock_source_sel0_bit = match new_selector { + CpuClkConfig::Xtal => 0, + CpuClkConfig::RcFast => 2, + CpuClkConfig::Pll => 1, + }; + let clock_source_sel1_bit = clocks.pll_clk == Some(PllClkConfig::_480); + let clock_source_sel2_bit = match (clocks.pll_clk, clocks.cpu_pll_div_out) { + (Some(_), Some(CpuPllDivOutConfig::_80)) => 0, + (Some(_), Some(CpuPllDivOutConfig::_160)) => 1, + (Some(PllClkConfig::_480), Some(CpuPllDivOutConfig::_240)) => 2, + + // don't care + _ => 0, + }; + + ensure_voltage_raised(clocks); + if new_selector == CpuClkConfig::Pll { + SYSTEM::regs().cpu_per_conf().modify(|_, w| { + unsafe { w.cpuperiod_sel().bits(clock_source_sel2_bit) }; + w.pll_freq_sel().bit(clock_source_sel1_bit) + }); + } + + SYSTEM::regs().sysclk_conf().modify(|_, w| unsafe { + w.pre_div_cnt().bits(0); + w.soc_clk_sel().bits(clock_source_sel0_bit) + }); + + let cpu_freq = Rate::from_hz(cpu_clk_frequency(clocks)); + ets_update_cpu_frequency_rom(cpu_freq.as_mhz()); + + ensure_voltage_minimal(clocks); +} + +// There are totally 6 LDO slaves(all on by default). At the moment of switching LDO slave, LDO +// voltage will also change instantaneously. LDO slave can reduce the voltage change caused +// by switching frequency. CPU frequency <= 40M : just open 3 LDO slaves; CPU frequency = +// 80M : open 4 LDO slaves; CPU frequency = 160M : open 5 LDO slaves; CPU frequency = 240M : +// open 6 LDO slaves; +// +// LDO voltage will decrease at the moment of switching from low frequency to high frequency; +// otherwise, LDO voltage will increase. In order to reduce LDO voltage drop, LDO voltage +// should rise first then fall. + +const RTC_CNTL_DBIAS_1V10: u8 = 4; +const RTC_CNTL_DBIAS_1V25: u8 = 7; + +const V_RTC_MID_MUL10000: i32 = 10181; +const V_DIG_MID_MUL10000: i32 = 10841; +const K_RTC_MID_MUL10000: i32 = 198; +const K_DIG_MID_MUL10000: i32 = 211; + +use crate::efuse; + +const fn sign_extend(value: u8, bits: u8) -> i32 { + let sign_bit = 1 << (bits - 1); + let mask = !(sign_bit as u32 - 1); + + if value & sign_bit != 0 { + (value as i32) | mask as i32 + } else { + value as i32 + } +} + +fn dig_dbias_v1() -> u8 { + efuse::read_field_le(efuse::DIG_DBIAS_HVT) +} +fn rtc_dbias_v1(dig_dbias: u8) -> u8 { + // 7-bit two's complement + let k_rtc_ldo = efuse::read_field_le::(efuse::K_RTC_LDO); + let k_dig_ldo = efuse::read_field_le::(efuse::K_DIG_LDO); + // 8-bit two's complement + let v_rtc_bias20 = efuse::read_field_le::(efuse::V_RTC_DBIAS20); + let v_dig_bias20 = efuse::read_field_le::(efuse::V_DIG_DBIAS20); + + // Sign extend values: + let k_rtc_ldo = sign_extend(k_rtc_ldo, 7); + let k_dig_ldo = sign_extend(k_dig_ldo, 7); + let v_rtc_bias20 = sign_extend(v_rtc_bias20, 8); + let v_dig_bias20 = sign_extend(v_dig_bias20, 8); + + let v_rtc_dbias20_real_mul10000 = V_RTC_MID_MUL10000 + v_rtc_bias20 * 10000 / 500; + let v_dig_dbias20_real_mul10000 = V_DIG_MID_MUL10000 + v_dig_bias20 * 10000 / 500; + let k_rtc_ldo_real_mul10000 = K_RTC_MID_MUL10000 + k_rtc_ldo; + let k_dig_ldo_real_mul10000 = K_DIG_MID_MUL10000 + k_dig_ldo; + + let v_dig_nearest_1v15_mul10000 = + v_dig_dbias20_real_mul10000 + k_dig_ldo_real_mul10000 * (dig_dbias as i32 - 20); + + search_nearest( + v_rtc_dbias20_real_mul10000, + k_rtc_ldo_real_mul10000, + v_dig_nearest_1v15_mul10000 - 250, + ) +} +fn dig1v3_dbias_v1() -> u8 { + // 7-bit two's complement + let k_dig_ldo = efuse::read_field_le::(efuse::K_DIG_LDO); + // 8-bit two's complement + let v_dig_bias20 = efuse::read_field_le::(efuse::V_DIG_DBIAS20); + + // Sign extend values: + let k_dig_ldo = sign_extend(k_dig_ldo, 7); + let v_dig_bias20 = sign_extend(v_dig_bias20, 8); + + let v_dig_dbias20_real_mul10000 = V_DIG_MID_MUL10000 + v_dig_bias20 * 10000 / 500; + let k_dig_ldo_real_mul10000 = K_DIG_MID_MUL10000 + k_dig_ldo; + + search_nearest(v_dig_dbias20_real_mul10000, k_dig_ldo_real_mul10000, 13000) +} + +fn search_nearest(v: i32, k: i32, max: i32) -> u8 { + for dbias in 15..31 { + let nearest = v + k * (dbias as i32 - 20); + if nearest >= max { + return dbias; + } + } + + 31 +} + +/// Returns whether the eFuse data contains values for PVT (Process/Voltage/Temperature) +/// calibration. +/// +/// The calibration is meant to widen the range of conditions under which the +/// chip can operate reliably. Based on factory calibration, it implements linear +/// search for `dbias` values which produce the closest minimum voltage applied to the RTC +/// and digital power domains. +fn pvt_supported() -> bool { + let (blk_major, blk_minor) = efuse::block_version(); + + // Block version was introduced at v1.2, above which the PVT are all supported. + // Before that, blk1_ver (a.k.a. blk_minor now) == 1 indicates the support of PVT. + (blk_major == 0 && blk_minor == 1) || (blk_major == 1 && blk_minor >= 2) || blk_major > 1 +} + +fn ensure_voltage_raised(clocks: &mut ClockTree) { + let cpu_freq = cpu_clk_frequency(clocks); + let pd_slave = cpu_freq / 80_000_000; + + if cpu_freq == 240_000_000 { + let mut rtc_dbias_pvt_240m = 28; + let mut dig_dbias_pvt_240m = 28; + + if pvt_supported() { + let dig_dbias = dig_dbias_v1(); + if dig_dbias != 0 { + let dig1v3_dbias = dig1v3_dbias_v1(); + + dig_dbias_pvt_240m = dig1v3_dbias.min(dig_dbias + 3); + rtc_dbias_pvt_240m = rtc_dbias_v1(dig_dbias_pvt_240m); + } + } + + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(rtc_dbias_pvt_240m); + regi2c::I2C_DIG_REG_EXT_DIG_DREG.write_field(dig_dbias_pvt_240m); + + ets_delay_us(40); + } + + LPWR::regs() + .date() + .modify(|_, w| unsafe { w.ldo_slave().bits(0x7 >> pd_slave) }); +} + +fn ensure_voltage_minimal(clocks: &mut ClockTree) { + let cpu_freq = cpu_clk_frequency(clocks); + let pd_slave = cpu_freq / 80_000_000; + + if cpu_freq < 240_000_000 { + let mut rtc_dbias_pvt_non_240m = 27; + let mut dig_dbias_pvt_non_240m = 27; + + if pvt_supported() { + let dig_dbias = dig_dbias_v1(); + if dig_dbias != 0 { + let dig1v3_dbias = dig1v3_dbias_v1(); + + dig_dbias_pvt_non_240m = dig1v3_dbias.min(dig_dbias + 2); + rtc_dbias_pvt_non_240m = rtc_dbias_v1(dig_dbias_pvt_non_240m); + } + } + + regi2c::I2C_DIG_REG_EXT_RTC_DREG.write_field(rtc_dbias_pvt_non_240m); + regi2c::I2C_DIG_REG_EXT_DIG_DREG.write_field(dig_dbias_pvt_non_240m); + + ets_delay_us(40); + } + + LPWR::regs() + .date() + .modify(|_, w| unsafe { w.ldo_slave().bits(0x7 >> pd_slave) }); +} + +// PLL_D2 + +fn enable_pll_d2_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// PLL_160M + +fn enable_pll_160m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// APB_80M + +fn enable_apb_80m_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RC_FAST_CLK_DIV_N + +fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, new_config: RcFastClkDivNConfig) { + let clk_conf = LPWR::regs().clk_conf(); + // Invalidate because we may be changing the divider from some other value + let new_value = clk_conf.modify(|_, w| w.ck8m_div_sel_vld().clear_bit()); + // Update divider + let new_value = clk_conf.write(|w| unsafe { + w.bits(new_value); + w.ck8m_div_sel().bits(new_config.divisor() as u8) + }); + // Re-synchronize + clk_conf.write(|w| { + unsafe { w.bits(new_value) }; + w.ck8m_div_sel_vld().set_bit() + }); +} + +// XTAL_DIV_CLK + +fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +// RTC_SLOW_CLK + +fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_slow_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcSlowClkConfig, +) { + LPWR::regs().clk_conf().modify(|_, w| unsafe { + // TODO: variants should be in PAC + w.ana_clk_rtc_sel().bits(match new_selector { + RtcSlowClkConfig::Xtal32k => 1, + RtcSlowClkConfig::RcSlow => 0, + RtcSlowClkConfig::RcFast => 2, + }) + }); + ets_delay_us(300); +} + +// RTC_FAST_CLK + +fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_rtc_fast_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RtcFastClkConfig, +) { + // TODO: variants should be fixed in PAC + LPWR::regs().clk_conf().modify(|_, w| match new_selector { + RtcFastClkConfig::Xtal => w.fast_clk_rtc_sel().clear_bit(), + RtcFastClkConfig::Rc => w.fast_clk_rtc_sel().set_bit(), + }); + ets_delay_us(3); +} + +// LOW_POWER_CLK + +fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing in esp-idf does this - is this managed by hardware, or the radio blobs? +} + +fn configure_low_power_clk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LowPowerClkConfig, +) { + SYSTEM::regs().bt_lpck_div_frac().modify(|_, w| { + w.lpclk_sel_8m() + .bit(new_selector == LowPowerClkConfig::RcFast); + w.lpclk_sel_rtc_slow() + .bit(new_selector == LowPowerClkConfig::RtcSlow); + w.lpclk_sel_xtal() + .bit(new_selector == LowPowerClkConfig::Xtal); + w.lpclk_sel_xtal32k() + .bit(new_selector == LowPowerClkConfig::Xtal32k) + }); +} + +// UART_MEM_CLK + +fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: these functions (peripheral bus clock control) should be generated, + // replacing current PeripheralClockControl code. + // Enabling clock should probably not reset the peripheral. + let regs = SYSTEM::regs(); + + if en { + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(true)); + regs.perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(false)); + } + + regs.perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(en)); +} + +// RMT_SCLK + +fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, en: bool) { + crate::peripherals::RMT::regs() + .sys_conf() + .modify(|_, w| w.sclk_active().bit(en)); +} + +fn configure_rmt_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: RmtSclkConfig, +) { + crate::peripherals::RMT::regs() + .sys_conf() + .modify(|_, w| unsafe { + w.clk_en().clear_bit(); + w.sclk_sel().bits(match new_selector { + RmtSclkConfig::ApbClk => 1, + RmtSclkConfig::RcFastClk => 2, + RmtSclkConfig::XtalClk => 3, + }) + }); +} + +// MCPWM0_FUNCTION_CLOCK + +fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mcpwm0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Mcpwm0FunctionClockConfig, +) { + // Nothing to do. +} + +// MCPWM1_FUNCTION_CLOCK + +fn enable_mcpwm1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_mcpwm1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Mcpwm0FunctionClockConfig, +) { + // Nothing to do. +} + +// TIMG0_FUNCTION_CLOCK + +// Note that the function clock is a pre-requisite of the timer, but does not enable the counter. + +fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + // TODO: should we model T0_DIVIDER, too? + TIMG0::regs().regclk().modify(|_, w| w.clk_en().bit(en)); +} + +fn configure_timg0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG0::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); + TIMG0::regs().t(1).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG0_CALIBRATION_CLOCK + +fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg0_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG0::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// TIMG1_FUNCTION_CLOCK + +fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + TIMG1::regs().regclk().modify(|_, w| w.clk_en().bit(en)); +} + +fn configure_timg1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0FunctionClockConfig, +) { + TIMG1::regs().t(0).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); + TIMG1::regs().t(1).config().modify(|_, w| { + w.use_xtal() + .bit(new_selector == Timg0FunctionClockConfig::XtalClk) + }); +} + +// TIMG1_CALIBRATION_CLOCK + +fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START bit, + // which is managed by the calibration process. +} + +fn configure_timg1_calibration_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Timg0CalibrationClockConfig, +) { + TIMG1::regs().rtccalicfg().modify(|_, w| unsafe { + w.rtc_cali_clk_sel().bits(match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => 0, + Timg0CalibrationClockConfig::RcFastDivClk => 1, + Timg0CalibrationClockConfig::Xtal32kClk => 2, + }) + }); +} + +// UART0_MEM_CLOCK + +fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart0_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART0_FUNCTION_CLOCK + +fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART0::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart0_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART0::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Apb => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART1_MEM_CLOCK + +fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart1_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART1_FUNCTION_CLOCK + +fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART1::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart1_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART1::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Apb => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} + +// UART2_MEM_CLOCK + +fn enable_uart2_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { + // Nothing to do. +} + +fn configure_uart2_mem_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + _new_selector: Uart0MemClockConfig, +) { + // Nothing to do. +} + +// UART2_FUNCTION_CLOCK + +fn enable_uart2_function_clock_impl(_clocks: &mut ClockTree, en: bool) { + UART2::regs().clk_conf().modify(|_, w| w.sclk_en().bit(en)); +} + +fn configure_uart2_function_clock_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: Uart0FunctionClockConfig, +) { + UART2::regs().clk_conf().modify(|_, w| unsafe { + w.sclk_sel().bits(match new_selector { + Uart0FunctionClockConfig::Apb => 1, + Uart0FunctionClockConfig::RcFast => 2, + Uart0FunctionClockConfig::Xtal => 3, + }) + }); +} diff --git a/esp-hal/src/soc/esp32s3/cpu_control.rs b/esp-hal/src/soc/esp32s3/cpu_control.rs new file mode 100644 index 00000000000..e18edb96ec9 --- /dev/null +++ b/esp-hal/src/soc/esp32s3/cpu_control.rs @@ -0,0 +1,142 @@ +//! # Control CPU Cores (ESP32-S3) +//! +//! ## Overview +//! +//! This module provides essential functionality for controlling +//! and managing the APP (second) CPU core on the `ESP32-S3` chip. It is used to +//! start and stop program execution on the APP core. + +use core::sync::atomic::Ordering; + +use crate::{ + peripherals::{LPWR, SYSTEM}, + system::{Cpu, multi_core::*}, +}; + +pub(crate) unsafe fn internal_park_core(core: Cpu, park: bool) { + let c1_value = if park { 0x21 } else { 0 }; + let c0_value = if park { 0x02 } else { 0 }; + match core { + Cpu::ProCpu => { + LPWR::regs() + .sw_cpu_stall() + .modify(|_, w| unsafe { w.sw_stall_procpu_c1().bits(c1_value) }); + LPWR::regs() + .options0() + .modify(|_, w| unsafe { w.sw_stall_procpu_c0().bits(c0_value) }); + } + Cpu::AppCpu => { + LPWR::regs() + .sw_cpu_stall() + .modify(|_, w| unsafe { w.sw_stall_appcpu_c1().bits(c1_value) }); + LPWR::regs() + .options0() + .modify(|_, w| unsafe { w.sw_stall_appcpu_c0().bits(c0_value) }); + } + } +} + +/// Returns `true` if the specified core is currently running (not stalled). +#[instability::unstable] +pub fn is_running(core: Cpu) -> bool { + if core == Cpu::AppCpu { + // CORE_1_RUNSTALL in bit 0 -> needs to be 0 to not stall + // CORE_1_CLKGATE_EN in bit 1 -> needs to be 1 to even be enabled + let system = SYSTEM::regs(); + let r = system.core_1_control_0().read(); + if r.control_core_1_clkgate_en().bit_is_clear() || r.control_core_1_runstall().bit_is_set() + { + // If the core is not enabled we can take this shortcut + return false; + } + } + + // sw_stall_appcpu_c1[5:0], sw_stall_appcpu_c0[1:0]} == 0x86 will stall APP CPU + // sw_stall_procpu_c1[5:0], reg_sw_stall_procpu_c0[1:0]} == 0x86 will stall PRO CPU + let is_stalled = match core { + Cpu::ProCpu => { + let c1 = LPWR::regs() + .sw_cpu_stall() + .read() + .sw_stall_procpu_c1() + .bits(); + let c0 = LPWR::regs().options0().read().sw_stall_procpu_c0().bits(); + (c1 << 2) | c0 + } + Cpu::AppCpu => { + let c1 = LPWR::regs() + .sw_cpu_stall() + .read() + .sw_stall_appcpu_c1() + .bits(); + let c0 = LPWR::regs().options0().read().sw_stall_appcpu_c0().bits(); + (c1 << 2) | c0 + } + }; + + is_stalled != 0x86 +} + +pub(crate) fn start_core1(entry_point: *const u32) { + let system_control = SYSTEM::regs(); + + crate::rom::ets_set_appcpu_boot_addr(entry_point as u32); + + system_control + .core_1_control_0() + .modify(|_, w| w.control_core_1_clkgate_en().set_bit()); + system_control + .core_1_control_0() + .modify(|_, w| w.control_core_1_runstall().clear_bit()); + system_control + .core_1_control_0() + .modify(|_, w| w.control_core_1_reseting().set_bit()); + system_control + .core_1_control_0() + .modify(|_, w| w.control_core_1_reseting().clear_bit()); +} + +pub(crate) fn start_core1_init() -> ! +where + F: FnOnce(), +{ + // disables interrupts + unsafe { + xtensa_lx::interrupt::set_mask(0); + } + + // reset cycle compare registers + xtensa_lx::timer::set_ccompare0(0); + xtensa_lx::timer::set_ccompare1(0); + xtensa_lx::timer::set_ccompare2(0); + + unsafe extern "C" { + static mut _init_start: u32; + } + + // set vector table and stack pointer + unsafe { + xtensa_lx::set_vecbase(&raw const _init_start); + xtensa_lx::set_stack_pointer(APP_CORE_STACK_TOP.load(Ordering::Acquire)); + + #[cfg(all(feature = "rt", stack_guard_monitoring))] + { + let stack_guard = APP_CORE_STACK_GUARD.load(Ordering::Acquire); + stack_guard.write_volatile(esp_config::esp_config_int!( + u32, + "ESP_HAL_CONFIG_STACK_GUARD_VALUE" + )); + // setting 0 effectively disables the functionality + crate::debugger::set_stack_watchpoint(stack_guard as usize); + } + } + + // Do not call setup_interrupts as that would disable peripheral interrupts, too. + unsafe { crate::interrupt::init_vectoring() }; + + // Trampoline to run from the new stack. + // start_core1_run should _NEVER_ be inlined + // as we rely on the function call to use + // the new stack. + unsafe { CpuControl::start_core1_run::() } +} diff --git a/esp-hal/src/soc/esp32s3/gpio.rs b/esp-hal/src/soc/esp32s3/gpio.rs new file mode 100644 index 00000000000..da438aefef2 --- /dev/null +++ b/esp-hal/src/soc/esp32s3/gpio.rs @@ -0,0 +1,149 @@ +//! # GPIO configuration module (ESP32-S3) +//! +//! ## Overview +//! +//! The `GPIO` module provides functions and configurations for controlling the +//! `General Purpose Input/Output` pins on the `ESP32-S3` chip. It allows you to +//! configure pins as inputs or outputs, set their state and read their state. +//! +//! Let's get through the functionality and configurations provided by this GPIO +//! module: +//! - `gpio` block: +//! * Defines the pin configurations for various GPIO pins. Each line represents a pin and its +//! associated options such as input/output mode, analog capability, and corresponding +//! functions. +//! - `analog` block: +//! * Block defines the analog capabilities of various GPIO pins. Each line represents a pin +//! and its associated options such as mux selection, function selection, and input enable. +//! - `enum InputSignal`: +//! * This enumeration defines input signals for the GPIO mux. Each input signal is assigned a +//! specific value. +//! - `enum OutputSignal`: +//! * This enumeration defines output signals for the GPIO mux. Each output signal is assigned +//! a specific value. +//! +//! This trait provides functions to read the interrupt status and NMI status +//! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the +//! `gpio` peripheral to access the appropriate registers. + +macro_rules! rtcio_analog { + ($pin_num:expr, $pin_reg:expr, $hold:ident $(, $analog:literal)?) => { + paste::paste! { + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPin for $crate::peripherals::[]<'_> { + fn rtc_number(&self) -> u8 { + $pin_num + } + + /// Set the RTC properties of the pin. If `mux` is true then then pin is + /// routed to RTC, when false it is routed to IO_MUX. + fn rtc_set_config(&self, input_enable: bool, mux: bool, func: $crate::gpio::RtcFunction) { + enable_iomux_clk_gate(); + + // We need `paste` to rewrite something in each function, so that rustc + // doesn't trip over trying to substitute a partial expression as `$pin_reg` + $crate::peripherals::[]::regs() + .$pin_reg.modify(|_,w| unsafe { + w.fun_ie().bit(input_enable); + w.mux_sel().bit(mux); + w.fun_sel().bits(func as u8) + }); + } + + fn rtcio_pad_hold(&self, enable: bool) { + $crate::peripherals::LPWR::regs() + .pad_hold() + .modify(|_, w| w.$hold().bit(enable)); + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + impl $crate::gpio::RtcPinWithResistors for $crate::peripherals::[]<'_> { + fn rtcio_pullup(&self, enable: bool) { + $crate::peripherals::[]::regs() + .$pin_reg.modify(|_, w| w.rue().bit(enable)); + } + + fn rtcio_pulldown(&self, enable: bool) { + $crate::peripherals::[]::regs() + .$pin_reg.modify(|_, w| w.rde().bit(enable)); + } + } + + $( + $crate::ignore!($analog); + impl $crate::peripherals::[]<'_> { + /// Configures the pin for analog mode. + #[cfg(feature = "unstable")] + pub(crate) fn set_analog_impl(&self) { + use $crate::gpio::RtcPin; + enable_iomux_clk_gate(); + + let rtcio = $crate::peripherals::[]::regs(); + + // disable output + rtcio.enable_w1tc().write(|w| unsafe { w.enable_w1tc().bits(1 << self.rtc_number()) }); + + // disable open drain + rtcio.pin(self.rtc_number() as usize).modify(|_,w| w.pad_driver().bit(false)); + + rtcio.$pin_reg.modify(|_,w| { + w.fun_ie().clear_bit(); + + // Connect pin to analog / RTC module instead of standard GPIO + w.mux_sel().set_bit(); + + // Select function "RTC function 1" (GPIO) for analog use + unsafe { w.fun_sel().bits(0b00) }; + + // Disable pull-up and pull-down resistors on the pin + w.rue().bit(false); + w.rde().bit(false); + + w + }); + } + } + )? + } + }; + + ( + $( ( $pin_num:expr, $pin_reg:expr, $hold:ident $(, $analog:literal)? ) )+ + ) => { + $( + rtcio_analog!($pin_num, $pin_reg, $hold $(, $analog)?); + )+ + }; +} + +rtcio_analog! { + ( 0, touch_pad(0), touch_pad0 ) + ( 1, touch_pad(1), touch_pad1 , true) + ( 2, touch_pad(2), touch_pad2 , true) + ( 3, touch_pad(3), touch_pad3 , true) + ( 4, touch_pad(4), touch_pad4 , true) + ( 5, touch_pad(5), touch_pad5 , true) + ( 6, touch_pad(6), touch_pad6 , true) + ( 7, touch_pad(7), touch_pad7 , true) + ( 8, touch_pad(8), touch_pad8 , true) + ( 9, touch_pad(9), touch_pad9 , true) + (10, touch_pad(10), touch_pad10, true) + (11, touch_pad(11), touch_pad11, true) + (12, touch_pad(12), touch_pad12, true) + (13, touch_pad(13), touch_pad13, true) + (14, touch_pad(14), touch_pad14, true) + (15, xtal_32p_pad(), x32p , true) + (16, xtal_32n_pad(), x32n , true) + (17, pad_dac1(), pdac1 , true) + (18, pad_dac2(), pdac2 , true) + (19, rtc_pad19(), pad19 , true) + (20, rtc_pad20(), pad20 , true) + (21, rtc_pad21(), pad21 ) +} + +fn enable_iomux_clk_gate() { + crate::peripherals::SENS::regs() + .sar_peri_clk_gate_conf() + .modify(|_, w| w.iomux_clk_en().set_bit()); +} diff --git a/esp-hal/src/soc/esp32s3/mod.rs b/esp-hal/src/soc/esp32s3/mod.rs new file mode 100644 index 00000000000..daf61bbacef --- /dev/null +++ b/esp-hal/src/soc/esp32s3/mod.rs @@ -0,0 +1,150 @@ +//! # SOC (System-on-Chip) module (ESP32-S3) +//! +//! ## Overview +//! +//! The `SOC` module provides access, functions and structures that are useful +//! for interacting with various system-related peripherals on `ESP32-S3` chip. +//! +//! Also few constants are defined in this module for `ESP32-S3` chip: +//! * I2S_SCLK: 160_000_000 - I2S clock frequency +//! * I2S_DEFAULT_CLK_SRC: 2 - I2S clock source + +crate::unstable_module! { + pub mod clocks; + pub mod trng; + pub mod ulp_core; +} +#[cfg(feature = "unstable")] +pub mod cpu_control; +pub mod gpio; +pub(crate) mod regi2c; + +pub use esp32s3 as pac; + +#[cfg_attr(not(feature = "unstable"), allow(unused))] +pub(crate) mod constants { + /// The base clock frequency for the I2S peripheral (Hertz). + pub const I2S_SCLK: u32 = 160_000_000; + /// The default clock source for I2S operations. + pub const I2S_DEFAULT_CLK_SRC: u8 = 2; +} + +#[unsafe(link_section = ".rwtext")] +pub(crate) unsafe fn configure_cpu_caches() { + // this is just the bare minimum we need to run code from flash + // consider implementing more advanced configurations + // see https://github.com/apache/incubator-nuttx/blob/master/arch/xtensa/src/esp32s3/esp32s3_start.c + + unsafe extern "C" { + fn Cache_Suspend_DCache(); + + fn Cache_Resume_DCache(param: u32); + + fn rom_config_instruction_cache_mode( + cfg_cache_size: u32, + cfg_cache_ways: u8, + cfg_cache_line_size: u8, + ); + + fn rom_config_data_cache_mode( + cfg_cache_size: u32, + cfg_cache_ways: u8, + cfg_cache_line_size: u8, + ); + } + + const CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE: u32 = match () { + _ if cfg!(instruction_cache_size_32kb) => 0x8000, + _ if cfg!(instruction_cache_size_16kb) => 0x4000, + _ => core::unreachable!(), + }; + const CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS: u8 = match () { + _ if cfg!(icache_associated_ways_8) => 8, + _ if cfg!(icache_associated_ways_4) => 4, + _ => core::unreachable!(), + }; + const CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE: u8 = match () { + _ if cfg!(instruction_cache_line_size_32b) => 32, + _ if cfg!(instruction_cache_line_size_16b) => 16, + _ => core::unreachable!(), + }; + + const CONFIG_ESP32S3_DATA_CACHE_SIZE: u32 = match () { + _ if cfg!(data_cache_size_64kb) => 0x10000, + _ if cfg!(data_cache_size_32kb) => 0x8000, + _ if cfg!(data_cache_size_16kb) => 0x4000, + _ => core::unreachable!(), + }; + const CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS: u8 = match () { + _ if cfg!(dcache_associated_ways_8) => 8, + _ if cfg!(dcache_associated_ways_4) => 4, + _ => core::unreachable!(), + }; + const CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE: u8 = match () { + _ if cfg!(data_cache_line_size_64b) => 64, + _ if cfg!(data_cache_line_size_32b) => 32, + _ if cfg!(data_cache_line_size_16b) => 16, + _ => core::unreachable!(), + }; + + // Configure the mode of instruction cache: cache size, cache line size. + unsafe { + rom_config_instruction_cache_mode( + CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE, + CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS, + CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE, + ); + } + + // Configure the mode of data : cache size, cache line size. + unsafe { + Cache_Suspend_DCache(); + rom_config_data_cache_mode( + CONFIG_ESP32S3_DATA_CACHE_SIZE, + CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS, + CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE, + ); + Cache_Resume_DCache(0); + } +} + +/// Write back a specific range of data in the cache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_writeback_addr(addr: u32, size: u32) { + unsafe extern "C" { + fn rom_Cache_WriteBack_Addr(addr: u32, size: u32); + fn Cache_Suspend_DCache_Autoload() -> u32; + fn Cache_Resume_DCache_Autoload(value: u32); + } + // suspend autoload, avoid load cachelines being written back + unsafe { + let autoload = Cache_Suspend_DCache_Autoload(); + rom_Cache_WriteBack_Addr(addr, size); + Cache_Resume_DCache_Autoload(autoload); + } +} + +/// Invalidate a specific range of addresses in the cache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { + unsafe extern "C" { + fn Cache_Invalidate_Addr(addr: u32, size: u32); + } + unsafe { + Cache_Invalidate_Addr(addr, size); + } +} + +/// Get the size of a cache line in the DCache. +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +pub unsafe fn cache_get_dcache_line_size() -> u32 { + unsafe extern "C" { + fn Cache_Get_DCache_Line_Size() -> u32; + } + unsafe { Cache_Get_DCache_Line_Size() } +} + +pub(crate) fn pre_init() {} diff --git a/esp-hal/src/soc/esp32s3/regi2c.rs b/esp-hal/src/soc/esp32s3/regi2c.rs new file mode 100644 index 00000000000..cf6fa2064f5 --- /dev/null +++ b/esp-hal/src/soc/esp32s3/regi2c.rs @@ -0,0 +1,130 @@ +use crate::rom::regi2c::{RawRegI2cField, RegI2cMaster, RegI2cRegister, define_regi2c}; + +define_regi2c! { + master: REGI2C_BBPLL(0x66, 1) { + reg: I2C_BBPLL_IR_CAL(0) { + field: I2C_BBPLL_IR_CAL_CK_DIV(7..4), + field: I2C_BBPLL_IR_CAL_DELAY(3..0) + } + reg: I2C_BBPLL_IR_CAL_EXT_REG(1) { + field: I2C_BBPLL_IR_CAL_UNSTOP(7..7), + field: I2C_BBPLL_IR_CAL_START(6..6), + field: I2C_BBPLL_IR_CAL_RSTB(5..5), + field: I2C_BBPLL_IR_CAL_ENX_CAP(4..4), + field: I2C_BBPLL_IR_CAL_EXT_CAP(3..0) + } + reg: I2C_BBPLL_OC_REF(2) { + field: I2C_BBPLL_OC_ENB_FCAL(7..7), + field: I2C_BBPLL_OC_DCHGP(6..4), + field: I2C_BBPLL_OC_REF_DIV(3..0) + } + reg: I2C_BBPLL_OC_DIV_REG(3) { + field: I2C_BBPLL_OC_DIV(7..0) + } + reg: I2C_BBPLL_REG4(4) { + field: I2C_BBPLL_OC_TSCHGP(7..7), + field: I2C_BBPLL_OC_ENB_VCON(6..6), + field: I2C_BBPLL_DIV_CPU(5..5), + field: I2C_BBPLL_DIV_DAC(4..4), + field: I2C_BBPLL_DIV_ADC(3..2), + field: I2C_BBPLL_MODE_HF(1..1), + field: I2C_BBPLL_RSTB_DIV_ADC(0..0) + } + reg: I2C_BBPLL_OC_DR(5) { + field: I2C_BBPLL_EN_USB(7..7), + field: I2C_BBPLL_OC_DR3(6..4), + field: I2C_BBPLL_OC_DR1(2..0) + } + reg: I2C_BBPLL_REG6(6) { + field: I2C_BBPLL_OC_DLREF_SEL(7..6), + field: I2C_BBPLL_OC_DHREF_SEL(5..4), + field: I2C_BBPLL_INC_CUR(3..3), + field: I2C_BBPLL_OC_DCUR(2..0) + } + reg: I2C_BBPLL_REG8(8) { + field: I2C_BBPLL_OR_LOCK(7..7), + field: I2C_BBPLL_OR_CAL_END(6..6), + field: I2C_BBPLL_OR_CAL_OVF(5..5), + field: I2C_BBPLL_OR_CAL_UDF(4..4), + field: I2C_BBPLL_OR_CAL_CAP(3..0) + } + reg: I2C_BBPLL_REG9(9) { + field: I2C_BBPLL_BBADC_DREF(7..6), + field: I2C_BBPLL_BBADC_DVDD(5..4), + field: I2C_BBPLL_BBADC_DELAY2(3..2), + field: I2C_BBPLL_OC_VCO_DBIAS(1..0) + } + reg: I2C_BBPLL_REG10(10) { + field: I2C_BBPLL_ENT_ADC(7..6), + field: I2C_BBPLL_DTEST(5..4), + field: I2C_BBPLL_ENT_PLL_MSB(3..3), + field: I2C_BBPLL_BBADC_INPUT_SHORT(2..2), + field: I2C_BBPLL_BBADC_DCUR(1..0) + } + } + master: REGI2C_SAR_I2C(0x69, 1) { + reg: I2C_SAR_REG0(0) { + field: ADC_SAR1_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG1(1) { + field: ADC_SAR1_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG2(2) { + field: ADC_SAR1_DREF(6..4), + field: ADC_SAR1_SAMPLE_CYCLE(2..0) + } + reg: I2C_SAR_REG3(3) { + field: ADC_SAR2_INITIAL_CODE_LOW(7..0) + } + reg: I2C_SAR_REG4(4) { + field: ADC_SAR2_INITIAL_CODE_HIGH(3..0) + } + reg: I2C_SAR_REG5(5) { + field: ADC_SAR2_DREF(6..4) + } + reg: I2C_SAR_REG6(6) { + field: I2C_SARADC_TSENS_DAC(3..0) + } + reg: I2C_SAR_REG7(7) { + field: ADC_SAR2_ENCAL_GND(7..7), + field: ADC_SAR2_ENCAL_REF(6..6), // inferred, esp-idf doesn't have this + field: ADC_SAR1_ENCAL_GND(5..5), + field: ADC_SAR1_ENCAL_REF(4..4), + field: ADC_SAR_ENT_RTC(3..3), + field: ADC_SAR_ENT_TSENS(2..2), + field: ADC_SAR_DTEST_RTC(1..0) + } + } + master: I2C_DIG_REG(0x6d, 1) { + reg: I2C_DIG_REG4(4) { + field: I2C_DIG_REG_EXT_RTC_DREG(4..0) + } + reg: I2C_DIG_REG5(5) { + field: I2C_DIG_REG_EXT_RTC_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG6(6) { + field: I2C_DIG_REG_EXT_DIG_DREG(4..0) + } + reg: I2C_DIG_REG7(7) { + field: I2C_DIG_REG_EXT_DIG_DREG_SLEEP(4..0) + } + reg: I2C_DIG_REG13(13) { + field: I2C_DIG_REG_XPD_DIG_REG(3..3), + field: I2C_DIG_REG_XPD_RTC_REG(2..2) + } + } +} + +pub(crate) fn regi2c_read(block: u8, host_id: u8, reg_add: u8) -> u8 { + unsafe extern "C" { + pub(crate) fn esp_rom_regi2c_read(block: u8, block_hostid: u8, reg_add: u8) -> u8; + } + unsafe { esp_rom_regi2c_read(block, host_id, reg_add) } +} + +pub(crate) fn regi2c_write(block: u8, host_id: u8, reg_add: u8, data: u8) { + unsafe extern "C" { + pub(crate) fn rom_i2c_writeReg(block: u8, block_hostid: u8, reg_add: u8, indata: u8); + } + unsafe { rom_i2c_writeReg(block, host_id, reg_add, data) }; +} diff --git a/esp-hal/src/soc/esp32s3/trng.rs b/esp-hal/src/soc/esp32s3/trng.rs new file mode 100644 index 00000000000..c9375d9fca9 --- /dev/null +++ b/esp-hal/src/soc/esp32s3/trng.rs @@ -0,0 +1,121 @@ +use crate::soc::regi2c; + +const SYSTEM_WIFI_CLK_RNG_EN: u32 = 1 << 15; + +/// Enable true randomness by enabling the entropy source. +/// Blocks `ADC` usage. +pub(crate) fn ensure_randomness() { + unsafe { + let rtc_cntl = crate::peripherals::LPWR::regs(); + let system = crate::peripherals::SYSTEM::regs(); + let apb_saradc = crate::peripherals::APB_SARADC::regs(); + let sens = crate::peripherals::SENS::regs(); + let syscon = crate::peripherals::APB_CTRL::regs(); + + // `wifi_clk_en` register is defined in a really weird way, for now just simple + // bit edit + syscon.wifi_clk_en().modify(|r, w| { + let current_bits = r.bits(); + let new_bits = current_bits | SYSTEM_WIFI_CLK_RNG_EN; + w.bits(new_bits) + }); + + // Enable 8M clock source for RNG (this is actually enough to produce strong + // random results, but enabling the SAR ADC as well adds some insurance.) + rtc_cntl + .clk_conf() + .modify(|_, w| w.dig_clk8m_en().set_bit()); + + // Enable SAR ADC to read a disconnected input for additional entropy + // Reset ADC clock + system + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().set_bit()); + + system + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().clear_bit()); + + apb_saradc.clkm_conf().modify(|_, w| w.clk_sel().bits(2)); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_gated().set_bit()); + apb_saradc.clkm_conf().modify(|_, w| w.clk_en().set_bit()); + + apb_saradc + .clkm_conf() + .modify(|_, w| w.clkm_div_num().bits(3)); + + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(3)); + apb_saradc.ctrl2().modify(|_, w| w.timer_target().bits(3)); + apb_saradc.ctrl().modify(|_, w| w.sar_clk_div().bits(3)); + + sens.sar_power_xpd_sar() + .modify(|_, w| w.force_xpd_sar().bits(3)); + + apb_saradc + .ctrl2() + .modify(|_, w| w.meas_num_limit().clear_bit()); + + apb_saradc.ctrl().modify(|_, w| w.work_mode().bits(1)); + apb_saradc.ctrl().modify(|_, w| w.sar2_patt_len().bits(0)); + apb_saradc.sar2_patt_tab1().modify(|_, w| w.bits(0xafffff)); + apb_saradc.ctrl().modify(|_, w| w.sar1_patt_len().bits(0)); + apb_saradc.sar1_patt_tab1().modify(|_, w| w.bits(0xafffff)); + + sens.sar_meas1_mux() + .modify(|_, w| w.sar1_dig_force().set_bit()); + + sens.sar_meas2_mux() + .modify(|_, w| w.sar2_rtc_force().clear_bit()); + + apb_saradc + .arb_ctrl() + .modify(|_, w| w.grant_force().clear_bit()); + + apb_saradc + .arb_ctrl() + .modify(|_, w| w.fix_priority().clear_bit()); + + apb_saradc + .filter_ctrl0() + .modify(|_, w| w.filter_channel0().bits(0xD)); + + apb_saradc + .filter_ctrl0() + .modify(|_, w| w.filter_channel1().bits(0xD)); + + apb_saradc.ctrl2().modify(|_, w| w.timer_sel().set_bit()); + apb_saradc.ctrl2().modify(|_, w| w.timer_en().set_bit()); + + regi2c::ADC_SAR1_ENCAL_REF.write_field(1); + regi2c::ADC_SAR_ENT_TSENS.write_field(1); + regi2c::ADC_SAR_ENT_RTC.write_field(1); + regi2c::ADC_SAR_DTEST_RTC.write_field(1); + } +} + +/// Disable true randomness. Unlocks `ADC` peripheral. +pub(crate) fn revert_trng() { + let system = crate::peripherals::SYSTEM::regs(); + let apb_saradc = crate::peripherals::APB_SARADC::regs(); + let sens = crate::peripherals::SENS::regs(); + + unsafe { + regi2c::ADC_SAR1_ENCAL_REF.write_field(0); + regi2c::ADC_SAR_ENT_TSENS.write_field(0); + regi2c::ADC_SAR_ENT_RTC.write_field(0); + regi2c::ADC_SAR_DTEST_RTC.write_field(0); + + sens.sar_power_xpd_sar() + .modify(|_, w| w.force_xpd_sar().bits(0)); + + apb_saradc.ctrl2().modify(|_, w| w.timer_en().clear_bit()); + + system + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().clear_bit()); + + system + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().set_bit()); + } +} diff --git a/esp-hal/src/soc/esp32s3/ulp_core.rs b/esp-hal/src/soc/esp32s3/ulp_core.rs new file mode 100644 index 00000000000..51c9c5f3de7 --- /dev/null +++ b/esp-hal/src/soc/esp32s3/ulp_core.rs @@ -0,0 +1,183 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! Control the ULP core +//! +//! ## Overview +//! +//! The `ULP CORE` peripheral allows control over the `Ultra-Low Power +//! (ULP) core` in `ESP` chips. The ULP core is a low-power processor +//! designed for executing tasks in deep sleep mode, enabling efficient power +//! management in ESP systems. +//! +//! The `UlpCore` struct provides an interface to interact with the `ULP +//! CORE` peripheral. It allows starting and configuring the ULP core for +//! operation. The `UlpCore` struct is initialized with a peripheral reference +//! to the `ULP CORE` instance. +//! +//! ## Examples +//! +//! ```rust, no_run +//! # {before_snippet} +//! const CODE: &[u8] = &[ +//! 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x05, 0x01, 0x81, 0x45, 0x85, 0x05, 0x0c, 0xc1, 0xf5, +//! 0xbf, 0x00, 0x00, 0x00, 0x00, +//! ]; +//! +//! let mut ulp_core = esp_hal::ulp_core::UlpCore::new(peripherals.ULP_RISCV_CORE); +//! ulp_core.stop(); +//! +//! // copy code to RTC ram +//! let lp_ram = 0x5000_0000 as *mut u8; +//! unsafe { +//! core::ptr::copy_nonoverlapping(CODE as *const _ as *const u8, lp_ram, CODE.len()); +//! } +//! +//! // start ULP core +//! ulp_core.run(esp_hal::ulp_core::UlpCoreWakeupSource::HpCpu); +//! +//! unsafe { +//! let data = 0x5000_0010 as *mut u32; +//! loop {} +//! } +//! # } +//! ``` + +use crate::peripherals::LPWR; + +/// Enum representing the possible wakeup sources for the ULP core. +#[derive(Debug, Clone, Copy)] +pub enum UlpCoreWakeupSource { + /// Wakeup source from the HP (High Performance) CPU. + HpCpu, +} + +/// Structure representing the ULP (Ultra-Low Power) core. +pub struct UlpCore<'d> { + _lp_core: crate::peripherals::ULP_RISCV_CORE<'d>, +} + +impl<'d> UlpCore<'d> { + /// Creates a new instance of the `UlpCore` struct. + pub fn new(lp_core: crate::peripherals::ULP_RISCV_CORE<'d>) -> Self { + let mut this = Self { _lp_core: lp_core }; + this.stop(); + + // clear all of RTC_SLOW_RAM - this makes sure .bss is cleared without relying + let lp_ram = + unsafe { core::slice::from_raw_parts_mut(0x5000_0000 as *mut u32, 8 * 1024 / 4) }; + lp_ram.fill(0u32); + + this + } + + /// Stops the ULP core. + pub fn stop(&mut self) { + ulp_stop(); + } + + /// Runs the ULP core with the specified wakeup source. + pub fn run(&mut self, wakeup_src: UlpCoreWakeupSource) { + ulp_run(wakeup_src); + } +} + +fn ulp_stop() { + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); + + // suspends the ulp operation + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done().set_bit()); + + // Resets the processor + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); + + crate::rom::ets_delay_us(20); + + // above doesn't seem to halt the ULP core - this will + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_clkgate_en().clear_bit()); +} + +fn ulp_run(wakeup_src: UlpCoreWakeupSource) { + let rtc_cntl = LPWR::regs(); + + // Reset COCPU when power on + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); + + // The coprocessor cpu trap signal doesn't have a stable reset value, + // force ULP-RISC-V clock on to stop RTC_COCPU_TRAP_TRIG_EN from waking the CPU + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_clk_fo().set_bit()); + + // Disable ULP timer + rtc_cntl + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); + + // wait for at least 1 RTC_SLOW_CLK cycle + crate::rom::ets_delay_us(20); + + // We do not select RISC-V as the Coprocessor here as this could lead to a hang + // in the main CPU. Instead, we reset RTC_CNTL_COCPU_SEL after we have enabled + // the ULP timer. + // + // IDF-4510 + + // Select ULP-RISC-V to send the DONE signal + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done_force().set_bit()); + + // Set the CLKGATE_EN signal + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_clkgate_en().set_bit()); + + ulp_config_wakeup_source(wakeup_src); + + // Select RISC-V as the ULP_TIMER trigger target + // Selecting the RISC-V as the Coprocessor at the end is a workaround + // for the hang issue recorded in IDF-4510. + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_sel().clear_bit()); + + // Clear any spurious wakeup trigger interrupts upon ULP startup + crate::rom::ets_delay_us(20); + + rtc_cntl.int_clr().write(|w| { + w.cocpu() + .clear_bit_by_one() + .cocpu_trap() + .clear_bit_by_one() + .ulp_cp() + .clear_bit_by_one() + }); + + rtc_cntl + .cocpu_ctrl() + .modify(|_, w| w.cocpu_clkgate_en().set_bit()); +} + +fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { + match wakeup_src { + UlpCoreWakeupSource::HpCpu => { + // use timer to wake up + LPWR::regs() + .ulp_cp_ctrl() + .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); + LPWR::regs() + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); + } + } +} diff --git a/esp-hal/src/soc/mod.rs b/esp-hal/src/soc/mod.rs new file mode 100644 index 00000000000..908283d1a56 --- /dev/null +++ b/esp-hal/src/soc/mod.rs @@ -0,0 +1,364 @@ +#![cfg_attr(not(feature = "rt"), expect(unused))] + +use core::{ops::Range, sync::atomic::Ordering}; + +use portable_atomic::AtomicU32; +use procmacros::ram; + +pub use self::implementation::*; + +#[cfg_attr(esp32, path = "esp32/mod.rs")] +#[cfg_attr(esp32c2, path = "esp32c2/mod.rs")] +#[cfg_attr(esp32c3, path = "esp32c3/mod.rs")] +#[cfg_attr(esp32c5, path = "esp32c5/mod.rs")] +#[cfg_attr(esp32c6, path = "esp32c6/mod.rs")] +#[cfg_attr(esp32h2, path = "esp32h2/mod.rs")] +#[cfg_attr(esp32s2, path = "esp32s2/mod.rs")] +#[cfg_attr(esp32s3, path = "esp32s3/mod.rs")] +mod implementation; + +#[allow(unused)] +pub(crate) fn is_valid_ram_address(address: usize) -> bool { + addr_in_range(address, memory_range!("DRAM")) +} + +#[allow(unused)] +pub(crate) fn is_slice_in_dram(slice: &[T]) -> bool { + slice_in_range(slice, memory_range!("DRAM")) +} + +#[allow(unused)] +#[cfg(psram)] +pub(crate) fn is_valid_psram_address(address: usize) -> bool { + addr_in_range(address, crate::psram::psram_range()) +} + +#[allow(unused)] +#[cfg(psram)] +pub(crate) fn is_slice_in_psram(slice: &[T]) -> bool { + slice_in_range(slice, crate::psram::psram_range()) +} + +#[allow(unused)] +pub(crate) fn is_valid_memory_address(address: usize) -> bool { + if is_valid_ram_address(address) { + return true; + } + #[cfg(psram)] + if is_valid_psram_address(address) { + return true; + } + + false +} + +fn slice_in_range(slice: &[T], range: Range) -> bool { + let slice = slice.as_ptr_range(); + let start = slice.start as usize; + let end = slice.end as usize; + // `end` is >= `start`, so we don't need to check that `end > range.start` + // `end` is also one past the last element, so it can be equal to the range's + // end which is also one past the memory region's last valid address. + addr_in_range(start, range.clone()) && end <= range.end +} + +pub(crate) fn addr_in_range(addr: usize, range: Range) -> bool { + range.contains(&addr) +} + +#[cfg(feature = "rt")] +#[cfg(riscv)] +#[unsafe(export_name = "hal_main")] +fn hal_main(a0: usize, a1: usize, a2: usize) -> ! { + unsafe extern "Rust" { + // This symbol will be provided by the user via `#[entry]` + fn main(a0: usize, a1: usize, a2: usize) -> !; + } + + setup_stack_guard(); + + unsafe { + main(a0, a1, a2); + } +} + +#[cfg(all(xtensa, feature = "rt"))] +mod xtensa { + use core::arch::{global_asm, naked_asm}; + + /// The ESP32 has a first stage bootloader that handles loading program data + /// into the right place therefore we skip loading it again. This function + /// is called by xtensa-lx-rt in Reset. + #[unsafe(export_name = "__init_data")] + extern "C" fn __init_data() -> bool { + false + } + + extern "C" fn __init_persistent() -> bool { + matches!( + crate::system::reset_reason(), + None | Some(crate::rtc_cntl::SocResetReason::ChipPowerOn) + ) + } + + unsafe extern "C" { + static _rtc_fast_bss_start: u32; + static _rtc_fast_bss_end: u32; + static _rtc_fast_persistent_end: u32; + static _rtc_fast_persistent_start: u32; + + static _rtc_slow_bss_start: u32; + static _rtc_slow_bss_end: u32; + static _rtc_slow_persistent_end: u32; + static _rtc_slow_persistent_start: u32; + + fn _xtensa_lx_rt_zero_fill(s: *mut u32, e: *mut u32); + + static mut __stack_chk_guard: u32; + } + + global_asm!( + " + .literal sym_init_persistent, {__init_persistent} + .literal sym_xtensa_lx_rt_zero_fill, {_xtensa_lx_rt_zero_fill} + + .literal sym_rtc_fast_bss_start, {_rtc_fast_bss_start} + .literal sym_rtc_fast_bss_end, {_rtc_fast_bss_end} + .literal sym_rtc_fast_persistent_end, {_rtc_fast_persistent_end} + .literal sym_rtc_fast_persistent_start, {_rtc_fast_persistent_start} + + .literal sym_rtc_slow_bss_start, {_rtc_slow_bss_start} + .literal sym_rtc_slow_bss_end, {_rtc_slow_bss_end} + .literal sym_rtc_slow_persistent_end, {_rtc_slow_persistent_end} + .literal sym_rtc_slow_persistent_start, {_rtc_slow_persistent_start} + ", + __init_persistent = sym __init_persistent, + _xtensa_lx_rt_zero_fill = sym _xtensa_lx_rt_zero_fill, + + _rtc_fast_bss_end = sym _rtc_fast_bss_end, + _rtc_fast_bss_start = sym _rtc_fast_bss_start, + _rtc_fast_persistent_end = sym _rtc_fast_persistent_end, + _rtc_fast_persistent_start = sym _rtc_fast_persistent_start, + + _rtc_slow_bss_end = sym _rtc_slow_bss_end, + _rtc_slow_bss_start = sym _rtc_slow_bss_start, + _rtc_slow_persistent_end = sym _rtc_slow_persistent_end, + _rtc_slow_persistent_start = sym _rtc_slow_persistent_start, + ); + + #[unsafe(export_name = "__post_init")] + #[unsafe(naked)] + #[allow(named_asm_labels)] + extern "C" fn post_init() { + naked_asm!( + " + entry a1, 0x10 // 4 words for callx4 spill area + + l32r a2, sym_xtensa_lx_rt_zero_fill // Pre-load address of zero-fill function + + l32r a6, sym_rtc_fast_bss_start // Set input range to .rtc_fast.bss + l32r a7, sym_rtc_fast_bss_end // + callx4 a2 // Zero-fill + + l32r a6, sym_rtc_slow_bss_start // Set input range to .rtc_slow.bss + l32r a7, sym_rtc_slow_bss_end // + callx4 a2 // Zero-fill + + l32r a3, sym_init_persistent // Do we need to initialize persistent data? + callx4 a3 + beqz a6, .Lpost_init_return // If not, skip initialization + + l32r a6, sym_rtc_fast_persistent_start // Set input range to .rtc_fast.persistent + l32r a7, sym_rtc_fast_persistent_end // + callx4 a2 // Zero-fill + + l32r a6, sym_rtc_slow_persistent_start // Set input range to .rtc_slow.persistent + l32r a7, sym_rtc_slow_persistent_end // + callx4 a2 // Zero-fill + + .Lpost_init_return: + retw.n + ", + ) + } + + #[cfg(esp32s3)] + global_asm!(".section .rwtext,\"ax\",@progbits"); + global_asm!( + " + .literal sym_stack_chk_guard, {__stack_chk_guard} + .literal stack_guard_value, {stack_guard_value} + .literal sym_esp32_init, {__esp32_init} + ", + __stack_chk_guard = sym __stack_chk_guard, + stack_guard_value = const esp_config::esp_config_int!( + u32, + "ESP_HAL_CONFIG_STACK_GUARD_VALUE" + ), + __esp32_init = sym esp32_init, + ); + + #[cfg_attr(esp32s3, unsafe(link_section = ".rwtext"))] + #[unsafe(export_name = "__pre_init")] + #[unsafe(naked)] + unsafe extern "C" fn esp32_reset() { + // Set up stack protector value before jumping to a rust function + naked_asm! { + " + entry a1, 0x10 // 4 words for callx4 spill area + + // Set up the stack protector value + l32r a2, sym_stack_chk_guard + l32r a3, stack_guard_value + s32i.n a3, a2, 0 + + l32r a2, sym_esp32_init + callx4 a2 + + retw.n + " + } + } + + #[cfg_attr(esp32s3, unsafe(link_section = ".rwtext"))] + fn esp32_init() { + unsafe { + super::configure_cpu_caches(); + } + + crate::interrupt::setup_interrupts(); + } +} + +#[cfg(feature = "rt")] +#[unsafe(export_name = "__stack_chk_fail")] +unsafe extern "C" fn stack_chk_fail() { + panic!("Stack corruption detected"); +} + +#[cfg(all(feature = "rt", riscv))] +fn setup_stack_guard() { + unsafe extern "C" { + static mut __stack_chk_guard: u32; + } + + unsafe { + let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard); + // we _should_ use a random value but we don't have a good source for random + // numbers here + stack_chk_guard.write_volatile(esp_config::esp_config_int!( + u32, + "ESP_HAL_CONFIG_STACK_GUARD_VALUE" + )); + } +} + +#[cfg(all(feature = "rt", stack_guard_monitoring))] +pub(crate) fn enable_main_stack_guard_monitoring() { + unsafe { + unsafe extern "C" { + static mut __stack_chk_guard: u32; + } + + let guard_addr = core::ptr::addr_of_mut!(__stack_chk_guard) as *mut _ as u32; + crate::debugger::set_stack_watchpoint(guard_addr as usize); + } +} + +#[cfg(all(riscv, write_vec_table_monitoring))] +pub(crate) fn trap_section_protected() -> bool { + cfg!(stack_guard_monitoring_with_debugger_connected) || !crate::debugger::debugger_connected() +} + +#[cfg(all(riscv, write_vec_table_monitoring))] +pub(crate) fn setup_trap_section_protection() { + if !trap_section_protected() { + return; + } + + unsafe extern "C" { + static _rwtext_len: u32; + static _trap_section_origin: u32; + } + + let rwtext_len = core::ptr::addr_of!(_rwtext_len) as usize; + + // protect as much as possible via NAPOT + let len = 1 << (usize::BITS - rwtext_len.leading_zeros() - 1) as usize; + if len == 0 { + warn!("No trap vector protection available"); + return; + } + + // protect MTVEC and trap handlers + // (probably plus some more bytes because of NAPOT) + // via watchpoint 1. + // + // Why not use PMP? On C2/C3 the bootloader locks all available PMP entries. + // And additionally we write to MTVEC for direct-vectoring and we write + // to __EXTERNAL_INTERRUPTS when setting an interrupt handler. + let addr = core::ptr::addr_of!(_trap_section_origin) as usize; + + unsafe { + crate::debugger::set_watchpoint(1, addr, len); + } +} + +static CHIP_REVISION: AtomicU32 = AtomicU32::new(0); +const LOADED: u32 = 1 << 31; + +#[cold] +fn load_chip_revision_from_efuse() -> u16 { + let chip_revision = crate::efuse::chip_revision(); + CHIP_REVISION.store(chip_revision as u32 | LOADED, Ordering::Release); + chip_revision +} + +#[ram] +fn load_chip_revision() -> u16 { + let stored = CHIP_REVISION.load(Ordering::Acquire); + if stored & LOADED == 0 { + return load_chip_revision_from_efuse(); + } + (stored & u16::MAX as u32) as u16 +} + +fn chip_revision_in_range(range: Range) -> bool { + const BUILD_TIME_MIN_REV: u16 = + esp_config::esp_config_int!(u16, "ESP_HAL_CONFIG_MIN_CHIP_REVISION"); + + // Check to determine chip is obviously in or out of range, without reading efuse + #[allow( + clippy::absurd_extreme_comparisons, + reason = "Not absurd depending on configuration" + )] + if range.end < BUILD_TIME_MIN_REV { + // Chip will not boot in this range + return false; + } + + #[allow( + clippy::absurd_extreme_comparisons, + reason = "Not absurd depending on configuration" + )] + if range.start <= BUILD_TIME_MIN_REV && range.end == u16::MAX { + return true; + } + + let chip_revision = load_chip_revision(); + + range.contains(&chip_revision) +} + +/// Returns true if the chip revision is at least the given revision. +#[allow(dead_code)] +pub(crate) fn chip_revision_above(min: u16) -> bool { + chip_revision_in_range(min..u16::MAX) +} + +/// Returns true if the chip is at least the given revision, in the same major version. +#[allow(dead_code)] +pub(crate) fn chip_minor_revision_above(rev: u16) -> bool { + let max = (rev + 1).next_multiple_of(100); + chip_revision_in_range(rev..max) +} diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs new file mode 100644 index 00000000000..7de48a916f4 --- /dev/null +++ b/esp-hal/src/spi/master.rs @@ -0,0 +1,4088 @@ +//! # Serial Peripheral Interface - Master Mode +//! +//! ## Overview +//! +//! In this mode, the SPI acts as master and initiates the SPI transactions. +//! +//! ## Configuration +//! +//! The peripheral can be used in full-duplex and half-duplex mode and can +//! leverage DMA for data transfers. It can also be used in blocking or async. +//! +//! ### Exclusive access to the SPI bus +//! +//! If all you want to do is to communicate to a single device, and you initiate +//! transactions yourself, there are a number of ways to achieve this: +//! +//! - Use the [`SpiBus`](embedded_hal::spi::SpiBus) trait and its associated functions to initiate +//! transactions with simultaneous reads and writes, or +//! - Use the `ExclusiveDevice` struct from [`embedded-hal-bus`] or `SpiDevice` from +//! [`embassy-embedded-hal`]. +//! +//! ### Shared SPI access +//! +//! If you have multiple devices on the same SPI bus that each have their own CS +//! line (and optionally, configuration), you may want to have a look at the +//! implementations provided by [`embedded-hal-bus`] and +//! [`embassy-embedded-hal`]. +//! +//! ## Usage +//! +//! The module implements several third-party traits from embedded-hal@1.x.x +//! and [`embassy-embedded-hal`]. +//! +//! [`embedded-hal-bus`]: https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/spi/index.html +//! [`embassy-embedded-hal`]: embassy_embedded_hal::shared_bus + +#[cfg(esp32)] +use core::cell::Cell; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +#[instability::unstable] +#[cfg(spi_master_supports_dma)] +pub use dma::*; +use enumset::{EnumSet, EnumSetType}; +use procmacros::doc_replace; + +use super::{BitOrder, Error, Mode}; +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + clock::Clocks, + gpio::{ + InputConfig, + InputSignal, + NoPin, + OutputConfig, + OutputSignal, + PinGuard, + interconnect::{self, PeripheralInput, PeripheralOutput}, + }, + handler, + interrupt::InterruptHandler, + pac::spi2::RegisterBlock, + private::{self, OnDrop, Sealed}, + ram, + system::PeripheralGuard, + time::Rate, +}; + +/// Enumeration of possible SPI interrupt events. +#[derive(Debug, Hash, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum SpiInterrupt { + /// Indicates that the SPI transaction has completed successfully. + /// + /// This interrupt is triggered when an SPI transaction has finished + /// transmitting and receiving data. + TransferDone, + + /// Triggered at the end of configurable segmented transfer. + #[cfg(spi_master_has_dma_segmented_transfer)] + DmaSegmentedTransferDone, + + /// Used and triggered by software. Only used for user defined function. + #[cfg(spi_master_has_app_interrupts)] + App2, + + /// Used and triggered by software. Only used for user defined function. + #[cfg(spi_master_has_app_interrupts)] + App1, +} + +/// The size of the FIFO buffer for SPI +const FIFO_SIZE: usize = if cfg!(esp32s2) { 72 } else { 64 }; + +/// Padding byte for empty write transfers +const EMPTY_WRITE_PAD: u8 = 0x00; + +/// SPI commands, each consisting of a 16-bit command value and a data mode. +/// +/// Used to define specific commands sent over the SPI bus. +/// Can be [Command::None] if command phase should be suppressed. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum Command { + /// No command is sent. + None, + /// A 1-bit command. + _1Bit(u16, DataMode), + /// A 2-bit command. + _2Bit(u16, DataMode), + /// A 3-bit command. + _3Bit(u16, DataMode), + /// A 4-bit command. + _4Bit(u16, DataMode), + /// A 5-bit command. + _5Bit(u16, DataMode), + /// A 6-bit command. + _6Bit(u16, DataMode), + /// A 7-bit command. + _7Bit(u16, DataMode), + /// A 8-bit command. + _8Bit(u16, DataMode), + /// A 9-bit command. + _9Bit(u16, DataMode), + /// A 10-bit command. + _10Bit(u16, DataMode), + /// A 11-bit command. + _11Bit(u16, DataMode), + /// A 12-bit command. + _12Bit(u16, DataMode), + /// A 13-bit command. + _13Bit(u16, DataMode), + /// A 14-bit command. + _14Bit(u16, DataMode), + /// A 15-bit command. + _15Bit(u16, DataMode), + /// A 16-bit command. + _16Bit(u16, DataMode), +} + +impl Command { + fn width(&self) -> usize { + match self { + Command::None => 0, + Command::_1Bit(_, _) => 1, + Command::_2Bit(_, _) => 2, + Command::_3Bit(_, _) => 3, + Command::_4Bit(_, _) => 4, + Command::_5Bit(_, _) => 5, + Command::_6Bit(_, _) => 6, + Command::_7Bit(_, _) => 7, + Command::_8Bit(_, _) => 8, + Command::_9Bit(_, _) => 9, + Command::_10Bit(_, _) => 10, + Command::_11Bit(_, _) => 11, + Command::_12Bit(_, _) => 12, + Command::_13Bit(_, _) => 13, + Command::_14Bit(_, _) => 14, + Command::_15Bit(_, _) => 15, + Command::_16Bit(_, _) => 16, + } + } + + fn value(&self) -> u16 { + match self { + Command::None => 0, + Command::_1Bit(value, _) + | Command::_2Bit(value, _) + | Command::_3Bit(value, _) + | Command::_4Bit(value, _) + | Command::_5Bit(value, _) + | Command::_6Bit(value, _) + | Command::_7Bit(value, _) + | Command::_8Bit(value, _) + | Command::_9Bit(value, _) + | Command::_10Bit(value, _) + | Command::_11Bit(value, _) + | Command::_12Bit(value, _) + | Command::_13Bit(value, _) + | Command::_14Bit(value, _) + | Command::_15Bit(value, _) + | Command::_16Bit(value, _) => *value, + } + } + + fn mode(&self) -> DataMode { + match self { + Command::None => DataMode::SingleTwoDataLines, + Command::_1Bit(_, mode) + | Command::_2Bit(_, mode) + | Command::_3Bit(_, mode) + | Command::_4Bit(_, mode) + | Command::_5Bit(_, mode) + | Command::_6Bit(_, mode) + | Command::_7Bit(_, mode) + | Command::_8Bit(_, mode) + | Command::_9Bit(_, mode) + | Command::_10Bit(_, mode) + | Command::_11Bit(_, mode) + | Command::_12Bit(_, mode) + | Command::_13Bit(_, mode) + | Command::_14Bit(_, mode) + | Command::_15Bit(_, mode) + | Command::_16Bit(_, mode) => *mode, + } + } + + fn is_none(&self) -> bool { + matches!(self, Command::None) + } +} + +/// SPI address, ranging from 1 to 32 bits, paired with a data mode. +/// +/// This can be used to specify the address phase of SPI transactions. +/// Can be [Address::None] if address phase should be suppressed. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum Address { + /// No address phase. + None, + /// A 1-bit address. + _1Bit(u32, DataMode), + /// A 2-bit address. + _2Bit(u32, DataMode), + /// A 3-bit address. + _3Bit(u32, DataMode), + /// A 4-bit address. + _4Bit(u32, DataMode), + /// A 5-bit address. + _5Bit(u32, DataMode), + /// A 6-bit address. + _6Bit(u32, DataMode), + /// A 7-bit address. + _7Bit(u32, DataMode), + /// A 8-bit address. + _8Bit(u32, DataMode), + /// A 9-bit address. + _9Bit(u32, DataMode), + /// A 10-bit address. + _10Bit(u32, DataMode), + /// A 11-bit address. + _11Bit(u32, DataMode), + /// A 12-bit address. + _12Bit(u32, DataMode), + /// A 13-bit address. + _13Bit(u32, DataMode), + /// A 14-bit address. + _14Bit(u32, DataMode), + /// A 15-bit address. + _15Bit(u32, DataMode), + /// A 16-bit address. + _16Bit(u32, DataMode), + /// A 17-bit address. + _17Bit(u32, DataMode), + /// A 18-bit address. + _18Bit(u32, DataMode), + /// A 19-bit address. + _19Bit(u32, DataMode), + /// A 20-bit address. + _20Bit(u32, DataMode), + /// A 21-bit address. + _21Bit(u32, DataMode), + /// A 22-bit address. + _22Bit(u32, DataMode), + /// A 23-bit address. + _23Bit(u32, DataMode), + /// A 24-bit address. + _24Bit(u32, DataMode), + /// A 25-bit address. + _25Bit(u32, DataMode), + /// A 26-bit address. + _26Bit(u32, DataMode), + /// A 27-bit address. + _27Bit(u32, DataMode), + /// A 28-bit address. + _28Bit(u32, DataMode), + /// A 29-bit address. + _29Bit(u32, DataMode), + /// A 30-bit address. + _30Bit(u32, DataMode), + /// A 31-bit address. + _31Bit(u32, DataMode), + /// A 32-bit address. + _32Bit(u32, DataMode), +} + +impl Address { + fn width(&self) -> usize { + match self { + Address::None => 0, + Address::_1Bit(_, _) => 1, + Address::_2Bit(_, _) => 2, + Address::_3Bit(_, _) => 3, + Address::_4Bit(_, _) => 4, + Address::_5Bit(_, _) => 5, + Address::_6Bit(_, _) => 6, + Address::_7Bit(_, _) => 7, + Address::_8Bit(_, _) => 8, + Address::_9Bit(_, _) => 9, + Address::_10Bit(_, _) => 10, + Address::_11Bit(_, _) => 11, + Address::_12Bit(_, _) => 12, + Address::_13Bit(_, _) => 13, + Address::_14Bit(_, _) => 14, + Address::_15Bit(_, _) => 15, + Address::_16Bit(_, _) => 16, + Address::_17Bit(_, _) => 17, + Address::_18Bit(_, _) => 18, + Address::_19Bit(_, _) => 19, + Address::_20Bit(_, _) => 20, + Address::_21Bit(_, _) => 21, + Address::_22Bit(_, _) => 22, + Address::_23Bit(_, _) => 23, + Address::_24Bit(_, _) => 24, + Address::_25Bit(_, _) => 25, + Address::_26Bit(_, _) => 26, + Address::_27Bit(_, _) => 27, + Address::_28Bit(_, _) => 28, + Address::_29Bit(_, _) => 29, + Address::_30Bit(_, _) => 30, + Address::_31Bit(_, _) => 31, + Address::_32Bit(_, _) => 32, + } + } + + fn value(&self) -> u32 { + match self { + Address::None => 0, + Address::_1Bit(value, _) + | Address::_2Bit(value, _) + | Address::_3Bit(value, _) + | Address::_4Bit(value, _) + | Address::_5Bit(value, _) + | Address::_6Bit(value, _) + | Address::_7Bit(value, _) + | Address::_8Bit(value, _) + | Address::_9Bit(value, _) + | Address::_10Bit(value, _) + | Address::_11Bit(value, _) + | Address::_12Bit(value, _) + | Address::_13Bit(value, _) + | Address::_14Bit(value, _) + | Address::_15Bit(value, _) + | Address::_16Bit(value, _) + | Address::_17Bit(value, _) + | Address::_18Bit(value, _) + | Address::_19Bit(value, _) + | Address::_20Bit(value, _) + | Address::_21Bit(value, _) + | Address::_22Bit(value, _) + | Address::_23Bit(value, _) + | Address::_24Bit(value, _) + | Address::_25Bit(value, _) + | Address::_26Bit(value, _) + | Address::_27Bit(value, _) + | Address::_28Bit(value, _) + | Address::_29Bit(value, _) + | Address::_30Bit(value, _) + | Address::_31Bit(value, _) + | Address::_32Bit(value, _) => *value, + } + } + + fn is_none(&self) -> bool { + matches!(self, Address::None) + } + + fn mode(&self) -> DataMode { + match self { + Address::None => DataMode::SingleTwoDataLines, + Address::_1Bit(_, mode) + | Address::_2Bit(_, mode) + | Address::_3Bit(_, mode) + | Address::_4Bit(_, mode) + | Address::_5Bit(_, mode) + | Address::_6Bit(_, mode) + | Address::_7Bit(_, mode) + | Address::_8Bit(_, mode) + | Address::_9Bit(_, mode) + | Address::_10Bit(_, mode) + | Address::_11Bit(_, mode) + | Address::_12Bit(_, mode) + | Address::_13Bit(_, mode) + | Address::_14Bit(_, mode) + | Address::_15Bit(_, mode) + | Address::_16Bit(_, mode) + | Address::_17Bit(_, mode) + | Address::_18Bit(_, mode) + | Address::_19Bit(_, mode) + | Address::_20Bit(_, mode) + | Address::_21Bit(_, mode) + | Address::_22Bit(_, mode) + | Address::_23Bit(_, mode) + | Address::_24Bit(_, mode) + | Address::_25Bit(_, mode) + | Address::_26Bit(_, mode) + | Address::_27Bit(_, mode) + | Address::_28Bit(_, mode) + | Address::_29Bit(_, mode) + | Address::_30Bit(_, mode) + | Address::_31Bit(_, mode) + | Address::_32Bit(_, mode) => *mode, + } + } +} + +/// SPI clock source. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum ClockSource { + /// Use the APB clock. + Apb, + // #[cfg(any(esp32c2, esp32c3, esp32s3))] + // Xtal, +} + +/// SPI peripheral configuration +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// The precomputed clock configuration register value. + /// + /// Clock divider calculations are relatively expensive, and the SPI + /// peripheral is commonly expected to be used in a shared bus + /// configuration, where different devices may need different bus clock + /// frequencies. To reduce the time required to reconfigure the bus, we + /// cache clock register's value here, for each configuration. + /// + /// This field is not intended to be set by the user, and is only used + /// internally. + #[builder_lite(skip)] + reg: Result, + + /// The target frequency + #[builder_lite(skip_setter)] + frequency: Rate, + + /// The clock source + #[builder_lite(unstable)] + #[builder_lite(skip_setter)] + clock_source: ClockSource, + + /// SPI sample/shift mode. + mode: Mode, + + /// Bit order of the read data. + read_bit_order: BitOrder, + + /// Bit order of the written data. + write_bit_order: BitOrder, +} + +impl Default for Config { + fn default() -> Self { + let mut this = Config { + reg: Ok(0), + frequency: Rate::from_mhz(1), + clock_source: ClockSource::Apb, + mode: Mode::_0, + read_bit_order: BitOrder::MsbFirst, + write_bit_order: BitOrder::MsbFirst, + }; + + this.reg = this.recalculate(); + + this + } +} + +impl Config { + /// Set the frequency of the SPI bus clock. + pub fn with_frequency(mut self, frequency: Rate) -> Self { + self.frequency = frequency; + self.reg = self.recalculate(); + + self + } + + /// Set the clock source of the SPI bus. + #[instability::unstable] + pub fn with_clock_source(mut self, clock_source: ClockSource) -> Self { + self.clock_source = clock_source; + self.reg = self.recalculate(); + + self + } + + fn clock_source_freq_hz(&self) -> Rate { + let clocks = Clocks::get(); + + // FIXME: take clock source into account + cfg_if::cfg_if! { + if #[cfg(esp32h2)] { + // ESP32-H2 is using PLL_48M_CLK source instead of APB_CLK + let _clocks = clocks; + Rate::from_mhz(48) + } else if #[cfg(esp32c5)] { + // We select the 160MHz PLL as the clock source in the driver. There is a by-2 divider + // configured between the PLL and the SPI clock (spi2_clkm_div_num). + let _clocks = clocks; + Rate::from_mhz(80) // clk_spi2_mst must be <= 80MHz + } else if #[cfg(esp32c6)] { + // We select the 80MHz PLL as the clock source in the driver + // FIXME we state that the default clock source is APB, which just isn't true + let _clocks = clocks; + Rate::from_mhz(80) + } else { + clocks.apb_clock + } + } + } + + fn recalculate(&self) -> Result { + // taken from https://github.com/apache/incubator-nuttx/blob/8267a7618629838231256edfa666e44b5313348e/arch/risc-v/src/esp32c3/esp32c3_spi.c#L496 + let source_freq = self.clock_source_freq_hz(); + + let reg_val: u32; + let duty_cycle = 128; + + // In HW, n, h and l fields range from 1 to 64, pre ranges from 1 to 8K. + // The value written to register is one lower than the used value. + + if self.frequency > ((source_freq / 4) * 3) { + // Using APB frequency directly will give us the best result here. + reg_val = 1 << 31; + } else { + // For best duty cycle resolution, we want n to be as close to 32 as + // possible, but we also need a pre/n combo that gets us as close as + // possible to the intended frequency. To do this, we bruteforce n and + // calculate the best pre to go along with that. If there's a choice + // between pre/n combos that give the same result, use the one with the + // higher n. + + let mut pre: i32; + let mut bestn: i32 = -1; + let mut bestpre: i32 = -1; + let mut besterr: i32 = 0; + let mut errval: i32; + + let target_freq_hz = self.frequency.as_hz() as i32; + let source_freq_hz = source_freq.as_hz() as i32; + + // Start at n = 2. We need to be able to set h/l so we have at least + // one high and one low pulse. + + for n in 2..=64 { + // Effectively, this does: + // pre = round((APB_CLK_FREQ / n) / frequency) + + pre = ((source_freq_hz / n) + (target_freq_hz / 2)) / target_freq_hz; + + if pre <= 0 { + pre = 1; + } + + if pre > 16 { + pre = 16; + } + + errval = (source_freq_hz / (pre * n) - target_freq_hz).abs(); + if bestn == -1 || errval <= besterr { + besterr = errval; + bestn = n; + bestpre = pre; + } + } + + let n: i32 = bestn; + pre = bestpre; + let l: i32 = n; + + // Effectively, this does: + // h = round((duty_cycle * n) / 256) + + let mut h: i32 = (duty_cycle * n + 127) / 256; + if h <= 0 { + h = 1; + } + + reg_val = (l as u32 - 1) + | ((h as u32 - 1) << 6) + | ((n as u32 - 1) << 12) + | ((pre as u32 - 1) << 18); + } + + Ok(reg_val) + } + + fn raw_clock_reg_value(&self) -> Result { + self.reg + } + + fn validate(&self) -> Result<(), ConfigError> { + let source_freq = self.clock_source_freq_hz(); + let min_divider = 1; + // FIXME: while ESP32 and S2 support pre dividers as large as 8192, + // those values are not currently supported by the divider calculation. + let max_divider = 16 * 64; // n * pre + + if self.frequency < source_freq / max_divider || self.frequency > source_freq / min_divider + { + return Err(ConfigError::FrequencyOutOfRange); + } + + Ok(()) + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct SpiPinGuard { + sclk_pin: PinGuard, + cs_pin: PinGuard, + sio0_pin: PinGuard, + sio1_pin: PinGuard, + sio2_pin: PinGuard, + sio3_pin: PinGuard, + #[cfg(spi_master_has_octal)] + sio4_pin: PinGuard, + #[cfg(spi_master_has_octal)] + sio5_pin: PinGuard, + #[cfg(spi_master_has_octal)] + sio6_pin: PinGuard, + #[cfg(spi_master_has_octal)] + sio7_pin: PinGuard, +} + +/// Configuration errors. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// The requested frequency is not in the supported range. + FrequencyOutOfRange, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::FrequencyOutOfRange => { + write!(f, "The requested frequency is not in the supported range") + } + } + } +} +#[procmacros::doc_replace] +/// SPI peripheral driver +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::spi::{ +/// Mode, +/// master::{Config, Spi}, +/// }; +/// let mut spi = Spi::new( +/// peripherals.SPI2, +/// Config::default() +/// .with_frequency(Rate::from_khz(100)) +/// .with_mode(Mode::_0), +/// )? +/// .with_sck(peripherals.GPIO0) +/// .with_mosi(peripherals.GPIO1) +/// .with_miso(peripherals.GPIO2); +/// # {after_snippet} +/// ``` +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Spi<'d, Dm: DriverMode> { + spi: AnySpi<'d>, + _mode: PhantomData, + guard: PeripheralGuard, + pins: SpiPinGuard, +} + +impl Sealed for Spi<'_, Dm> {} + +impl<'d> Spi<'d, Blocking> { + #[procmacros::doc_replace] + /// Constructs an SPI instance in 8bit dataframe mode. + /// + /// ## Errors + /// + /// See [`Spi::apply_config`]. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + pub fn new(spi: impl Instance + 'd, config: Config) -> Result { + let guard = PeripheralGuard::new(spi.info().peripheral); + + let mut this = Spi { + _mode: PhantomData, + guard, + pins: SpiPinGuard { + sclk_pin: PinGuard::new_unconnected(), + cs_pin: PinGuard::new_unconnected(), + sio0_pin: PinGuard::new_unconnected(), + sio1_pin: PinGuard::new_unconnected(), + sio2_pin: PinGuard::new_unconnected(), + sio3_pin: PinGuard::new_unconnected(), + #[cfg(spi_master_has_octal)] + sio4_pin: PinGuard::new_unconnected(), + #[cfg(spi_master_has_octal)] + sio5_pin: PinGuard::new_unconnected(), + #[cfg(spi_master_has_octal)] + sio6_pin: PinGuard::new_unconnected(), + #[cfg(spi_master_has_octal)] + sio7_pin: PinGuard::new_unconnected(), + }, + spi: spi.degrade(), + }; + + this.driver().init(); + this.apply_config(&config)?; + + let this = this.with_sck(NoPin).with_cs(NoPin); + + for sio in 0..8 { + if let Some(signal) = this.driver().info.opt_sio_input(sio) { + signal.connect_to(&NoPin); + } + if let Some(signal) = this.driver().info.opt_sio_output(sio) { + signal.connect_to(&NoPin); + } + } + + Ok(this) + } + + /// Reconfigures the driver to operate in [`Async`] mode. + /// + /// See the [`Async`] documentation for an example on how to use this + /// method. + pub fn into_async(mut self) -> Spi<'d, Async> { + self.set_interrupt_handler(self.spi.info().async_handler); + Spi { + spi: self.spi, + _mode: PhantomData, + guard: self.guard, + pins: self.pins, + } + } + + #[doc_replace( + "peripheral_on" => { + cfg(multi_core) => "peripheral on the current core", + _ => "peripheral", + } + )] + /// # Registers an interrupt handler for the __peripheral_on__. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER] + /// + /// # Panics + /// + /// Panics if passed interrupt handler is invalid (e.g. has priority + /// `None`) + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.spi.set_interrupt_handler(handler); + } +} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Spi<'_, Blocking> { + /// Sets the interrupt handler + /// + /// Interrupts are not enabled at the peripheral level here. + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +impl<'d> Spi<'d, Async> { + /// Reconfigures the driver to operate in [`Blocking`] mode. + /// + /// See the [`Blocking`] documentation for an example on how to use this + /// method. + pub fn into_blocking(self) -> Spi<'d, Blocking> { + self.spi.disable_peri_interrupt_on_all_cores(); + Spi { + spi: self.spi, + _mode: PhantomData, + guard: self.guard, + pins: self.pins, + } + } + + #[procmacros::doc_replace] + /// Waits for the completion of previous operations. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut buffer = [0; 10]; + /// spi.transfer_in_place_async(&mut buffer).await?; + /// spi.flush_async().await?; + /// + /// # {after_snippet} + /// ``` + pub async fn flush_async(&mut self) -> Result<(), Error> { + self.driver().flush_async().await + } + + #[procmacros::doc_replace] + /// Sends `words` to the slave. Returns the `words` received from the slave. + /// + /// This function aborts the transfer when its Future is dropped. Some + /// amount of data may have been transferred before the Future is + /// dropped. Dropping the future may block for a short while to ensure + /// the transfer is aborted. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut buffer = [0; 10]; + /// spi.transfer_in_place_async(&mut buffer).await?; + /// + /// # {after_snippet} + /// ``` + pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.driver().flush_async().await?; + self.driver().setup_full_duplex()?; + self.driver().transfer_in_place_async(words).await + } +} + +macro_rules! def_with_sio_pin { + ($fn:ident, $field:ident, $n:literal) => { + #[doc = concat!(" Assign the SIO", stringify!($n), " pin for the SPI instance.")] + #[doc = " "] + #[doc = " Enables both input and output functionality for the pin, and connects it"] + #[doc = concat!(" to the SIO", stringify!($n), " output and input signals.")] + #[instability::unstable] + pub fn $fn(mut self, sio: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self { + self.pins.$field = self.connect_sio_pin(sio.into(), $n); + + self + } + }; +} + +impl<'d, Dm> Spi<'d, Dm> +where + Dm: DriverMode, +{ + fn connect_sio_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard { + let in_signal = self.spi.info().sio_input(n); + let out_signal = self.spi.info().sio_output(n); + + pin.apply_input_config(&InputConfig::default()); + pin.apply_output_config(&OutputConfig::default()); + + pin.set_input_enable(true); + pin.set_output_enable(false); + + in_signal.connect_to(&pin); + pin.connect_with_guard(out_signal) + } + + fn connect_sio_output_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard { + let out_signal = self.spi.info().sio_output(n); + + self.connect_output_pin(pin, out_signal) + } + + fn connect_output_pin( + &self, + pin: interconnect::OutputSignal<'d>, + signal: OutputSignal, + ) -> PinGuard { + pin.apply_output_config(&OutputConfig::default()); + pin.set_output_enable(true); // TODO turn this bool into a Yes/No/PeripheralControl trio + + pin.connect_with_guard(signal) + } + + #[procmacros::doc_replace] + /// Assign the SCK (Serial Clock) pin for the SPI instance. + /// + /// Configures the specified pin to push-pull output and connects it to the + /// SPI clock signal. + /// + /// Disconnects the previous pin that was assigned with `with_sck`. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_sck(peripherals.GPIO0); + /// + /// # {after_snippet} + /// ``` + pub fn with_sck(mut self, sclk: impl PeripheralOutput<'d>) -> Self { + self.pins.sclk_pin = self.connect_output_pin(sclk.into(), self.driver().info.sclk); + + self + } + + #[procmacros::doc_replace] + /// Assign the MOSI (Master Out Slave In) pin for the SPI instance. + /// + /// Enables output functionality for the pin, and connects it as the MOSI + /// signal. You want to use this for full-duplex SPI or + /// if you intend to use [DataMode::SingleTwoDataLines]. + /// + /// Disconnects the previous pin that was assigned with `with_mosi` or + /// `with_sio0`. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_mosi(peripherals.GPIO1); + /// + /// # {after_snippet} + /// ``` + pub fn with_mosi(mut self, mosi: impl PeripheralOutput<'d>) -> Self { + self.pins.sio0_pin = self.connect_sio_output_pin(mosi.into(), 0); + + self + } + + #[procmacros::doc_replace] + /// Assign the MISO (Master In Slave Out) pin for the SPI instance. + /// + /// Enables input functionality for the pin, and connects it to the MISO + /// signal. + /// + /// You want to use this for full-duplex SPI or + /// [DataMode::SingleTwoDataLines] + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_miso(peripherals.GPIO2); + /// + /// # {after_snippet} + /// ``` + pub fn with_miso(self, miso: impl PeripheralInput<'d>) -> Self { + let miso = miso.into(); + + miso.apply_input_config(&InputConfig::default()); + miso.set_input_enable(true); + + self.driver().info.sio_input(1).connect_to(&miso); + + self + } + + /// Assign the SIO0 pin for the SPI instance. + /// + /// Enables both input and output functionality for the pin, and connects it + /// to the MOSI output signal and SIO0 input signal. + /// + /// Disconnects the previous pin that was assigned with `with_sio0` or + /// `with_mosi`. + /// + /// Use this if any of the devices on the bus use half-duplex SPI. + /// + /// See also [Self::with_mosi] when you only need a one-directional MOSI + /// signal. + #[instability::unstable] + pub fn with_sio0(mut self, mosi: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self { + self.pins.sio0_pin = self.connect_sio_pin(mosi.into(), 0); + + self + } + + /// Assign the SIO1/MISO pin for the SPI instance. + /// + /// Enables both input and output functionality for the pin, and connects it + /// to the MISO input signal and SIO1 output signal. + /// + /// Disconnects the previous pin that was assigned with `with_sio1`. + /// + /// Use this if any of the devices on the bus use half-duplex SPI. + /// + /// See also [Self::with_miso] when you only need a one-directional MISO + /// signal. + #[instability::unstable] + pub fn with_sio1(mut self, sio1: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self { + self.pins.sio1_pin = self.connect_sio_pin(sio1.into(), 1); + + self + } + + def_with_sio_pin!(with_sio2, sio2_pin, 2); + def_with_sio_pin!(with_sio3, sio3_pin, 3); + + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio4, sio4_pin, 4); + + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio5, sio5_pin, 5); + + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio6, sio6_pin, 6); + + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio7, sio7_pin, 7); + + /// Assign the CS (Chip Select) pin for the SPI instance. + /// + /// Configures the specified pin to push-pull output and connects it to the + /// SPI CS signal. + /// + /// Disconnects the previous pin that was assigned with `with_cs`. + /// + /// # Current Stability Limitations + /// The hardware chip select functionality is limited; only one CS line can + /// be set, regardless of the total number available. There is no + /// mechanism to select which CS line to use. + #[instability::unstable] + pub fn with_cs(mut self, cs: impl PeripheralOutput<'d>) -> Self { + self.pins.cs_pin = self.connect_output_pin(cs.into(), self.driver().info.cs(0)); + self + } + + #[doc_replace( + "max_frequency" => { + cfg(esp32h2) => "48MHz", + _ => "80MHz", + } + )] + /// Change the bus configuration. + /// + /// # Errors + /// + /// If frequency passed in config exceeds __max_frequency__ or is below 70kHz, + /// [`ConfigError::FrequencyOutOfRange`] error will be returned. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?; + /// + /// spi.apply_config(&Config::default().with_frequency(Rate::from_khz(100))); + /// # + /// # {after_snippet} + /// ``` + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.driver().apply_config(config) + } + + #[procmacros::doc_replace] + /// Write bytes to SPI. After writing, flush is called to ensure all data + /// has been transmitted. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2) + /// .into_async(); + /// + /// let buffer = [0; 10]; + /// spi.write(&buffer)?; + /// + /// # {after_snippet} + /// ``` + pub fn write(&mut self, words: &[u8]) -> Result<(), Error> { + self.driver().setup_full_duplex()?; + self.driver().write(words)?; + self.driver().flush()?; + + Ok(()) + } + + #[procmacros::doc_replace] + /// Read bytes from SPI. The provided slice is filled with data received + /// from the slave. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut buffer = [0; 10]; + /// spi.read(&mut buffer)?; + /// + /// # {after_snippet} + /// ``` + pub fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.driver().setup_full_duplex()?; + self.driver().read(words) + } + + #[procmacros::doc_replace] + /// Sends `words` to the slave. The received data will be written to + /// `words`, overwriting its contents. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }; + /// let mut spi = Spi::new(peripherals.SPI2, Config::default())? + /// .with_sck(peripherals.GPIO0) + /// .with_mosi(peripherals.GPIO1) + /// .with_miso(peripherals.GPIO2) + /// .into_async(); + /// + /// let mut buffer = [0; 10]; + /// spi.transfer(&mut buffer)?; + /// + /// # {after_snippet} + /// ``` + pub fn transfer(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.driver().setup_full_duplex()?; + self.driver().transfer(words) + } + + /// Half-duplex read. + /// + /// # Errors + /// + /// [`Error::FifoSizeExeeded`] or [`Error::Unsupported`] will be returned if + /// passed buffer is bigger than FIFO size or if buffer is empty (currently + /// unsupported). `DataMode::Single` cannot be combined with any other + /// [`DataMode`], otherwise [`Error::Unsupported`] will be returned. + #[instability::unstable] + pub fn half_duplex_read( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &mut [u8], + ) -> Result<(), Error> { + if buffer.len() > FIFO_SIZE { + return Err(Error::FifoSizeExeeded); + } + + if buffer.is_empty() { + error!("Half-duplex mode does not support empty buffer"); + return Err(Error::Unsupported); + } + + self.driver().setup_half_duplex( + false, + cmd, + address, + false, + dummy, + buffer.is_empty(), + data_mode, + )?; + + self.driver().configure_datalen(buffer.len(), 0); + self.driver().start_operation(); + self.driver().flush()?; + self.driver().read_from_fifo(buffer) + } + + /// Half-duplex write. + /// + /// # Errors + /// + /// [`Error::FifoSizeExeeded`] will be returned if + /// passed buffer is bigger than FIFO size. + #[cfg_attr( + esp32, + doc = "Dummy phase configuration is currently not supported, only value `0` is valid (see issue [#2240](https://github.com/esp-rs/esp-hal/issues/2240))." + )] + #[instability::unstable] + pub fn half_duplex_write( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &[u8], + ) -> Result<(), Error> { + if buffer.len() > FIFO_SIZE { + return Err(Error::FifoSizeExeeded); + } + + cfg_if::cfg_if! { + if #[cfg(all(esp32, spi_address_workaround))] { + let mut buffer = buffer; + let mut data_mode = data_mode; + let mut address = address; + let addr_bytes; + if buffer.is_empty() && !address.is_none() { + // If the buffer is empty, we need to send a dummy byte + // to trigger the address phase. + let bytes_to_write = address.width().div_ceil(8); + // The address register is read in big-endian order, + // we have to prepare the emulated write in the same way. + addr_bytes = address.value().to_be_bytes(); + buffer = &addr_bytes[4 - bytes_to_write..][..bytes_to_write]; + data_mode = address.mode(); + address = Address::None; + } + + if dummy > 0 { + // FIXME: https://github.com/esp-rs/esp-hal/issues/2240 + error!("Dummy bits are not supported without data"); + return Err(Error::Unsupported); + } + } + } + + self.driver().setup_half_duplex( + true, + cmd, + address, + false, + dummy, + buffer.is_empty(), + data_mode, + )?; + + if !buffer.is_empty() { + // re-using the full-duplex write here + self.driver().write(buffer)?; + } else { + self.driver().start_operation(); + } + + self.driver().flush() + } + + fn driver(&self) -> Driver { + Driver { + info: self.spi.info(), + state: self.spi.state(), + } + } +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for Spi<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +#[cfg(spi_master_supports_dma)] +mod dma { + use core::{ + cmp::min, + mem::ManuallyDrop, + sync::atomic::{Ordering, fence}, + }; + + use super::*; + use crate::{ + dma::{ + Channel, + DmaChannelFor, + DmaEligible, + DmaRxBuf, + DmaRxBuffer, + DmaTxBuf, + DmaTxBuffer, + EmptyBuf, + PeripheralDmaChannel, + asynch::DmaRxFuture, + }, + spi::{DmaError, master::dma::asynch::DropGuard}, + }; + + const MAX_DMA_SIZE: usize = 32736; + + impl<'d> Spi<'d, Blocking> { + #[doc_replace( + "dma_channel" => { + cfg(any(esp32, esp32s2)) => "DMA_SPI2", + _ => "DMA_CH0", + } + )] + /// Configures the SPI instance to use DMA with the specified channel. + /// + /// This method prepares the SPI instance for DMA transfers using SPI + /// and returns an instance of `SpiDma` that supports DMA + /// operations. + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// dma::{DmaRxBuf, DmaTxBuf}, + /// dma_buffers, + /// spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }, + /// }; + /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000); + /// + /// let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; + /// let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer)?; + /// + /// let mut spi = Spi::new( + /// peripherals.SPI2, + /// Config::default() + /// .with_frequency(Rate::from_khz(100)) + /// .with_mode(Mode::_0), + /// )? + /// .with_dma(peripherals.__dma_channel__) + /// .with_buffers(dma_rx_buf, dma_tx_buf); + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub fn with_dma(self, channel: impl DmaChannelFor>) -> SpiDma<'d, Blocking> { + SpiDma::new(self.spi, self.pins, channel.degrade()) + } + } + + #[doc_replace( + "dma_channel" => { + cfg(any(esp32, esp32s2)) => "DMA_SPI2", + _ => "DMA_CH0", + } + )] + /// A DMA capable SPI instance. + /// + /// Using `SpiDma` is not recommended unless you wish + /// to manage buffers yourself. It's recommended to use + /// [`SpiDmaBus`] via `with_buffers` to get access + /// to a DMA capable SPI bus that implements the + /// embedded-hal traits. + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// dma::{DmaRxBuf, DmaTxBuf}, + /// dma_buffers, + /// spi::{ + /// Mode, + /// master::{Config, Spi}, + /// }, + /// }; + /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000); + /// + /// let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; + /// let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer)?; + /// + /// let mut spi = Spi::new( + /// peripherals.SPI2, + /// Config::default() + /// .with_frequency(Rate::from_khz(100)) + /// .with_mode(Mode::_0), + /// )? + /// .with_dma(peripherals.__dma_channel__) + /// .with_buffers(dma_rx_buf, dma_tx_buf); + /// # + /// # {after_snippet} + /// ``` + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SpiDma<'d, Dm> + where + Dm: DriverMode, + { + pub(crate) spi: AnySpi<'d>, + pub(crate) channel: Channel>>, + tx_transfer_in_progress: bool, + rx_transfer_in_progress: bool, + #[cfg(all(esp32, spi_address_workaround))] + address_buffer: DmaTxBuf, + guard: PeripheralGuard, + pins: SpiPinGuard, + } + + impl crate::private::Sealed for SpiDma<'_, Dm> where Dm: DriverMode {} + + impl<'d> SpiDma<'d, Blocking> { + /// Converts the SPI instance into async mode. + #[instability::unstable] + pub fn into_async(mut self) -> SpiDma<'d, Async> { + self.set_interrupt_handler(self.spi.info().async_handler); + SpiDma { + spi: self.spi, + channel: self.channel.into_async(), + tx_transfer_in_progress: self.tx_transfer_in_progress, + rx_transfer_in_progress: self.rx_transfer_in_progress, + #[cfg(all(esp32, spi_address_workaround))] + address_buffer: self.address_buffer, + guard: self.guard, + pins: self.pins, + } + } + + pub(super) fn new( + spi: AnySpi<'d>, + pins: SpiPinGuard, + channel: PeripheralDmaChannel>, + ) -> Self { + let channel = Channel::new(channel); + channel.runtime_ensure_compatible(&spi); + #[cfg(all(esp32, spi_address_workaround))] + let address_buffer = { + use crate::dma::DmaDescriptor; + const SPI_NUM: usize = 2; + static mut DESCRIPTORS: [[DmaDescriptor; 1]; SPI_NUM] = + [[DmaDescriptor::EMPTY]; SPI_NUM]; + static mut BUFFERS: [[u32; 1]; SPI_NUM] = [[0; 1]; SPI_NUM]; + + let id = if spi.info() == unsafe { crate::peripherals::SPI2::steal().info() } { + 0 + } else { + 1 + }; + + unwrap!(DmaTxBuf::new( + unsafe { &mut DESCRIPTORS[id] }, + crate::dma::as_mut_byte_array!(BUFFERS[id], 4) + )) + }; + + let guard = PeripheralGuard::new(spi.info().peripheral); + + Self { + spi, + channel, + #[cfg(all(esp32, spi_address_workaround))] + address_buffer, + tx_transfer_in_progress: false, + rx_transfer_in_progress: false, + guard, + pins, + } + } + + /// Listen for the given interrupts + #[instability::unstable] + pub fn listen(&mut self, interrupts: impl Into>) { + self.driver().enable_listen(interrupts.into(), true); + } + + /// Unlisten the given interrupts + #[instability::unstable] + pub fn unlisten(&mut self, interrupts: impl Into>) { + self.driver().enable_listen(interrupts.into(), false); + } + + /// Gets asserted interrupts + #[instability::unstable] + pub fn interrupts(&mut self) -> EnumSet { + self.driver().interrupts() + } + + /// Resets asserted interrupts + #[instability::unstable] + pub fn clear_interrupts(&mut self, interrupts: impl Into>) { + self.driver().clear_interrupts(interrupts.into()); + } + + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER] + /// + /// # Panics + /// + /// Panics if passed interrupt handler is invalid (e.g. has priority + /// `None`) + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.spi.set_interrupt_handler(handler); + } + } + + impl<'d> SpiDma<'d, Async> { + /// Converts the SPI instance into async mode. + #[instability::unstable] + pub fn into_blocking(self) -> SpiDma<'d, Blocking> { + self.spi.disable_peri_interrupt_on_all_cores(); + SpiDma { + spi: self.spi, + channel: self.channel.into_blocking(), + tx_transfer_in_progress: self.tx_transfer_in_progress, + rx_transfer_in_progress: self.rx_transfer_in_progress, + #[cfg(all(esp32, spi_address_workaround))] + address_buffer: self.address_buffer, + guard: self.guard, + pins: self.pins, + } + } + + async fn wait_for_idle_async(&mut self) { + if self.rx_transfer_in_progress { + _ = DmaRxFuture::new(&mut self.channel.rx).await; + self.rx_transfer_in_progress = false; + } + + struct Fut(Driver); + impl Fut { + const DONE_EVENTS: EnumSet = + enumset::enum_set!(SpiInterrupt::TransferDone); + } + impl Future for Fut { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.0.interrupts().is_disjoint(Self::DONE_EVENTS) { + #[cfg(any(esp32, esp32s2))] + // Need to poll for done-ness even after interrupt fires. + if self.0.busy() { + cx.waker().wake_by_ref(); + return Poll::Pending; + } + + self.0.clear_interrupts(Self::DONE_EVENTS); + return Poll::Ready(()); + } + + self.0.state.waker.register(cx.waker()); + self.0.enable_listen(Self::DONE_EVENTS, true); + Poll::Pending + } + } + impl Drop for Fut { + fn drop(&mut self) { + self.0.enable_listen(Self::DONE_EVENTS, false); + } + } + + if !self.is_done() { + Fut(self.driver()).await; + } + + if self.tx_transfer_in_progress { + // In case DMA TX buffer is bigger than what the SPI consumes, stop the DMA. + if !self.channel.tx.is_done() { + self.channel.tx.stop_transfer(); + } + self.tx_transfer_in_progress = false; + } + } + } + + impl core::fmt::Debug for SpiDma<'_, Dm> + where + Dm: DriverMode, + { + /// Formats the `SpiDma` instance for debugging purposes. + /// + /// This method returns a debug struct with the name "SpiDma" without + /// exposing internal details. + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SpiDma").field("spi", &self.spi).finish() + } + } + + #[instability::unstable] + impl crate::interrupt::InterruptConfigurable for SpiDma<'_, Blocking> { + /// Sets the interrupt handler + /// + /// Interrupts are not enabled at the peripheral level here. + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } + } + + impl SpiDma<'_, Dm> + where + Dm: DriverMode, + { + fn driver(&self) -> Driver { + Driver { + info: self.spi.info(), + state: self.spi.state(), + } + } + + fn dma_driver(&self) -> DmaDriver { + DmaDriver { + driver: self.driver(), + dma_peripheral: self.spi.dma_peripheral(), + } + } + + fn is_done(&self) -> bool { + if self.driver().busy() { + return false; + } + if self.rx_transfer_in_progress { + // If this is an asymmetric transfer and the RX side is smaller, the RX channel + // will never be "done" as it won't have enough descriptors/buffer to receive + // the EOF bit from the SPI. So instead the RX channel will hit + // a "descriptor empty" which means the DMA is written as much + // of the received data as possible into the buffer and + // discarded the rest. The user doesn't care about this discarded data. + + if !self.channel.rx.is_done() && !self.channel.rx.has_dscr_empty_error() { + return false; + } + } + true + } + + fn wait_for_idle(&mut self) { + while !self.is_done() { + // Wait for the SPI to become idle + } + self.rx_transfer_in_progress = false; + self.tx_transfer_in_progress = false; + fence(Ordering::Acquire); + } + + /// # Safety: + /// + /// The caller must ensure to not access the buffer contents while the + /// transfer is in progress. Moving the buffer itself is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_transfer_dma( + &mut self, + full_duplex: bool, + bytes_to_read: usize, + bytes_to_write: usize, + rx_buffer: &mut RX, + tx_buffer: &mut TX, + ) -> Result<(), Error> { + if bytes_to_read > MAX_DMA_SIZE || bytes_to_write > MAX_DMA_SIZE { + return Err(Error::MaxDmaTransferSizeExceeded); + } + + self.rx_transfer_in_progress = bytes_to_read > 0; + self.tx_transfer_in_progress = bytes_to_write > 0; + unsafe { + self.dma_driver().start_transfer_dma( + full_duplex, + bytes_to_read, + bytes_to_write, + rx_buffer, + tx_buffer, + &mut self.channel, + ) + } + } + + #[cfg(all(esp32, spi_address_workaround))] + fn set_up_address_workaround( + &mut self, + cmd: Command, + address: Address, + dummy: u8, + ) -> Result<(), Error> { + if dummy > 0 { + // FIXME: https://github.com/esp-rs/esp-hal/issues/2240 + error!("Dummy bits are not supported when there is no data to write"); + return Err(Error::Unsupported); + } + + let bytes_to_write = address.width().div_ceil(8); + // The address register is read in big-endian order, + // we have to prepare the emulated write in the same way. + let addr_bytes = address.value().to_be_bytes(); + let addr_bytes = &addr_bytes[4 - bytes_to_write..][..bytes_to_write]; + self.address_buffer.fill(addr_bytes); + + self.driver().setup_half_duplex( + true, + cmd, + Address::None, + false, + dummy, + bytes_to_write == 0, + address.mode(), + )?; + + // FIXME: we could use self.start_transfer_dma if the address buffer was part of + // the (yet-to-be-created) State struct. + self.tx_transfer_in_progress = true; + unsafe { + self.dma_driver().start_transfer_dma( + false, + 0, + bytes_to_write, + &mut EmptyBuf, + &mut self.address_buffer, + &mut self.channel, + ) + } + } + + fn cancel_transfer(&mut self) { + // The SPI peripheral is controlling how much data we transfer, so let's + // update its counter. + // 0 doesn't take effect on ESP32 and cuts the currently transmitted byte + // immediately. + // 1 seems to stop after transmitting the current byte which is somewhat less + // impolite. + if self.tx_transfer_in_progress || self.rx_transfer_in_progress { + self.dma_driver().abort_transfer(); + + // We need to stop the DMA transfer, too. + if self.tx_transfer_in_progress { + self.channel.tx.stop_transfer(); + self.tx_transfer_in_progress = false; + } + if self.rx_transfer_in_progress { + self.channel.rx.stop_transfer(); + self.rx_transfer_in_progress = false; + } + } + } + } + + #[instability::unstable] + impl embassy_embedded_hal::SetConfig for SpiDma<'_, Dm> + where + Dm: DriverMode, + { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } + } + + /// A structure representing a DMA transfer for SPI. + /// + /// This structure holds references to the SPI instance, DMA buffers, and + /// transfer status. + #[instability::unstable] + pub struct SpiDmaTransfer<'d, Dm, Buf> + where + Dm: DriverMode, + { + spi_dma: ManuallyDrop>, + dma_buf: ManuallyDrop, + } + + impl SpiDmaTransfer<'_, Async, Buf> { + /// Waits for the DMA transfer to complete asynchronously. + /// + /// This method awaits the completion of both RX and TX operations. + #[instability::unstable] + pub async fn wait_for_done(&mut self) { + self.spi_dma.wait_for_idle_async().await; + } + } + + impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf> + where + Dm: DriverMode, + { + fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf) -> Self { + Self { + spi_dma: ManuallyDrop::new(spi_dma), + dma_buf: ManuallyDrop::new(dma_buf), + } + } + + /// Checks if the transfer is complete. + /// + /// This method returns `true` if both RX and TX operations are done, + /// and the SPI instance is no longer busy. + pub fn is_done(&self) -> bool { + self.spi_dma.is_done() + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `SpiDma` instance and the associated buffer. + #[instability::unstable] + pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) { + self.spi_dma.wait_for_idle(); + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.spi_dma), + ManuallyDrop::take(&mut self.dma_buf), + ) + }; + core::mem::forget(self); + retval + } + + /// Cancels the DMA transfer. + #[instability::unstable] + pub fn cancel(&mut self) { + if !self.spi_dma.is_done() { + self.spi_dma.cancel_transfer(); + } + } + } + + impl Drop for SpiDmaTransfer<'_, Dm, Buf> + where + Dm: DriverMode, + { + fn drop(&mut self) { + if !self.is_done() { + self.spi_dma.cancel_transfer(); + self.spi_dma.wait_for_idle(); + + unsafe { + ManuallyDrop::drop(&mut self.spi_dma); + ManuallyDrop::drop(&mut self.dma_buf); + } + } + } + } + + impl<'d, Dm> SpiDma<'d, Dm> + where + Dm: DriverMode, + { + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_dma_write( + &mut self, + bytes_to_write: usize, + buffer: &mut impl DmaTxBuffer, + ) -> Result<(), Error> { + unsafe { self.start_dma_transfer(0, bytes_to_write, &mut EmptyBuf, buffer) } + } + + /// Configures the DMA buffers for the SPI instance. + /// + /// This method sets up both RX and TX buffers for DMA transfers. + /// It returns an instance of `SpiDmaBus` that can be used for SPI + /// communication. + #[instability::unstable] + pub fn with_buffers(self, dma_rx_buf: DmaRxBuf, dma_tx_buf: DmaTxBuf) -> SpiDmaBus<'d, Dm> { + SpiDmaBus::new(self, dma_rx_buf, dma_tx_buf) + } + + /// Perform a DMA write. + /// + /// This will return a [SpiDmaTransfer] owning the buffer and the + /// SPI instance. The maximum amount of data to be sent is 32736 + /// bytes. + #[allow(clippy::type_complexity)] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + #[instability::unstable] + pub fn write( + mut self, + bytes_to_write: usize, + mut buffer: TX, + ) -> Result, (Error, Self, TX)> { + self.wait_for_idle(); + if let Err(e) = self.driver().setup_full_duplex() { + return Err((e, self, buffer)); + }; + match unsafe { self.start_dma_write(bytes_to_write, &mut buffer) } { + Ok(_) => Ok(SpiDmaTransfer::new(self, buffer)), + Err(e) => Err((e, self, buffer)), + } + } + + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_dma_read( + &mut self, + bytes_to_read: usize, + buffer: &mut impl DmaRxBuffer, + ) -> Result<(), Error> { + unsafe { self.start_dma_transfer(bytes_to_read, 0, buffer, &mut EmptyBuf) } + } + + /// Perform a DMA read. + /// + /// This will return a [SpiDmaTransfer] owning the buffer and + /// the SPI instance. The maximum amount of data to be + /// received is 32736 bytes. + #[allow(clippy::type_complexity)] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + #[instability::unstable] + pub fn read( + mut self, + bytes_to_read: usize, + mut buffer: RX, + ) -> Result, (Error, Self, RX)> { + self.wait_for_idle(); + if let Err(e) = self.driver().setup_full_duplex() { + return Err((e, self, buffer)); + }; + match unsafe { self.start_dma_read(bytes_to_read, &mut buffer) } { + Ok(_) => Ok(SpiDmaTransfer::new(self, buffer)), + Err(e) => Err((e, self, buffer)), + } + } + + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_dma_transfer( + &mut self, + bytes_to_read: usize, + bytes_to_write: usize, + rx_buffer: &mut impl DmaRxBuffer, + tx_buffer: &mut impl DmaTxBuffer, + ) -> Result<(), Error> { + unsafe { + self.start_transfer_dma(true, bytes_to_read, bytes_to_write, rx_buffer, tx_buffer) + } + } + + /// Perform a DMA transfer + /// + /// This will return a [SpiDmaTransfer] owning the buffers and + /// the SPI instance. The maximum amount of data to be + /// sent/received is 32736 bytes. + #[allow(clippy::type_complexity)] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + #[instability::unstable] + pub fn transfer( + mut self, + bytes_to_read: usize, + mut rx_buffer: RX, + bytes_to_write: usize, + mut tx_buffer: TX, + ) -> Result, (Error, Self, RX, TX)> { + self.wait_for_idle(); + if let Err(e) = self.driver().setup_full_duplex() { + return Err((e, self, rx_buffer, tx_buffer)); + }; + match unsafe { + self.start_dma_transfer( + bytes_to_read, + bytes_to_write, + &mut rx_buffer, + &mut tx_buffer, + ) + } { + Ok(_) => Ok(SpiDmaTransfer::new(self, (rx_buffer, tx_buffer))), + Err(e) => Err((e, self, rx_buffer, tx_buffer)), + } + } + + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_half_duplex_read( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + bytes_to_read: usize, + buffer: &mut impl DmaRxBuffer, + ) -> Result<(), Error> { + self.driver().setup_half_duplex( + false, + cmd, + address, + false, + dummy, + bytes_to_read == 0, + data_mode, + )?; + + unsafe { self.start_transfer_dma(false, bytes_to_read, 0, buffer, &mut EmptyBuf) } + } + + /// Perform a half-duplex read operation using DMA. + #[allow(clippy::type_complexity)] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + #[instability::unstable] + pub fn half_duplex_read( + mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + bytes_to_read: usize, + mut buffer: RX, + ) -> Result, (Error, Self, RX)> { + self.wait_for_idle(); + + match unsafe { + self.start_half_duplex_read( + data_mode, + cmd, + address, + dummy, + bytes_to_read, + &mut buffer, + ) + } { + Ok(_) => Ok(SpiDmaTransfer::new(self, buffer)), + Err(e) => Err((e, self, buffer)), + } + } + + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_half_duplex_write( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + bytes_to_write: usize, + buffer: &mut impl DmaTxBuffer, + ) -> Result<(), Error> { + #[cfg(all(esp32, spi_address_workaround))] + { + // On the ESP32, if we don't have data, the address is always sent + // on a single line, regardless of its data mode. + if bytes_to_write == 0 && address.mode() != DataMode::SingleTwoDataLines { + return self.set_up_address_workaround(cmd, address, dummy); + } + } + + self.driver().setup_half_duplex( + true, + cmd, + address, + false, + dummy, + bytes_to_write == 0, + data_mode, + )?; + + unsafe { self.start_transfer_dma(false, 0, bytes_to_write, &mut EmptyBuf, buffer) } + } + + /// Perform a half-duplex write operation using DMA. + #[allow(clippy::type_complexity)] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + #[instability::unstable] + pub fn half_duplex_write( + mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + bytes_to_write: usize, + mut buffer: TX, + ) -> Result, (Error, Self, TX)> { + self.wait_for_idle(); + + match unsafe { + self.start_half_duplex_write( + data_mode, + cmd, + address, + dummy, + bytes_to_write, + &mut buffer, + ) + } { + Ok(_) => Ok(SpiDmaTransfer::new(self, buffer)), + Err(e) => Err((e, self, buffer)), + } + } + + /// Change the bus configuration. + /// + /// # Errors + /// + /// If frequency passed in config exceeds + #[cfg_attr(not(esp32h2), doc = " 80MHz")] + #[cfg_attr(esp32h2, doc = " 48MHz")] + /// or is below 70kHz, + /// [`ConfigError::UnsupportedFrequency`] error will be returned. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.driver().apply_config(config) + } + } + + /// A DMA-capable SPI bus. + /// + /// This structure is responsible for managing SPI transfers using DMA + /// buffers. + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct SpiDmaBus<'d, Dm> + where + Dm: DriverMode, + { + spi_dma: SpiDma<'d, Dm>, + rx_buf: DmaRxBuf, + tx_buf: DmaTxBuf, + } + + impl crate::private::Sealed for SpiDmaBus<'_, Dm> where Dm: DriverMode {} + + impl<'d> SpiDmaBus<'d, Blocking> { + /// Converts the SPI instance into async mode. + #[instability::unstable] + pub fn into_async(self) -> SpiDmaBus<'d, Async> { + SpiDmaBus { + spi_dma: self.spi_dma.into_async(), + rx_buf: self.rx_buf, + tx_buf: self.tx_buf, + } + } + + /// Listen for the given interrupts + #[instability::unstable] + pub fn listen(&mut self, interrupts: impl Into>) { + self.spi_dma.listen(interrupts.into()); + } + + /// Unlisten the given interrupts + #[instability::unstable] + pub fn unlisten(&mut self, interrupts: impl Into>) { + self.spi_dma.unlisten(interrupts.into()); + } + + /// Gets asserted interrupts + #[instability::unstable] + pub fn interrupts(&mut self) -> EnumSet { + self.spi_dma.interrupts() + } + + /// Resets asserted interrupts + #[instability::unstable] + pub fn clear_interrupts(&mut self, interrupts: impl Into>) { + self.spi_dma.clear_interrupts(interrupts.into()); + } + } + + impl<'d> SpiDmaBus<'d, Async> { + /// Converts the SPI instance into async mode. + #[instability::unstable] + pub fn into_blocking(self) -> SpiDmaBus<'d, Blocking> { + SpiDmaBus { + spi_dma: self.spi_dma.into_blocking(), + rx_buf: self.rx_buf, + tx_buf: self.tx_buf, + } + } + + /// Fill the given buffer with data from the bus. + #[instability::unstable] + pub async fn read_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.spi_dma.wait_for_idle_async().await; + self.spi_dma.driver().setup_full_duplex()?; + let chunk_size = self.rx_buf.capacity(); + + for chunk in words.chunks_mut(chunk_size) { + let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); + + unsafe { spi.start_dma_transfer(chunk.len(), 0, &mut self.rx_buf, &mut EmptyBuf)? }; + + spi.wait_for_idle_async().await; + + chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + + spi.defuse(); + } + + Ok(()) + } + + /// Transmit the given buffer to the bus. + #[instability::unstable] + pub async fn write_async(&mut self, words: &[u8]) -> Result<(), Error> { + self.spi_dma.wait_for_idle_async().await; + self.spi_dma.driver().setup_full_duplex()?; + + let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); + let chunk_size = self.tx_buf.capacity(); + + for chunk in words.chunks(chunk_size) { + self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { spi.start_dma_transfer(0, chunk.len(), &mut EmptyBuf, &mut self.tx_buf)? }; + + spi.wait_for_idle_async().await; + } + spi.defuse(); + + Ok(()) + } + + /// Transfer by writing out a buffer and reading the response from + /// the bus into another buffer. + #[instability::unstable] + pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.spi_dma.wait_for_idle_async().await; + self.spi_dma.driver().setup_full_duplex()?; + + let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); + let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); + + let common_length = min(read.len(), write.len()); + let (read_common, read_remainder) = read.split_at_mut(common_length); + let (write_common, write_remainder) = write.split_at(common_length); + + for (read_chunk, write_chunk) in read_common + .chunks_mut(chunk_size) + .zip(write_common.chunks(chunk_size)) + { + self.tx_buf.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); + + unsafe { + spi.start_dma_transfer( + read_chunk.len(), + write_chunk.len(), + &mut self.rx_buf, + &mut self.tx_buf, + )?; + } + spi.wait_for_idle_async().await; + + read_chunk.copy_from_slice(&self.rx_buf.as_slice()[..read_chunk.len()]); + } + + spi.defuse(); + + if !read_remainder.is_empty() { + self.read_async(read_remainder).await + } else if !write_remainder.is_empty() { + self.write_async(write_remainder).await + } else { + Ok(()) + } + } + + /// Transfer by writing out a buffer and reading the response from + /// the bus into the same buffer. + #[instability::unstable] + pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.spi_dma.wait_for_idle_async().await; + self.spi_dma.driver().setup_full_duplex()?; + + let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); + for chunk in words.chunks_mut(self.tx_buf.capacity()) { + self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { + spi.start_dma_transfer( + chunk.len(), + chunk.len(), + &mut self.rx_buf, + &mut self.tx_buf, + )?; + } + spi.wait_for_idle_async().await; + chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + } + + spi.defuse(); + + Ok(()) + } + } + + impl<'d, Dm> SpiDmaBus<'d, Dm> + where + Dm: DriverMode, + { + /// Creates a new `SpiDmaBus` with the specified SPI instance and DMA + /// buffers. + pub fn new(spi_dma: SpiDma<'d, Dm>, rx_buf: DmaRxBuf, tx_buf: DmaTxBuf) -> Self { + Self { + spi_dma, + rx_buf, + tx_buf, + } + } + + /// Splits [SpiDmaBus] back into [SpiDma], [DmaRxBuf] and [DmaTxBuf]. + #[instability::unstable] + pub fn split(mut self) -> (SpiDma<'d, Dm>, DmaRxBuf, DmaTxBuf) { + self.wait_for_idle(); + (self.spi_dma, self.rx_buf, self.tx_buf) + } + + fn wait_for_idle(&mut self) { + self.spi_dma.wait_for_idle(); + } + + /// Change the bus configuration. + /// + /// # Errors + /// + /// If frequency passed in config exceeds + #[cfg_attr(not(esp32h2), doc = " 80MHz")] + #[cfg_attr(esp32h2, doc = " 48MHz")] + /// or is below 70kHz, + /// [`ConfigError::UnsupportedFrequency`] error will be returned. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.spi_dma.apply_config(config) + } + + /// Reads data from the SPI bus using DMA. + #[instability::unstable] + pub fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.wait_for_idle(); + self.spi_dma.driver().setup_full_duplex()?; + for chunk in words.chunks_mut(self.rx_buf.capacity()) { + unsafe { + self.spi_dma.start_dma_transfer( + chunk.len(), + 0, + &mut self.rx_buf, + &mut EmptyBuf, + )?; + } + + self.wait_for_idle(); + chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + } + + Ok(()) + } + + /// Writes data to the SPI bus using DMA. + #[instability::unstable] + pub fn write(&mut self, words: &[u8]) -> Result<(), Error> { + self.wait_for_idle(); + self.spi_dma.driver().setup_full_duplex()?; + for chunk in words.chunks(self.tx_buf.capacity()) { + self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { + self.spi_dma.start_dma_transfer( + 0, + chunk.len(), + &mut EmptyBuf, + &mut self.tx_buf, + )?; + } + + self.wait_for_idle(); + } + + Ok(()) + } + + /// Transfers data to and from the SPI bus simultaneously using DMA. + #[instability::unstable] + pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.wait_for_idle(); + self.spi_dma.driver().setup_full_duplex()?; + let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); + + let common_length = min(read.len(), write.len()); + let (read_common, read_remainder) = read.split_at_mut(common_length); + let (write_common, write_remainder) = write.split_at(common_length); + + for (read_chunk, write_chunk) in read_common + .chunks_mut(chunk_size) + .zip(write_common.chunks(chunk_size)) + { + self.tx_buf.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); + + unsafe { + self.spi_dma.start_dma_transfer( + read_chunk.len(), + write_chunk.len(), + &mut self.rx_buf, + &mut self.tx_buf, + )?; + } + self.wait_for_idle(); + + read_chunk.copy_from_slice(&self.rx_buf.as_slice()[..read_chunk.len()]); + } + + if !read_remainder.is_empty() { + self.read(read_remainder) + } else if !write_remainder.is_empty() { + self.write(write_remainder) + } else { + Ok(()) + } + } + + /// Transfers data in place on the SPI bus using DMA. + #[instability::unstable] + pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.wait_for_idle(); + self.spi_dma.driver().setup_full_duplex()?; + let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); + + for chunk in words.chunks_mut(chunk_size) { + self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { + self.spi_dma.start_dma_transfer( + chunk.len(), + chunk.len(), + &mut self.rx_buf, + &mut self.tx_buf, + )?; + } + self.wait_for_idle(); + chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + } + + Ok(()) + } + + /// Half-duplex read. + #[instability::unstable] + pub fn half_duplex_read( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &mut [u8], + ) -> Result<(), Error> { + if buffer.len() > self.rx_buf.capacity() { + return Err(Error::from(DmaError::Overflow)); + } + self.wait_for_idle(); + + unsafe { + self.spi_dma.start_half_duplex_read( + data_mode, + cmd, + address, + dummy, + buffer.len(), + &mut self.rx_buf, + )?; + } + + self.wait_for_idle(); + + buffer.copy_from_slice(&self.rx_buf.as_slice()[..buffer.len()]); + + Ok(()) + } + + /// Half-duplex write. + #[instability::unstable] + pub fn half_duplex_write( + &mut self, + data_mode: DataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &[u8], + ) -> Result<(), Error> { + if buffer.len() > self.tx_buf.capacity() { + return Err(Error::from(DmaError::Overflow)); + } + self.wait_for_idle(); + self.tx_buf.as_mut_slice()[..buffer.len()].copy_from_slice(buffer); + + unsafe { + self.spi_dma.start_half_duplex_write( + data_mode, + cmd, + address, + dummy, + buffer.len(), + &mut self.tx_buf, + )?; + } + + self.wait_for_idle(); + + Ok(()) + } + } + + #[instability::unstable] + impl crate::interrupt::InterruptConfigurable for SpiDmaBus<'_, Blocking> { + /// Sets the interrupt handler + /// + /// Interrupts are not enabled at the peripheral level here. + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.spi_dma.set_interrupt_handler(handler); + } + } + + #[instability::unstable] + impl embassy_embedded_hal::SetConfig for SpiDmaBus<'_, Dm> + where + Dm: DriverMode, + { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } + } + + pub(super) struct DmaDriver { + driver: Driver, + dma_peripheral: crate::dma::DmaPeripheral, + } + + impl DmaDriver { + fn abort_transfer(&self) { + self.driver.configure_datalen(1, 1); + self.driver.update(); + } + + fn regs(&self) -> &RegisterBlock { + self.driver.regs() + } + + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_transfer_dma( + &self, + _full_duplex: bool, + rx_len: usize, + tx_len: usize, + rx_buffer: &mut impl DmaRxBuffer, + tx_buffer: &mut impl DmaTxBuffer, + channel: &mut Channel>>, + ) -> Result<(), Error> { + #[cfg(esp32s2)] + { + // without this a transfer after a write will fail + self.regs().dma_out_link().write(|w| unsafe { w.bits(0) }); + self.regs().dma_in_link().write(|w| unsafe { w.bits(0) }); + } + + self.driver.configure_datalen(rx_len, tx_len); + + // enable the MISO and MOSI if needed + self.regs() + .user() + .modify(|_, w| w.usr_miso().bit(rx_len > 0).usr_mosi().bit(tx_len > 0)); + + self.enable_dma(); + + if rx_len > 0 { + unsafe { + channel + .rx + .prepare_transfer(self.dma_peripheral, rx_buffer) + .and_then(|_| channel.rx.start_transfer())?; + } + } else { + #[cfg(esp32)] + { + // see https://github.com/espressif/esp-idf/commit/366e4397e9dae9d93fe69ea9d389b5743295886f + // see https://github.com/espressif/esp-idf/commit/0c3653b1fd7151001143451d4aa95dbf15ee8506 + if _full_duplex { + self.regs() + .dma_in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(0) }); + self.regs() + .dma_in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + } + } + if tx_len > 0 { + unsafe { + channel + .tx + .prepare_transfer(self.dma_peripheral, tx_buffer) + .and_then(|_| channel.tx.start_transfer())?; + } + } + + #[cfg(dma_kind = "gdma")] + self.reset_dma(); + + self.driver.start_operation(); + + Ok(()) + } + + fn enable_dma(&self) { + #[cfg(dma_kind = "gdma")] + // for non GDMA this is done in `assign_tx_device` / `assign_rx_device` + self.regs().dma_conf().modify(|_, w| { + w.dma_tx_ena().set_bit(); + w.dma_rx_ena().set_bit() + }); + + #[cfg(dma_kind = "pdma")] + self.reset_dma(); + } + + fn reset_dma(&self) { + fn set_reset_bit(reg_block: &RegisterBlock, bit: bool) { + #[cfg(dma_kind = "pdma")] + reg_block.dma_conf().modify(|_, w| { + w.out_rst().bit(bit); + w.in_rst().bit(bit); + w.ahbm_fifo_rst().bit(bit); + w.ahbm_rst().bit(bit) + }); + #[cfg(dma_kind = "gdma")] + reg_block.dma_conf().modify(|_, w| { + w.rx_afifo_rst().bit(bit); + w.buf_afifo_rst().bit(bit); + w.dma_afifo_rst().bit(bit) + }); + } + + set_reset_bit(self.regs(), true); + set_reset_bit(self.regs(), false); + self.clear_dma_interrupts(); + } + + #[cfg(dma_kind = "gdma")] + fn clear_dma_interrupts(&self) { + self.regs().dma_int_clr().write(|w| { + w.dma_infifo_full_err().clear_bit_by_one(); + w.dma_outfifo_empty_err().clear_bit_by_one(); + w.trans_done().clear_bit_by_one(); + w.mst_rx_afifo_wfull_err().clear_bit_by_one(); + w.mst_tx_afifo_rempty_err().clear_bit_by_one() + }); + } + + #[cfg(dma_kind = "pdma")] + fn clear_dma_interrupts(&self) { + self.regs().dma_int_clr().write(|w| { + w.inlink_dscr_empty().clear_bit_by_one(); + w.outlink_dscr_error().clear_bit_by_one(); + w.inlink_dscr_error().clear_bit_by_one(); + w.in_done().clear_bit_by_one(); + w.in_err_eof().clear_bit_by_one(); + w.in_suc_eof().clear_bit_by_one(); + w.out_done().clear_bit_by_one(); + w.out_eof().clear_bit_by_one(); + w.out_total_eof().clear_bit_by_one() + }); + } + } + + impl<'d> DmaEligible for AnySpi<'d> { + #[cfg(dma_kind = "gdma")] + type Dma = crate::dma::AnyGdmaChannel<'d>; + #[cfg(dma_kind = "pdma")] + type Dma = crate::dma::AnySpiDmaChannel<'d>; + + fn dma_peripheral(&self) -> crate::dma::DmaPeripheral { + match &self.0 { + #[cfg(spi_master_spi2)] + any::Inner::Spi2(_) => crate::dma::DmaPeripheral::Spi2, + #[cfg(spi_master_spi3)] + any::Inner::Spi3(_) => crate::dma::DmaPeripheral::Spi3, + } + } + } + + /// Async functionality + mod asynch { + use core::ops::{Deref, DerefMut}; + + use super::*; + + #[cfg_attr(not(feature = "unstable"), allow(dead_code))] + pub(crate) struct DropGuard { + inner: ManuallyDrop, + on_drop: ManuallyDrop, + } + + #[cfg_attr(not(feature = "unstable"), allow(dead_code))] + impl DropGuard { + pub(crate) fn new(inner: I, on_drop: F) -> Self { + Self { + inner: ManuallyDrop::new(inner), + on_drop: ManuallyDrop::new(on_drop), + } + } + + pub(crate) fn defuse(self) {} + } + + impl Drop for DropGuard { + fn drop(&mut self) { + let inner = unsafe { ManuallyDrop::take(&mut self.inner) }; + let on_drop = unsafe { ManuallyDrop::take(&mut self.on_drop) }; + (on_drop)(inner) + } + } + + impl Deref for DropGuard { + type Target = I; + + fn deref(&self) -> &I { + &self.inner + } + } + + impl DerefMut for DropGuard { + fn deref_mut(&mut self) -> &mut I { + &mut self.inner + } + } + + #[instability::unstable] + impl embedded_hal_async::spi::SpiBus for SpiDmaBus<'_, Async> { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read_async(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write_async(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer_async(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place_async(words).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + // All operations currently flush so this is no-op. + Ok(()) + } + } + } + + mod ehal1 { + #[cfg(feature = "unstable")] + use embedded_hal::spi::{ErrorType, SpiBus}; + + #[cfg(feature = "unstable")] + use super::*; + + #[instability::unstable] + impl ErrorType for SpiDmaBus<'_, Dm> + where + Dm: DriverMode, + { + type Error = Error; + } + + #[instability::unstable] + impl SpiBus for SpiDmaBus<'_, Dm> + where + Dm: DriverMode, + { + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + // All operations currently flush so this is no-op. + Ok(()) + } + } + } +} + +mod ehal1 { + use embedded_hal::spi::SpiBus; + use embedded_hal_async::spi::SpiBus as SpiBusAsync; + + use super::*; + + impl embedded_hal::spi::ErrorType for Spi<'_, Dm> + where + Dm: DriverMode, + { + type Error = Error; + } + + impl SpiBus for Spi<'_, Dm> + where + Dm: DriverMode, + { + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.driver().read(words) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.driver().write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Optimizations + if read.is_empty() { + return SpiBus::write(self, write); + } else if write.is_empty() { + return SpiBus::read(self, read); + } + + let mut write_from = 0; + let mut read_from = 0; + + loop { + // How many bytes we write in this chunk + let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from); + let write_to = write_from + write_inc; + // How many bytes we read in this chunk + let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from); + let read_to = read_from + read_inc; + + if (write_inc == 0) && (read_inc == 0) { + break; + } + + // No need to flush here, `SpiBus::write` will do it for us + + if write_to < read_to { + // Read more than we write, must pad writing part with zeros + let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE]; + empty[0..write_inc].copy_from_slice(&write[write_from..write_to]); + SpiBus::write(self, &empty)?; + } else { + SpiBus::write(self, &write[write_from..write_to])?; + } + + if read_inc > 0 { + SpiBus::flush(self)?; + self.driver() + .read_from_fifo(&mut read[read_from..read_to])?; + } + + write_from = write_to; + read_from = read_to; + } + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.driver().transfer(words) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.driver().flush() + } + } + + impl SpiBusAsync for Spi<'_, Async> { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.flush_async().await?; + self.driver().setup_full_duplex()?; + self.driver().read_async(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.flush_async().await?; + self.driver().setup_full_duplex()?; + self.driver().write_async(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Optimizations + if read.is_empty() { + return SpiBusAsync::write(self, write).await; + } else if write.is_empty() { + return SpiBusAsync::read(self, read).await; + } + + let mut write_from = 0; + let mut read_from = 0; + + loop { + // How many bytes we write in this chunk + let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from); + let write_to = write_from + write_inc; + // How many bytes we read in this chunk + let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from); + let read_to = read_from + read_inc; + + if (write_inc == 0) && (read_inc == 0) { + break; + } + + // No need to flush here, `SpiBusAsync::write` will do it for us + + if write_to < read_to { + // Read more than we write, must pad writing part with zeros + let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE]; + empty[0..write_inc].copy_from_slice(&write[write_from..write_to]); + SpiBusAsync::write(self, &empty).await?; + } else { + SpiBusAsync::write(self, &write[write_from..write_to]).await?; + } + + if read_inc > 0 { + self.driver() + .read_from_fifo(&mut read[read_from..read_to])?; + } + + write_from = write_to; + read_from = read_to; + } + Ok(()) + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place_async(words).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await + } + } +} + +/// SPI peripheral instance. +pub trait Instance: private::Sealed + any::Degrade { + #[doc(hidden)] + /// Returns the peripheral data and state describing this instance. + fn parts(&self) -> (&'static Info, &'static State); + + /// Returns the peripheral data describing this instance. + #[doc(hidden)] + #[inline(always)] + fn info(&self) -> &'static Info { + self.parts().0 + } + + /// Returns the peripheral state for this instance. + #[doc(hidden)] + #[inline(always)] + fn state(&self) -> &'static State { + self.parts().1 + } +} + +/// Marker trait for QSPI-capable SPI peripherals. +#[doc(hidden)] +pub trait QspiInstance: Instance {} + +/// Peripheral data describing a particular SPI instance. +#[doc(hidden)] +#[non_exhaustive] +#[allow(private_interfaces, reason = "Unstable details")] +pub struct Info { + /// Pointer to the register block for this SPI instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, + + /// The system peripheral marker. + pub peripheral: crate::system::Peripheral, + + /// Interrupt handler for the asynchronous operations. + pub async_handler: InterruptHandler, + + /// SCLK signal. + pub sclk: OutputSignal, + + /// Chip select signals. + pub cs: &'static [OutputSignal], + + pub sio_inputs: &'static [InputSignal], + pub sio_outputs: &'static [OutputSignal], +} + +impl Info { + fn cs(&self, n: usize) -> OutputSignal { + *unwrap!(self.cs.get(n), "CS{} is not defined", n) + } + + fn opt_sio_input(&self, n: usize) -> Option { + self.sio_inputs.get(n).copied() + } + + fn opt_sio_output(&self, n: usize) -> Option { + self.sio_outputs.get(n).copied() + } + + fn sio_input(&self, n: usize) -> InputSignal { + unwrap!(self.opt_sio_input(n), "SIO{} is not defined", n) + } + + fn sio_output(&self, n: usize) -> OutputSignal { + unwrap!(self.opt_sio_output(n), "SIO{} is not defined", n) + } +} + +struct Driver { + info: &'static Info, + state: &'static State, +} + +// private implementation bits +// FIXME: split this up into peripheral versions +impl Driver { + /// Returns the register block for this SPI instance. + pub fn regs(&self) -> &RegisterBlock { + unsafe { &*self.info.register_block } + } + + fn abort_transfer(&self) { + // Note(danielb): This method came later than DmaDriver::abort_transfer. That + // function works for DMA so I have left it unchanged, but does not work + // for CPU-controlled transfers on the ESP32. Toggling slave mode works on + // ESP32, but not on later chips. + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().slave().modify(|_, w| w.mode().set_bit()); + self.regs().slave().modify(|_, w| w.mode().clear_bit()); + } else { + self.configure_datalen(1, 1); + } + } + self.update(); + } + + /// Initialize for full-duplex 1 bit mode + fn init(&self) { + self.regs().user().modify(|_, w| { + w.usr_miso_highpart().clear_bit(); + w.usr_mosi_highpart().clear_bit(); + w.doutdin().set_bit(); + w.usr_miso().set_bit(); + w.usr_mosi().set_bit(); + w.cs_hold().set_bit(); + w.usr_dummy_idle().set_bit(); + w.usr_addr().clear_bit(); + w.usr_command().clear_bit() + }); + + #[cfg(not(any(esp32, esp32s2)))] + self.regs().clk_gate().modify(|_, w| { + w.clk_en().set_bit(); + w.mst_clk_active().set_bit(); + w.mst_clk_sel().set_bit() + }); + + #[cfg(soc_has_pcr)] + unsafe { + // use default clock source + crate::peripherals::PCR::regs() + .spi2_clkm_conf() + .modify(|_, w| { + #[cfg(spi_master_has_clk_pre_div)] + w.spi2_clkm_div_num().bits(1); + w.spi2_clkm_sel().bits(1); + w.spi2_clkm_en().set_bit() + }); + } + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().ctrl().modify(|_, w| w.wp().clear_bit()); + } else { + self.regs().ctrl().modify(|_, w| { + w.q_pol().clear_bit(); + w.d_pol().clear_bit(); + #[cfg(esp32s2)] + w.wp().clear_bit(); + #[cfg(not(esp32s2))] + w.hold_pol().clear_bit(); + w + }); + self.regs().misc().write(|w| unsafe { w.bits(0) }); + } + } + + self.regs().slave().write(|w| unsafe { w.bits(0) }); + } + + #[cfg(not(esp32))] + fn init_spi_data_mode( + &self, + cmd_mode: DataMode, + address_mode: DataMode, + data_mode: DataMode, + ) -> Result<(), Error> { + self.regs().ctrl().modify(|_, w| { + w.fcmd_dual().bit(cmd_mode == DataMode::Dual); + w.fcmd_quad().bit(cmd_mode == DataMode::Quad); + w.faddr_dual().bit(address_mode == DataMode::Dual); + w.faddr_quad().bit(address_mode == DataMode::Quad); + w.fread_dual().bit(data_mode == DataMode::Dual); + w.fread_quad().bit(data_mode == DataMode::Quad) + }); + self.regs().user().modify(|_, w| { + w.fwrite_dual().bit(data_mode == DataMode::Dual); + w.fwrite_quad().bit(data_mode == DataMode::Quad) + }); + Ok(()) + } + + #[cfg(esp32)] + fn init_spi_data_mode( + &self, + cmd_mode: DataMode, + address_mode: DataMode, + data_mode: DataMode, + ) -> Result<(), Error> { + match cmd_mode { + DataMode::Single => (), + DataMode::SingleTwoDataLines => (), + // FIXME: more detailed error - Only 1-bit commands are supported. + _ => { + error!("Commands must be single bit wide"); + return Err(Error::Unsupported); + } + } + + match address_mode { + DataMode::Single | DataMode::SingleTwoDataLines => { + self.regs().ctrl().modify(|_, w| { + w.fastrd_mode() + .bit(matches!(data_mode, DataMode::Dual | DataMode::Quad)); + w.fread_dio().clear_bit(); + w.fread_qio().clear_bit(); + w.fread_dual().bit(data_mode == DataMode::Dual); + w.fread_quad().bit(data_mode == DataMode::Quad) + }); + + self.regs().user().modify(|_, w| { + w.fwrite_dio().clear_bit(); + w.fwrite_qio().clear_bit(); + w.fwrite_dual().bit(data_mode == DataMode::Dual); + w.fwrite_quad().bit(data_mode == DataMode::Quad) + }); + } + address_mode if address_mode == data_mode => { + self.regs().ctrl().modify(|_, w| { + w.fastrd_mode() + .bit(matches!(data_mode, DataMode::Dual | DataMode::Quad)); + w.fread_dio().bit(address_mode == DataMode::Dual); + w.fread_qio().bit(address_mode == DataMode::Quad); + w.fread_dual().clear_bit(); + w.fread_quad().clear_bit() + }); + + self.regs().user().modify(|_, w| { + w.fwrite_dio().bit(address_mode == DataMode::Dual); + w.fwrite_qio().bit(address_mode == DataMode::Quad); + w.fwrite_dual().clear_bit(); + w.fwrite_quad().clear_bit() + }); + } + _ => { + // FIXME: more detailed error - Unsupported combination of data-modes + error!("Address mode must be single bit wide or equal to the data mode"); + return Err(Error::Unsupported); + } + } + + Ok(()) + } + + /// Enable or disable listening for the given interrupts. + #[cfg_attr(not(feature = "unstable"), allow(dead_code))] + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().slave().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => w.trans_inten().bit(enable), + }; + } + w + }); + } else if #[cfg(esp32s2)] { + self.regs().slave().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => w.int_trans_done_en().bit(enable), + SpiInterrupt::DmaSegmentedTransferDone => w.int_dma_seg_trans_en().bit(enable), + }; + } + w + }); + } else { + self.regs().dma_int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => w.trans_done().bit(enable), + #[cfg(spi_master_has_dma_segmented_transfer)] + SpiInterrupt::DmaSegmentedTransferDone => w.dma_seg_trans_done().bit(enable), + #[cfg(spi_master_has_app_interrupts)] + SpiInterrupt::App2 => w.app2().bit(enable), + #[cfg(spi_master_has_app_interrupts)] + SpiInterrupt::App1 => w.app1().bit(enable), + }; + } + w + }); + } + } + } + + /// Gets asserted interrupts + #[cfg_attr(not(feature = "unstable"), allow(dead_code))] + fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + if self.regs().slave().read().trans_done().bit() { + res.insert(SpiInterrupt::TransferDone); + } + } else if #[cfg(esp32s2)] { + if self.regs().slave().read().trans_done().bit() { + res.insert(SpiInterrupt::TransferDone); + } + if self.regs().hold().read().dma_seg_trans_done().bit() { + res.insert(SpiInterrupt::DmaSegmentedTransferDone); + } + } else { + let ints = self.regs().dma_int_raw().read(); + + if ints.trans_done().bit() { + res.insert(SpiInterrupt::TransferDone); + } + #[cfg(spi_master_has_dma_segmented_transfer)] + if ints.dma_seg_trans_done().bit() { + res.insert(SpiInterrupt::DmaSegmentedTransferDone); + } + #[cfg(spi_master_has_app_interrupts)] + if ints.app2().bit() { + res.insert(SpiInterrupt::App2); + } + #[cfg(spi_master_has_app_interrupts)] + if ints.app1().bit() { + res.insert(SpiInterrupt::App1); + } + } + } + + res + } + + /// Resets asserted interrupts + #[cfg_attr(not(feature = "unstable"), allow(dead_code))] + fn clear_interrupts(&self, interrupts: EnumSet) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => { + self.regs().slave().modify(|_, w| w.trans_done().clear_bit()); + } + } + } + } else if #[cfg(esp32s2)] { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => { + self.regs().slave().modify(|_, w| w.trans_done().clear_bit()); + } + + SpiInterrupt::DmaSegmentedTransferDone => { + self.regs() + .hold() + .modify(|_, w| w.dma_seg_trans_done().clear_bit()); + } + } + } + } else { + self.regs().dma_int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + SpiInterrupt::TransferDone => w.trans_done().clear_bit_by_one(), + #[cfg(spi_master_has_dma_segmented_transfer)] + SpiInterrupt::DmaSegmentedTransferDone => w.dma_seg_trans_done().clear_bit_by_one(), + #[cfg(spi_master_has_app_interrupts)] + SpiInterrupt::App2 => w.app2().clear_bit_by_one(), + #[cfg(spi_master_has_app_interrupts)] + SpiInterrupt::App1 => w.app1().clear_bit_by_one(), + }; + } + w + }); + } + } + } + + fn apply_config(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + self.ch_bus_freq(config)?; + self.set_bit_order(config.read_bit_order, config.write_bit_order); + self.set_data_mode(config.mode); + + #[cfg(esp32)] + self.calculate_half_duplex_values(config); + + Ok(()) + } + + #[cfg(esp32)] + fn calculate_half_duplex_values(&self, config: &Config) { + let f_apb = 80_000_000; + let source_freq_hz = match config.clock_source { + ClockSource::Apb => f_apb, + }; + + let clock_reg = self.regs().clock().read(); + let eff_clk = if clock_reg.clk_equ_sysclk().bit_is_set() { + f_apb + } else { + let pre = clock_reg.clkdiv_pre().bits() as i32 + 1; + let n = clock_reg.clkcnt_n().bits() as i32 + 1; + f_apb / (pre * n) + }; + + let apbclk_khz = source_freq_hz / 1000; + // how many apb clocks a period has + let spiclk_apb_n = source_freq_hz / eff_clk; + + // How many apb clocks the delay is, the 1 is to compensate in case + // ``input_delay_ns`` is rounded off. Change from esp-idf: we use + // `input_delay_ns` to also represent the GPIO matrix delay. + let input_delay_ns = 25; // TODO: allow configuring input delay. + let delay_apb_n = (1 + input_delay_ns) * apbclk_khz / 1000 / 1000; + + let dummy_required = delay_apb_n / spiclk_apb_n; + let timing_miso_delay = if dummy_required > 0 { + // due to the clock delay between master and slave, there's a range in which + // data is random give MISO a delay if needed to make sure we + // sample at the time MISO is stable + Some(((dummy_required + 1) * spiclk_apb_n - delay_apb_n - 1) as u8) + } else if delay_apb_n * 4 <= spiclk_apb_n { + // if the dummy is not required, maybe we should also delay half a SPI clock if + // the data comes too early + None + } else { + Some(0) + }; + + self.state.esp32_hack.extra_dummy.set(dummy_required as u8); + self.state + .esp32_hack + .timing_miso_delay + .set(timing_miso_delay); + } + + fn set_data_mode(&self, data_mode: Mode) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let pin_reg = self.regs().pin(); + } else { + let pin_reg = self.regs().misc(); + } + }; + + pin_reg.modify(|_, w| { + w.ck_idle_edge() + .bit(matches!(data_mode, Mode::_2 | Mode::_3)) + }); + self.regs().user().modify(|_, w| { + w.ck_out_edge() + .bit(matches!(data_mode, Mode::_1 | Mode::_2)) + }); + } + + fn ch_bus_freq(&self, bus_clock_config: &Config) -> Result<(), ConfigError> { + fn enable_clocks(_reg_block: &RegisterBlock, _enable: bool) { + #[cfg(not(any(esp32, esp32s2)))] + _reg_block.clk_gate().modify(|_, w| { + w.clk_en().bit(_enable); + w.mst_clk_active().bit(_enable); + w.mst_clk_sel().bit(true) // TODO: support XTAL clock source + }); + } + + // Change clock frequency + let raw = bus_clock_config.raw_clock_reg_value()?; + + enable_clocks(self.regs(), false); + self.regs().clock().write(|w| unsafe { w.bits(raw) }); + enable_clocks(self.regs(), true); + + Ok(()) + } + + #[cfg(not(any(esp32, esp32c3, esp32s2)))] + fn set_bit_order(&self, read_order: BitOrder, write_order: BitOrder) { + let read_value = match read_order { + BitOrder::MsbFirst => 0, + BitOrder::LsbFirst => 1, + }; + let write_value = match write_order { + BitOrder::MsbFirst => 0, + BitOrder::LsbFirst => 1, + }; + self.regs().ctrl().modify(|_, w| unsafe { + w.rd_bit_order().bits(read_value); + w.wr_bit_order().bits(write_value); + w + }); + } + + #[cfg(any(esp32, esp32c3, esp32s2))] + fn set_bit_order(&self, read_order: BitOrder, write_order: BitOrder) { + let read_value = match read_order { + BitOrder::MsbFirst => false, + BitOrder::LsbFirst => true, + }; + let write_value = match write_order { + BitOrder::MsbFirst => false, + BitOrder::LsbFirst => true, + }; + self.regs().ctrl().modify(|_, w| { + w.rd_bit_order().bit(read_value); + w.wr_bit_order().bit(write_value); + w + }); + } + + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn fill_fifo(&self, chunk: &[u8]) { + // TODO: replace with `array_chunks` and `from_le_bytes` + let mut c_iter = chunk.chunks_exact(4); + let mut w_iter = self.regs().w_iter(); + for c in c_iter.by_ref() { + if let Some(w_reg) = w_iter.next() { + let word = (c[0] as u32) + | ((c[1] as u32) << 8) + | ((c[2] as u32) << 16) + | ((c[3] as u32) << 24); + w_reg.write(|w| w.buf().set(word)); + } + } + let rem = c_iter.remainder(); + if !rem.is_empty() + && let Some(w_reg) = w_iter.next() + { + let word = match rem.len() { + 3 => (rem[0] as u32) | ((rem[1] as u32) << 8) | ((rem[2] as u32) << 16), + 2 => (rem[0] as u32) | ((rem[1] as u32) << 8), + 1 => rem[0] as u32, + _ => unreachable!(), + }; + w_reg.write(|w| w.buf().set(word)); + } + } + + /// Write bytes to SPI. + /// + /// This function will return before all bytes of the last chunk to transmit + /// have been sent to the wire. If you must ensure that the whole + /// messages was written correctly, use [`Self::flush`]. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn write(&self, words: &[u8]) -> Result<(), Error> { + let num_chunks = words.len() / FIFO_SIZE; + + // Flush in case previous writes have not completed yet, required as per + // embedded-hal documentation (#1369). + self.flush()?; + + // The fifo has a limited fixed size, so the data must be chunked and then + // transmitted + for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() { + self.configure_datalen(0, chunk.len()); + self.fill_fifo(chunk); + + self.start_operation(); + + // Wait for all chunks to complete except the last one. + // The function is allowed to return before the bus is idle. + // see [embedded-hal flushing](https://docs.rs/embedded-hal/1.0.0/embedded_hal/spi/index.html#flushing) + if i < num_chunks { + self.flush()?; + } + } + Ok(()) + } + + /// Write bytes to SPI. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + async fn write_async(&self, words: &[u8]) -> Result<(), Error> { + // The fifo has a limited fixed size, so the data must be chunked and then + // transmitted + for chunk in words.chunks(FIFO_SIZE) { + self.configure_datalen(0, chunk.len()); + self.fill_fifo(chunk); + self.execute_operation_async().await; + } + Ok(()) + } + + /// Read bytes from SPI. + /// + /// Sends out a stuffing byte for every byte to read. This function doesn't + /// perform flushing. If you want to read the response to something you + /// have written before, consider using [`Self::transfer`] instead. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn read(&self, words: &mut [u8]) -> Result<(), Error> { + let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write(&empty_array[0..chunk.len()])?; + self.flush()?; + self.read_from_fifo(chunk)?; + } + Ok(()) + } + + /// Read bytes from SPI. + /// + /// Sends out a stuffing byte for every byte to read. This function doesn't + /// perform flushing. If you want to read the response to something you + /// have written before, consider using [`Self::transfer`] instead. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + async fn read_async(&self, words: &mut [u8]) -> Result<(), Error> { + let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write_async(&empty_array[0..chunk.len()]).await?; + self.read_from_fifo(chunk)?; + } + Ok(()) + } + + /// Read received bytes from SPI FIFO. + /// + /// Copies the contents of the SPI receive FIFO into `words`. This function + /// doesn't perform flushing. If you want to read the response to + /// something you have written before, consider using [`Self::transfer`] + /// instead. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn read_from_fifo(&self, words: &mut [u8]) -> Result<(), Error> { + let reg_block = self.regs(); + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.configure_datalen(chunk.len(), 0); + + for (index, w_reg) in (0..chunk.len()).step_by(4).zip(reg_block.w_iter()) { + let reg_val = w_reg.read().bits(); + let bytes = reg_val.to_le_bytes(); + + let len = usize::min(chunk.len(), index + 4) - index; + chunk[index..(index + len)].clone_from_slice(&bytes[0..len]); + } + } + + Ok(()) + } + + fn busy(&self) -> bool { + self.regs().cmd().read().usr().bit_is_set() + } + + // Check if the bus is busy and if it is wait for it to be idle + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + async fn flush_async(&self) -> Result<(), Error> { + if self.busy() { + let future = SpiFuture::setup(self).await; + future.await; + } + + Ok(()) + } + + // Check if the bus is busy and if it is wait for it to be idle + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn flush(&self) -> Result<(), Error> { + while self.busy() { + // wait for bus to be clear + } + Ok(()) + } + + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn transfer(&self, words: &mut [u8]) -> Result<(), Error> { + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write(chunk)?; + self.flush()?; + self.read_from_fifo(chunk)?; + } + + Ok(()) + } + + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + async fn transfer_in_place_async(&self, words: &mut [u8]) -> Result<(), Error> { + for chunk in words.chunks_mut(FIFO_SIZE) { + // Cut the transfer short if the future is dropped. We'll block for a short + // while to ensure the peripheral is idle. + let cancel_on_drop = OnDrop::new(|| { + self.abort_transfer(); + while self.busy() {} + }); + let res = self.write_async(chunk).await; + cancel_on_drop.defuse(); + res?; + + self.read_from_fifo(chunk)?; + } + + Ok(()) + } + + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn start_operation(&self) { + self.update(); + self.regs().cmd().modify(|_, w| w.usr().set_bit()); + } + + /// Starts the operation and waits for it to complete. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + async fn execute_operation_async(&self) { + // On ESP32, the interrupt seems to not fire in specific circumstances, when + // `listen` is called after `start_operation`. Let's call it before, to be sure. + let future = SpiFuture::setup(self).await; + self.start_operation(); + future.await; + } + + fn setup_full_duplex(&self) -> Result<(), Error> { + self.regs().user().modify(|_, w| { + w.usr_miso().set_bit(); + w.usr_mosi().set_bit(); + w.doutdin().set_bit(); + w.usr_dummy().clear_bit(); + w.sio().clear_bit() + }); + + self.init_spi_data_mode( + DataMode::SingleTwoDataLines, + DataMode::SingleTwoDataLines, + DataMode::SingleTwoDataLines, + )?; + + // For full-duplex, we don't need compensation according to esp-idf. + #[cfg(esp32)] + self.regs().ctrl2().modify(|_, w| unsafe { + w.miso_delay_mode().bits(0); + w.miso_delay_num().bits(0) + }); + + Ok(()) + } + + #[expect(clippy::too_many_arguments)] + fn setup_half_duplex( + &self, + is_write: bool, + cmd: Command, + address: Address, + dummy_idle: bool, + dummy: u8, + no_mosi_miso: bool, + data_mode: DataMode, + ) -> Result<(), Error> { + self.init_spi_data_mode(cmd.mode(), address.mode(), data_mode)?; + + #[cfg(esp32)] + let mut dummy = dummy; + + #[cfg(esp32)] + self.regs().ctrl2().modify(|_, w| { + let mut delay_mode = 0; + let mut delay_num = 0; + + if !is_write { + // Values are set up in apply_config + let timing_miso_delay = self.state.esp32_hack.timing_miso_delay.get(); + let extra_dummy = self.state.esp32_hack.extra_dummy.get(); + dummy += extra_dummy; + + if let Some(delay) = timing_miso_delay { + delay_num = if extra_dummy > 0 { delay } else { 0 }; + } else { + let out_edge = self.regs().user().read().ck_out_edge().bit_is_set(); + // SPI modes 1 and 2 need delay mode 1 according to esp-idf. + delay_mode = if out_edge { 1 } else { 2 }; + } + } + + unsafe { + w.miso_delay_mode().bits(delay_mode); + w.miso_delay_num().bits(delay_num) + } + }); + + let reg_block = self.regs(); + reg_block.user().modify(|_, w| { + w.usr_miso_highpart().clear_bit(); + w.usr_mosi_highpart().clear_bit(); + // This bit tells the hardware whether we use Single or SingleTwoDataLines + w.sio().bit(data_mode == DataMode::Single); + w.doutdin().clear_bit(); + w.usr_miso().bit(!is_write && !no_mosi_miso); + w.usr_mosi().bit(is_write && !no_mosi_miso); + w.cs_hold().set_bit(); + w.usr_dummy_idle().bit(dummy_idle); + w.usr_dummy().bit(dummy != 0); + w.usr_addr().bit(!address.is_none()); + w.usr_command().bit(!cmd.is_none()) + }); + + // FIXME why is clock config even here? + #[cfg(not(any(esp32, esp32s2)))] + reg_block.clk_gate().modify(|_, w| { + w.clk_en().set_bit(); + w.mst_clk_active().set_bit(); + w.mst_clk_sel().set_bit() + }); + + #[cfg(soc_has_pcr)] + // use default clock source + crate::peripherals::PCR::regs() + .spi2_clkm_conf() + .modify(|_, w| unsafe { w.spi2_clkm_sel().bits(1) }); + + #[cfg(not(esp32))] + reg_block.misc().write(|w| unsafe { w.bits(0) }); + + reg_block.slave().write(|w| unsafe { w.bits(0) }); + + self.update(); + + // set cmd, address, dummy cycles + self.set_up_common_phases(cmd, address, dummy); + + Ok(()) + } + + fn set_up_common_phases(&self, cmd: Command, address: Address, dummy: u8) { + let reg_block = self.regs(); + if !cmd.is_none() { + reg_block.user2().modify(|_, w| unsafe { + w.usr_command_bitlen().bits((cmd.width() - 1) as u8); + w.usr_command_value().bits(cmd.value()) + }); + } + + if !address.is_none() { + reg_block + .user1() + .modify(|_, w| unsafe { w.usr_addr_bitlen().bits((address.width() - 1) as u8) }); + + let addr = address.value() << (32 - address.width()); + #[cfg(not(esp32))] + reg_block + .addr() + .write(|w| unsafe { w.usr_addr_value().bits(addr) }); + #[cfg(esp32)] + reg_block.addr().write(|w| unsafe { w.bits(addr) }); + } + + if dummy > 0 { + reg_block + .user1() + .modify(|_, w| unsafe { w.usr_dummy_cyclelen().bits(dummy - 1) }); + } + } + + fn update(&self) { + cfg_if::cfg_if! { + if #[cfg(not(any(esp32, esp32s2)))] { + let reg_block = self.regs(); + + reg_block.cmd().modify(|_, w| w.update().set_bit()); + + while reg_block.cmd().read().update().bit_is_set() { + // wait + } + } else { + // Doesn't seem to be needed for ESP32 and ESP32-S2 + } + } + } + + fn configure_datalen(&self, rx_len_bytes: usize, tx_len_bytes: usize) { + let reg_block = self.regs(); + + let rx_len = rx_len_bytes as u32 * 8; + let tx_len = tx_len_bytes as u32 * 8; + + let rx_len = rx_len.saturating_sub(1); + let tx_len = tx_len.saturating_sub(1); + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let len = rx_len.max(tx_len); + reg_block + .mosi_dlen() + .write(|w| unsafe { w.usr_mosi_dbitlen().bits(len) }); + + reg_block + .miso_dlen() + .write(|w| unsafe { w.usr_miso_dbitlen().bits(len) }); + } else if #[cfg(esp32s2)] { + reg_block + .mosi_dlen() + .write(|w| unsafe { w.usr_mosi_dbitlen().bits(tx_len) }); + + reg_block + .miso_dlen() + .write(|w| unsafe { w.usr_miso_dbitlen().bits(rx_len) }); + } else { + reg_block + .ms_dlen() + .write(|w| unsafe { w.ms_data_bitlen().bits(rx_len.max(tx_len)) }); + } + + } + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + core::ptr::eq(self.register_block, other.register_block) + } +} + +unsafe impl Sync for Info {} + +for_each_spi_master! { + ($peri:ident, $sys:ident, $sclk:ident [$($cs:ident),+] [$($sio:ident),*] $(, $is_qspi:tt)?) => { + impl Instance for crate::peripherals::$peri<'_> { + #[inline(always)] + fn parts(&self) -> (&'static Info, &'static State) { + #[handler] + #[ram] + fn irq_handler() { + handle_async(&INFO, &STATE) + } + + static INFO: Info = Info { + register_block: crate::peripherals::$peri::regs(), + peripheral: crate::system::Peripheral::$sys, + async_handler: irq_handler, + sclk: OutputSignal::$sclk, + cs: &[$(OutputSignal::$cs),+], + sio_inputs: &[$(InputSignal::$sio),*], + sio_outputs: &[$(OutputSignal::$sio),*], + }; + + static STATE: State = State { + waker: AtomicWaker::new(), + #[cfg(esp32)] + esp32_hack: Esp32Hack { + timing_miso_delay: Cell::new(None), + extra_dummy: Cell::new(0), + }, + }; + + (&INFO, &STATE) + } + } + + $( + // If the extra pins are set, implement QspiInstance + $crate::ignore!($is_qspi); + impl QspiInstance for crate::peripherals::$peri<'_> {} + )? + }; +} + +impl QspiInstance for AnySpi<'_> {} + +#[doc(hidden)] +pub struct State { + waker: AtomicWaker, + + #[cfg(esp32)] + esp32_hack: Esp32Hack, +} + +#[cfg(esp32)] +struct Esp32Hack { + timing_miso_delay: Cell>, + extra_dummy: Cell, +} + +#[cfg(esp32)] +unsafe impl Sync for Esp32Hack {} + +#[ram] +fn handle_async(info: &'static Info, state: &'static State) { + let driver = Driver { info, state }; + if driver.interrupts().contains(SpiInterrupt::TransferDone) { + driver.enable_listen(SpiInterrupt::TransferDone.into(), false); + state.waker.wake(); + } +} + +/// SPI data mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum DataMode { + /// 1 bit, two data lines. (MOSI, MISO) + SingleTwoDataLines, + /// 1 bit, 1 data line (SIO0) + Single, + /// 2 bits, two data lines. (SIO0, SIO1) + Dual, + /// 4 bit, 4 data lines. (SIO0 .. SIO3) + Quad, + #[cfg(spi_master_has_octal)] + /// 8 bit, 8 data lines. (SIO0 .. SIO7) + Octal, +} + +crate::any_peripheral! { + /// Any SPI peripheral. + pub peripheral AnySpi<'d> { + #[cfg(spi_master_spi2)] + Spi2(crate::peripherals::SPI2<'d>), + #[cfg(spi_master_spi3)] + Spi3(crate::peripherals::SPI3<'d>), + } +} + +impl Instance for AnySpi<'_> { + #[inline] + fn parts(&self) -> (&'static Info, &'static State) { + any::delegate!(self, spi => { spi.parts() }) + } +} + +impl AnySpi<'_> { + fn bind_peri_interrupt(&self, handler: InterruptHandler) { + any::delegate!(self, spi => { spi.bind_peri_interrupt(handler) }) + } + + fn disable_peri_interrupt_on_all_cores(&self) { + any::delegate!(self, spi => { spi.disable_peri_interrupt_on_all_cores() }) + } + + fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.disable_peri_interrupt_on_all_cores(); + self.bind_peri_interrupt(handler); + } +} + +struct SpiFuture<'a> { + driver: &'a Driver, +} + +impl<'a> SpiFuture<'a> { + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn setup(driver: &'a Driver) -> impl Future { + // Make sure this is called before starting an async operation. On the ESP32, + // calling after may cause the interrupt to not fire. + core::future::poll_fn(move |cx| { + driver.state.waker.register(cx.waker()); + driver.clear_interrupts(SpiInterrupt::TransferDone.into()); + driver.enable_listen(SpiInterrupt::TransferDone.into(), true); + Poll::Ready(Self { driver }) + }) + } +} + +impl Future for SpiFuture<'_> { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + if self + .driver + .interrupts() + .contains(SpiInterrupt::TransferDone) + { + self.driver + .clear_interrupts(SpiInterrupt::TransferDone.into()); + return Poll::Ready(()); + } + + Poll::Pending + } +} + +impl Drop for SpiFuture<'_> { + fn drop(&mut self) { + self.driver + .enable_listen(SpiInterrupt::TransferDone.into(), false); + } +} diff --git a/esp-hal/src/spi/mod.rs b/esp-hal/src/spi/mod.rs new file mode 100644 index 00000000000..b348d4fbd23 --- /dev/null +++ b/esp-hal/src/spi/mod.rs @@ -0,0 +1,100 @@ +//! Serial Peripheral Interface (SPI) +//! +//! ## Overview +//! The Serial Peripheral Interface (SPI) is a synchronous serial interface +//! useful for communication with external peripherals. +//! +//! ## Configuration +//! This peripheral is capable of operating in either master or slave mode. For +//! more information on these modes, please refer to the documentation in their +//! respective modules. + +#[cfg(any(spi_master_supports_dma, spi_slave_supports_dma))] +use crate::dma::DmaError; + +#[cfg(spi_master_driver_supported)] +pub mod master; + +crate::unstable_module! { + #[cfg(spi_slave_driver_supported)] + pub mod slave; +} + +/// SPI errors +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Error occurred due to a DMA-related issue. + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + #[allow(clippy::enum_variant_names, reason = "DMA is unstable")] + #[cfg(any(spi_master_supports_dma, spi_slave_supports_dma))] + DmaError(DmaError), + /// Error indicating that the maximum DMA transfer size was exceeded. + MaxDmaTransferSizeExceeded, + /// Error indicating that the FIFO size was exceeded during SPI + /// communication. + FifoSizeExeeded, + /// Error indicating that the operation is unsupported by the current + /// implementation or for the given arguments. + Unsupported, + /// An unknown error occurred during SPI communication. + Unknown, +} + +#[doc(hidden)] +#[cfg(feature = "unstable")] +#[cfg(any(spi_master_supports_dma, spi_slave_supports_dma))] +impl From for Error { + fn from(value: DmaError) -> Self { + Error::DmaError(value) + } +} + +#[doc(hidden)] +#[cfg(not(feature = "unstable"))] +#[cfg(any(spi_master_supports_dma, spi_slave_supports_dma))] +impl From for Error { + fn from(_value: DmaError) -> Self { + Error::Unknown + } +} + +impl embedded_hal::spi::Error for Error { + fn kind(&self) -> embedded_hal::spi::ErrorKind { + embedded_hal::spi::ErrorKind::Other + } +} + +/// SPI communication modes, defined by clock polarity (CPOL) and clock phase +/// (CPHA). +/// +/// These modes control the clock signal's idle state and when data is sampled +/// and shifted. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Mode { + /// Mode 0 (CPOL = 0, CPHA = 0): Clock is low when idle, data is captured on + /// the rising edge and propagated on the falling edge. + _0, + /// Mode 1 (CPOL = 0, CPHA = 1): Clock is low when idle, data is captured on + /// the falling edge and propagated on the rising edge. + _1, + /// Mode 2 (CPOL = 1, CPHA = 0): Clock is high when idle, data is captured + /// on the falling edge and propagated on the rising edge. + _2, + /// Mode 3 (CPOL = 1, CPHA = 1): Clock is high when idle, data is captured + /// on the rising edge and propagated on the falling edge. + _3, +} + +/// SPI Bit Order +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BitOrder { + /// Most Significant Bit (MSB) is transmitted first. + MsbFirst, + /// Least Significant Bit (LSB) is transmitted first. + LsbFirst, +} diff --git a/esp-hal/src/spi/slave.rs b/esp-hal/src/spi/slave.rs new file mode 100644 index 00000000000..c7d2e909721 --- /dev/null +++ b/esp-hal/src/spi/slave.rs @@ -0,0 +1,845 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "dma_channel" => { + cfg(any(esp32, esp32s2)) => "DMA_SPI2", + _ => "DMA_CH0", + }, +))] +//! # Serial Peripheral Interface - Slave Mode +//! +//! ## Overview +//! +//! In this mode, the SPI acts as slave and transfers data with its master when +//! its CS is asserted. +//! +//! ## Configuration +//! +//! The SPI slave driver allows using full-duplex and can only be used with DMA. +#![cfg_attr( + spi_slave_supports_dma, + doc = r#" +## Examples + +### SPI Slave with DMA + +```rust, no_run +# {before_snippet} +# use esp_hal::dma_buffers; +# use esp_hal::dma::{DmaRxBuf, DmaTxBuf}; +# use esp_hal::spi::Mode; +# use esp_hal::spi::slave::Spi; +let sclk = peripherals.GPIO0; +let miso = peripherals.GPIO1; +let mosi = peripherals.GPIO2; +let cs = peripherals.GPIO3; + +let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000); +let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); +let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); +let mut spi = Spi::new(peripherals.SPI2, Mode::_0) + .with_sck(sclk) + .with_mosi(mosi) + .with_miso(miso) + .with_cs(cs) + .with_dma(peripherals.__dma_channel__); + +let transfer = spi.transfer(50, dma_rx_buf, 50, dma_tx_buf)?; + +transfer.wait(); +# {after_snippet} +``` +"# +)] +//! ## Implementation State +//! +//! This driver is currently **unstable**. +//! +//! There are several options for working with the SPI peripheral in slave mode, +//! but the code currently only supports: +//! - Single transfers (not segmented transfers) +//! - Full duplex, single bit (not dual or quad SPI) +//! - DMA mode (not CPU mode). +#![cfg_attr(esp32, doc = "- ESP32 only supports SPI mode 1 and 3.\n\n")] +//! It also does not support blocking operations, as the actual +//! transfer is controlled by the SPI master; if these are necessary, +//! then the `SpiDmaTransfer` object can be `wait()`ed on or polled for +//! `is_done()`. +//! +//! See [tracking issue](https://github.com/esp-rs/esp-hal/issues/469) for more information. + +use core::marker::PhantomData; + +use super::Mode; +use crate::{ + Blocking, + DriverMode, + gpio::{ + InputSignal, + NoPin, + OutputConfig, + OutputSignal, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + pac::spi2::RegisterBlock, + system::PeripheralGuard, +}; + +/// SPI peripheral driver. +/// +/// See the [module-level documentation][self] for more details. +#[instability::unstable] +pub struct Spi<'d, Dm: DriverMode> { + spi: AnySpi<'d>, + #[allow(dead_code)] + data_mode: Mode, + _mode: PhantomData, + _guard: PeripheralGuard, +} +impl<'d> Spi<'d, Blocking> { + /// Constructs an SPI instance in 8bit dataframe mode. + #[instability::unstable] + pub fn new(spi: impl Instance + 'd, mode: Mode) -> Spi<'d, Blocking> { + let guard = PeripheralGuard::new(spi.info().peripheral); + + let this = Spi { + spi: spi.degrade(), + data_mode: mode, + _mode: PhantomData, + _guard: guard, + }; + + this.spi.info().init(); + this.spi.info().set_data_mode(mode, false); + + this.with_mosi(NoPin) + .with_miso(NoPin) + .with_sck(NoPin) + .with_cs(NoPin) + } + + fn connect_input_pin(&self, pin: impl PeripheralInput<'d>, signal: InputSignal) { + let pin = pin.into(); + pin.set_input_enable(true); + signal.connect_to(&pin); + } + + /// Assign the SCK (Serial Clock) pin for the SPI instance. + #[instability::unstable] + pub fn with_sck(self, sclk: impl PeripheralInput<'d>) -> Self { + self.connect_input_pin(sclk, self.spi.info().sclk); + self + } + + /// Assign the MOSI (Master Out Slave In) pin for the SPI instance. + #[instability::unstable] + pub fn with_mosi(self, mosi: impl PeripheralInput<'d>) -> Self { + self.connect_input_pin(mosi, self.spi.info().mosi); + self + } + + /// Assign the MISO (Master In Slave Out) pin for the SPI instance. + #[instability::unstable] + pub fn with_miso(self, miso: impl PeripheralOutput<'d>) -> Self { + let miso = miso.into(); + + miso.apply_output_config(&OutputConfig::default()); + miso.set_output_enable(true); + + self.spi.info().miso.connect_to(&miso); + self + } + + /// Assign the CS (Chip Select) pin for the SPI instance. + #[instability::unstable] + pub fn with_cs(self, cs: impl PeripheralInput<'d>) -> Self { + self.connect_input_pin(cs, self.spi.info().cs); + self + } +} + +/// DMA (Direct Memory Access) functionality (Slave). +#[instability::unstable] +#[cfg(spi_slave_supports_dma)] +pub mod dma { + use core::mem::ManuallyDrop; + + use enumset::enum_set; + + use super::*; + use crate::{ + DriverMode, + dma::{ + Channel, + DmaChannelFor, + DmaEligible, + DmaRxBuffer, + DmaRxInterrupt, + DmaTxBuffer, + EmptyBuf, + PeripheralDmaChannel, + }, + spi::Error, + }; + + const MAX_DMA_SIZE: usize = 32768 - 32; + + impl<'d> Spi<'d, Blocking> { + /// Configures the SPI peripheral with the provided DMA channel and + /// descriptors. + #[cfg_attr(esp32, doc = "\n\n**Note**: ESP32 only supports Mode 1 and 3.")] + #[instability::unstable] + pub fn with_dma(self, channel: impl DmaChannelFor>) -> SpiDma<'d, Blocking> { + self.spi.info().set_data_mode(self.data_mode, true); + SpiDma::new(self.spi, channel.degrade()) + } + } + + /// A structure representing a DMA transfer for SPI. + #[instability::unstable] + pub struct SpiDma<'d, Dm> + where + Dm: DriverMode, + { + pub(crate) spi: AnySpi<'d>, + pub(crate) channel: Channel>>, + _guard: PeripheralGuard, + } + + impl core::fmt::Debug for SpiDma<'_, Dm> + where + Dm: DriverMode, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SpiDma").finish() + } + } + + impl<'d> SpiDma<'d, Blocking> { + fn new(spi: AnySpi<'d>, channel: PeripheralDmaChannel>) -> Self { + let channel = Channel::new(channel); + channel.runtime_ensure_compatible(&spi); + let guard = PeripheralGuard::new(spi.info().peripheral); + + Self { + spi, + channel, + _guard: guard, + } + } + } + + impl<'d, Dm> SpiDma<'d, Dm> + where + Dm: DriverMode, + { + fn driver(&self) -> DmaDriver { + DmaDriver { + info: self.spi.info(), + dma_peripheral: self.spi.dma_peripheral(), + } + } + + /// Register a buffer for a DMA write. + /// + /// This will return a [SpiDmaTransfer]. The maximum amount of data to + /// be sent is 32736 bytes. + /// + /// The write is driven by the SPI master's sclk signal and cs line. + #[instability::unstable] + pub fn write( + mut self, + bytes_to_write: usize, + mut buffer: TX, + ) -> Result, (Error, Self, TX)> + where + TX: DmaTxBuffer, + { + if bytes_to_write > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); + } + + let result = unsafe { + self.driver().start_transfer_dma( + 0, + bytes_to_write, + &mut EmptyBuf, + &mut buffer, + &mut self.channel, + ) + }; + if let Err(err) = result { + return Err((err, self, buffer)); + } + + Ok(SpiDmaTransfer::new(self, buffer, false, true)) + } + + /// Register a buffer for a DMA read. + /// + /// This will return a [SpiDmaTransfer]. The maximum amount of data to + /// be received is 32736 bytes. + /// + /// The read is driven by the SPI master's sclk signal and cs line. + #[instability::unstable] + pub fn read( + mut self, + bytes_to_read: usize, + mut buffer: RX, + ) -> Result, (Error, Self, RX)> + where + RX: DmaRxBuffer, + { + if bytes_to_read > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); + } + + let result = unsafe { + self.driver().start_transfer_dma( + bytes_to_read, + 0, + &mut buffer, + &mut EmptyBuf, + &mut self.channel, + ) + }; + if let Err(err) = result { + return Err((err, self, buffer)); + } + + Ok(SpiDmaTransfer::new(self, buffer, true, false)) + } + + /// Register buffers for a DMA transfer. + /// + /// This will return a [SpiDmaTransfer]. The maximum amount of data to + /// be sent/received is 32736 bytes. + /// + /// The data transfer is driven by the SPI master's sclk signal and cs + /// line. + #[instability::unstable] + #[allow(clippy::type_complexity)] + pub fn transfer( + mut self, + bytes_to_read: usize, + mut rx_buffer: RX, + bytes_to_write: usize, + mut tx_buffer: TX, + ) -> Result, (Error, Self, RX, TX)> + where + RX: DmaRxBuffer, + TX: DmaTxBuffer, + { + if bytes_to_read > MAX_DMA_SIZE || bytes_to_write > MAX_DMA_SIZE { + return Err(( + Error::MaxDmaTransferSizeExceeded, + self, + rx_buffer, + tx_buffer, + )); + } + + let result = unsafe { + self.driver().start_transfer_dma( + bytes_to_read, + bytes_to_write, + &mut rx_buffer, + &mut tx_buffer, + &mut self.channel, + ) + }; + if let Err(err) = result { + return Err((err, self, rx_buffer, tx_buffer)); + } + + Ok(SpiDmaTransfer::new( + self, + (rx_buffer, tx_buffer), + true, + true, + )) + } + } + + /// A structure representing a DMA transfer for SPI. + /// + /// This structure holds references to the SPI instance, DMA buffers, and + /// transfer status. + #[instability::unstable] + pub struct SpiDmaTransfer<'d, Dm, Buf> + where + Dm: DriverMode, + { + spi_dma: ManuallyDrop>, + dma_buf: ManuallyDrop, + has_rx: bool, + has_tx: bool, + } + + impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf> + where + Dm: DriverMode, + { + fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf, has_rx: bool, has_tx: bool) -> Self { + Self { + spi_dma: ManuallyDrop::new(spi_dma), + dma_buf: ManuallyDrop::new(dma_buf), + has_rx, + has_tx, + } + } + + /// Checks if the transfer is complete. + /// + /// This method returns `true` if both RX and TX operations are done, + /// and the SPI instance is no longer busy. + #[instability::unstable] + pub fn is_done(&self) -> bool { + if self.has_rx { + let done_int = + enum_set!(DmaRxInterrupt::SuccessfulEof | DmaRxInterrupt::DescriptorEmpty); + if self + .spi_dma + .channel + .rx + .pending_in_interrupts() + .is_disjoint(done_int) + { + return false; + } + } + !self.spi_dma.spi.info().is_bus_busy() + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `SpiDma` instance and the associated buffer. + #[instability::unstable] + pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) { + while !self.is_done() { + // Wait for the SPI to become idle + } + + if self.has_tx { + // In case DMA TX buffer is bigger than what the SPI consumes, stop the DMA. + if !self.spi_dma.channel.tx.is_done() { + self.spi_dma.channel.tx.stop_transfer(); + } + } + + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.spi_dma), + ManuallyDrop::take(&mut self.dma_buf), + ) + }; + core::mem::forget(self); + retval + } + } + + impl Drop for SpiDmaTransfer<'_, Dm, Buf> + where + Dm: DriverMode, + { + fn drop(&mut self) { + while !self.is_done() { + // Wait for the SPI to become idle + } + unsafe { + ManuallyDrop::drop(&mut self.spi_dma); + ManuallyDrop::drop(&mut self.dma_buf); + } + } + } + + struct DmaDriver { + info: &'static Info, + dma_peripheral: crate::dma::DmaPeripheral, + } + + impl DmaDriver { + fn regs(&self) -> &RegisterBlock { + self.info.regs() + } + + unsafe fn start_transfer_dma( + &self, + read_buffer_len: usize, + write_buffer_len: usize, + rx_buffer: &mut impl DmaRxBuffer, + tx_buffer: &mut impl DmaTxBuffer, + channel: &mut Channel>>, + ) -> Result<(), Error> { + self.enable_dma(); + + self.info.reset_spi(); + + if read_buffer_len > 0 { + unsafe { + channel + .rx + .prepare_transfer(self.dma_peripheral, rx_buffer)?; + } + } + + if write_buffer_len > 0 { + unsafe { + channel + .tx + .prepare_transfer(self.dma_peripheral, tx_buffer)?; + } + } + + #[cfg(esp32)] + self.info + .prepare_length_and_lines(read_buffer_len, write_buffer_len); + + self.reset_dma_before_usr_cmd(); + + #[cfg(not(esp32))] + self.regs() + .dma_conf() + .modify(|_, w| w.dma_slv_seg_trans_en().clear_bit()); + + self.clear_dma_interrupts(); + self.info.setup_for_flush(); + self.regs().cmd().modify(|_, w| w.usr().set_bit()); + + if read_buffer_len > 0 { + channel.rx.start_transfer()?; + } + + if write_buffer_len > 0 { + channel.tx.start_transfer()?; + } + + Ok(()) + } + + fn reset_dma_before_usr_cmd(&self) { + #[cfg(dma_kind = "gdma")] + self.regs().dma_conf().modify(|_, w| { + w.rx_afifo_rst().set_bit(); + w.buf_afifo_rst().set_bit(); + w.dma_afifo_rst().set_bit() + }); + } + + fn enable_dma(&self) { + #[cfg(dma_kind = "gdma")] + self.regs().dma_conf().modify(|_, w| { + w.dma_tx_ena().set_bit(); + w.dma_rx_ena().set_bit(); + w.rx_eof_en().clear_bit() + }); + + #[cfg(dma_kind = "pdma")] + { + fn set_rst_bit(reg_block: &RegisterBlock, bit: bool) { + reg_block.dma_conf().modify(|_, w| { + w.in_rst().bit(bit); + w.out_rst().bit(bit); + w.ahbm_fifo_rst().bit(bit); + w.ahbm_rst().bit(bit) + }); + + #[cfg(esp32s2)] + reg_block + .dma_conf() + .modify(|_, w| w.dma_infifo_full_clr().bit(bit)); + } + set_rst_bit(self.regs(), true); + set_rst_bit(self.regs(), false); + } + } + + fn clear_dma_interrupts(&self) { + #[cfg(dma_kind = "gdma")] + self.regs().dma_int_clr().write(|w| { + w.dma_infifo_full_err().clear_bit_by_one(); + w.dma_outfifo_empty_err().clear_bit_by_one(); + w.trans_done().clear_bit_by_one(); + w.mst_rx_afifo_wfull_err().clear_bit_by_one(); + w.mst_tx_afifo_rempty_err().clear_bit_by_one() + }); + + #[cfg(dma_kind = "pdma")] + self.regs().dma_int_clr().write(|w| { + w.inlink_dscr_empty().clear_bit_by_one(); + w.outlink_dscr_error().clear_bit_by_one(); + w.inlink_dscr_error().clear_bit_by_one(); + w.in_done().clear_bit_by_one(); + w.in_err_eof().clear_bit_by_one(); + w.in_suc_eof().clear_bit_by_one(); + w.out_done().clear_bit_by_one(); + w.out_eof().clear_bit_by_one(); + w.out_total_eof().clear_bit_by_one() + }); + } + } + + /// A marker for DMA-capable SPI peripheral instances. + #[doc(hidden)] + #[allow(private_bounds)] + pub trait InstanceDma: Instance + DmaEligible {} + + impl<'d> DmaEligible for AnySpi<'d> { + #[cfg(dma_kind = "gdma")] + type Dma = crate::dma::AnyGdmaChannel<'d>; + #[cfg(dma_kind = "pdma")] + type Dma = crate::dma::AnySpiDmaChannel<'d>; + + fn dma_peripheral(&self) -> crate::dma::DmaPeripheral { + match &self.0 { + #[cfg(spi_master_spi2)] + any::Inner::Spi2(_) => crate::dma::DmaPeripheral::Spi2, + #[cfg(spi_master_spi3)] + any::Inner::Spi3(_) => crate::dma::DmaPeripheral::Spi3, + } + } + } + + #[cfg(soc_has_spi2)] + impl InstanceDma for crate::peripherals::SPI2<'_> {} + #[cfg(soc_has_spi3)] + impl InstanceDma for crate::peripherals::SPI3<'_> {} + + impl InstanceDma for AnySpi<'_> {} +} + +/// A peripheral singleton compatible with the SPI slave driver. +pub trait Instance: crate::private::Sealed + any::Degrade { + /// Returns the peripheral data describing this SPI instance. + #[doc(hidden)] + fn info(&self) -> &'static Info; +} + +/// Peripheral data describing a particular SPI instance. +#[non_exhaustive] +#[doc(hidden)] +pub struct Info { + /// Pointer to the register block for this SPI instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, + + /// System peripheral marker. + pub peripheral: crate::system::Peripheral, + + /// SCLK signal. + pub sclk: InputSignal, + + /// MOSI signal. + pub mosi: InputSignal, + + /// MISO signal. + pub miso: OutputSignal, + + /// Chip select signal. + pub cs: InputSignal, +} + +impl Info { + /// Returns the register block for this SPI instance. + #[instability::unstable] + pub fn regs(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } + + fn reset_spi(&self) { + #[cfg(esp32)] + { + self.regs().slave().modify(|_, w| w.sync_reset().set_bit()); + self.regs() + .slave() + .modify(|_, w| w.sync_reset().clear_bit()); + } + + #[cfg(not(esp32))] + { + self.regs().slave().modify(|_, w| w.soft_reset().set_bit()); + self.regs() + .slave() + .modify(|_, w| w.soft_reset().clear_bit()); + } + } + + #[cfg(esp32)] + fn prepare_length_and_lines(&self, rx_len: usize, tx_len: usize) { + self.regs() + .slv_rdbuf_dlen() + .write(|w| unsafe { w.bits((rx_len as u32 * 8).saturating_sub(1)) }); + self.regs() + .slv_wrbuf_dlen() + .write(|w| unsafe { w.bits((tx_len as u32 * 8).saturating_sub(1)) }); + + // SPI Slave mode on ESP32 requires MOSI/MISO enable + self.regs().user().modify(|_, w| { + w.usr_mosi().bit(rx_len > 0); + w.usr_miso().bit(tx_len > 0) + }); + } + + /// Initialize for full-duplex 1 bit mode + fn init(&self) { + self.regs().clock().write(|w| unsafe { w.bits(0) }); + self.regs().user().write(|w| unsafe { w.bits(0) }); + self.regs().ctrl().write(|w| unsafe { w.bits(0) }); + + self.regs().slave().write(|w| { + #[cfg(esp32)] + w.slv_wr_rd_buf_en().set_bit(); + + w.mode().set_bit() + }); + self.reset_spi(); + + self.regs().user().modify(|_, w| { + w.doutdin().set_bit(); + w.sio().clear_bit() + }); + + #[cfg(not(esp32))] + self.regs().misc().write(|w| unsafe { w.bits(0) }); + } + + fn set_data_mode(&self, data_mode: Mode, dma: bool) { + #[cfg(esp32)] + { + self.regs().pin().modify(|_, w| { + w.ck_idle_edge() + .bit(matches!(data_mode, Mode::_0 | Mode::_1)) + }); + self.regs() + .user() + .modify(|_, w| w.ck_i_edge().bit(matches!(data_mode, Mode::_1 | Mode::_2))); + self.regs().ctrl2().modify(|_, w| unsafe { + match data_mode { + Mode::_0 => { + w.miso_delay_mode().bits(0); + w.miso_delay_num().bits(0); + w.mosi_delay_mode().bits(2); + w.mosi_delay_num().bits(2) + } + Mode::_1 => { + w.miso_delay_mode().bits(2); + w.miso_delay_num().bits(0); + w.mosi_delay_mode().bits(0); + w.mosi_delay_num().bits(0) + } + Mode::_2 => { + w.miso_delay_mode().bits(0); + w.miso_delay_num().bits(0); + w.mosi_delay_mode().bits(1); + w.mosi_delay_num().bits(2) + } + Mode::_3 => { + w.miso_delay_mode().bits(1); + w.miso_delay_num().bits(0); + w.mosi_delay_mode().bits(0); + w.mosi_delay_num().bits(0) + } + } + }); + + if dma { + assert!( + matches!(data_mode, Mode::_1 | Mode::_3), + "Mode {:?} is not supported with DMA", + data_mode + ); + } + } + + #[cfg(not(esp32))] + { + _ = dma; + self.regs().user().modify(|_, w| { + w.tsck_i_edge() + .bit(matches!(data_mode, Mode::_1 | Mode::_2)); + w.rsck_i_edge() + .bit(matches!(data_mode, Mode::_1 | Mode::_2)) + }); + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + let ctrl1_reg = self.regs().ctrl1(); + } else { + let ctrl1_reg = self.regs().slave(); + } + } + ctrl1_reg.modify(|_, w| { + w.clk_mode_13() + .bit(matches!(data_mode, Mode::_1 | Mode::_3)) + }); + } + } + + #[cfg(spi_slave_supports_dma)] + fn is_bus_busy(&self) -> bool { + #[cfg(dma_kind = "pdma")] + { + self.regs().slave().read().trans_done().bit_is_clear() + } + #[cfg(dma_kind = "gdma")] + { + self.regs().dma_int_raw().read().trans_done().bit_is_clear() + } + } + + // Clear the transaction-done interrupt flag so flush() can work properly. + #[cfg(spi_slave_supports_dma)] + fn setup_for_flush(&self) { + #[cfg(dma_kind = "pdma")] + self.regs() + .slave() + .modify(|_, w| w.trans_done().clear_bit()); + #[cfg(dma_kind = "gdma")] + self.regs() + .dma_int_clr() + .write(|w| w.trans_done().clear_bit_by_one()); + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + core::ptr::eq(self.register_block, other.register_block) + } +} + +unsafe impl Sync for Info {} + +for_each_spi_slave! { + ($peri:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident) => { + impl Instance for crate::peripherals::$peri<'_> { + #[inline(always)] + fn info(&self) -> &'static Info { + static INFO: Info = Info { + register_block: crate::peripherals::$peri::regs(), + peripheral: crate::system::Peripheral::$sys, + sclk: InputSignal::$sclk, + mosi: InputSignal::$mosi, + miso: OutputSignal::$miso, + cs: InputSignal::$cs, + }; + + &INFO + } + } + }; +} + +crate::any_peripheral! { + /// Any SPI peripheral. + pub peripheral AnySpi<'d> { + #[cfg(spi_master_spi2)] + Spi2(crate::peripherals::SPI2<'d>), + #[cfg(spi_master_spi3)] + Spi3(crate::peripherals::SPI3<'d>), + } +} + +impl Instance for AnySpi<'_> { + fn info(&self) -> &'static Info { + any::delegate!(self, spi => { spi.info() }) + } +} diff --git a/esp-hal/src/sync.rs b/esp-hal/src/sync.rs new file mode 100644 index 00000000000..7e0b8f2d43b --- /dev/null +++ b/esp-hal/src/sync.rs @@ -0,0 +1,110 @@ +//! Under construction: This is public only for tests, please avoid using it +//! directly. + +use core::sync::atomic::{Ordering, compiler_fence}; + +#[cfg(riscv)] +use esp_sync::raw::SingleCoreInterruptLock; +use esp_sync::{GenericRawMutex, RestoreState, raw::RawLock}; + +use crate::interrupt::{Priority, RunLevel}; + +/// A lock that disables interrupts below a certain priority. +pub struct PriorityLock(pub RunLevel); + +impl PriorityLock { + fn current_priority() -> RunLevel { + RunLevel::current() + } + + /// Prevents interrupts above `level` from firing and returns the + /// current run level. + unsafe fn change_current_level(level: RunLevel) -> RunLevel { + unsafe { RunLevel::change(level) } + } +} + +impl RawLock for PriorityLock { + unsafe fn enter(&self) -> RestoreState { + #[cfg(riscv)] + if self.0 == Priority::max() { + return unsafe { SingleCoreInterruptLock.enter() }; + } + + let prev_level = unsafe { Self::change_current_level(self.0) }; + assert!(prev_level <= self.0); + + // Ensure no subsequent memory accesses are reordered to before interrupts are + // disabled. + compiler_fence(Ordering::SeqCst); + + unsafe { RestoreState::new(u32::from(prev_level)) } + } + + unsafe fn exit(&self, token: RestoreState) { + #[cfg(riscv)] + if self.0 == Priority::max() { + return unsafe { SingleCoreInterruptLock.exit(token) }; + } + assert!(Self::current_priority() <= self.0); + // Ensure no preceeding memory accesses are reordered to after interrupts are + // enabled. + compiler_fence(Ordering::SeqCst); + + let level = unwrap!(RunLevel::try_from_u32(token.inner())); + unsafe { Self::change_current_level(level) }; + } +} + +/// A mutual exclusion primitive that only disables a limited range of +/// interrupts. +/// +/// Trying to acquire or release the lock at a higher priority level will panic. +pub struct RawPriorityLimitedMutex { + inner: GenericRawMutex, +} + +impl RawPriorityLimitedMutex { + /// Create a new lock that is accessible at or below the given `priority`. + pub const fn new(priority: Priority) -> Self { + Self { + inner: GenericRawMutex::new(PriorityLock(RunLevel::Interrupt(priority))), + } + } + + /// Runs the callback with this lock locked. + pub fn lock(&self, f: impl FnOnce() -> R) -> R { + self.inner.lock(f) + } +} + +unsafe impl embassy_sync::blocking_mutex::raw::RawMutex for RawPriorityLimitedMutex { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = Self::new(Priority::max()); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + self.inner.lock(f) + } +} + +#[cfg(impl_critical_section)] +#[cfg(feature = "rt")] +mod critical_section { + struct CriticalSection; + + critical_section::set_impl!(CriticalSection); + + static CRITICAL_SECTION: esp_sync::RawMutex = esp_sync::RawMutex::new(); + + unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> critical_section::RawRestoreState { + unsafe { CRITICAL_SECTION.acquire().inner() } + } + + unsafe fn release(token: critical_section::RawRestoreState) { + unsafe { + CRITICAL_SECTION.release(esp_sync::RestoreState::new(token)); + } + } + } +} diff --git a/esp-hal/src/system.rs b/esp-hal/src/system.rs new file mode 100644 index 00000000000..25414af87b5 --- /dev/null +++ b/esp-hal/src/system.rs @@ -0,0 +1,396 @@ +//! # System Control + +use esp_sync::NonReentrantMutex; + +cfg_if::cfg_if! { + if #[cfg(all(soc_multi_core_enabled, feature = "unstable"))] { + pub(crate) mod multi_core; + #[cfg(feature = "unstable")] + pub use multi_core::*; + } +} + +// Implements the Peripheral enum based on esp-metadata/device.soc/peripheral_clocks +implement_peripheral_clocks!(); + +impl Peripheral { + pub const fn try_from(value: u8) -> Option { + if value >= Peripheral::COUNT as u8 { + return None; + } + + Some(unsafe { core::mem::transmute::(value) }) + } +} + +struct RefCounts { + counts: [usize; Peripheral::COUNT], +} + +impl RefCounts { + pub const fn new() -> Self { + Self { + counts: [0; Peripheral::COUNT], + } + } +} + +static PERIPHERAL_REF_COUNT: NonReentrantMutex = + NonReentrantMutex::new(RefCounts::new()); + +/// Disable all peripherals. +/// +/// Peripherals listed in [KEEP_ENABLED] are NOT disabled. +#[cfg_attr(not(feature = "rt"), expect(dead_code))] +pub(crate) fn disable_peripherals() { + // Take the critical section up front to avoid taking it multiple times. + PERIPHERAL_REF_COUNT.with(|refcounts| { + for p in Peripheral::KEEP_ENABLED { + refcounts.counts[*p as usize] += 1; + } + for p in Peripheral::ALL { + let ref_count = refcounts.counts[*p as usize]; + if ref_count == 0 { + PeripheralClockControl::enable_forced_with_counts(*p, false, true, refcounts); + } + } + }) +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct PeripheralGuard { + peripheral: Peripheral, +} + +impl PeripheralGuard { + pub(crate) fn new_with(p: Peripheral, init: fn()) -> Self { + if PeripheralClockControl::enable(p) { + PeripheralClockControl::reset(p); + init(); + } + + Self { peripheral: p } + } + + pub(crate) fn new(p: Peripheral) -> Self { + Self::new_with(p, || {}) + } +} + +impl Clone for PeripheralGuard { + fn clone(&self) -> Self { + Self::new(self.peripheral) + } + + fn clone_from(&mut self, _source: &Self) { + // This is a no-op since the ref count for P remains the same. + } +} + +impl Drop for PeripheralGuard { + fn drop(&mut self) { + PeripheralClockControl::disable(self.peripheral); + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct GenericPeripheralGuard {} + +impl GenericPeripheralGuard

    { + pub(crate) fn new_with(init: fn()) -> Self { + let peripheral = const { Peripheral::try_from(P).unwrap() }; + + PERIPHERAL_REF_COUNT.with(|ref_counts| { + if PeripheralClockControl::enable_with_counts(peripheral, ref_counts) { + unsafe { PeripheralClockControl::reset_racey(peripheral) }; + init(); + } + }); + + Self {} + } + + #[cfg_attr(not(feature = "unstable"), allow(unused))] + pub(crate) fn new() -> Self { + Self::new_with(|| {}) + } +} + +impl Clone for GenericPeripheralGuard

    { + fn clone(&self) -> Self { + Self::new() + } + + fn clone_from(&mut self, _source: &Self) { + // This is a no-op since the ref count for P remains the same. + } +} + +impl Drop for GenericPeripheralGuard

    { + fn drop(&mut self) { + let peripheral = const { Peripheral::try_from(P).unwrap() }; + PeripheralClockControl::disable(peripheral); + } +} + +/// Controls the enablement of peripheral clocks. +pub(crate) struct PeripheralClockControl; + +impl PeripheralClockControl { + /// Enables the given peripheral. + /// + /// This keeps track of enabling a peripheral - i.e. a peripheral + /// is only enabled with the first call attempt to enable it. + /// + /// Returns `true` if it actually enabled the peripheral. + pub(crate) fn enable(peripheral: Peripheral) -> bool { + PERIPHERAL_REF_COUNT.with(|ref_counts| Self::enable_with_counts(peripheral, ref_counts)) + } + + /// Enables the given peripheral. + /// + /// This keeps track of enabling a peripheral - i.e. a peripheral + /// is only enabled with the first call attempt to enable it. + /// + /// Returns `true` if it actually enabled the peripheral. + fn enable_with_counts(peripheral: Peripheral, ref_counts: &mut RefCounts) -> bool { + Self::enable_forced_with_counts(peripheral, true, false, ref_counts) + } + + /// Disables the given peripheral. + /// + /// This keeps track of disabling a peripheral - i.e. it only + /// gets disabled when the number of enable/disable attempts is balanced. + /// + /// Returns `true` if it actually disabled the peripheral. + /// + /// Before disabling a peripheral it will also get reset + pub(crate) fn disable(peripheral: Peripheral) -> bool { + PERIPHERAL_REF_COUNT.with(|ref_counts| { + Self::enable_forced_with_counts(peripheral, false, false, ref_counts) + }) + } + + fn enable_forced_with_counts( + peripheral: Peripheral, + enable: bool, + force: bool, + ref_counts: &mut RefCounts, + ) -> bool { + let ref_count = &mut ref_counts.counts[peripheral as usize]; + if !force { + let prev = *ref_count; + if enable { + *ref_count += 1; + trace!("Enable {:?} {} -> {}", peripheral, prev, *ref_count); + if prev > 0 { + return false; + } + } else { + assert!(prev != 0); + *ref_count -= 1; + trace!("Disable {:?} {} -> {}", peripheral, prev, *ref_count); + if prev > 1 { + return false; + } + }; + } else if !enable { + assert!(*ref_count == 0); + } + + if !enable { + unsafe { Self::reset_racey(peripheral) }; + } + + debug!("Enable {:?} {}", peripheral, enable); + unsafe { enable_internal_racey(peripheral, enable) }; + + true + } + + /// Resets the given peripheral + pub(crate) unsafe fn reset_racey(peripheral: Peripheral) { + debug!("Reset {:?}", peripheral); + + unsafe { + assert_peri_reset_racey(peripheral, true); + assert_peri_reset_racey(peripheral, false); + } + } + + /// Resets the given peripheral + pub(crate) fn reset(peripheral: Peripheral) { + PERIPHERAL_REF_COUNT.with(|_| unsafe { Self::reset_racey(peripheral) }) + } +} + +/// Available CPU cores +/// +/// The actual number of available cores depends on the target. +#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::FromRepr)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub enum Cpu { + /// The first core + ProCpu = 0, + /// The second core + #[cfg(multi_core)] + AppCpu = 1, +} + +impl Cpu { + /// The number of available cores. + pub const COUNT: usize = 1 + cfg!(multi_core) as usize; + + #[procmacros::doc_replace] + /// Returns the core the application is currently executing on + /// + /// ```rust, no_run + /// # {before_snippet} + /// # + /// use esp_hal::system::Cpu; + /// let current_cpu = Cpu::current(); + /// # + /// # {after_snippet} + /// ``` + #[inline(always)] + pub fn current() -> Self { + // This works for both RISCV and Xtensa because both + // get_raw_core functions return zero, _or_ something + // greater than zero; 1 in the case of RISCV and 0x2000 + // in the case of Xtensa. + match raw_core() { + 0 => Cpu::ProCpu, + #[cfg(all(multi_core, riscv))] + 1 => Cpu::AppCpu, + #[cfg(all(multi_core, xtensa))] + 0x2000 => Cpu::AppCpu, + _ => unreachable!(), + } + } + + /// Returns an iterator over the "other" cores. + #[inline(always)] + #[instability::unstable] + pub fn other() -> impl Iterator { + cfg_if::cfg_if! { + if #[cfg(multi_core)] { + match Self::current() { + Cpu::ProCpu => [Cpu::AppCpu].into_iter(), + Cpu::AppCpu => [Cpu::ProCpu].into_iter(), + } + } else { + [].into_iter() + } + } + } + + /// Returns an iterator over all cores. + #[inline(always)] + pub fn all() -> impl Iterator { + cfg_if::cfg_if! { + if #[cfg(multi_core)] { + [Cpu::ProCpu, Cpu::AppCpu].into_iter() + } else { + [Cpu::ProCpu].into_iter() + } + } + } +} + +/// Returns the raw value of the mhartid register. +/// +/// On RISC-V, this is the hardware thread ID. +/// +/// On Xtensa, this returns the result of reading the PRID register logically +/// ANDed with 0x2000, the 13th bit in the register. Espressif Xtensa chips use +/// this bit to determine the core id. +#[inline(always)] +pub(crate) fn raw_core() -> usize { + // This method must never return UNUSED_THREAD_ID_VALUE + cfg_if::cfg_if! { + if #[cfg(all(multi_core, riscv))] { + riscv::register::mhartid::read() + } else if #[cfg(all(multi_core, xtensa))] { + (xtensa_lx::get_processor_id() & 0x2000) as usize + } else { + 0 + } + } +} + +use crate::rtc_cntl::SocResetReason; + +/// Source of the wakeup event +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum SleepSource { + /// In case of deep sleep, reset was not caused by exit from deep sleep + Undefined = 0, + /// Not a wakeup cause, used to disable all wakeup sources with + /// esp_sleep_disable_wakeup_source + All, + /// Wakeup caused by external signal using RTC_IO + Ext0, + /// Wakeup caused by external signal using RTC_CNTL + Ext1, + /// Wakeup caused by timer + Timer, + /// Wakeup caused by touchpad + TouchPad, + /// Wakeup caused by ULP program + Ulp, + /// Wakeup caused by GPIO (light sleep only on ESP32, S2 and S3) + Gpio, + /// Wakeup caused by UART (light sleep only) + Uart, + /// Wakeup caused by WIFI (light sleep only) + Wifi, + /// Wakeup caused by COCPU int + Cocpu, + /// Wakeup caused by COCPU crash + CocpuTrapTrig, + /// Wakeup caused by BT (light sleep only) + BT, +} + +#[procmacros::doc_replace] +/// Performs a software reset on the chip. +/// +/// # Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::system::software_reset; +/// software_reset(); +/// # {after_snippet} +/// ``` +#[inline] +pub fn software_reset() -> ! { + crate::rom::software_reset() +} + +/// Resets the given CPU, leaving peripherals unchanged. +#[instability::unstable] +#[inline] +pub fn software_reset_cpu(cpu: Cpu) { + crate::rom::software_reset_cpu(cpu as u32) +} + +/// Retrieves the reason for the last reset as a SocResetReason enum value. +/// Returns `None` if the reset reason cannot be determined. +#[instability::unstable] +#[inline] +pub fn reset_reason() -> Option { + crate::rtc_cntl::reset_reason(Cpu::current()) +} + +/// Retrieves the cause of the last wakeup event as a SleepSource enum value. +#[instability::unstable] +#[inline] +pub fn wakeup_cause() -> SleepSource { + crate::rtc_cntl::wakeup_cause() +} diff --git a/esp-hal/src/system/multi_core.rs b/esp-hal/src/system/multi_core.rs new file mode 100644 index 00000000000..23834ae1ca2 --- /dev/null +++ b/esp-hal/src/system/multi_core.rs @@ -0,0 +1,302 @@ +//! Multi-core support + +use core::{ + marker::PhantomData, + mem::{ManuallyDrop, MaybeUninit}, + sync::atomic::{AtomicPtr, Ordering}, +}; + +#[cfg(feature = "unstable")] +pub use crate::soc::cpu_control::is_running; +#[cfg(not(feature = "unstable"))] +use crate::soc::cpu_control::is_running; +use crate::{ + peripherals::CPU_CTRL, + soc::cpu_control::{internal_park_core, start_core1_init}, + system::Cpu, +}; + +/// Data type for a properly aligned stack of N bytes +// Xtensa ISA 10.5: [B]y default, the +// stack frame is 16-byte aligned. However, the maximal alignment allowed for a +// TIE ctype is 64-bytes. If a function has any wide-aligned (>16-byte aligned) +// data type for their arguments or the return values, the caller has to ensure +// that the SP is aligned to the largest alignment right before the call. +// +// ^ this means that we should be able to get away with 16 bytes of alignment +// because our root stack frame has no arguments and no return values. +// +// This alignment also doesn't align the stack frames, only the end of stack. +// Stack frame alignment depends on the SIZE as well as the placement of the +// array. +#[repr(C, align(16))] +#[instability::unstable] +pub struct Stack { + /// Memory to be used for the stack + pub mem: MaybeUninit<[u8; SIZE]>, +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +#[allow(clippy::len_without_is_empty)] +impl Stack { + /// Construct a stack of length SIZE, uninitialized + #[instability::unstable] + pub const fn new() -> Stack { + const { + // Make sure stack top is aligned, too. + ::core::assert!(SIZE.is_multiple_of(16)); + } + + Stack { + mem: MaybeUninit::uninit(), + } + } + + /// Returns the length of the stack in bytes. + #[instability::unstable] + pub const fn len(&self) -> usize { + SIZE + } + + /// Returns a mutable pointer to the bottom of the stack. + #[instability::unstable] + pub fn bottom(&mut self) -> *mut u32 { + self.mem.as_mut_ptr() as *mut u32 + } + + /// Returns a mutable pointer to the top of the stack. + #[instability::unstable] + pub fn top(&mut self) -> *mut u32 { + unsafe { self.bottom().add(SIZE / 4) } + } +} + +// Pointer to the closure that will be executed on the second core. The closure +// is copied to the core's stack. +pub(crate) static START_CORE1_FUNCTION: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); +pub(crate) static APP_CORE_STACK_TOP: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); +pub(crate) static APP_CORE_STACK_GUARD: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +/// Will park the APP (second) core when dropped +#[must_use = "Dropping this guard will park the APP core"] +#[instability::unstable] +pub struct AppCoreGuard<'a> { + phantom: PhantomData<&'a ()>, +} + +impl Drop for AppCoreGuard<'_> { + fn drop(&mut self) { + unsafe { internal_park_core(Cpu::AppCpu, true) }; + } +} + +/// Represents errors that can occur while working with the core. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum Error { + /// The core is already running. + CoreAlreadyRunning, +} + +#[procmacros::doc_replace] +/// Control CPU Cores +/// +/// ## Examples +/// ```rust, no_run +/// # {before_snippet} +/// # use esp_hal::delay::Delay; +/// # use esp_hal::system::{CpuControl, Stack}; +/// # use core::{cell::RefCell, ptr::addr_of_mut}; +/// # use critical_section::Mutex; +/// # let delay = Delay::new(); +/// static mut APP_CORE_STACK: Stack<8192> = Stack::new(); +/// +/// let counter = Mutex::new(RefCell::new(0)); +/// +/// let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL); +/// let cpu1_fnctn = || { +/// cpu1_task(&delay, &counter); +/// }; +/// let _guard = +/// cpu_control.start_app_core(unsafe { &mut *addr_of_mut!(APP_CORE_STACK) }, cpu1_fnctn)?; +/// +/// loop { +/// delay.delay(Duration::from_secs(1)); +/// let count = critical_section::with(|cs| *counter.borrow_ref(cs)); +/// } +/// # } +/// +/// // Where `cpu1_task()` may be defined as: +/// # use esp_hal::delay::Delay; +/// # use core::cell::RefCell; +/// +/// fn cpu1_task(delay: &Delay, counter: &critical_section::Mutex>) -> ! { +/// loop { +/// delay.delay(Duration::from_millis(500)); +/// +/// critical_section::with(|cs| { +/// let mut val = counter.borrow_ref_mut(cs); +/// *val = val.wrapping_add(1); +/// }); +/// } +/// } +/// ``` +#[instability::unstable] +pub struct CpuControl<'d> { + _cpu_control: CPU_CTRL<'d>, +} + +impl<'d> CpuControl<'d> { + /// Creates a new instance of `CpuControl`. + #[instability::unstable] + pub fn new(cpu_control: CPU_CTRL<'d>) -> CpuControl<'d> { + CpuControl { + _cpu_control: cpu_control, + } + } + + /// Park the given core + /// + /// # Safety + /// + /// The user must ensure that the core being parked is not the core which is + /// currently executing their code. + #[instability::unstable] + pub unsafe fn park_core(&mut self, core: Cpu) { + unsafe { internal_park_core(core, true) }; + } + + /// Unpark the given core + #[instability::unstable] + pub fn unpark_core(&mut self, core: Cpu) { + unsafe { internal_park_core(core, false) }; + } + + /// Run the core1 closure. + #[inline(never)] + pub(crate) unsafe fn start_core1_run() -> ! + where + F: FnOnce(), + { + let entry = START_CORE1_FUNCTION.load(Ordering::Acquire); + debug_assert!(!entry.is_null()); + + unsafe { + let entry = ManuallyDrop::take(&mut *entry.cast::>()); + entry(); + loop { + internal_park_core(Cpu::current(), true); + } + } + } + + /// Start the APP (second) core. + /// + /// The second core will start running the closure `entry`. Note that if the + /// closure exits, the core will be parked. + /// + /// Dropping the returned guard will park the core. + #[instability::unstable] + pub fn start_app_core<'a, const SIZE: usize, F>( + &mut self, + stack: &'static mut Stack, + entry: F, + ) -> Result, Error> + where + F: FnOnce(), + F: Send + 'a, + { + cfg_if::cfg_if! { + if #[cfg(all(stack_guard_monitoring))] { + let stack_guard_offset = Some(esp_config::esp_config_int!( + usize, + "ESP_HAL_CONFIG_STACK_GUARD_OFFSET" + )); + } else { + let stack_guard_offset = None; + } + }; + + self.start_app_core_with_stack_guard_offset(stack, stack_guard_offset, entry) + } + + /// Start the APP (second) core. + /// + /// The second core will start running the closure `entry`. Note that if the + /// closure exits, the core will be parked. + /// + /// Dropping the returned guard will park the core. + #[instability::unstable] + pub fn start_app_core_with_stack_guard_offset<'a, const SIZE: usize, F>( + &mut self, + stack: &'static mut Stack, + stack_guard_offset: Option, + entry: F, + ) -> Result, Error> + where + F: FnOnce(), + F: Send + 'a, + { + if !crate::debugger::debugger_connected() && is_running(Cpu::AppCpu) { + return Err(Error::CoreAlreadyRunning); + } + + setup_second_core_stack(stack, stack_guard_offset, entry); + + crate::soc::cpu_control::start_core1(start_core1_init:: as *const u32); + + self.unpark_core(Cpu::AppCpu); + + Ok(AppCoreGuard { + phantom: PhantomData, + }) + } +} + +fn setup_second_core_stack<'a, F, const SIZE: usize>( + stack: &'static mut Stack, + stack_guard_offset: Option, + entry: F, +) where + F: FnOnce(), + F: Send + 'a, +{ + // We don't want to drop this, since it's getting moved to the other core. + let entry = ManuallyDrop::new(entry); + + unsafe { + let stack_bottom = stack.bottom().cast::(); + let (stack_guard, stack_bottom_above_guard) = + if let Some(stack_guard_offset) = stack_guard_offset { + assert!(stack_guard_offset.is_multiple_of(4)); + assert!(stack_guard_offset <= stack.len() - 4); + ( + stack_bottom.byte_add(stack_guard_offset), + stack_bottom.byte_add(stack_guard_offset).byte_add(4), + ) + } else { + (core::ptr::null_mut(), stack_bottom) + }; + + // Push `entry` to an aligned address at the (physical) bottom of the stack, but above + // the stack guard. The second core will copy it into its proper place, then + // calls it. + let align_offset = stack_bottom_above_guard.align_offset(core::mem::align_of::()); + let entry_dst = stack_bottom_above_guard + .add(align_offset) + .cast::>(); + + entry_dst.write(entry); + + let entry_fn = entry_dst.cast::<()>(); + START_CORE1_FUNCTION.store(entry_fn, Ordering::Release); + APP_CORE_STACK_TOP.store(stack.top(), Ordering::Release); + APP_CORE_STACK_GUARD.store(stack_guard.cast(), Ordering::Release); + } +} diff --git a/esp-hal/src/time.rs b/esp-hal/src/time.rs new file mode 100644 index 00000000000..1cfc2ecd3ac --- /dev/null +++ b/esp-hal/src/time.rs @@ -0,0 +1,782 @@ +//! # Timekeeping +//! +//! This module provides types for representing frequency and duration, as well +//! as an instant in time. Time is measured since boot, and can be accessed +//! by the [`Instant::now`] function. + +use core::fmt::{Debug, Display, Formatter, Result as FmtResult}; + +type InnerRate = fugit::Rate; +type InnerInstant = fugit::Instant; +type InnerDuration = fugit::Duration; + +/// Represents a rate or frequency of events. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Rate(InnerRate); + +impl core::hash::Hash for Rate { + #[inline] + fn hash(&self, state: &mut H) { + self.as_hz().hash(state); + } +} + +impl Display for Rate { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{} Hz", self.as_hz()) + } +} + +impl Debug for Rate { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Rate({} Hz)", self.as_hz()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Rate { + #[inline] + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "{=u32} Hz", self.as_hz()) + } +} + +impl Rate { + #[procmacros::doc_replace] + /// Shorthand for creating a rate which represents hertz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_hz(1000); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_hz(val: u32) -> Self { + Self(InnerRate::Hz(val)) + } + + #[procmacros::doc_replace] + /// Shorthand for creating a rate which represents kilohertz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_khz(1000); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_khz(val: u32) -> Self { + Self(InnerRate::kHz(val)) + } + + #[procmacros::doc_replace] + /// Shorthand for creating a rate which represents megahertz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_mhz(1000); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_mhz(val: u32) -> Self { + Self(InnerRate::MHz(val)) + } + + #[procmacros::doc_replace] + /// Convert the `Rate` to an integer number of Hz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_hz(1000); + /// let hz = rate.as_hz(); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn as_hz(&self) -> u32 { + self.0.to_Hz() + } + + #[procmacros::doc_replace] + /// Convert the `Rate` to an integer number of kHz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_khz(1000); + /// let khz = rate.as_khz(); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn as_khz(&self) -> u32 { + self.0.to_kHz() + } + + #[procmacros::doc_replace] + /// Convert the `Rate` to an integer number of MHz. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_mhz(1000); + /// let mhz = rate.as_mhz(); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn as_mhz(&self) -> u32 { + self.0.to_MHz() + } + + #[procmacros::doc_replace] + /// Convert the `Rate` to a `Duration`. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Rate; + /// let rate = Rate::from_hz(1000); + /// let duration = rate.as_duration(); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn as_duration(&self) -> Duration { + Duration::from_micros(1_000_000 / self.as_hz() as u64) + } +} + +impl core::ops::Div for Rate { + type Output = u32; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + self.0 / rhs.0 + } +} + +impl core::ops::Mul for Rate { + type Output = Rate; + + #[inline] + fn mul(self, rhs: u32) -> Self::Output { + Rate(self.0 * rhs) + } +} + +impl core::ops::Div for Rate { + type Output = Rate; + + #[inline] + fn div(self, rhs: u32) -> Self::Output { + Rate(self.0 / rhs) + } +} + +/// Represents an instant in time. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instant(InnerInstant); + +impl Debug for Instant { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!( + f, + "Instant({} µs since epoch)", + self.duration_since_epoch().as_micros() + ) + } +} + +impl Display for Instant { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!( + f, + "{} µs since epoch", + self.duration_since_epoch().as_micros() + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Instant { + #[inline] + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!( + f, + "{=u64} µs since epoch", + self.duration_since_epoch().as_micros() + ) + } +} + +impl core::hash::Hash for Instant { + #[inline] + fn hash(&self, state: &mut H) { + self.duration_since_epoch().hash(state); + } +} + +impl Instant { + /// Represents the moment the system booted. + pub const EPOCH: Instant = Instant(InnerInstant::from_ticks(0)); + + #[procmacros::doc_replace( + "wrap_after" => { + cfg(esp32) => "36_558 years", + cfg(esp32s2) => "7_311 years", + _ => "more than 7 years" + } + )] + /// Returns the current instant. + /// + /// The counter won’t measure time in sleep-mode. + /// + /// The timer has a 1 microsecond resolution and will wrap after __wrap_after__. + /// + ///

    + /// Note that this function returns an unreliable value before esp_hal::init() is + /// called.
    + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Instant; + /// let now = Instant::now(); + /// # {after_snippet} + /// ``` + #[inline] + pub fn now() -> Self { + implem::now() + } + + #[inline] + pub(crate) fn from_ticks(ticks: u64) -> Self { + Instant(InnerInstant::from_ticks(ticks)) + } + + #[procmacros::doc_replace] + /// Returns the elapsed `Duration` since boot. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Instant; + /// let now = Instant::now(); + /// let duration = now.duration_since_epoch(); + /// # {after_snippet} + /// ``` + #[inline] + pub fn duration_since_epoch(&self) -> Duration { + *self - Self::EPOCH + } + + #[procmacros::doc_replace] + /// Returns the elapsed `Duration` since this `Instant` was created. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Instant; + /// let now = Instant::now(); + /// let duration = now.elapsed(); + /// # {after_snippet} + /// ``` + #[inline] + pub fn elapsed(&self) -> Duration { + Self::now() - *self + } +} + +impl core::ops::Add for Instant { + type Output = Self; + + #[inline] + fn add(self, rhs: Duration) -> Self::Output { + Instant(self.0 + rhs.0) + } +} + +impl core::ops::AddAssign for Instant { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + self.0 += rhs.0; + } +} + +impl core::ops::Sub for Instant { + type Output = Duration; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + // Avoid "Sub failed! Other > self" panics + Duration::from_micros(self.0.ticks().wrapping_sub(rhs.0.ticks())) + } +} + +impl core::ops::Sub for Instant { + type Output = Self; + + #[inline] + fn sub(self, rhs: Duration) -> Self::Output { + Instant(self.0 - rhs.0) + } +} + +impl core::ops::SubAssign for Instant { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + self.0 -= rhs.0; + } +} + +/// Represents a duration of time. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Duration(InnerDuration); + +impl Debug for Duration { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Duration({} µs)", self.as_micros()) + } +} + +impl Display for Duration { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{} µs", self.as_micros()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Duration { + #[inline] + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "{=u64} µs", self.as_micros()) + } +} + +impl core::hash::Hash for Duration { + #[inline] + fn hash(&self, state: &mut H) { + self.as_micros().hash(state); + } +} + +impl Duration { + /// A duration of zero time. + pub const ZERO: Self = Self(InnerDuration::from_ticks(0)); + + /// A duration representing the maximum possible time. + pub const MAX: Self = Self(InnerDuration::from_ticks(u64::MAX)); + + #[procmacros::doc_replace] + /// Creates a duration which represents microseconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_micros(1000); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_micros(val: u64) -> Self { + Self(InnerDuration::micros(val)) + } + + #[procmacros::doc_replace] + /// Creates a duration which represents milliseconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_millis(100); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_millis(val: u64) -> Self { + Self(InnerDuration::millis(val)) + } + + #[procmacros::doc_replace] + /// Creates a duration which represents seconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(1); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_secs(val: u64) -> Self { + Self(InnerDuration::secs(val)) + } + + #[procmacros::doc_replace] + /// Creates a duration which represents minutes. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_minutes(1); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_minutes(val: u64) -> Self { + Self(InnerDuration::minutes(val)) + } + + #[procmacros::doc_replace] + /// Creates a duration which represents hours. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_hours(1); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn from_hours(val: u64) -> Self { + Self(InnerDuration::hours(val)) + } + + delegate::delegate! { + #[inline] + to self.0 { + #[procmacros::doc_replace] + /// Convert the `Duration` to an integer number of microseconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_micros(1000); + /// let micros = duration.as_micros(); + /// # {after_snippet} + /// ``` + #[call(to_micros)] + pub const fn as_micros(&self) -> u64; + + #[procmacros::doc_replace] + /// Convert the `Duration` to an integer number of milliseconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_millis(100); + /// let millis = duration.as_millis(); + /// # {after_snippet} + /// ``` + #[call(to_millis)] + pub const fn as_millis(&self) -> u64; + + #[procmacros::doc_replace] + /// Convert the `Duration` to an integer number of seconds. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(1); + /// let secs = duration.as_secs(); + /// # {after_snippet} + /// ``` + #[call(to_secs)] + pub const fn as_secs(&self) -> u64; + + #[procmacros::doc_replace] + /// Convert the `Duration` to an integer number of minutes. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_minutes(1); + /// let minutes = duration.as_minutes(); + /// # {after_snippet} + /// ``` + #[call(to_minutes)] + pub const fn as_minutes(&self) -> u64; + + #[procmacros::doc_replace] + /// Convert the `Duration` to an integer number of hours. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_hours(1); + /// let hours = duration.as_hours(); + /// # {after_snippet} + /// ``` + #[call(to_hours)] + pub const fn as_hours(&self) -> u64; + } + } + + #[procmacros::doc_replace] + /// Add two durations while checking for overflow. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(1); + /// let duration2 = Duration::from_secs(2); + /// + /// if let Some(sum) = duration.checked_add(duration2) { + /// println!("Sum: {}", sum); + /// } else { + /// println!("Overflow occurred"); + /// } + /// # {after_snippet} + /// ``` + #[inline] + pub const fn checked_add(self, rhs: Self) -> Option { + if let Some(val) = self.0.checked_add(rhs.0) { + Some(Duration(val)) + } else { + None + } + } + + #[procmacros::doc_replace] + /// Subtract two durations while checking for overflow. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(3); + /// let duration2 = Duration::from_secs(1); + /// + /// if let Some(diff) = duration.checked_sub(duration2) { + /// println!("Difference: {}", diff); + /// } else { + /// println!("Underflow occurred"); + /// } + /// # {after_snippet} + /// ``` + #[inline] + pub const fn checked_sub(self, rhs: Self) -> Option { + if let Some(val) = self.0.checked_sub(rhs.0) { + Some(Duration(val)) + } else { + None + } + } + + #[procmacros::doc_replace] + /// Add two durations, returning the maximum value if overflow occurred. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(1); + /// let duration2 = Duration::from_secs(2); + /// + /// let sum = duration.saturating_add(duration2); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn saturating_add(self, rhs: Self) -> Self { + if let Some(val) = self.checked_add(rhs) { + val + } else { + Self::MAX + } + } + + #[procmacros::doc_replace] + /// Subtract two durations, returning the minimum value if the result would + /// be negative. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::time::Duration; + /// let duration = Duration::from_secs(3); + /// let duration2 = Duration::from_secs(1); + /// + /// let diff = duration.saturating_sub(duration2); + /// # {after_snippet} + /// ``` + #[inline] + pub const fn saturating_sub(self, rhs: Self) -> Self { + if let Some(val) = self.checked_sub(rhs) { + val + } else { + Self::ZERO + } + } +} + +impl core::ops::Add for Duration { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Duration(self.0 + rhs.0) + } +} + +impl core::ops::AddAssign for Duration { + #[inline] + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl core::ops::Sub for Duration { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + Duration(self.0 - rhs.0) + } +} + +impl core::ops::SubAssign for Duration { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + +impl core::ops::Mul for Duration { + type Output = Self; + + #[inline] + fn mul(self, rhs: u32) -> Self::Output { + Duration(self.0 * rhs) + } +} + +impl core::ops::Div for Duration { + type Output = Self; + + #[inline] + fn div(self, rhs: u32) -> Self::Output { + Duration(self.0 / rhs) + } +} + +impl core::ops::Div for Duration { + type Output = u64; + + #[inline] + fn div(self, rhs: Duration) -> Self::Output { + self.0 / rhs.0 + } +} + +#[cfg(esp32)] +pub(crate) mod implem { + use super::Instant; + use crate::peripherals::TIMG0; + + #[cfg(feature = "rt")] + pub(crate) fn time_init() { + let apb = crate::Clocks::get().apb_clock.as_hz(); + + let tg0 = TIMG0::regs(); + + tg0.lactconfig().write(|w| unsafe { w.bits(0) }); + tg0.lactalarmhi().write(|w| unsafe { w.bits(u32::MAX) }); + tg0.lactalarmlo().write(|w| unsafe { w.bits(u32::MAX) }); + tg0.lactload().write(|w| unsafe { w.load().bits(1) }); + + // 16 MHz counter + tg0.lactconfig().write(|w| { + unsafe { w.divider().bits((apb / 16_000_000u32) as u16) }; + w.increase().bit(true); + w.autoreload().bit(true); + w.en().bit(true) + }); + } + + #[inline] + pub(super) fn now() -> Instant { + // on ESP32 use LACT + let tg0 = TIMG0::regs(); + tg0.lactupdate().write(|w| unsafe { w.update().bits(1) }); + + // The peripheral doesn't have a bit to indicate that the update is done, so we + // poll the lower 32 bit part of the counter until it changes, or a timeout + // expires. + let lo_initial = tg0.lactlo().read().bits(); + let mut div = tg0.lactconfig().read().divider().bits(); + let lo = loop { + let lo = tg0.lactlo().read().bits(); + if lo != lo_initial || div == 0 { + break lo; + } + div -= 1; + }; + let hi = tg0.lacthi().read().bits(); + + let ticks = ((hi as u64) << 32u64) | lo as u64; + + Instant::from_ticks(ticks / 16) + } +} + +#[cfg(systimer_driver_supported)] +pub(crate) mod implem { + use super::Instant; + use crate::timer::systimer::{SystemTimer, Unit}; + + #[cfg(feature = "rt")] + pub(crate) fn time_init() { + SystemTimer::init_timestamp_scaler(); + } + + #[inline] + pub(super) fn now() -> Instant { + let ticks = SystemTimer::unit_value(Unit::Unit0); + + let micros = SystemTimer::ticks_to_us(ticks); + + Instant::from_ticks(micros) + } +} diff --git a/esp-hal/src/timer/mod.rs b/esp-hal/src/timer/mod.rs new file mode 100644 index 00000000000..409191b04bf --- /dev/null +++ b/esp-hal/src/timer/mod.rs @@ -0,0 +1,463 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # General-purpose Timers +//! +//! ## Overview +//! The [OneShotTimer] and [PeriodicTimer] types can be backed by any hardware +//! peripheral which implements the [Timer] trait. This means that the same API +//! can be used to interact with different hardware timers, like the `TIMG` and +//! SYSTIMER. +#![cfg_attr( + systimer_driver_supported, + doc = "See the [timg] and [systimer] modules for more information." +)] +#![cfg_attr( + not(systimer_driver_supported), + doc = "See the [timg] module for more information." +)] +//! ## Examples +//! +//! ### One-shot Timer +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::timer::{OneShotTimer, PeriodicTimer, timg::TimerGroup}; +//! # +//! let timg0 = TimerGroup::new(peripherals.TIMG0); +//! let mut one_shot = OneShotTimer::new(timg0.timer0); +//! +//! one_shot.delay_millis(500); +//! # {after_snippet} +//! ``` +//! +//! ### Periodic Timer +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::timer::{PeriodicTimer, timg::TimerGroup}; +//! # +//! let timg0 = TimerGroup::new(peripherals.TIMG0); +//! let mut periodic = PeriodicTimer::new(timg0.timer0); +//! +//! periodic.start(Duration::from_secs(1)); +//! loop { +//! periodic.wait(); +//! } +//! # } +//! ``` + +use core::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + interrupt::{InterruptConfigurable, InterruptHandler}, + peripherals::Interrupt, + system::Cpu, + time::{Duration, Instant}, +}; + +#[cfg(systimer_driver_supported)] +pub mod systimer; +#[cfg(timergroup_driver_supported)] +pub mod timg; + +/// Timer errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The timer is already active. + TimerActive, + /// The timer is not currently active. + TimerInactive, + /// The alarm is not currently active. + AlarmInactive, + /// The provided timeout is too large. + InvalidTimeout, +} + +/// Functionality provided by any timer peripheral. +pub trait Timer: crate::private::Sealed { + /// Start the timer. + #[doc(hidden)] + fn start(&self); + + /// Stop the timer. + #[doc(hidden)] + fn stop(&self); + + /// Reset the timer value to 0. + #[doc(hidden)] + fn reset(&self); + + /// Is the timer running? + #[doc(hidden)] + fn is_running(&self) -> bool; + + /// The current timer value. + #[doc(hidden)] + fn now(&self) -> Instant; + + /// Load a target value into the timer. + #[doc(hidden)] + fn load_value(&self, value: Duration) -> Result<(), Error>; + + /// Enable auto reload of the loaded value. + #[doc(hidden)] + fn enable_auto_reload(&self, auto_reload: bool); + + /// Enable or disable the timer's interrupt. + #[doc(hidden)] + fn enable_interrupt(&self, state: bool); + + /// Clear the timer's interrupt. + fn clear_interrupt(&self); + + /// Has the timer triggered? + fn is_interrupt_set(&self) -> bool; + + /// Returns the HAL provided async interrupt handler + #[doc(hidden)] + fn async_interrupt_handler(&self) -> InterruptHandler; + + /// Returns the interrupt source for the underlying timer + fn peripheral_interrupt(&self) -> Interrupt; + + /// Configures the interrupt handler. + #[doc(hidden)] + fn set_interrupt_handler(&self, handler: InterruptHandler); + + #[doc(hidden)] + fn waker(&self) -> &AtomicWaker; +} + +/// A one-shot timer. +pub struct OneShotTimer<'d, Dm: DriverMode> { + inner: AnyTimer<'d>, + _ph: PhantomData, +} + +impl<'d> OneShotTimer<'d, Blocking> { + /// Construct a new instance of [`OneShotTimer`]. + pub fn new(inner: impl Timer + Into>) -> OneShotTimer<'d, Blocking> { + Self { + inner: inner.into(), + _ph: PhantomData, + } + } +} + +impl<'d> OneShotTimer<'d, Blocking> { + /// Converts the driver to [`Async`] mode. + pub fn into_async(self) -> OneShotTimer<'d, Async> { + let handler = self.inner.async_interrupt_handler(); + self.inner.set_interrupt_handler(handler); + OneShotTimer { + inner: self.inner, + _ph: PhantomData, + } + } +} + +impl OneShotTimer<'_, Async> { + /// Converts the driver to [`Blocking`] mode. + pub fn into_blocking(self) -> Self { + crate::interrupt::disable(Cpu::current(), self.inner.peripheral_interrupt()); + Self { + inner: self.inner, + _ph: PhantomData, + } + } + + /// Delay for *at least* `ns` nanoseconds. + pub async fn delay_nanos_async(&mut self, ns: u32) { + self.delay_async(Duration::from_micros(ns.div_ceil(1000) as u64)) + .await + } + + /// Delay for *at least* `ms` milliseconds. + pub async fn delay_millis_async(&mut self, ms: u32) { + self.delay_async(Duration::from_millis(ms as u64)).await; + } + + /// Delay for *at least* `us` microseconds. + pub async fn delay_micros_async(&mut self, us: u32) { + self.delay_async(Duration::from_micros(us as u64)).await; + } + + async fn delay_async(&mut self, us: Duration) { + unwrap!(self.schedule(us)); + + WaitFuture::new(self.inner.reborrow()).await; + + self.stop(); + self.clear_interrupt(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct WaitFuture<'d> { + timer: AnyTimer<'d>, +} + +impl<'d> WaitFuture<'d> { + fn new(timer: AnyTimer<'d>) -> Self { + // For some reason, on the S2 we need to enable the interrupt before we + // read its status. Doing so in the other order causes the interrupt + // request to never be fired. + timer.enable_interrupt(true); + Self { timer } + } + + fn is_done(&self) -> bool { + self.timer.is_interrupt_set() + } +} + +impl core::future::Future for WaitFuture<'_> { + type Output = (); + + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + // Interrupts are enabled, so we need to register the waker before we check for + // done. Otherwise we might miss the interrupt that would wake us. + self.timer.waker().register(ctx.waker()); + + if self.is_done() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl Drop for WaitFuture<'_> { + fn drop(&mut self) { + self.timer.enable_interrupt(false); + } +} + +impl OneShotTimer<'_, Dm> +where + Dm: DriverMode, +{ + /// Delay for *at least* `ms` milliseconds. + pub fn delay_millis(&mut self, ms: u32) { + self.delay(Duration::from_millis(ms as u64)); + } + + /// Delay for *at least* `us` microseconds. + pub fn delay_micros(&mut self, us: u32) { + self.delay(Duration::from_micros(us as u64)); + } + + /// Delay for *at least* `ns` nanoseconds. + pub fn delay_nanos(&mut self, ns: u32) { + self.delay(Duration::from_micros(ns.div_ceil(1000) as u64)) + } + + fn delay(&mut self, us: Duration) { + self.schedule(us).unwrap(); + + while !self.inner.is_interrupt_set() { + // Wait + } + + self.stop(); + self.clear_interrupt(); + } + + /// Start counting until the given timeout and raise an interrupt + pub fn schedule(&mut self, timeout: Duration) -> Result<(), Error> { + if self.inner.is_running() { + self.inner.stop(); + } + + self.inner.clear_interrupt(); + self.inner.reset(); + + self.inner.enable_auto_reload(false); + self.inner.load_value(timeout)?; + self.inner.start(); + + Ok(()) + } + + /// Stop the timer + pub fn stop(&mut self) { + self.inner.stop(); + } + + /// Set the interrupt handler + /// + /// Note that this will replace any previously set interrupt handler + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.inner.set_interrupt_handler(handler); + } + + /// Listen for interrupt + pub fn listen(&mut self) { + self.inner.enable_interrupt(true); + } + + /// Unlisten for interrupt + pub fn unlisten(&mut self) { + self.inner.enable_interrupt(false); + } + + /// Clear the interrupt flag + pub fn clear_interrupt(&mut self) { + self.inner.clear_interrupt(); + } +} + +impl crate::private::Sealed for OneShotTimer<'_, Dm> where Dm: DriverMode {} + +impl InterruptConfigurable for OneShotTimer<'_, Dm> +where + Dm: DriverMode, +{ + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + OneShotTimer::set_interrupt_handler(self, handler); + } +} + +impl embedded_hal::delay::DelayNs for OneShotTimer<'_, Blocking> { + fn delay_ns(&mut self, ns: u32) { + self.delay_nanos(ns); + } +} + +impl embedded_hal_async::delay::DelayNs for OneShotTimer<'_, Async> { + async fn delay_ns(&mut self, ns: u32) { + self.delay_nanos_async(ns).await + } +} + +/// A periodic timer. +pub struct PeriodicTimer<'d, Dm: DriverMode> { + inner: AnyTimer<'d>, + _ph: PhantomData, +} + +impl<'d> PeriodicTimer<'d, Blocking> { + /// Construct a new instance of [`PeriodicTimer`]. + pub fn new(inner: impl Timer + Into>) -> PeriodicTimer<'d, Blocking> { + Self { + inner: inner.into(), + _ph: PhantomData, + } + } +} + +impl PeriodicTimer<'_, Dm> +where + Dm: DriverMode, +{ + /// Start a new count down. + pub fn start(&mut self, period: Duration) -> Result<(), Error> { + if self.inner.is_running() { + self.inner.stop(); + } + + self.inner.clear_interrupt(); + self.inner.reset(); + + self.inner.enable_auto_reload(true); + self.inner.load_value(period)?; + self.inner.start(); + + Ok(()) + } + + /// "Wait", by blocking, until the count down finishes. + pub fn wait(&mut self) { + while !self.inner.is_interrupt_set() {} + self.inner.clear_interrupt(); + } + + /// Tries to cancel the active count down. + pub fn cancel(&mut self) -> Result<(), Error> { + if !self.inner.is_running() { + return Err(Error::TimerInactive); + } + + self.inner.stop(); + + Ok(()) + } + + /// Set the interrupt handler + /// + /// Note that this will replace any previously set interrupt handler + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.inner.set_interrupt_handler(handler); + } + + /// Listen for interrupt + pub fn listen(&mut self) { + self.inner.enable_interrupt(true); + } + + /// Unlisten for interrupt + pub fn unlisten(&mut self) { + self.inner.enable_interrupt(false); + } + + /// Clear the interrupt flag + pub fn clear_interrupt(&mut self) { + self.inner.clear_interrupt(); + } +} + +impl crate::private::Sealed for PeriodicTimer<'_, Dm> where Dm: DriverMode {} + +impl InterruptConfigurable for PeriodicTimer<'_, Dm> +where + Dm: DriverMode, +{ + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + PeriodicTimer::set_interrupt_handler(self, handler); + } +} + +crate::any_peripheral! { + /// Any Timer peripheral. + pub peripheral AnyTimer<'d> { + #[cfg(timergroup_driver_supported)] + TimgTimer(timg::Timer<'d>), + #[cfg(systimer_driver_supported)] + SystimerAlarm(systimer::Alarm<'d>), + } +} + +impl Timer for AnyTimer<'_> { + delegate::delegate! { + to match &self.0 { + #[cfg(timergroup_driver_supported)] + any::Inner::TimgTimer(inner) => inner, + #[cfg(systimer_driver_supported)] + any::Inner::SystimerAlarm(inner) => inner, + } { + fn start(&self); + fn stop(&self); + fn reset(&self); + fn is_running(&self) -> bool; + fn now(&self) -> Instant; + fn load_value(&self, value: Duration) -> Result<(), Error>; + fn enable_auto_reload(&self, auto_reload: bool); + fn enable_interrupt(&self, state: bool); + fn clear_interrupt(&self); + fn is_interrupt_set(&self) -> bool; + fn async_interrupt_handler(&self) -> InterruptHandler; + fn peripheral_interrupt(&self) -> Interrupt; + fn set_interrupt_handler(&self, handler: InterruptHandler); + fn waker(&self) -> &AtomicWaker; + } + } +} diff --git a/esp-hal/src/timer/systimer.rs b/esp-hal/src/timer/systimer.rs new file mode 100644 index 00000000000..af8c28701a0 --- /dev/null +++ b/esp-hal/src/timer/systimer.rs @@ -0,0 +1,877 @@ +//! # System Timer (SYSTIMER) +//! +//! ## Overview +//! The System Timer is a +#![cfg_attr(esp32s2, doc = "64-bit")] +#![cfg_attr(not(esp32s2), doc = "52-bit")] +//! timer which can be used, for example, to generate tick interrupts for an +//! operating system, or simply as a general-purpose timer. +//! +//! ## Configuration +//! +//! The timer consists of two counters, `Unit0` and `Unit1`. The counter values +//! can be monitored by 3 [`Alarm`]s +//! +//! It is recommended to pass the [`Alarm`]s into a high level driver like +//! [`OneShotTimer`](super::OneShotTimer) and +//! [`PeriodicTimer`](super::PeriodicTimer). Using the System timer directly is +//! only possible through the low level [`Timer`](crate::timer::Timer) trait. + +use core::{fmt::Debug, marker::PhantomData, num::NonZeroU32}; + +use esp_sync::RawMutex; + +use super::{Error, Timer as _}; +use crate::{ + asynch::AtomicWaker, + interrupt::{self, InterruptHandler}, + peripherals::{Interrupt, SYSTIMER}, + system::{Cpu, Peripheral as PeripheralEnable, PeripheralClockControl}, + time::{Duration, Instant}, +}; + +// System Timer is only clocked by XTAL divided by 2 or 2.5 (or RC_FAST_CLK which is not supported +// yet). Some XTAL options (most, in fact) divided by this value may not be an integer multiple of +// 1_000_000. Because the timer API works with microseconds, we need to correct for this. To avoid +// u64 division as much as possible, we use the two highest bits of the divisor to determine the +// division method. +// - If these flags are 0b00, we divide by the divisor. +// - If these flags are 0b01, we multiply by a constant before dividing by the divisor to improve +// accuracy. +// - If these flags are 0b10, we shift the timestamp by the lower bits. +// +// Apart from the S2, the System Timer clock divider outputs a 16MHz timer clock when using +// the "default" XTAL configuration, so this method will commonly use the shifting based +// division. +// +// On a 26MHz C2, the divider outputs 10.4MHz. On a 32MHz C3, the divider outputs 12.8MHz. +// +// Time is unreliable before `init_timestamp_scaler` is called. +// +// Because a single crate version can have "rt" enabled, `ESP_HAL_SYSTIMER_CORRECTION` needs +// to be shared between versions. This aspect of this driver must be therefore kept stable. +#[unsafe(no_mangle)] +#[cfg(feature = "rt")] +static mut ESP_HAL_SYSTIMER_CORRECTION: NonZeroU32 = NonZeroU32::new(SHIFT_TIMESTAMP_FLAG).unwrap(); // Shift-by-0 = no-op + +#[cfg(not(feature = "rt"))] +unsafe extern "Rust" { + static mut ESP_HAL_SYSTIMER_CORRECTION: NonZeroU32; +} + +const SHIFT_TIMESTAMP_FLAG: u32 = 0x8000_0000; +const SHIFT_MASK: u32 = 0x0000_FFFF; +// If the tick rate is not an integer number of microseconds: Since the divider is 2.5, +// and we assume XTAL is an integer number of MHz, we can multiply by 5, then divide by +// 5 to improve accuracy. On H2 the divider is 2, so we can multiply by 2, then divide by +// 2. +const UNEVEN_DIVIDER_FLAG: u32 = 0x4000_0000; +const UNEVEN_MULTIPLIER: u32 = if cfg!(esp32h2) { 2 } else { 5 }; +const UNEVEN_DIVIDER_MASK: u32 = 0x0000_FFFF; + +/// The configuration of a unit. +#[derive(Copy, Clone)] +pub enum UnitConfig { + /// Unit is not counting. + Disabled, + + /// Unit is counting unless the Cpu is stalled. + DisabledIfCpuIsStalled(Cpu), + + /// Unit is counting. + Enabled, +} + +/// System Timer driver. +pub struct SystemTimer<'d> { + /// Alarm 0. + pub alarm0: Alarm<'d>, + + /// Alarm 1. + pub alarm1: Alarm<'d>, + + /// Alarm 2. + pub alarm2: Alarm<'d>, +} + +impl<'d> SystemTimer<'d> { + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + /// Bitmask to be applied to the raw register value. + pub const BIT_MASK: u64 = u64::MAX; + // Bitmask to be applied to the raw period register value. + const PERIOD_MASK: u64 = 0x1FFF_FFFF; + } else { + /// Bitmask to be applied to the raw register value. + pub const BIT_MASK: u64 = 0xF_FFFF_FFFF_FFFF; + // Bitmask to be applied to the raw period register value. + const PERIOD_MASK: u64 = 0x3FF_FFFF; + } + } + + /// One-time initialization for the timestamp conversion/scaling. + #[cfg(feature = "rt")] + pub(crate) fn init_timestamp_scaler() { + // Maximum tick rate is 80MHz (S2), which fits in a u32, so let's narrow the type. + let systimer_rate = Self::ticks_per_second(); + + // Select the optimal way to divide timestamps. + let packed_rate_and_method = if systimer_rate.is_multiple_of(1_000_000) { + let ticks_per_us = systimer_rate as u32 / 1_000_000; + if ticks_per_us.is_power_of_two() { + // Turn the division into a shift + SHIFT_TIMESTAMP_FLAG | (ticks_per_us.ilog2() & SHIFT_MASK) + } else { + // We need to divide by an integer :( + ticks_per_us + } + } else { + // The rate is not a multiple of 1 MHz, we need to scale it up to prevent precision + // loss. + let multiplied_ticks_per_us = (systimer_rate * UNEVEN_MULTIPLIER as u64) / 1_000_000; + UNEVEN_DIVIDER_FLAG | (multiplied_ticks_per_us as u32) + }; + + // Safety: we only ever write ESP_HAL_SYSTIMER_CORRECTION in `init_timestamp_scaler`, which + // is called once and only once during startup, from `time_init`. + unsafe { + let correction_ptr = &raw mut ESP_HAL_SYSTIMER_CORRECTION; + *correction_ptr = unwrap!(NonZeroU32::new(packed_rate_and_method)); + } + } + + #[inline] + pub(crate) fn ticks_to_us(ticks: u64) -> u64 { + // Safety: we only ever write ESP_HAL_SYSTIMER_CORRECTION in `init_timestamp_scaler`, which + // is called once and only once during startup. + let correction = unsafe { ESP_HAL_SYSTIMER_CORRECTION }; + + let correction = correction.get(); + match correction & (SHIFT_TIMESTAMP_FLAG | UNEVEN_DIVIDER_FLAG) { + v if v == SHIFT_TIMESTAMP_FLAG => ticks >> (correction & SHIFT_MASK), + v if v == UNEVEN_DIVIDER_FLAG => { + // Not only that, but we need to multiply the timestamp first otherwise + // we'd count slower than the timer. + let multiplied = if UNEVEN_MULTIPLIER.is_power_of_two() { + ticks << UNEVEN_MULTIPLIER.ilog2() + } else { + ticks * UNEVEN_MULTIPLIER as u64 + }; + + let divider = correction & UNEVEN_DIVIDER_MASK; + multiplied / divider as u64 + } + _ => ticks / correction as u64, + } + } + + #[inline] + fn us_to_ticks(us: u64) -> u64 { + // Safety: we only ever write ESP_HAL_SYSTIMER_CORRECTION in `init_timestamp_scaler`, which + // is called once and only once during startup. + let correction = unsafe { ESP_HAL_SYSTIMER_CORRECTION }; + + let correction = correction.get(); + match correction & (SHIFT_TIMESTAMP_FLAG | UNEVEN_DIVIDER_FLAG) { + v if v == SHIFT_TIMESTAMP_FLAG => us << (correction & SHIFT_MASK), + v if v == UNEVEN_DIVIDER_FLAG => { + let multiplier = correction & UNEVEN_DIVIDER_MASK; + let multiplied = us * multiplier as u64; + + // Not only that, but we need to divide the timestamp first otherwise + // we'd return a slightly too-big value. + if UNEVEN_MULTIPLIER.is_power_of_two() { + multiplied >> UNEVEN_MULTIPLIER.ilog2() + } else { + multiplied / UNEVEN_MULTIPLIER as u64 + } + } + _ => us * correction as u64, + } + } + + /// Returns the tick frequency of the underlying timer unit. + #[inline] + pub fn ticks_per_second() -> u64 { + // FIXME: this requires a critical section. We can probably do better, if we can formulate + // invariants well. + cfg_if::cfg_if! { + if #[cfg(esp32c5)] { + // Assuming SYSTIMER runs from XTAL, the hardware always runs at 16 MHz. + 16_000_000 + } else { + crate::soc::clocks::ClockTree::with(|clocks| { + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + crate::soc::clocks::apb_clk_frequency(clocks) as u64 + } else if #[cfg(esp32h2)] { + // The counters and comparators are driven using `XTAL_CLK`. + // The average clock frequency is fXTAL_CLK/2, (16 MHz for XTAL = 32 MHz) + (crate::soc::clocks::xtal_clk_frequency(clocks) / 2) as u64 + } else { + // The counters and comparators are driven using `XTAL_CLK`. + // The average clock frequency is fXTAL_CLK/2.5 (16 MHz for XTAL = 40 MHz) + (crate::soc::clocks::xtal_clk_frequency(clocks) * 10 / 25) as u64 + } + } + }) + } + } + } + + /// Create a new instance. + pub fn new(_systimer: SYSTIMER<'d>) -> Self { + // Don't reset Systimer as it will break `time::Instant::now`, only enable it + PeripheralClockControl::enable(PeripheralEnable::Systimer); + + #[cfg(etm_driver_supported)] + etm::enable_etm(); + + Self { + alarm0: Alarm::new(Comparator::Comparator0), + alarm1: Alarm::new(Comparator::Comparator1), + alarm2: Alarm::new(Comparator::Comparator2), + } + } + + /// Get the current count of the given unit in the System Timer. + #[inline] + pub fn unit_value(unit: Unit) -> u64 { + // This should be safe to access from multiple contexts + // worst case scenario the second accessor ends up reading + // an older time stamp + + unit.read_count() + } + + #[cfg(not(esp32s2))] + /// Configures when this counter can run. + /// It can be configured to stall or continue running when CPU stalls + /// or enters on-chip-debugging mode. + /// + /// # Safety + /// + /// - Disabling a `Unit` whilst [`Alarm`]s are using it will affect the [`Alarm`]s operation. + /// - Disabling Unit0 will affect [`Instant::now`]. + pub unsafe fn configure_unit(unit: Unit, config: UnitConfig) { + unit.configure(config) + } + + /// Set the value of the counter immediately. If the unit is at work, + /// the counter will continue to count up from the new reloaded value. + /// + /// This can be used to load back the sleep time recorded by RTC timer + /// via software after Light-sleep + /// + /// # Safety + /// + /// - Modifying a unit's count whilst [`Alarm`]s are using it may cause unexpected behaviour + /// - Any modification of the unit0 count will affect [`Instant::now`] + pub unsafe fn set_unit_value(unit: Unit, value: u64) { + unit.set_count(value) + } +} + +/// A +#[cfg_attr(esp32s2, doc = "64-bit")] +#[cfg_attr(not(esp32s2), doc = "52-bit")] +/// counter. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Unit { + /// Unit 0 + Unit0 = 0, + #[cfg(not(esp32s2))] + /// Unit 1 + Unit1 = 1, +} + +impl Unit { + #[inline] + fn channel(&self) -> u8 { + *self as _ + } + + #[cfg(not(esp32s2))] + fn configure(&self, config: UnitConfig) { + CONF_LOCK.lock(|| { + SYSTIMER::regs().conf().modify(|_, w| match config { + UnitConfig::Disabled => match self.channel() { + 0 => w.timer_unit0_work_en().clear_bit(), + 1 => w.timer_unit1_work_en().clear_bit(), + _ => unreachable!(), + }, + UnitConfig::DisabledIfCpuIsStalled(cpu) => match self.channel() { + 0 => { + w.timer_unit0_work_en().set_bit(); + w.timer_unit0_core0_stall_en().bit(cpu == Cpu::ProCpu); + w.timer_unit0_core1_stall_en().bit(cpu != Cpu::ProCpu) + } + 1 => { + w.timer_unit1_work_en().set_bit(); + w.timer_unit1_core0_stall_en().bit(cpu == Cpu::ProCpu); + w.timer_unit1_core1_stall_en().bit(cpu != Cpu::ProCpu) + } + _ => unreachable!(), + }, + UnitConfig::Enabled => match self.channel() { + 0 => { + w.timer_unit0_work_en().set_bit(); + w.timer_unit0_core0_stall_en().clear_bit(); + w.timer_unit0_core1_stall_en().clear_bit() + } + 1 => { + w.timer_unit1_work_en().set_bit(); + w.timer_unit1_core0_stall_en().clear_bit(); + w.timer_unit1_core1_stall_en().clear_bit() + } + _ => unreachable!(), + }, + }); + }); + } + + fn set_count(&self, value: u64) { + let systimer = SYSTIMER::regs(); + #[cfg(not(esp32s2))] + { + let unitload = systimer.unitload(self.channel() as _); + let unit_load = systimer.unit_load(self.channel() as _); + + unitload.hi().write(|w| w.load_hi().set((value << 32) as _)); + unitload + .lo() + .write(|w| w.load_lo().set((value & 0xFFFF_FFFF) as _)); + + unit_load.write(|w| w.load().set_bit()); + } + #[cfg(esp32s2)] + { + systimer + .load_hi() + .write(|w| w.load_hi().set((value << 32) as _)); + systimer + .load_lo() + .write(|w| w.load_lo().set((value & 0xFFFF_FFFF) as _)); + + systimer.load().write(|w| w.load().set_bit()); + } + } + + #[inline] + fn read_count(&self) -> u64 { + // This can be a shared reference as long as this type isn't Sync. + + let channel = self.channel() as usize; + let systimer = SYSTIMER::regs(); + + systimer.unit_op(channel).write(|w| w.update().set_bit()); + while !systimer.unit_op(channel).read().value_valid().bit_is_set() {} + + // Read LO, HI, then LO again, check that LO returns the same value. + // This accounts for the case when an interrupt may happen between reading + // HI and LO values (or the other core updates the counter mid-read), and this + // function may get called from the ISR. In this case, the repeated read + // will return consistent values. + let unit_value = systimer.unit_value(channel); + let mut lo_prev = unit_value.lo().read().bits(); + loop { + let lo = lo_prev; + let hi = unit_value.hi().read().bits(); + lo_prev = unit_value.lo().read().bits(); + + if lo == lo_prev { + return ((hi as u64) << 32) | lo as u64; + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Comparator { + Comparator0, + Comparator1, + Comparator2, +} + +/// An alarm unit +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Alarm<'d> { + comp: Comparator, + unit: Unit, + _lifetime: PhantomData<&'d mut ()>, +} + +impl Alarm<'_> { + const fn new(comp: Comparator) -> Self { + Alarm { + comp, + unit: Unit::Unit0, + _lifetime: PhantomData, + } + } + + /// Unsafely clone this peripheral reference. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a + /// time. + pub unsafe fn clone_unchecked(&self) -> Self { + Self { + comp: self.comp, + unit: self.unit, + _lifetime: PhantomData, + } + } + + /// Creates a new peripheral reference with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripheral + /// after you dropped the driver that consumes this. + /// + /// See [Peripheral singleton] section for more information. + /// + /// [Peripheral singleton]: crate#peripheral-singletons + pub fn reborrow(&mut self) -> Alarm<'_> { + unsafe { self.clone_unchecked() } + } + + /// Returns the comparator's number. + #[inline] + fn channel(&self) -> u8 { + self.comp as u8 + } + + /// Enables/disables the comparator. If enabled, this means + /// it will generate interrupt based on its configuration. + fn set_enable(&self, enable: bool) { + CONF_LOCK.lock(|| { + #[cfg(not(esp32s2))] + SYSTIMER::regs().conf().modify(|_, w| match self.channel() { + 0 => w.target0_work_en().bit(enable), + 1 => w.target1_work_en().bit(enable), + 2 => w.target2_work_en().bit(enable), + _ => unreachable!(), + }); + }); + + // Note: The ESP32-S2 doesn't require a lock because each + // comparator's enable bit in a different register. + #[cfg(esp32s2)] + SYSTIMER::regs() + .target_conf(self.channel() as usize) + .modify(|_r, w| w.work_en().bit(enable)); + } + + /// Returns true if the comparator has been enabled. This means + /// it will generate interrupt based on its configuration. + fn is_enabled(&self) -> bool { + #[cfg(not(esp32s2))] + match self.channel() { + 0 => SYSTIMER::regs().conf().read().target0_work_en().bit(), + 1 => SYSTIMER::regs().conf().read().target1_work_en().bit(), + 2 => SYSTIMER::regs().conf().read().target2_work_en().bit(), + _ => unreachable!(), + } + + #[cfg(esp32s2)] + SYSTIMER::regs() + .target_conf(self.channel() as usize) + .read() + .work_en() + .bit() + } + + /// Sets the unit this comparator uses as a reference count. + #[cfg(not(esp32s2))] + pub fn set_unit(&self, unit: Unit) { + SYSTIMER::regs() + .target_conf(self.channel() as usize) + .modify(|_, w| w.timer_unit_sel().bit(matches!(unit, Unit::Unit1))); + } + + /// Set the mode of the comparator to be either target or periodic. + fn set_mode(&self, mode: ComparatorMode) { + let is_period_mode = match mode { + ComparatorMode::Period => true, + ComparatorMode::Target => false, + }; + SYSTIMER::regs() + .target_conf(self.channel() as usize) + .modify(|_, w| w.period_mode().bit(is_period_mode)); + } + + /// Get the current mode of the comparator, which is either target or + /// periodic. + fn mode(&self) -> ComparatorMode { + if SYSTIMER::regs() + .target_conf(self.channel() as usize) + .read() + .period_mode() + .bit() + { + ComparatorMode::Period + } else { + ComparatorMode::Target + } + } + + /// Set how often the comparator should generate an interrupt when in + /// periodic mode. + fn set_period(&self, value: u32) { + let systimer = SYSTIMER::regs(); + let tconf = systimer.target_conf(self.channel() as usize); + unsafe { tconf.modify(|_, w| w.period().bits(value)) }; + #[cfg(not(esp32s2))] + { + let comp_load = systimer.comp_load(self.channel() as usize); + comp_load.write(|w| w.load().set_bit()); + } + } + + /// Set when the comparator should generate an interrupt in target mode. + fn set_target(&self, value: u64) { + let systimer = SYSTIMER::regs(); + let target = systimer.trgt(self.channel() as usize); + target.hi().write(|w| w.hi().set((value >> 32) as u32)); + target + .lo() + .write(|w| w.lo().set((value & 0xFFFF_FFFF) as u32)); + #[cfg(not(esp32s2))] + { + let comp_load = systimer.comp_load(self.channel() as usize); + comp_load.write(|w| w.load().set_bit()); + } + } + + /// Set the interrupt handler for this comparator. + fn set_interrupt_handler(&self, handler: InterruptHandler) { + let interrupt = match self.channel() { + 0 => Interrupt::SYSTIMER_TARGET0, + 1 => Interrupt::SYSTIMER_TARGET1, + 2 => Interrupt::SYSTIMER_TARGET2, + _ => unreachable!(), + }; + + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + + #[cfg(not(esp32s2))] + interrupt::bind_handler(interrupt, handler); + + #[cfg(esp32s2)] + { + // ESP32-S2 Systimer interrupts are edge triggered. Our interrupt + // handler calls each of the handlers, regardless of which one triggered the + // interrupt. This mess registers an intermediate handler that + // checks if an interrupt is active before calling the associated + // handler functions. + + static mut HANDLERS: [Option; 3] = [None, None, None]; + + #[crate::ram] + extern "C" fn _handle_interrupt() { + if SYSTIMER::regs().int_raw().read().target(CH).bit_is_set() { + let handler = unsafe { HANDLERS[CH as usize] }; + if let Some(handler) = handler { + (handler.callback())(); + } + } + } + + let priority = handler.priority(); + unsafe { + HANDLERS[self.channel() as usize] = Some(handler.handler()); + } + let handler = match self.channel() { + 0 => _handle_interrupt::<0>, + 1 => _handle_interrupt::<1>, + 2 => _handle_interrupt::<2>, + _ => unreachable!(), + }; + interrupt::bind_handler( + interrupt, + crate::interrupt::InterruptHandler::new(handler, priority), + ); + } + } +} + +/// The modes of a comparator. +#[derive(Copy, Clone)] +enum ComparatorMode { + /// The comparator will generate interrupts periodically. + Period, + + /// The comparator will generate an interrupt when the unit reaches the + /// target. + Target, +} + +impl super::Timer for Alarm<'_> { + fn start(&self) { + self.set_enable(true); + } + + fn stop(&self) { + self.set_enable(false); + } + + fn reset(&self) { + #[cfg(esp32s2)] + // Run at XTAL freq, not 80 * XTAL freq: + SYSTIMER::regs() + .step() + .modify(|_, w| unsafe { w.xtal_step().bits(0x1) }); + + #[cfg(not(esp32s2))] + SYSTIMER::regs() + .conf() + .modify(|_, w| w.timer_unit0_core0_stall_en().clear_bit()); + } + + fn is_running(&self) -> bool { + self.is_enabled() + } + + fn now(&self) -> Instant { + // This should be safe to access from multiple contexts; worst case + // scenario the second accessor ends up reading an older time stamp. + + let ticks = self.unit.read_count(); + + let us = SystemTimer::ticks_to_us(ticks); + + Instant::from_ticks(us) + } + + fn load_value(&self, value: Duration) -> Result<(), Error> { + let mode = self.mode(); + + let us = value.as_micros(); + let ticks = SystemTimer::us_to_ticks(us); + + if matches!(mode, ComparatorMode::Period) { + // Period mode + + // The `SYSTIMER_TARGETx_PERIOD` field is 26-bits wide (or + // 29-bits on the ESP32-S2), so we must ensure that the provided + // value is not too wide: + if (ticks & !SystemTimer::PERIOD_MASK) != 0 { + return Err(Error::InvalidTimeout); + } + + self.set_period(ticks as u32); + + // Clear and then set SYSTIMER_TARGETx_PERIOD_MODE to configure COMPx into + // period mode + self.set_mode(ComparatorMode::Target); + self.set_mode(ComparatorMode::Period); + } else { + // Target mode + + // The counters/comparators are 52-bits wide (except on ESP32-S2, + // which is 64-bits), so we must ensure that the provided value + // is not too wide: + #[cfg(not(esp32s2))] + if (ticks & !SystemTimer::BIT_MASK) != 0 { + return Err(Error::InvalidTimeout); + } + + let v = self.unit.read_count(); + let t = v + ticks; + + self.set_target(t); + } + + Ok(()) + } + + fn enable_auto_reload(&self, auto_reload: bool) { + // If `auto_reload` is true use Period Mode, otherwise use Target Mode: + let mode = if auto_reload { + ComparatorMode::Period + } else { + ComparatorMode::Target + }; + self.set_mode(mode) + } + + fn enable_interrupt(&self, state: bool) { + INT_ENA_LOCK.lock(|| { + SYSTIMER::regs() + .int_ena() + .modify(|_, w| w.target(self.channel()).bit(state)); + }); + } + + fn clear_interrupt(&self) { + SYSTIMER::regs() + .int_clr() + .write(|w| w.target(self.channel()).clear_bit_by_one()); + } + + fn is_interrupt_set(&self) -> bool { + SYSTIMER::regs() + .int_raw() + .read() + .target(self.channel()) + .bit_is_set() + } + + fn async_interrupt_handler(&self) -> InterruptHandler { + match self.channel() { + 0 => asynch::target0_handler, + 1 => asynch::target1_handler, + 2 => asynch::target2_handler, + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Interrupt { + match self.channel() { + 0 => Interrupt::SYSTIMER_TARGET0, + 1 => Interrupt::SYSTIMER_TARGET1, + 2 => Interrupt::SYSTIMER_TARGET2, + _ => unreachable!(), + } + } + + fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.set_interrupt_handler(handler) + } + + fn waker(&self) -> &AtomicWaker { + asynch::waker(self) + } +} + +impl crate::private::Sealed for Alarm<'_> {} + +static CONF_LOCK: RawMutex = RawMutex::new(); +static INT_ENA_LOCK: RawMutex = RawMutex::new(); + +// Async functionality of the system timer. +mod asynch { + use core::marker::PhantomData; + + use procmacros::handler; + + use super::*; + use crate::asynch::AtomicWaker; + + const NUM_ALARMS: usize = 3; + static WAKERS: [AtomicWaker; NUM_ALARMS] = [const { AtomicWaker::new() }; NUM_ALARMS]; + + pub(super) fn waker(alarm: &Alarm<'_>) -> &'static AtomicWaker { + &WAKERS[alarm.channel() as usize] + } + + #[inline] + fn handle_alarm(comp: Comparator) { + Alarm { + comp, + unit: Unit::Unit0, + _lifetime: PhantomData, + } + .enable_interrupt(false); + + WAKERS[comp as usize].wake(); + } + + #[handler] + pub(crate) fn target0_handler() { + handle_alarm(Comparator::Comparator0); + } + + #[handler] + pub(crate) fn target1_handler() { + handle_alarm(Comparator::Comparator1); + } + + #[handler] + pub(crate) fn target2_handler() { + handle_alarm(Comparator::Comparator2); + } +} + +#[cfg(etm_driver_supported)] +pub mod etm { + #![cfg_attr(docsrs, procmacros::doc_replace)] + //! # Event Task Matrix Function + //! + //! ## Overview + //! + //! The system timer supports the Event Task Matrix (ETM) function, which + //! allows the system timer’s ETM events to trigger any peripherals’ ETM + //! tasks. + //! + //! The system timer can generate the following ETM events: + //! - SYSTIMER_EVT_CNT_CMPx: Indicates the alarm pulses generated by COMPx + //! ## Example + //! ```rust, no_run + //! # {before_snippet} + //! # use esp_hal::timer::systimer::{etm::Event, SystemTimer}; + //! # use esp_hal::timer::PeriodicTimer; + //! # use esp_hal::etm::Etm; + //! # use esp_hal::gpio::{ + //! # etm::{Channels, OutputConfig}, + //! # Level, + //! # Pull, + //! # }; + //! let syst = SystemTimer::new(peripherals.SYSTIMER); + //! let etm = Etm::new(peripherals.ETM); + //! let gpio_ext = Channels::new(peripherals.GPIO_SD); + //! let alarm0 = syst.alarm0; + //! let mut led = peripherals.GPIO1; + //! + //! let timer_event = Event::new(&alarm0); + //! let led_task = gpio_ext.channel0_task.toggle( + //! led, + //! OutputConfig { + //! open_drain: false, + //! pull: Pull::None, + //! initial_state: Level::High, + //! }, + //! ); + //! + //! let _configured_etm_channel = etm.channel0.setup(&timer_event, &led_task); + //! + //! let timer = PeriodicTimer::new(alarm0); + //! // configure the timer as usual + //! // when it fires it will toggle the GPIO + //! # {after_snippet} + //! ``` + + use super::*; + + /// An ETM controlled SYSTIMER event + pub struct Event { + id: u8, + } + + impl Event { + /// Creates an ETM event from the given [Alarm] + pub fn new(alarm: &Alarm<'_>) -> Self { + Self { + id: 50 + alarm.channel(), + } + } + } + + impl crate::private::Sealed for Event {} + + impl crate::etm::EtmEvent for Event { + fn id(&self) -> u8 { + self.id + } + } + + pub(super) fn enable_etm() { + SYSTIMER::regs().conf().modify(|_, w| w.etm_en().set_bit()); + } +} diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs new file mode 100644 index 00000000000..697f27a51bc --- /dev/null +++ b/esp-hal/src/timer/timg.rs @@ -0,0 +1,1016 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Timer Group (TIMG) +//! +//! ## Overview +//! +//! The Timer Group (TIMG) peripherals contain one or more general-purpose +//! timers, plus one or more watchdog timers. +//! +//! The general-purpose timers are based on a 16-bit pre-scaler and a 54-bit +//! auto-reload-capable up-down counter. +//! +//! ## Configuration +//! +//! The timers have configurable alarms, which are triggered when the internal +//! counter of the timers reaches a specific target value. The timers are +//! clocked using the APB clock source. +//! +//! Typically, a general-purpose timer can be used in scenarios such as: +//! +//! - Generate period alarms; trigger events periodically +//! - Generate one-shot alarms; trigger events once +//! - Free-running; fetching a high-resolution timestamp on demand +//! +//! ## Examples +//! +//! ### General-purpose Timer +//! +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::timer::{Timer, timg::TimerGroup}; +//! +//! let timg0 = TimerGroup::new(peripherals.TIMG0); +//! let timer0 = timg0.timer0; +//! +//! // Get the current timestamp, in microseconds: +//! let now = timer0.now(); +//! +//! // Wait for timeout: +//! timer0.load_value(Duration::from_secs(1)); +//! timer0.start(); +//! +//! while !timer0.is_interrupt_set() { +//! // Wait +//! } +//! +//! timer0.clear_interrupt(); +//! # {after_snippet} +//! ``` +//! +//! ### Watchdog Timer +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::timer::{ +//! Timer, +//! timg::{MwdtStage, TimerGroup}, +//! }; +//! +//! let timg0 = TimerGroup::new(peripherals.TIMG0); +//! let mut wdt = timg0.wdt; +//! +//! wdt.set_timeout(MwdtStage::Stage0, Duration::from_millis(5_000)); +//! wdt.enable(); +//! +//! loop { +//! wdt.feed(); +//! } +//! # } +//! ``` +use core::marker::PhantomData; + +use super::Error; +#[cfg(timergroup_timg1)] +use crate::peripherals::TIMG1; +#[cfg(soc_has_clock_node_timg0_function_clock)] +use crate::soc::clocks::Timg0FunctionClockConfig; +#[cfg(soc_has_clock_node_timg0_wdt_clock)] +use crate::soc::clocks::Timg0WdtClockConfig; +use crate::{ + asynch::AtomicWaker, + interrupt::{self, InterruptConfigurable, InterruptHandler}, + pac::timg0::RegisterBlock, + peripherals::{Interrupt, TIMG0}, + private::Sealed, + soc::clocks::ClockTree, + system::PeripheralClockControl, + time::{Duration, Instant, Rate}, +}; + +const NUM_TIMG: usize = 1 + cfg!(timergroup_timg1) as usize; + +cfg_if::cfg_if! { + // We need no locks when a TIMG has a single timer, and we don't need locks for ESP32 + // and S2 where the effective interrupt enable register (config) is not shared between + // the timers. + if #[cfg(all(timergroup_timg_has_timer1, not(any(esp32, esp32s2))))] { + use esp_sync::RawMutex; + static INT_ENA_LOCK: [RawMutex; NUM_TIMG] = [const { RawMutex::new() }; NUM_TIMG]; + } +} + +#[procmacros::doc_replace( + "timers" => { + cfg(timergroup_timg_has_timer1) => "2 timers", + _ => "a general purpose timer", + } +)] +/// A timer group consisting of __timers__ and a watchdog timer. +pub struct TimerGroup<'d, T> +where + T: TimerGroupInstance + 'd, +{ + _timer_group: PhantomData, + /// Timer 0 + pub timer0: Timer<'d>, + /// Timer 1 + #[cfg(timergroup_timg_has_timer1)] + pub timer1: Timer<'d>, + /// Watchdog timer + pub wdt: Wdt, +} + +#[doc(hidden)] +pub trait TimerGroupInstance { + fn id() -> u8; + fn register_block() -> *const RegisterBlock; + #[cfg(soc_has_clock_node_timg0_function_clock)] + fn configure_src_clk(src: Timg0FunctionClockConfig); + fn enable_peripheral(); + fn reset_peripheral(); + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn configure_wdt_src_clk(src: Timg0WdtClockConfig); + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn gate_wdt_src_clk(enable: bool); + fn wdt_src_frequency() -> Rate; + fn wdt_interrupt() -> Interrupt; +} + +#[cfg(timergroup_timg0)] +impl TimerGroupInstance for TIMG0<'_> { + fn id() -> u8 { + 0 + } + + #[inline(always)] + fn register_block() -> *const RegisterBlock { + Self::regs() + } + + #[cfg(soc_has_clock_node_timg0_function_clock)] + fn configure_src_clk(src: Timg0FunctionClockConfig) { + crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::configure_timg0_function_clock(clocks, src); + crate::soc::clocks::request_timg0_function_clock(clocks); + }); + } + + fn enable_peripheral() { + PeripheralClockControl::enable(crate::system::Peripheral::Timg0); + } + + fn reset_peripheral() { + // FIXME: for TIMG0 do nothing for now because the reset breaks + // `time::Instant::now` + } + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn configure_wdt_src_clk(src: Timg0WdtClockConfig) { + crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::configure_timg0_wdt_clock(clocks, src) + }); + } + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn gate_wdt_src_clk(enable: bool) { + crate::soc::clocks::ClockTree::with(|clocks| { + if enable { + crate::soc::clocks::request_timg0_wdt_clock(clocks) + } else { + crate::soc::clocks::release_timg0_wdt_clock(clocks) + } + }); + } + + fn wdt_src_frequency() -> Rate { + crate::soc::clocks::ClockTree::with(|clocks| { + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg0_wdt_clock)] { + Rate::from_hz(crate::soc::clocks::timg0_wdt_clock_frequency(clocks)) + } else { + Rate::from_hz(crate::soc::clocks::apb_clk_frequency(clocks)) + } + } + }) + } + + fn wdt_interrupt() -> Interrupt { + Interrupt::TG0_WDT_LEVEL + } +} + +#[cfg(timergroup_timg1)] +impl TimerGroupInstance for crate::peripherals::TIMG1<'_> { + fn id() -> u8 { + 1 + } + + #[inline(always)] + fn register_block() -> *const RegisterBlock { + Self::regs() + } + + #[cfg(soc_has_clock_node_timg0_function_clock)] + fn configure_src_clk(src: Timg0FunctionClockConfig) { + crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::configure_timg1_function_clock(clocks, src); + crate::soc::clocks::request_timg1_function_clock(clocks); + }); + } + + fn enable_peripheral() { + PeripheralClockControl::enable(crate::system::Peripheral::Timg1); + } + + fn reset_peripheral() { + PeripheralClockControl::reset(crate::system::Peripheral::Timg1); + } + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn configure_wdt_src_clk(src: Timg0WdtClockConfig) { + crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::configure_timg1_wdt_clock(clocks, src) + }); + } + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + fn gate_wdt_src_clk(enable: bool) { + crate::soc::clocks::ClockTree::with(|clocks| { + if enable { + crate::soc::clocks::request_timg1_wdt_clock(clocks) + } else { + crate::soc::clocks::release_timg1_wdt_clock(clocks) + } + }); + } + + fn wdt_src_frequency() -> Rate { + crate::soc::clocks::ClockTree::with(|clocks| { + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg1_wdt_clock)] { + Rate::from_hz(crate::soc::clocks::timg1_wdt_clock_frequency(clocks)) + } else { + Rate::from_hz(crate::soc::clocks::apb_clk_frequency(clocks)) + } + } + }) + } + + fn wdt_interrupt() -> Interrupt { + Interrupt::TG1_WDT_LEVEL + } +} + +impl<'d, T> TimerGroup<'d, T> +where + T: TimerGroupInstance + 'd, +{ + /// Construct a new instance of [`TimerGroup`] in blocking mode + pub fn new(_timer_group: T) -> Self { + T::reset_peripheral(); + T::enable_peripheral(); + + #[cfg(soc_has_clock_node_timg0_function_clock)] + T::configure_src_clk(Timg0FunctionClockConfig::default()); + + Self { + _timer_group: PhantomData, + timer0: Timer { + timer: TimerId::Timer0, + tg: T::id(), + register_block: T::register_block(), + _lifetime: PhantomData, + }, + #[cfg(timergroup_timg_has_timer1)] + timer1: Timer { + timer: TimerId::Timer1, + tg: T::id(), + register_block: T::register_block(), + _lifetime: PhantomData, + }, + wdt: Wdt::new(), + } + } +} + +impl super::Timer for Timer<'_> { + fn start(&self) { + self.set_counter_active(false); + self.set_alarm_active(false); + + self.reset_counter(); + self.set_counter_decrementing(false); + + self.set_counter_active(true); + self.set_alarm_active(true); + } + + fn stop(&self) { + self.set_counter_active(false); + } + + fn reset(&self) { + self.reset_counter() + } + + fn is_running(&self) -> bool { + self.is_counter_active() + } + + fn now(&self) -> Instant { + self.now() + } + + fn load_value(&self, value: Duration) -> Result<(), Error> { + self.load_value(value) + } + + fn enable_auto_reload(&self, auto_reload: bool) { + self.set_auto_reload(auto_reload) + } + + fn enable_interrupt(&self, state: bool) { + self.set_interrupt_enabled(state); + } + + fn clear_interrupt(&self) { + self.clear_interrupt() + } + + fn is_interrupt_set(&self) -> bool { + self.is_interrupt_set() + } + + fn async_interrupt_handler(&self) -> InterruptHandler { + match (self.timer_group(), self.timer_number()) { + (0, 0) => asynch::timg0_timer0_handler, + #[cfg(timergroup_timg_has_timer1)] + (0, 1) => asynch::timg0_timer1_handler, + #[cfg(timergroup_timg1)] + (1, 0) => asynch::timg1_timer0_handler, + #[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))] + (1, 1) => asynch::timg1_timer1_handler, + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Interrupt { + match (self.timer_group(), self.timer_number()) { + (0, 0) => Interrupt::TG0_T0_LEVEL, + #[cfg(timergroup_timg_has_timer1)] + (0, 1) => Interrupt::TG0_T1_LEVEL, + #[cfg(timergroup_timg1)] + (1, 0) => Interrupt::TG1_T0_LEVEL, + #[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))] + (1, 1) => Interrupt::TG1_T1_LEVEL, + _ => unreachable!(), + } + } + + fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.set_interrupt_handler(handler) + } + + fn waker(&self) -> &AtomicWaker { + asynch::waker(self) + } +} + +/// A timer within a Timer Group. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Timer<'d> { + register_block: *const RegisterBlock, + _lifetime: PhantomData<&'d mut ()>, + timer: TimerId, + tg: u8, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum TimerId { + Timer0, + #[cfg(timergroup_timg_has_timer1)] + Timer1, +} + +impl Sealed for Timer<'_> {} +unsafe impl Send for Timer<'_> {} + +/// Timer peripheral instance +impl Timer<'_> { + /// Unsafely clone this peripheral reference. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a + /// time. + pub unsafe fn clone_unchecked(&self) -> Self { + Self { + register_block: self.register_block, + timer: self.timer, + tg: self.tg, + _lifetime: PhantomData, + } + } + + /// Creates a new peripheral reference with a shorter lifetime. + /// + /// Use this method if you would like to keep working with the peripheral + /// after you dropped the driver that consumes this. + /// + /// See [Peripheral singleton] section for more information. + /// + /// [Peripheral singleton]: crate#peripheral-singletons + pub fn reborrow(&mut self) -> Timer<'_> { + unsafe { self.clone_unchecked() } + } + + pub(crate) fn set_interrupt_handler(&self, handler: InterruptHandler) { + let interrupt = match (self.timer_group(), self.timer_number()) { + (0, 0) => Interrupt::TG0_T0_LEVEL, + #[cfg(timergroup_timg_has_timer1)] + (0, 1) => Interrupt::TG0_T1_LEVEL, + #[cfg(timergroup_timg1)] + (1, 0) => Interrupt::TG1_T0_LEVEL, + #[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))] + (1, 1) => Interrupt::TG1_T1_LEVEL, + _ => unreachable!(), + }; + + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + interrupt::bind_handler(interrupt, handler); + } + + fn register_block(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } + + fn timer_group(&self) -> u8 { + self.tg + } + + fn timer_number(&self) -> u8 { + self.timer as u8 + } + + fn t(&self) -> &crate::pac::timg0::T { + self.register_block().t(self.timer_number().into()) + } + + fn reset_counter(&self) { + let t = self.t(); + + t.loadlo().write(|w| unsafe { w.load_lo().bits(0) }); + t.loadhi().write(|w| unsafe { w.load_hi().bits(0) }); + + t.load().write(|w| unsafe { w.load().bits(1) }); + } + + fn set_counter_active(&self, state: bool) { + self.t().config().modify(|_, w| w.en().bit(state)); + } + + fn is_counter_active(&self) -> bool { + self.t().config().read().en().bit_is_set() + } + + fn set_counter_decrementing(&self, decrementing: bool) { + self.t() + .config() + .modify(|_, w| w.increase().bit(!decrementing)); + } + + fn set_auto_reload(&self, auto_reload: bool) { + self.t() + .config() + .modify(|_, w| w.autoreload().bit(auto_reload)); + } + + fn set_alarm_active(&self, state: bool) { + self.t().config().modify(|_, w| w.alarm_en().bit(state)); + } + + fn source_frequency(&self) -> Rate { + let hz = ClockTree::with(|clocks| { + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_timg0_function_clock)] { + match self.timer_group() { + 0 => crate::soc::clocks::timg0_function_clock_frequency(clocks), + #[cfg(soc_has_clock_node_timg1_function_clock)] + 1 => crate::soc::clocks::timg1_function_clock_frequency(clocks), + _ => unreachable!() + } + } else { + crate::soc::clocks::apb_clk_frequency(clocks) + } + } + }); + Rate::from_hz(hz) + } + + fn load_value(&self, value: Duration) -> Result<(), Error> { + let clk_src = self.source_frequency(); + let Some(ticks) = timeout_to_ticks(value, clk_src, self.divider()) else { + return Err(Error::InvalidTimeout); + }; + + // The counter is 54-bits wide, so we must ensure that the provided + // value is not too wide: + if (ticks & !0x3F_FFFF_FFFF_FFFF) != 0 { + return Err(Error::InvalidTimeout); + } + + let high = (ticks >> 32) as u32; + let low = (ticks & 0xFFFF_FFFF) as u32; + + let t = self.t(); + + t.alarmlo().write(|w| unsafe { w.alarm_lo().bits(low) }); + t.alarmhi().write(|w| unsafe { w.alarm_hi().bits(high) }); + + Ok(()) + } + + fn clear_interrupt(&self) { + self.register_block() + .int_clr() + .write(|w| w.t(self.timer as _).clear_bit_by_one()); + let periodic = self.t().config().read().autoreload().bit_is_set(); + self.set_alarm_active(periodic); + } + + fn now(&self) -> Instant { + let t = self.t(); + + t.update().write(|w| w.update().set_bit()); + while t.update().read().update().bit_is_set() { + // Wait for the update to complete + } + + let value_lo = t.lo().read().bits() as u64; + let value_hi = t.hi().read().bits() as u64; + + let ticks = (value_hi << 32) | value_lo; + let clk_src = self.source_frequency(); + let micros = ticks_to_timeout(ticks, clk_src, self.divider()); + + Instant::from_ticks(micros) + } + + fn divider(&self) -> u32 { + let t = self.t(); + + // From the ESP32 TRM, "11.2.1 16­-bit Prescaler and Clock Selection": + // + // "The prescaler can divide the APB clock by a factor from 2 to 65536. + // Specifically, when TIMGn_Tx_DIVIDER is either 1 or 2, the clock divisor is 2; + // when TIMGn_Tx_DIVIDER is 0, the clock divisor is 65536. Any other value will + // cause the clock to be divided by exactly that value." + match t.config().read().divider().bits() { + 0 => 65536, + 1 | 2 => 2, + n => n as u32, + } + } + + fn is_interrupt_set(&self) -> bool { + self.register_block() + .int_raw() + .read() + .t(self.timer as _) + .bit_is_set() + } + + fn set_interrupt_enabled(&self, state: bool) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + // On ESP32 and S2, the `int_ena` register is ineffective - interrupts fire even + // without int_ena enabling them. We use level interrupts so that we have a status + // bit available. + self.t() + .config() + .modify(|_, w| w.level_int_en().bit(state)); + } else if #[cfg(timergroup_timg_has_timer1)] { + INT_ENA_LOCK[self.timer_group() as usize].lock(|| { + self.register_block() + .int_ena() + .modify(|_, w| w.t(self.timer_number()).bit(state)); + }); + } else { + self.register_block() + .int_ena() + .modify(|_, w| w.t(0).bit(state)); + } + } + } +} + +fn ticks_to_timeout(ticks: u64, clock: Rate, divider: u32) -> u64 { + // 1_000_000 is used to get rid of `float` calculations + let period: u64 = 1_000_000 * 1_000_000 / (clock.as_hz() as u64 / divider as u64); + + ticks * period / 1_000_000 +} + +fn timeout_to_ticks(timeout: Duration, clock: Rate, divider: u32) -> Option { + let micros = timeout.as_micros(); + let ticks_per_sec = (clock.as_hz() / divider) as u64; + + micros.checked_mul(ticks_per_sec).map(|n| n / 1_000_000) +} + +/// Behavior of the MWDT stage if it times out. +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub enum MwdtStageAction { + /// No effect on the system. + Off = 0, + /// Trigger an interrupt. + Interrupt = 1, + /// Reset the CPU core. + ResetCpu = 2, + /// Reset the main system, power management unit and RTC peripherals. + ResetSystem = 3, +} + +/// MWDT stages. +/// +/// Timer stages allow for a timer to have a series of different timeout values +/// and corresponding expiry action. +#[derive(Debug, Clone, Copy)] +pub enum MwdtStage { + /// MWDT stage 0. + Stage0, + /// MWDT stage 1. + Stage1, + /// MWDT stage 2. + Stage2, + /// MWDT stage 3. + Stage3, +} + +/// Watchdog timer +pub struct Wdt { + phantom: PhantomData, +} + +/// Watchdog driver +impl Wdt +where + TG: TimerGroupInstance, +{ + /// Construct a new instance of [`Wdt`] + pub fn new() -> Self { + let mut this = Self { + phantom: PhantomData, + }; + + this.set_write_protection(false); + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + TG::configure_wdt_src_clk(Timg0WdtClockConfig::default()); + this.set_write_protection(true); + + this + } + + /// Enable the watchdog timer instance + pub fn enable(&mut self) { + // SAFETY: The `TG` instance being modified is owned by `self`, which is behind + // a mutable reference. + unsafe { self.set_wdt_enabled(true) }; + } + + /// Disable the watchdog timer instance + pub fn disable(&mut self) { + // SAFETY: The `TG` instance being modified is owned by `self`, which is behind + // a mutable reference. + unsafe { self.set_wdt_enabled(false) }; + } + + /// Forcibly enable or disable the watchdog timer + /// + /// # Safety + /// + /// This bypasses the usual ownership rules for the peripheral, so users + /// must take care to ensure that no driver instance is active for the + /// timer. + pub unsafe fn set_wdt_enabled(&mut self, enabled: bool) { + let reg_block = unsafe { &*TG::register_block() }; + + self.set_write_protection(false); + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + if enabled { + TG::gate_wdt_src_clk(true); + } + + reg_block + .wdtconfig0() + .modify(|_, w| w.wdt_en().bit(enabled)); + if enabled { + reg_block.wdtconfig0().modify(|_, w| unsafe { + w.wdt_flashboot_mod_en().bit(false); + w.wdt_stg0().bits(MwdtStageAction::ResetSystem as u8); + w.wdt_cpu_reset_length().bits(7); + w.wdt_sys_reset_length().bits(7); + w.wdt_stg1().bits(MwdtStageAction::Off as u8); + w.wdt_stg2().bits(MwdtStageAction::Off as u8); + w.wdt_stg3().bits(MwdtStageAction::Off as u8) + }); + + #[cfg(any(esp32c2, esp32c3, esp32c6))] + reg_block + .wdtconfig0() + .modify(|_, w| w.wdt_conf_update_en().set_bit()); + } + + #[cfg(soc_has_clock_node_timg0_wdt_clock)] + if !enabled { + TG::gate_wdt_src_clk(false); + } + + self.set_write_protection(true); + } + + /// Feed the watchdog timer + pub fn feed(&mut self) { + let reg_block = unsafe { &*TG::register_block() }; + + self.set_write_protection(false); + + reg_block.wdtfeed().write(|w| unsafe { w.bits(1) }); + + self.set_write_protection(true); + } + + fn set_write_protection(&mut self, enable: bool) { + let reg_block = unsafe { &*TG::register_block() }; + + let wkey = if enable { 0u32 } else { 0x50D8_3AA1u32 }; + + reg_block + .wdtwprotect() + .write(|w| unsafe { w.wdt_wkey().bits(wkey) }); + } + + /// Set the timeout, in microseconds, of the watchdog timer + pub fn set_timeout(&mut self, stage: MwdtStage, timeout: Duration) { + let clk_src = TG::wdt_src_frequency(); + let timeout_ticks = timeout.as_micros() * clk_src.as_mhz() as u64; + + let reg_block = unsafe { &*TG::register_block() }; + + let (prescaler, timeout) = if timeout_ticks > u32::MAX as u64 { + let prescaler = timeout_ticks + .div_ceil(u32::MAX as u64 + 1) + .min(u16::MAX as u64) as u16; + let timeout = timeout_ticks + .div_ceil(prescaler as u64) + .min(u32::MAX as u64); + (prescaler, timeout as u32) + } else { + (1, timeout_ticks as u32) + }; + + self.set_write_protection(false); + + reg_block.wdtconfig1().write(|w| unsafe { + #[cfg(timergroup_timg_has_divcnt_rst)] + w.wdt_divcnt_rst().set_bit(); + w.wdt_clk_prescale().bits(prescaler) + }); + + let config_register = match stage { + MwdtStage::Stage0 => reg_block.wdtconfig2(), + MwdtStage::Stage1 => reg_block.wdtconfig3(), + MwdtStage::Stage2 => reg_block.wdtconfig4(), + MwdtStage::Stage3 => reg_block.wdtconfig5(), + }; + + config_register.write(|w| unsafe { w.hold().bits(timeout) }); + + #[cfg(any(esp32c2, esp32c3, esp32c6))] + reg_block + .wdtconfig0() + .modify(|_, w| w.wdt_conf_update_en().set_bit()); + + self.set_write_protection(true); + } + + /// Set the stage action of the MWDT for a specific stage. + /// + /// This function modifies MWDT behavior only if a custom bootloader with + /// the following modifications is used: + /// - `ESP_TASK_WDT_EN` parameter **disabled** + /// - `ESP_INT_WDT` parameter **disabled** + pub fn set_stage_action(&mut self, stage: MwdtStage, action: MwdtStageAction) { + let reg_block = unsafe { &*TG::register_block() }; + + self.set_write_protection(false); + + reg_block.wdtconfig0().modify(|_, w| unsafe { + match stage { + MwdtStage::Stage0 => w.wdt_stg0().bits(action as u8), + MwdtStage::Stage1 => w.wdt_stg1().bits(action as u8), + MwdtStage::Stage2 => w.wdt_stg2().bits(action as u8), + MwdtStage::Stage3 => w.wdt_stg3().bits(action as u8), + } + }); + + self.set_write_protection(true); + } +} + +impl crate::private::Sealed for Wdt where TG: TimerGroupInstance {} + +impl InterruptConfigurable for Wdt +where + TG: TimerGroupInstance, +{ + fn set_interrupt_handler(&mut self, handler: interrupt::InterruptHandler) { + let interrupt = TG::wdt_interrupt(); + interrupt::bind_handler(interrupt, handler); + } +} + +impl Default for Wdt +where + TG: TimerGroupInstance, +{ + fn default() -> Self { + Self::new() + } +} + +// Async functionality of the timer groups. +mod asynch { + use procmacros::handler; + + use super::*; + use crate::asynch::AtomicWaker; + + const NUM_WAKERS: usize = { + let timer_per_group = 1 + cfg!(timergroup_timg_has_timer1) as usize; + NUM_TIMG * timer_per_group + }; + + static WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS]; + + pub(super) fn waker(timer: &Timer<'_>) -> &'static AtomicWaker { + let index = (timer.timer_number() << 1) | timer.timer_group(); + &WAKERS[index as usize] + } + + #[inline] + fn handle_irq(timer: Timer<'_>) { + timer.set_interrupt_enabled(false); + waker(&timer).wake(); + } + + // INT_ENA means that when the interrupt occurs, it will show up in the + // INT_ST. Clearing INT_ENA that it won't show up on INT_ST but if + // interrupt is already there, it won't clear it - that's why we need to + // clear the INT_CLR as well. + #[handler] + pub(crate) fn timg0_timer0_handler() { + handle_irq(Timer { + register_block: TIMG0::regs(), + _lifetime: PhantomData, + timer: TimerId::Timer0, + tg: 0, + }); + } + + #[cfg(timergroup_timg1)] + #[handler] + pub(crate) fn timg1_timer0_handler() { + handle_irq(Timer { + register_block: TIMG1::regs(), + _lifetime: PhantomData, + timer: TimerId::Timer0, + tg: 1, + }); + } + + #[cfg(timergroup_timg_has_timer1)] + #[handler] + pub(crate) fn timg0_timer1_handler() { + handle_irq(Timer { + register_block: TIMG0::regs(), + _lifetime: PhantomData, + timer: TimerId::Timer1, + tg: 0, + }); + } + + #[cfg(all(timergroup_timg1, timergroup_timg_has_timer1))] + #[handler] + pub(crate) fn timg1_timer1_handler() { + handle_irq(Timer { + register_block: TIMG1::regs(), + _lifetime: PhantomData, + timer: TimerId::Timer1, + tg: 1, + }); + } +} + +/// Event Task Matrix +#[cfg(etm_driver_supported)] +pub mod etm { + use super::*; + use crate::etm::{EtmEvent, EtmTask}; + + /// Event Task Matrix event for a timer. + pub struct Event { + id: u8, + } + + /// Event Task Matrix task for a timer. + pub struct Task { + id: u8, + } + + impl EtmEvent for Event { + fn id(&self) -> u8 { + self.id + } + } + + impl Sealed for Event {} + + impl EtmTask for Task { + fn id(&self) -> u8 { + self.id + } + } + + impl Sealed for Task {} + + /// General purpose timer ETM events. + pub trait Events { + /// ETM event triggered on alarm + fn on_alarm(&self) -> Event; + } + + /// General purpose timer ETM tasks + pub trait Tasks { + /// ETM task to start the counter + fn cnt_start(&self) -> Task; + + /// ETM task to start the alarm + fn cnt_stop(&self) -> Task; + + /// ETM task to stop the counter + fn cnt_reload(&self) -> Task; + + /// ETM task to reload the counter + fn cnt_cap(&self) -> Task; + + /// ETM task to load the counter with the value stored when the last + /// `now()` was called + fn alarm_start(&self) -> Task; + } + + impl Events for Timer<'_> { + fn on_alarm(&self) -> Event { + Event { + id: 48 + self.timer_group(), + } + } + } + + impl Tasks for Timer<'_> { + fn cnt_start(&self) -> Task { + Task { + id: 88 + self.timer_group(), + } + } + + fn alarm_start(&self) -> Task { + Task { + id: 90 + self.timer_group(), + } + } + + fn cnt_stop(&self) -> Task { + Task { + id: 92 + self.timer_group(), + } + } + + fn cnt_reload(&self) -> Task { + Task { + id: 94 + self.timer_group(), + } + } + + fn cnt_cap(&self) -> Task { + Task { + id: 96 + self.timer_group(), + } + } + } +} diff --git a/esp-hal/src/touch.rs b/esp-hal/src/touch.rs new file mode 100644 index 00000000000..76efd6a1132 --- /dev/null +++ b/esp-hal/src/touch.rs @@ -0,0 +1,580 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Capacitive Touch Sensor +//! +//! ## Overview +//! +//! The touch sensor peripheral allows for cheap and robust user interfaces by +//! e.g., dedicating a part of the pcb as touch button. +//! +//! ## Examples +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::touch::{Touch, TouchPad}; +//! let touch_pin0 = peripherals.GPIO2; +//! let touch = Touch::continuous_mode(peripherals.TOUCH, None); +//! let mut touchpad = TouchPad::new(touch_pin0, &touch); +//! // ... give the peripheral some time for the measurement +//! let touch_val = touchpad.read(); +//! # {after_snippet} +//! ``` +//! +//! ## Implementation State: +//! +//! Mostly feature complete, missing: +//! +//! - Touch sensor slope control +//! - Deep Sleep support (wakeup from Deep Sleep) + +use core::marker::PhantomData; + +use crate::{ + Async, + Blocking, + DriverMode, + gpio::TouchPin, + peripherals::{LPWR, SENS, TOUCH}, + private::{Internal, Sealed}, + rtc_cntl::Rtc, +}; + +/// A marker trait describing the mode the touch pad is set to. +pub trait TouchMode: Sealed {} + +/// Marker struct for the touch peripherals manual trigger mode. In the +/// technical reference manual, this is referred to as "start FSM via software". +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OneShot; + +/// Marker struct for the touch peripherals continuous reading mode. In the +/// technical reference manual, this is referred to as "start FSM via timer". +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Continuous; + +impl TouchMode for OneShot {} +impl TouchMode for Continuous {} +impl Sealed for OneShot {} +impl Sealed for Continuous {} + +/// Touchpad threshold type. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ThresholdMode { + /// Pad is considered touched if value is greater than threshold. + GreaterThan, + /// Pad is considered touched if value is less than threshold. + LessThan, +} + +/// Configurations for the touch pad driver +#[derive(Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TouchConfig { + /// The [`ThresholdMode`] for the pads. Defaults to + /// `ThresholdMode::LessThan` + pub threshold_mode: Option, + /// Duration of a single measurement (in cycles of the 8 MHz touch clock). + /// Defaults to `0x7fff` + pub measurement_duration: Option, + /// Sleep cycles for the touch timer in [`Continuous`]-mode. Defaults to + /// `0x100` + pub sleep_cycles: Option, +} + +/// This struct marks a successfully initialized touch peripheral +pub struct Touch<'d, Tm: TouchMode, Dm: DriverMode> { + _inner: TOUCH<'d>, + _touch_mode: PhantomData, + _mode: PhantomData, +} +impl Touch<'_, Tm, Dm> { + /// Common initialization of the touch peripheral. + fn initialize_common(config: Option) { + let rtccntl = LPWR::regs(); + let sens = SENS::regs(); + + let mut threshold_mode = false; + let mut meas_dur = 0x7fff; + + if let Some(config) = config { + threshold_mode = match config.threshold_mode { + Some(ThresholdMode::LessThan) => false, + Some(ThresholdMode::GreaterThan) => true, + None => false, + }; + if let Some(dur) = config.measurement_duration { + meas_dur = dur; + } + } + + // stop touch fsm + rtccntl + .state0() + .write(|w| w.touch_slp_timer_en().clear_bit()); + // Disable touch interrupt + rtccntl.int_ena().write(|w| w.touch().clear_bit()); + // Clear pending interrupts + rtccntl.int_clr().write(|w| w.touch().bit(true)); + + // Disable all interrupts and touch pads + sens.sar_touch_enable().write(|w| unsafe { + w.touch_pad_outen1() + .bits(0b0) + .touch_pad_outen2() + .bits(0b0) + .touch_pad_worken() + .bits(0b0) + }); + + sens.sar_touch_ctrl1().write(|w| unsafe { + w + // Default to trigger when touch is below threshold + .touch_out_sel() + .bit(threshold_mode) + // Interrupt only on set 1 + .touch_out_1en() + .set_bit() + .touch_meas_delay() + .bits(meas_dur) + // TODO Chip Specific + .touch_xpd_wait() + .bits(0xff) + }); + } + + /// Common parts of the continuous mode initialization. + fn initialize_common_continuous(config: Option) { + let rtccntl = LPWR::regs(); + let sens = SENS::regs(); + + // Default nr of sleep cycles from IDF + let mut sleep_cyc = 0x1000; + if let Some(config) = config + && let Some(slp) = config.sleep_cycles + { + sleep_cyc = slp; + } + + Self::initialize_common(config); + + sens.sar_touch_ctrl2().write(|w| unsafe { + w + // Reset existing touch measurements + .touch_meas_en_clr() + .set_bit() + .touch_sleep_cycles() + .bits(sleep_cyc) + // Configure FSM for timer mode + .touch_start_fsm_en() + .set_bit() + .touch_start_force() + .clear_bit() + }); + + // start touch fsm + rtccntl.state0().write(|w| w.touch_slp_timer_en().set_bit()); + } +} +// Async mode and OneShot does not seem to be a sensible combination.... +impl<'d> Touch<'d, OneShot, Blocking> { + #[procmacros::doc_replace] + /// Initializes the touch peripheral and returns this marker struct. + /// Optionally accepts configuration options. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::touch::{Touch, TouchConfig}; + /// let touch_cfg = Some(TouchConfig { + /// measurement_duration: Some(0x2000), + /// ..Default::default() + /// }); + /// let touch = Touch::one_shot_mode(peripherals.TOUCH, touch_cfg); + /// # {after_snippet} + /// ``` + pub fn one_shot_mode(touch_peripheral: TOUCH<'d>, config: Option) -> Self { + let rtccntl = LPWR::regs(); + let sens = SENS::regs(); + + // Default nr of sleep cycles from IDF + let mut sleep_cyc = 0x1000; + if let Some(config) = config + && let Some(slp) = config.sleep_cycles + { + sleep_cyc = slp; + } + + Self::initialize_common(config); + + sens.sar_touch_ctrl2().write(|w| unsafe { + w + // Reset existing touch measurements + .touch_meas_en_clr() + .set_bit() + .touch_sleep_cycles() + .bits(sleep_cyc) + // Configure FSM for SW mode + .touch_start_fsm_en() + .set_bit() + .touch_start_en() + .clear_bit() + .touch_start_force() + .set_bit() + }); + + // start touch fsm + rtccntl.state0().write(|w| w.touch_slp_timer_en().set_bit()); + + Self { + _inner: touch_peripheral, + _mode: PhantomData, + _touch_mode: PhantomData, + } + } +} +impl<'d> Touch<'d, Continuous, Blocking> { + #[procmacros::doc_replace] + /// Initializes the touch peripheral in continuous mode and returns this + /// marker struct. Optionally accepts configuration options. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::touch::{Touch, TouchConfig}; + /// let touch_cfg = Some(TouchConfig { + /// measurement_duration: Some(0x3000), + /// ..Default::default() + /// }); + /// let touch = Touch::continuous_mode(peripherals.TOUCH, touch_cfg); + /// # {after_snippet} + /// ``` + pub fn continuous_mode(touch_peripheral: TOUCH<'d>, config: Option) -> Self { + Self::initialize_common_continuous(config); + + Self { + _inner: touch_peripheral, + _mode: PhantomData, + _touch_mode: PhantomData, + } + } +} +impl<'d> Touch<'d, Continuous, Async> { + #[procmacros::doc_replace] + /// Initializes the touch peripheral in continuous async mode and returns + /// this marker struct. + /// + /// ## Warning: + /// + /// This uses [`RTC_CORE`](crate::peripherals::Interrupt::RTC_CORE) + /// interrupts under the hood. So the whole async part breaks if you install + /// an interrupt handler with [`Rtc::set_interrupt_handler()`][1]. + /// + /// [1]: ../rtc_cntl/struct.Rtc.html#method.set_interrupt_handler + /// + /// ## Parameters: + /// + /// - `rtc`: The RTC peripheral is needed to configure the required interrupts. + /// - `config`: Optional configuration options. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::rtc_cntl::Rtc; + /// # use esp_hal::touch::{Touch, TouchConfig}; + /// let mut rtc = Rtc::new(peripherals.LPWR); + /// let touch = Touch::async_mode(peripherals.TOUCH, &mut rtc, None); + /// # {after_snippet} + /// ``` + pub fn async_mode( + touch_peripheral: TOUCH<'d>, + rtc: &mut Rtc<'_>, + config: Option, + ) -> Self { + Self::initialize_common_continuous(config); + + rtc.set_interrupt_handler(asynch::handle_touch_interrupt); + + Self { + _inner: touch_peripheral, + _mode: PhantomData, + _touch_mode: PhantomData, + } + } +} + +/// A pin that is configured as a TouchPad. +pub struct TouchPad { + pin: P, + _touch_mode: PhantomData, + _mode: PhantomData, +} +impl TouchPad { + /// (Re-)Start a touch measurement on the pin. You can get the result by + /// calling [`read`](Self::read) once it is finished. + pub fn start_measurement(&mut self) { + crate::peripherals::RTC_IO::regs() + .touch_pad2() + .write(|w| unsafe { + w.start().set_bit(); + w.xpd().set_bit(); + // clear input_enable + w.fun_ie().clear_bit(); + // Connect pin to analog / RTC module instead of standard GPIO + w.mux_sel().set_bit(); + // Disable pull-up and pull-down resistors on the pin + w.rue().clear_bit(); + w.rde().clear_bit(); + w.tie_opt().clear_bit(); + w.to_gpio().set_bit(); + // Select function "RTC function 1" (GPIO) for analog use + w.fun_sel().bits(0b00) + }); + + crate::peripherals::SENS::regs() + .sar_touch_ctrl2() + .modify(|_, w| w.touch_start_en().clear_bit()); + crate::peripherals::SENS::regs() + .sar_touch_ctrl2() + .modify(|_, w| w.touch_start_en().set_bit()); + } +} +impl TouchPad { + /// Construct a new instance of [`TouchPad`]. + /// + /// ## Parameters: + /// - `pin`: The pin that gets configured as touch pad + /// - `touch`: The [`Touch`] struct indicating that touch is configured. + pub fn new(pin: P, _touch: &Touch<'_, Tm, Dm>) -> Self { + // TODO revert this on drop + pin.set_touch(Internal); + + Self { + pin, + _mode: PhantomData, + _touch_mode: PhantomData, + } + } + + /// Read the current touch pad capacitance counter. + /// + /// Usually a lower value means higher capacitance, thus indicating touch + /// event. + /// + /// Returns `None` if the value is not yet ready. (Note: Measurement must be + /// started manually with [`start_measurement`](Self::start_measurement) if + /// the touch peripheral is in [`OneShot`] mode). + pub fn try_read(&mut self) -> Option { + if unsafe { &*crate::peripherals::SENS::ptr() } + .sar_touch_ctrl2() + .read() + .touch_meas_done() + .bit_is_set() + { + Some(self.pin.touch_measurement(Internal)) + } else { + None + } + } +} +impl TouchPad { + /// Blocking read of the current touch pad capacitance counter. + /// + /// Usually a lower value means higher capacitance, thus indicating touch + /// event. + /// + /// ## Note for [`OneShot`] mode: + /// + /// This function might block forever, if + /// [`start_measurement`](Self::start_measurement) was not called before. As + /// measurements are not cleared, the touch values might also be + /// outdated, if it has been some time since the last call to that + /// function. + pub fn read(&mut self) -> u16 { + while unsafe { &*crate::peripherals::SENS::ptr() } + .sar_touch_ctrl2() + .read() + .touch_meas_done() + .bit_is_clear() + {} + self.pin.touch_measurement(Internal) + } + + /// Listens for the touch_pad interrupt. + /// + /// The raised interrupt is actually + /// [`RTC_CORE`](crate::peripherals::Interrupt::RTC_CORE). A handler can + /// be installed with [`Rtc::set_interrupt_handler()`][1]. + /// + /// [1]: ../rtc_cntl/struct.Rtc.html#method.set_interrupt_handler + /// + /// ## Parameters: + /// - `threshold`: The threshold above/below which the pin is considered touched. Above/below + /// depends on the configuration of `touch` in [`new`](Self::new) (defaults to below). + /// + /// ## Example + pub fn listen(&mut self, threshold: u16) { + self.pin.set_threshold(threshold, Internal); + listen(self.pin.touch_nr(Internal)) + } + + /// Unlisten for the touch pad's interrupt. + /// + /// If no other touch pad interrupts are active, the touch interrupt is + /// disabled completely. + pub fn unlisten(&mut self) { + unlisten(self.pin.touch_nr(Internal)) + } + + /// Clears a pending touch interrupt. + /// + /// ## Note on interrupt clearing behaviour: + /// + /// There is only a single interrupt for the touch pad. + /// [`is_interrupt_set`](Self::is_interrupt_set) can be used to check + /// which pins are touchted. However, this function clears the interrupt + /// status for all pins. So only call it when all pins are handled. + pub fn clear_interrupt(&mut self) { + internal_clear_interrupt() + } + + /// Checks if the pad is touched, based on the configured threshold value. + pub fn is_interrupt_set(&mut self) -> bool { + internal_is_interrupt_set(self.pin.touch_nr(Internal)) + } +} + +fn listen(touch_nr: u8) { + // enable touch interrupts + LPWR::regs().int_ena().write(|w| w.touch().set_bit()); + + SENS::regs().sar_touch_enable().modify(|r, w| unsafe { + w.touch_pad_outen1() + .bits(r.touch_pad_outen1().bits() | (1 << touch_nr)) + }); +} + +fn unlisten(touch_nr: u8) { + SENS::regs().sar_touch_enable().modify(|r, w| unsafe { + w.touch_pad_outen1() + .bits(r.touch_pad_outen1().bits() & !(1 << touch_nr)) + }); + if SENS::regs() + .sar_touch_enable() + .read() + .touch_pad_outen1() + .bits() + == 0 + { + LPWR::regs().int_ena().write(|w| w.touch().clear_bit()); + } +} + +fn internal_unlisten() { + SENS::regs() + .sar_touch_enable() + .write(|w| unsafe { w.touch_pad_outen1().bits(0) }); + if SENS::regs() + .sar_touch_enable() + .read() + .touch_pad_outen1() + .bits() + == 0 + { + LPWR::regs().int_ena().write(|w| w.touch().clear_bit()); + } +} + +fn internal_clear_interrupt() { + LPWR::regs() + .int_clr() + .write(|w| w.touch().clear_bit_by_one()); + SENS::regs() + .sar_touch_ctrl2() + .write(|w| w.touch_meas_en_clr().set_bit()); +} + +fn internal_pins_touched() -> u16 { + // Only god knows, why the "interrupt flag" register is called "meas_en" on this + // chip... + SENS::regs().sar_touch_ctrl2().read().touch_meas_en().bits() +} + +fn internal_is_interrupt_set(touch_nr: u8) -> bool { + internal_pins_touched() & (1 << touch_nr) != 0 +} + +mod asynch { + use core::{ + sync::atomic::{AtomicU16, Ordering}, + task::{Context, Poll}, + }; + + use super::*; + use crate::{Async, asynch::AtomicWaker, handler, ram}; + + const NUM_TOUCH_PINS: usize = 10; + + static TOUCH_WAKERS: [AtomicWaker; NUM_TOUCH_PINS] = + [const { AtomicWaker::new() }; NUM_TOUCH_PINS]; + + // Helper variable to store which pins need handling. + static TOUCHED_PINS: AtomicU16 = AtomicU16::new(0); + + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct TouchFuture { + touch_nr: u8, + } + + impl TouchFuture { + pub fn new(touch_nr: u8) -> Self { + Self { touch_nr } + } + } + + impl core::future::Future for TouchFuture { + type Output = (); + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + TOUCH_WAKERS[self.touch_nr as usize].register(cx.waker()); + + let pins = TOUCHED_PINS.load(Ordering::Acquire); + + if pins & (1 << self.touch_nr) != 0 { + // clear the pin to signal that this pin was handled. + TOUCHED_PINS.fetch_and(!(1 << self.touch_nr), Ordering::Release); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + + #[handler] + #[ram] + pub(super) fn handle_touch_interrupt() { + let touch_pads = internal_pins_touched(); + for (i, waker) in TOUCH_WAKERS.iter().enumerate() { + if touch_pads & (1 << i) != 0 { + waker.wake(); + } + } + TOUCHED_PINS.store(touch_pads, Ordering::Relaxed); + internal_clear_interrupt(); + internal_unlisten(); + } + + impl TouchPad { + /// Wait for the pad to be touched. + pub async fn wait_for_touch(&mut self, threshold: u16) { + self.pin.set_threshold(threshold, Internal); + let touch_nr = self.pin.touch_nr(Internal); + listen(touch_nr); + TouchFuture::new(touch_nr).await; + } + } +} diff --git a/esp-hal/src/trace.rs b/esp-hal/src/trace.rs new file mode 100644 index 00000000000..811f3dcb4f1 --- /dev/null +++ b/esp-hal/src/trace.rs @@ -0,0 +1,217 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # RISC-­V Trace Encoder (TRACE) +//! +//! ## Overview +//! The high-performance CPU supports instruction trace interface through the +//! trace encoder. The trace encoder connects to HP CPU’s instruction trace +//! interface, compresses the information into smaller packets, and then stores +//! the packets in internal SRAM. +//! +//! In complex systems, understanding program execution flow is not +//! straightforward. This may be due to a number of factors, for example, +//! interactions with other cores, peripherals, real-time events, poor +//! implementations, or some combination of all of the above. +//! +//! It is hard to use a debugger to monitor the program execution flow of a +//! running system in real time, as this is intrusive and might affect the +//! running state. But providing visibility of program execution is important. +//! +//! That is where instruction trace comes in, which provides trace of the +//! program execution. +//! +//! ## Examples +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::trace::Trace; +//! let mut buffer = [0_u8; 1024]; +//! let mut trace = Trace::new(peripherals.TRACE0); +//! trace.start_trace(&mut buffer); +//! // traced code +//! +//! // end traced code +//! let res = trace.stop_trace()?; +//! // transfer the trace result to the host and decode it there +//! # {after_snippet} +//! ``` + +use crate::{pac::trace::RegisterBlock, peripherals::TRACE0, system::PeripheralGuard}; + +/// Errors returned from [Trace::stop_trace] +#[derive(Debug, Clone, Copy)] +pub enum Error { + /// Attempted to stop a trace which had not been started yet + NotStarted, +} + +/// Returned by [Trace::stop_trace] +#[derive(Debug, Clone, Copy)] +pub struct TraceResult { + /// Start index of the valid data + pub valid_start_index: usize, + /// Length of the valid data + pub valid_length: usize, +} + +/// TRACE Encoder Instance +pub struct Trace<'d> { + peripheral: TRACE0<'d>, + buffer: Option<&'d mut [u8]>, + _guard: PeripheralGuard, +} + +impl<'d> Trace<'d> { + /// Construct a new instance + pub fn new(peripheral: TRACE0<'d>) -> Self { + let guard = PeripheralGuard::new(peripheral.peripheral()); + + Self { + peripheral, + buffer: None, + _guard: guard, + } + } + + /// Start tracing, writing data into the `buffer` + pub fn start_trace(&mut self, buffer: &'d mut [u8]) { + let reg_block = self.peripheral.register_block(); + + reg_block + .mem_start_addr() + .modify(|_, w| unsafe { w.mem_start_addr().bits(buffer.as_ptr() as *const _ as u32) }); + reg_block.mem_end_addr().modify(|_, w| unsafe { + w.mem_end_addr() + .bits((buffer.as_ptr() as *const _ as u32) + (buffer.len() as u32)) + }); + reg_block + .mem_addr_update() + .write(|w| w.mem_current_addr_update().set_bit()); + + // won't set bit in int-raw without enabling + reg_block + .intr_ena() + .modify(|_, w| w.mem_full_intr_ena().set_bit()); + + // for now always use looping mode + reg_block + .trigger() + .write(|w| w.mem_loop().set_bit().restart_ena().set_bit()); + + reg_block.intr_clr().write(|w| { + w.fifo_overflow_intr_clr() + .set_bit() + .mem_full_intr_clr() + .set_bit() + }); + + self.buffer.replace(buffer); + reg_block.trigger().write(|w| w.on().set_bit()); + } + + /// Stop tracing + /// + /// Be aware that valid data might not start at index 0 and you need to + /// account for wrapping when reading the data. + pub fn stop_trace(&mut self) -> Result { + let reg_block = self.peripheral.register_block(); + + reg_block + .trigger() + .write(|w| w.off().set_bit().restart_ena().clear_bit()); + + if self.buffer.is_none() { + return Err(Error::NotStarted); + } + + let buffer = self.buffer.take().unwrap(); + + while !reg_block.fifo_status().read().fifo_empty().bit_is_set() {} + + let overflow = reg_block.intr_raw().read().mem_full_intr_raw().bit(); + let idx = if overflow { + reg_block + .mem_current_addr() + .read() + .mem_current_addr() + .bits() + - &buffer as *const _ as u32 + } else { + 0 + }; + + let len = if overflow { + buffer.len() + } else { + reg_block + .mem_current_addr() + .read() + .mem_current_addr() + .bits() as usize + - buffer.as_ptr() as *const _ as usize + - 14 // there will be 14 zero bytes at the start + }; + + let mut valid = false; + let mut fourteen_zeroes = false; + let mut zeroes = 0; + let start_index = if !valid { + let mut i = 0; + loop { + let b = unsafe { + buffer + .as_ptr() + .add((i + idx as usize) % buffer.len()) + .read_volatile() + }; + + if !valid { + if b == 0 { + zeroes += 1; + } else { + zeroes = 0; + } + + if zeroes >= 14 { + fourteen_zeroes = true; + } + + if fourteen_zeroes && b != 0 { + valid = true; + } + } + + if valid { + break i; + } + + i += 1; + } + } else { + 0 + }; + + Ok(TraceResult { + valid_start_index: start_index, + valid_length: len, + }) + } +} + +/// Trace peripheral instance +#[doc(hidden)] +pub trait Instance: crate::private::Sealed { + /// Get a reference to the peripheral's underlying register block + fn register_block(&self) -> &RegisterBlock; + + /// Peripheral + fn peripheral(&self) -> crate::system::Peripheral; +} + +impl Instance for TRACE0<'_> { + fn register_block(&self) -> &RegisterBlock { + self.register_block() + } + + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::Trace0 + } +} diff --git a/esp-hal/src/tsens.rs b/esp-hal/src/tsens.rs new file mode 100644 index 00000000000..0a2eefe681f --- /dev/null +++ b/esp-hal/src/tsens.rs @@ -0,0 +1,182 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Temperature Sensor (tsens) +//! +//! ## Overview +//! +//! The Temperature Sensor peripheral is used to measure the internal +//! temperature inside the chip. The voltage is internally converted via an ADC +//! into a digital value, and has a measuring range of –40 °C to 125 °C. +//! The temperature value depends on factors like microcontroller clock +//! frequency or I/O load. Generally, the chip’s internal temperature is higher +//! than the operating ambient temperature. +//! +//! It is recommended to wait a few hundred microseconds after turning it on +//! before measuring, in order to allow the sensor to stabilize. +//! +//! ## Configuration +//! +//! The temperature sensor can be configured with different clock sources. +//! +//! ## Examples +//! +//! The following example will measure the internal chip temperature every +//! second, and print it +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::tsens::{TemperatureSensor, Config}; +//! # use esp_hal::delay::Delay; +//! +//! let temperature_sensor = TemperatureSensor::new(peripherals.TSENS, Config::default())?; +//! let delay = Delay::new(); +//! delay.delay_micros(200); +//! loop { +//! let temp = temperature_sensor.get_temperature(); +//! println!("Temperature: {:.2}°C", temp.to_celcius()); +//! delay.delay_millis(1_000); +//! } +//! # } +//! ``` +//! +//! ## Implementation State +//! +//! - Temperature calibration range is not supported +//! - Interrupts are not supported + +use crate::{ + peripherals::{APB_SARADC, TSENS}, + system::GenericPeripheralGuard, +}; + +/// Clock source for the temperature sensor +#[derive(Debug, Clone, Default, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ClockSource { + /// Use RC_FAST clock source + RcFast, + /// Use XTAL clock source + #[default] + Xtal, +} + +/// Temperature sensor configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, Copy, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// Clock source for the temperature sensor + clock_source: ClockSource, +} + +/// Temperature sensor configuration error +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[non_exhaustive] +pub enum ConfigError {} + +/// Temperature value +/// This struct stores the raw ADC value, and can be used to calculate the +/// temperature in Celsius using the formula: +/// `(raw_value * 0.4386) - (offset * 27.88) - 20.52` +#[derive(Debug)] +pub struct Temperature { + /// Raw ADC value + pub raw_value: u8, + + /// Offset value - depends on the temperature range configured + pub offset: i8, +} + +impl Temperature { + /// Create a new temperature value + #[inline] + pub fn new(raw_value: u8, offset: i8) -> Self { + Self { raw_value, offset } + } + + /// Get the temperature in Celsius + #[inline] + pub fn to_celsius(&self) -> f32 { + (self.raw_value as f32) * 0.4386 - (self.offset as f32) * 27.88 - 20.52 + } + + /// Get the temperature in Fahrenheit + #[inline] + pub fn to_fahrenheit(&self) -> f32 { + let celsius = self.to_celsius(); + (celsius * 1.8) + 32.0 + } + + /// Get the temperature in Kelvin + #[inline] + pub fn to_kelvin(&self) -> f32 { + let celsius = self.to_celsius(); + celsius + 273.15 + } +} + +/// Temperature sensor driver +#[derive(Debug)] +pub struct TemperatureSensor<'d> { + _peripheral: TSENS<'d>, + _tsens_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Tsens as u8 }>, + _abp_saradc_guard: GenericPeripheralGuard<{ crate::system::Peripheral::ApbSarAdc as u8 }>, +} + +impl<'d> TemperatureSensor<'d> { + /// Create a new temperature sensor instance with configuration + /// The sensor will be automatically powered up + pub fn new(peripheral: TSENS<'d>, config: Config) -> Result { + // NOTE: We need enable ApbSarAdc before enabling Tsens + let apb_saradc_guard = GenericPeripheralGuard::new(); + let tsens_guard = GenericPeripheralGuard::new(); + + let mut tsens = Self { + _peripheral: peripheral, + _tsens_guard: tsens_guard, + _abp_saradc_guard: apb_saradc_guard, + }; + tsens.apply_config(&config)?; + + tsens.power_up(); + + Ok(tsens) + } + + /// Power up the temperature sensor + pub fn power_up(&self) { + debug!("Power up"); + APB_SARADC::regs() + .tsens_ctrl() + .modify(|_, w| w.pu().set_bit()); + } + + /// Power down the temperature sensor - useful if you want to save power + pub fn power_down(&self) { + APB_SARADC::regs() + .tsens_ctrl() + .modify(|_, w| w.pu().clear_bit()); + } + + /// Change the temperature sensor configuration + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + // Set clock source + APB_SARADC::regs().tsens_ctrl2().write(|w| { + w.clk_sel() + .bit(matches!(config.clock_source, ClockSource::Xtal)) + }); + + Ok(()) + } + + /// Get the raw temperature value + #[inline] + pub fn get_temperature(&self) -> Temperature { + let raw_value = APB_SARADC::regs().tsens_ctrl().read().out().bits(); + + // TODO Address multiple temperature ranges and offsets + let offset = -1i8; + + Temperature::new(raw_value, offset) + } +} diff --git a/esp-hal-common/src/twai/filter.rs b/esp-hal/src/twai/filter.rs similarity index 79% rename from esp-hal-common/src/twai/filter.rs rename to esp-hal/src/twai/filter.rs index f2636e320c3..1539d982fdd 100644 --- a/esp-hal-common/src/twai/filter.rs +++ b/esp-hal/src/twai/filter.rs @@ -1,22 +1,47 @@ -//! # Two-wire Automotive Interface (TWAI) Filters +//! Two-wire Automotive Interface (TWAI) Filters //! -//! These are acceptance filters that limit which packets are received by the -//! TWAI peripheral. +//! ## Overview +//! +//! The TWAI controller contains a hardware acceptance filter which can be used +//! to filter messages of a particular ID. A node that filters out a message +//! does not receive the message, but will still acknowledge it. Acceptance +//! filters can make a node more efficient by filtering out messages sent over +//! the bus that are irrelevant to the node. +//! +//! ## Configuration +//! +//! The acceptance filters are configured using two 32-bit values known as the +//! acceptance code and the acceptance mask. -#[cfg(feature = "eh1")] -use embedded_can::{ExtendedId, StandardId}; -#[cfg(not(feature = "eh1"))] -use embedded_hal::can::{ExtendedId, StandardId}; +use super::{ExtendedId, StandardId}; #[derive(Debug, PartialEq, Eq)] +/// Represents the type of filtering to be applied to incoming TWAI frames. pub enum FilterType { + /// Uses the acceptance code and mask to define a single filter, which + /// allows for the first two data bytes of a standard frame to be filtered, + /// or the entirety of an extended frame's 29-bit ID. Single, + /// Uses the acceptance code and mask to define two separate filters + /// allowing for increased flexibility of ID's to accept, but does not allow + /// for all 29-bits of an extended ID to be filtered. Dual, } +/// Interface for interacting with Acceptance Filters. +/// +/// The Acceptance Filter is a programmable message filtering unit that allows +/// the TWAI controller to accept or reject a received message based on the +/// message’s ID field. +/// +/// Only accepted messages will be stored in the Receive FIFO. +/// +/// The Acceptance Filter’s registers can be programmed to specify a single +/// filter, or two separate filters (dual filter mode). pub trait Filter { /// The type of the filter. const FILTER_TYPE: FilterType; + /// Returns filter type. fn filter_type(&self) -> FilterType { Self::FILTER_TYPE } @@ -25,10 +50,10 @@ pub trait Filter { fn to_registers(&self) -> [u8; 8]; } +/// A type representing the bitmask used to filter incoming TWAI frames. pub type BitFilter = [u8; N]; -/// Internal macro used to convert a byte from a bytestring into a bit inside a -/// given code and mask. +// Convert a byte from a bytestring into a bit inside a given code and mask. macro_rules! set_bit_from_byte { ($code:expr, $mask:expr, $byte:expr, $shift:expr) => { match $byte { @@ -41,16 +66,16 @@ macro_rules! set_bit_from_byte { $mask |= 1 << $shift; } b'x' => {} - _ => panic!("BitFilter bits must be either '1', '0' or 'x'."), + _ => ::core::panic!("BitFilter bits must be either '1', '0' or 'x'."), } }; } -/// Convert a code and mask to the byte array needed at a register level. -/// -/// On the input mask, set bits (1) mean we care about the exact value of the -/// corresponding bit in the code, reset bits (0) mean the bit could be any -/// value. +// Convert a code and mask to the byte array needed at a register level. +// +// On the input mask, set bits (1) mean we care about the exact value of the +// corresponding bit in the code, reset bits (0) mean the bit could be any +// value. const fn code_mask_to_register_array(code: u32, mask: u32) -> [u8; 8] { // Convert the filter code and mask into the full byte array needed for the // registers. @@ -66,10 +91,10 @@ const fn code_mask_to_register_array(code: u32, mask: u32) -> [u8; 8] { ] } -/// A filter that matches against a single 11 bit id, the rtr bit, and the first +/// A filter that matches against a single 11 bit id, the RTR bit, and the first /// two bytes of the payload. /// -/// Warning: This is not a perfect filter. Extended ids that match the bit +/// Warning: This is not a perfect filter. Extended IDs that match the bit /// layout of this filter will also be accepted. pub struct SingleStandardFilter { /// The register representation of the filter. @@ -77,15 +102,19 @@ pub struct SingleStandardFilter { } impl SingleStandardFilter { + #[procmacros::doc_replace] /// Create a new filter that matches against a single 11-bit standard id. - /// The filter can match against the packet's id, rtr bit, and first two + /// The filter can match against the packet's id, RTR bit, and first two /// bytes of the payload. /// - /// Example matching only even ids, allowing any rtr value and any payload + /// Example matching only even IDs, allowing any rtr value and any payload /// data: - /// ``` + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::twai::filter::SingleStandardFilter; /// const FILTER: SingleStandardFilter = /// SingleStandardFilter::new(b"xxxxxxxxxx0", b"x", [b"xxxxxxxx", b"xxxxxxxx"]); + /// # {after_snippet} /// ``` pub const fn new(id: &BitFilter<11>, rtr: &BitFilter<1>, payload: [&BitFilter<8>; 2]) -> Self { // The bit values we desire to match against. This determines whether we want a @@ -105,7 +134,7 @@ impl SingleStandardFilter { idx += 1; } } - // Convert the rtr bit filter into the code and mask bits. + // Convert the RTR bit filter into the code and mask bits. { let shift = 20; set_bit_from_byte!(acceptance_code, acceptance_mask, rtr[0], shift); @@ -143,10 +172,10 @@ impl SingleStandardFilter { /// /// A filter that matches every standard id that is even, is not an rtr /// frame, with any bytes for the first two payload bytes. - /// ``` + /// ```rust, ignore /// let filter = twai::filter::SingleStandardFilter::new_from_code_mask( - /// StandardId::new(0x000).unwrap(), - /// StandardId::new(0x001).unwrap(), + /// StandardId::new(0x000)?, + /// StandardId::new(0x001)?, /// false, /// true, /// [0x00, 0x00], @@ -173,13 +202,13 @@ impl SingleStandardFilter { acceptance_code |= (id_code.as_raw() as u32) << 21; acceptance_mask |= (id_mask.as_raw() as u32) << 21; - // Pack the rtr bit into the full layout. + // Pack the RTR bit into the full layout. acceptance_code |= (rtr_code as u32) << 20; acceptance_mask |= (rtr_mask as u32) << 20; // Pack the payload bytes into the full layout. - acceptance_code |= (payload_code[0] as u32) << 8 | (payload_code[1] as u32); - acceptance_mask |= (payload_mask[0] as u32) << 8 | (payload_mask[1] as u32); + acceptance_code |= ((payload_code[0] as u32) << 8) | (payload_code[1] as u32); + acceptance_mask |= ((payload_mask[0] as u32) << 8) | (payload_mask[1] as u32); Self { raw: code_mask_to_register_array(acceptance_code, acceptance_mask), @@ -193,19 +222,21 @@ impl Filter for SingleStandardFilter { self.raw } } -/// Warning: This is not a perfect filter. Standard ids that match the bit + +/// Warning: This is not a perfect filter. Standard IDs that match the bit /// layout of this filter will also be accepted. pub struct SingleExtendedFilter { raw: [u8; 8], } + impl SingleExtendedFilter { /// Create a filter that matches against a single 29-bit extended id. /// - /// The filter can match against the packet's id and the rtr bit. + /// The filter can match against the packet's id and the RTR bit. /// /// # Examples - /// A filter matching any odd extended ids, with any rtr value. - /// ``` + /// A filter matching any odd extended IDs, with any rtr value. + /// ```rust, ignore /// const FILTER: twai::filter::SingleExtendedFilter = /// twai::filter::SingleExtendedFilter::new(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxx1", b"x"); /// ``` @@ -227,7 +258,7 @@ impl SingleExtendedFilter { idx += 1; } } - // Convert the rtr bit filter into the code and mask bits. + // Convert the RTR bit filter into the code and mask bits. { let shift = 2; set_bit_from_byte!(acceptance_code, acceptance_mask, rtr[0], shift); @@ -255,10 +286,10 @@ impl SingleExtendedFilter { let mut acceptance_mask: u32 = 0; // Pack the id into the full layout. - acceptance_code |= (id_code.as_raw() as u32) << 3; - acceptance_mask |= (id_mask.as_raw() as u32) << 3; + acceptance_code |= id_code.as_raw() << 3; + acceptance_mask |= id_mask.as_raw() << 3; - // Pack the rtr bit into the full layout. + // Pack the RTR bit into the full layout. acceptance_code |= (rtr_code as u32) << 2; acceptance_mask |= (rtr_mask as u32) << 2; @@ -275,27 +306,28 @@ impl Filter for SingleExtendedFilter { } } -/// A filter that matches against two standard 11-bit standard ids. +/// A filter that matches against two standard 11-bit standard IDs. /// -/// The first filter part can match a packet's id, rtr bit, and the first byte -/// of the payload. The second filter part can match a packet's id and rtr bit. +/// The first filter part can match a packet's id, RTR bit, and the first byte +/// of the payload. The second filter part can match a packet's id and RTR bit. /// -/// Warning: This is not a perfect filter. Extended ids that match the bit +/// Warning: This is not a perfect filter. Extended IDs that match the bit /// layout of this filter will also be accepted. pub struct DualStandardFilter { raw: [u8; 8], } + impl DualStandardFilter { - /// Create a filter that matches against two standard 11-bit standard ids. + /// Create a filter that matches against two standard 11-bit standard IDs. /// - /// The first filter part can match a packet's id, rtr bit, and the first + /// The first filter part can match a packet's id, RTR bit, and the first /// byte of the payload. The second filter part can match a packet's id - /// and rtr bit. + /// and RTR bit. /// /// # Examples /// A filter that matches any standard id that ends with a 00 or a 11, with - /// any rtr, and with any payload on the first filter. - /// ``` + /// any RTR, and with any payload on the first filter. + /// ```rust, ignore /// const FILTER: twai::filter::DualStandardFilter = twai::filter::DualStandardFilter::new( /// b"xxxxxxxxx00", /// b"x", @@ -328,7 +360,7 @@ impl DualStandardFilter { idx += 1; } } - // Convert the first rtr bit filter into the code and mask bits. + // Convert the first RTR bit filter into the code and mask bits. { let shift = 20; set_bit_from_byte!(acceptance_code, acceptance_mask, first_rtr[0], shift); @@ -356,7 +388,7 @@ impl DualStandardFilter { idx += 1; } } - // Convert the second rtr bit filter into the code and mask bits. + // Convert the second RTR bit filter into the code and mask bits. { let shift = 4; set_bit_from_byte!(acceptance_code, acceptance_mask, second_rtr[0], shift); @@ -369,6 +401,7 @@ impl DualStandardFilter { /// The masks indicate which bits of the code the filter should match /// against. Set bits in the mask indicate that the corresponding bit in /// the code should match. + #[expect(clippy::too_many_arguments)] pub fn new_from_code_mask( first_id_code: StandardId, first_id_mask: StandardId, @@ -393,7 +426,7 @@ impl DualStandardFilter { acceptance_code |= (first_id_code.as_raw() as u32) << 21; acceptance_mask |= (first_id_mask.as_raw() as u32) << 21; - // Pack the rtr bit into the full layout. + // Pack the RTR bit into the full layout. acceptance_code |= (first_rtr_code as u32) << 20; acceptance_mask |= (first_rtr_mask as u32) << 20; @@ -407,7 +440,7 @@ impl DualStandardFilter { acceptance_code |= (second_id_code.as_raw() as u32) << 5; acceptance_mask |= (second_id_mask.as_raw() as u32) << 5; - // Pack the second rtr bit into the full layout. + // Pack the second RTR bit into the full layout. acceptance_code |= (second_rtr_code as u32) << 4; acceptance_mask |= (second_rtr_mask as u32) << 4; @@ -423,25 +456,26 @@ impl Filter for DualStandardFilter { self.raw } } + +/// Warning: This is not a perfect filter. Standard IDs that match the bit +/// layout of this filter will also be accepted. +/// /// NOTE: The dual extended id acceptance filters can only match "the first 16 /// bits of the 29-bit ID". -/// -/// -/// Warning: This is not a perfect filter. Standard ids that match the bit -/// layout of this filter will also be accepted. pub struct DualExtendedFilter { raw: [u8; 8], } + impl DualExtendedFilter { /// Create a filter that matches the first 16 bits of two 29-bit extended - /// ids. + /// IDs. /// /// # Examples - /// A filter that matches ids with 4 bits either set or reset in the higher + /// A filter that matches IDs with 4 bits either set or reset in the higher /// part of the id. For example this id matches: 0x000f000f, 0x000f000a, /// 0x0000000a, 0x0000000b. /// But it does not match: 0x000a000a - /// ``` + /// ```rust, ignore /// const FILTER: twai::filter::DualExtendedFilter = /// twai::filter::DualExtendedFilter::new([b"xxxxxxxxx0000xxx", b"xxxxxxxxx1111xxx"]); /// ``` @@ -477,7 +511,7 @@ impl DualExtendedFilter { raw: code_mask_to_register_array(acceptance_code, acceptance_mask), } } - /// Create a new filter matching the first 16 bits of two 29-bit ids. + /// Create a new filter matching the first 16 bits of two 29-bit IDs. /// /// The masks indicate which bits of the code the filter should match /// against. Set bits in the mask indicate that the corresponding bit in @@ -504,6 +538,7 @@ impl DualExtendedFilter { } } } + impl Filter for DualExtendedFilter { const FILTER_TYPE: FilterType = FilterType::Dual; fn to_registers(&self) -> [u8; 8] { diff --git a/esp-hal/src/twai/mod.rs b/esp-hal/src/twai/mod.rs new file mode 100644 index 00000000000..058bc94323e --- /dev/null +++ b/esp-hal/src/twai/mod.rs @@ -0,0 +1,1820 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! # Two-wire Automotive Interface (TWAI) +//! +//! ## Overview +//! +//! The TWAI is a multi-master, multi-cast communication protocol with error +//! detection and signaling and inbuilt message priorities and arbitration. The +//! TWAI protocol is suited for automotive and industrial applications. +//! +//! See ESP-IDF's +#![doc = concat!("[TWAI documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", chip!(), "/api-reference/peripherals/twai.html#twai-protocol-summary)")] +//! for a summary on the protocol. +//! +//! ## Configuration +//! The driver offers functions for initializing the TWAI peripheral, setting +//! up the timing parameters, configuring acceptance filters, handling +//! interrupts, and transmitting/receiving messages on the TWAI bus. +//! +//! This driver manages the ISO 11898-1 compatible TWAI +//! controllers. It supports Standard Frame Format (11-bit) and Extended Frame +//! Format (29-bit) frame identifiers. +//! +//! ## Examples +//! +//! ### Transmitting and Receiving Messages +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::twai; +//! # use esp_hal::twai::filter; +//! # use esp_hal::twai::filter::SingleStandardFilter; +//! # use esp_hal::twai::TwaiConfiguration; +//! # use esp_hal::twai::BaudRate; +//! # use esp_hal::twai::TwaiMode; +//! # use nb::block; +//! // Use GPIO pins 2 and 3 to connect to the respective pins on the TWAI +//! // transceiver. +//! let twai_rx_pin = peripherals.GPIO3; +//! let twai_tx_pin = peripherals.GPIO2; +//! +//! // The speed of the TWAI bus. +//! const TWAI_BAUDRATE: twai::BaudRate = BaudRate::B1000K; +//! +//! // Begin configuring the TWAI peripheral. The peripheral is in a reset like +//! // state that prevents transmission but allows configuration. +//! let mut twai_config = twai::TwaiConfiguration::new( +//! peripherals.TWAI0, +//! twai_rx_pin, +//! twai_tx_pin, +//! TWAI_BAUDRATE, +//! TwaiMode::Normal, +//! ); +//! +//! // Partially filter the incoming messages to reduce overhead of receiving +//! // undesired messages +//! twai_config.set_filter( +//! const { SingleStandardFilter::new(b"xxxxxxxxxx0", b"x", [b"xxxxxxxx", b"xxxxxxxx"]) }, +//! ); +//! +//! // Start the peripheral. This locks the configuration settings of the +//! // peripheral and puts it into operation mode, allowing packets to be sent +//! // and received. +//! let mut twai = twai_config.start(); +//! +//! loop { +//! // Wait for a frame to be received. +//! let frame = block!(twai.receive())?; +//! +//! // Transmit the frame back. +//! let _result = block!(twai.transmit(&frame))?; +//! } +//! # } +//! ``` +//! +//! ### Self-testing (self reception of transmitted messages) +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::twai; +//! # use esp_hal::twai::filter; +//! # use esp_hal::twai::filter::SingleStandardFilter; +//! # use esp_hal::twai::TwaiConfiguration; +//! # use esp_hal::twai::BaudRate; +//! # use esp_hal::twai::EspTwaiFrame; +//! # use esp_hal::twai::StandardId; +//! # use esp_hal::twai::TwaiMode; +//! # use nb::block; +//! // Use GPIO pins 2 and 3 to connect to the respective pins on the TWAI +//! // transceiver. +//! let can_rx_pin = peripherals.GPIO3; +//! let can_tx_pin = peripherals.GPIO2; +//! +//! // The speed of the TWAI bus. +//! const TWAI_BAUDRATE: twai::BaudRate = BaudRate::B1000K; +//! +//! // Begin configuring the TWAI peripheral. +//! let mut can_config = twai::TwaiConfiguration::new( +//! peripherals.TWAI0, +//! can_rx_pin, +//! can_tx_pin, +//! TWAI_BAUDRATE, +//! TwaiMode::SelfTest +//! ); +//! +//! // Partially filter the incoming messages to reduce overhead of receiving +//! // undesired messages +//! can_config.set_filter(const { SingleStandardFilter::new(b"xxxxxxxxxx0", +//! b"x", [b"xxxxxxxx", b"xxxxxxxx"]) }); +//! +//! // Start the peripheral. This locks the configuration settings of the +//! // peripheral and puts it into operation mode, allowing packets to be sent +//! // and received. +//! let mut can = can_config.start(); +//! +//! # // TODO: `new_*` should return Result not Option +//! let frame = EspTwaiFrame::new_self_reception(StandardId::ZERO, +//! &[1, 2, 3]).unwrap(); // Wait for a frame to be received. +//! let frame = block!(can.receive())?; +//! +//! # loop {} +//! # } +//! ``` + +use core::marker::PhantomData; + +use enumset::{EnumSet, EnumSetType}; +use procmacros::handler; + +use self::filter::{Filter, FilterType}; +use crate::{ + Async, + Blocking, + DriverMode, + gpio::{ + DriveMode, + InputConfig, + InputSignal, + OutputConfig, + OutputSignal, + Pull, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + interrupt::InterruptHandler, + pac::twai0::RegisterBlock, + system::{Cpu, PeripheralGuard}, + twai::filter::SingleStandardFilter, +}; +pub mod filter; + +/// TWAI error kind +/// +/// This represents a common set of TWAI operation errors. HAL implementations +/// are free to define more specific or additional error types. However, by +/// providing a mapping to these common TWAI errors, generic code can still +/// react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[non_exhaustive] +pub enum ErrorKind { + /// The peripheral receive buffer was overrun. + Overrun, + // MAC sublayer errors + /// A bit error is detected at that bit time when the bit value that is + /// monitored differs from the bit value sent. + Bit, + /// A stuff error is detected at the bit time of the sixth consecutive + /// equal bit level in a frame field that shall be coded by the method + /// of bit stuffing. + Stuff, + /// Calculated CRC sequence does not equal the received one. + Crc, + /// A form error shall be detected when a fixed-form bit field contains + /// one or more illegal bits. + Form, + /// An ACK error shall be detected by a transmitter whenever it does not + /// monitor a dominant bit during the ACK slot. + Acknowledge, + /// A different error occurred. The original error may contain more + /// information. + Other, +} + +macro_rules! impl_display { + ($($kind:ident => $msg:expr),* $(,)?) => { + impl core::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + $(Self::$kind => write!(f, $msg)),* + } + } + } + + #[cfg(feature = "defmt")] + impl defmt::Format for ErrorKind { + fn format(&self, f: defmt::Formatter<'_>) { + match self { + $(Self::$kind => defmt::write!(f, $msg)),* + } + } + } + }; +} + +impl_display! { + Overrun => "The peripheral receive buffer was overrun", + Bit => "Bit value that is monitored differs from the bit value sent", + Stuff => "Sixth consecutive equal bits detected", + Crc => "Calculated CRC sequence does not equal the received one", + Form => "A fixed-form bit field contains one or more illegal bits", + Acknowledge => "Transmitted frame was not acknowledged", + Other => "A different error occurred. The original error may contain more information", +} + +#[instability::unstable] +impl From for embedded_can::ErrorKind { + fn from(value: ErrorKind) -> Self { + match value { + ErrorKind::Overrun => embedded_can::ErrorKind::Overrun, + ErrorKind::Bit => embedded_can::ErrorKind::Bit, + ErrorKind::Stuff => embedded_can::ErrorKind::Stuff, + ErrorKind::Crc => embedded_can::ErrorKind::Crc, + ErrorKind::Form => embedded_can::ErrorKind::Form, + ErrorKind::Acknowledge => embedded_can::ErrorKind::Acknowledge, + ErrorKind::Other => embedded_can::ErrorKind::Other, + } + } +} + +#[instability::unstable] +impl embedded_can::Error for ErrorKind { + fn kind(&self) -> embedded_can::ErrorKind { + (*self).into() + } +} + +/// Specifies in which mode the TWAI controller will operate. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TwaiMode { + /// Normal operating mode + Normal, + /// Self-test mode (no acknowledgement required for a successful message + /// transmission) + SelfTest, + /// Listen only operating mode + ListenOnly, +} + +/// Standard 11-bit TWAI Identifier (`0..=0x7FF`). +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StandardId(u16); + +impl StandardId { + /// TWAI ID `0`, the highest priority. + pub const ZERO: Self = StandardId(0); + + /// TWAI ID `0x7FF`, the lowest priority. + pub const MAX: Self = StandardId(0x7FF); + + /// Tries to create a `StandardId` from a raw 16-bit integer. + /// + /// This will return `None` if `raw` is out of range of an 11-bit integer + /// (`> 0x7FF`). + #[inline] + pub fn new(raw: u16) -> Option { + if raw <= 0x7FF { + Some(StandardId(raw)) + } else { + None + } + } + + /// Creates a new `StandardId` without checking if it is inside the valid + /// range. + /// + /// # Safety + /// Using this method can create an invalid ID and is thus marked as unsafe. + #[inline] + pub const unsafe fn new_unchecked(raw: u16) -> Self { + StandardId(raw) + } + + /// Returns TWAI Identifier as a raw 16-bit integer. + #[inline] + pub fn as_raw(&self) -> u16 { + self.0 + } +} + +#[instability::unstable] +impl From for embedded_can::StandardId { + fn from(value: StandardId) -> Self { + embedded_can::StandardId::new(value.as_raw()).unwrap() + } +} + +#[instability::unstable] +impl From for StandardId { + fn from(value: embedded_can::StandardId) -> Self { + StandardId::new(value.as_raw()).unwrap() + } +} + +/// Extended 29-bit TWAI Identifier (`0..=1FFF_FFFF`). +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ExtendedId(u32); + +impl ExtendedId { + /// TWAI ID `0`, the highest priority. + pub const ZERO: Self = ExtendedId(0); + + /// TWAI ID `0x1FFFFFFF`, the lowest priority. + pub const MAX: Self = ExtendedId(0x1FFF_FFFF); + + /// Tries to create a `ExtendedId` from a raw 32-bit integer. + /// + /// This will return `None` if `raw` is out of range of an 29-bit integer + /// (`> 0x1FFF_FFFF`). + #[inline] + pub fn new(raw: u32) -> Option { + if raw <= 0x1FFF_FFFF { + Some(ExtendedId(raw)) + } else { + None + } + } + + /// Creates a new `ExtendedId` without checking if it is inside the valid + /// range. + /// + /// # Safety + /// Using this method can create an invalid ID and is thus marked as unsafe. + #[inline] + pub const unsafe fn new_unchecked(raw: u32) -> Self { + ExtendedId(raw) + } + + /// Returns TWAI Identifier as a raw 32-bit integer. + #[inline] + pub fn as_raw(&self) -> u32 { + self.0 + } + + /// Returns the Base ID part of this extended identifier. + pub fn standard_id(&self) -> StandardId { + // ID-28 to ID-18 + StandardId((self.0 >> 18) as u16) + } +} + +#[instability::unstable] +impl From for embedded_can::ExtendedId { + fn from(value: ExtendedId) -> Self { + embedded_can::ExtendedId::new(value.0).unwrap() + } +} + +#[instability::unstable] +impl From for ExtendedId { + fn from(value: embedded_can::ExtendedId) -> Self { + ExtendedId::new(value.as_raw()).unwrap() + } +} + +/// A TWAI Identifier (standard or extended). +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Id { + /// Standard 11-bit Identifier (`0..=0x7FF`). + Standard(StandardId), + /// Extended 29-bit Identifier (`0..=0x1FFF_FFFF`). + Extended(ExtendedId), +} + +impl From for Id { + #[inline] + fn from(id: StandardId) -> Self { + Id::Standard(id) + } +} + +impl From for Id { + #[inline] + fn from(id: ExtendedId) -> Self { + Id::Extended(id) + } +} + +#[instability::unstable] +impl From for embedded_can::Id { + fn from(value: Id) -> Self { + match value { + Id::Standard(id) => embedded_can::Id::Standard(id.into()), + Id::Extended(id) => embedded_can::Id::Extended(id.into()), + } + } +} + +#[instability::unstable] +impl From for Id { + fn from(value: embedded_can::Id) -> Self { + match value { + embedded_can::Id::Standard(id) => Id::Standard(id.into()), + embedded_can::Id::Extended(id) => Id::Extended(id.into()), + } + } +} + +/// A TWAI Frame. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EspTwaiFrame { + id: Id, + dlc: usize, + data: [u8; 8], + is_remote: bool, + self_reception: bool, +} + +impl EspTwaiFrame { + /// Creates a new `EspTwaiFrame` with the specified ID and data payload. + pub fn new(id: impl Into, data: &[u8]) -> Option { + // TWAI frames cannot contain more than 8 bytes of data. + if data.len() > 8 { + return None; + } + + let mut d: [u8; 8] = [0; 8]; + d[..data.len()].copy_from_slice(data); + + Some(EspTwaiFrame { + id: id.into(), + data: d, + dlc: data.len(), + is_remote: false, + self_reception: false, + }) + } + + /// Creates a new `EspTwaiFrame` for a transmission request with the + /// specified ID and data length (DLC). + pub fn new_remote(id: impl Into, dlc: usize) -> Option { + // TWAI frames cannot have more than 8 bytes. + if dlc > 8 { + return None; + } + + Some(EspTwaiFrame { + id: id.into(), + data: [0; 8], + dlc, + is_remote: true, + self_reception: false, + }) + } + + /// Creates a new `EspTwaiFrame` ready for self-reception with the specified + /// ID and data payload. + pub fn new_self_reception(id: impl Into, data: &[u8]) -> Option { + if data.len() > 8 { + return None; + } + + let mut d: [u8; 8] = [0; 8]; + d[..data.len()].copy_from_slice(data); + + Some(EspTwaiFrame { + id: id.into(), + data: d, + dlc: data.len(), + is_remote: false, + self_reception: true, + }) + } + + /// Make a new frame from an id, pointer to the TWAI_DATA_x_REG registers, + /// and the length of the data payload (dlc). + /// + /// # Safety + /// This is unsafe because it directly accesses peripheral registers. + unsafe fn new_from_data_registers( + id: impl Into, + registers: *const u32, + dlc: usize, + ) -> Self { + let mut data: [u8; 8] = [0; 8]; + + // Copy the data from the memory mapped peripheral into actual memory. + unsafe { + copy_from_data_register(&mut data[..dlc], registers); + } + + Self { + id: id.into(), + data, + dlc, + is_remote: false, + self_reception: false, + } + } +} + +#[instability::unstable] +impl embedded_can::Frame for EspTwaiFrame { + fn new(id: impl Into, data: &[u8]) -> Option { + Self::new(id.into(), data) + } + + fn new_remote(id: impl Into, dlc: usize) -> Option { + Self::new_remote(id.into(), dlc) + } + + fn is_extended(&self) -> bool { + matches!(self.id, Id::Extended(_)) + } + + fn is_remote_frame(&self) -> bool { + self.is_remote + } + + fn id(&self) -> embedded_can::Id { + self.id.into() + } + + fn dlc(&self) -> usize { + self.dlc + } + + fn data(&self) -> &[u8] { + // Remote frames do not contain data, yet have a value for the dlc so return + // an empty slice for remote frames. + match self.is_remote { + true => &[], + false => &self.data[0..self.dlc], + } + } +} + +/// The underlying timings for the TWAI peripheral. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimingConfig { + /// The baudrate prescaler is used to determine the period of each time + /// quantum by dividing the TWAI controller's source clock. + pub baud_rate_prescaler: u16, + + /// The synchronization jump width is used to determine the maximum number + /// of time quanta a single bit time can be lengthened/shortened for + /// synchronization purposes. + pub sync_jump_width: u8, + + /// Timing segment 1 consists of 1 to 16 time quanta before sample point. + pub tseg_1: u8, + + /// Timing Segment 2 consists of 1 to 8 time quanta after sample point. + pub tseg_2: u8, + + /// Enabling triple sampling causes 3 time quanta to be sampled per bit + /// instead of 1. + pub triple_sample: bool, +} + +/// A selection of pre-determined baudrates for the TWAI driver. +/// Currently these timings are sourced from the ESP IDF C driver which assumes +/// an APB clock of 80MHz. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BaudRate { + /// A baud rate of 125 Kbps. + B125K, + /// A baud rate of 250 Kbps. + B250K, + /// A baud rate of 500 Kbps. + B500K, + /// A baud rate of 1 Mbps. + B1000K, + /// A custom baud rate defined by the user. + /// + /// This variant allows users to specify their own timing configuration + /// using a `TimingConfig` struct. + Custom(TimingConfig), +} + +impl BaudRate { + /// Convert the BaudRate into the timings that the peripheral needs. + // See: https://github.com/espressif/esp-idf/tree/ab4200e/components/esp_hal_twai/include/hal/twai_types.h + const fn timing(self) -> TimingConfig { + #[cfg(not(esp32h2))] + let timing = match self { + Self::B125K => TimingConfig { + baud_rate_prescaler: 32, + sync_jump_width: 3, + tseg_1: 15, + tseg_2: 4, + triple_sample: false, + }, + Self::B250K => TimingConfig { + baud_rate_prescaler: 16, + sync_jump_width: 3, + tseg_1: 15, + tseg_2: 4, + triple_sample: false, + }, + Self::B500K => TimingConfig { + baud_rate_prescaler: 8, + sync_jump_width: 3, + tseg_1: 15, + tseg_2: 4, + triple_sample: false, + }, + Self::B1000K => TimingConfig { + baud_rate_prescaler: 4, + sync_jump_width: 3, + tseg_1: 15, + tseg_2: 4, + triple_sample: false, + }, + Self::Custom(timing_config) => timing_config, + }; + + #[cfg(esp32h2)] + let timing = match self { + Self::B125K => TimingConfig { + baud_rate_prescaler: 16, + sync_jump_width: 3, + tseg_1: 11, + tseg_2: 4, + triple_sample: false, + }, + Self::B250K => TimingConfig { + baud_rate_prescaler: 8, + sync_jump_width: 3, + tseg_1: 11, + tseg_2: 4, + triple_sample: false, + }, + Self::B500K => TimingConfig { + baud_rate_prescaler: 4, + sync_jump_width: 3, + tseg_1: 11, + tseg_2: 4, + triple_sample: false, + }, + Self::B1000K => TimingConfig { + baud_rate_prescaler: 2, + sync_jump_width: 3, + tseg_1: 11, + tseg_2: 4, + triple_sample: false, + }, + Self::Custom(timing_config) => timing_config, + }; + + // clock source on ESP32-C6 is xtal (40MHz) + #[cfg(esp32c6)] + let timing = TimingConfig { + baud_rate_prescaler: timing.baud_rate_prescaler / 2, + ..timing + }; + + timing + } +} + +/// An inactive TWAI peripheral in the "Reset"/configuration state. +pub struct TwaiConfiguration<'d, Dm: DriverMode> { + twai: AnyTwai<'d>, + filter: Option<(FilterType, [u8; 8])>, + phantom: PhantomData, + mode: TwaiMode, + _guard: PeripheralGuard, +} + +impl<'d, Dm> TwaiConfiguration<'d, Dm> +where + Dm: DriverMode, +{ + fn new_internal( + twai: AnyTwai<'d>, + rx_pin: impl PeripheralInput<'d>, + tx_pin: impl PeripheralOutput<'d>, + baud_rate: BaudRate, + no_transceiver: bool, + mode: TwaiMode, + ) -> Self { + let rx_pin = rx_pin.into(); + let tx_pin = tx_pin.into(); + + let guard = PeripheralGuard::new(twai.peripheral()); + + let mut this = TwaiConfiguration { + twai, + filter: None, // We'll immediately call `set_filter` + phantom: PhantomData, + mode, + _guard: guard, + }; + + // Accept all messages by default. + this.set_filter( + const { SingleStandardFilter::new(b"xxxxxxxxxxx", b"x", [b"xxxxxxxx", b"xxxxxxxx"]) }, + ); + + // Set RESET bit to 1 + this.regs().mode().write(|w| w.reset_mode().set_bit()); + + // Enable extended register layout + #[cfg(esp32)] + this.regs() + .clock_divider() + .modify(|_, w| w.ext_mode().set_bit()); + + // Set up the GPIO pins. + let tx_config = if no_transceiver { + OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::Up) + } else { + OutputConfig::default() + }; + + tx_pin.apply_output_config(&tx_config); + rx_pin.apply_input_config(&InputConfig::default().with_pull(if no_transceiver { + Pull::Up + } else { + Pull::None + })); + + tx_pin.set_output_enable(true); + rx_pin.set_input_enable(true); + + this.twai.output_signal().connect_to(&tx_pin); + this.twai.input_signal().connect_to(&rx_pin); + + // Freeze REC by changing to LOM mode + this.set_mode(TwaiMode::ListenOnly); + + // Set TEC to 0 + this.regs() + .tx_err_cnt() + .write(|w| unsafe { w.tx_err_cnt().bits(0) }); + + // Set REC to 0 + this.regs() + .rx_err_cnt() + .write(|w| unsafe { w.rx_err_cnt().bits(0) }); + + // Set EWL to 96 + this.regs() + .err_warning_limit() + .write(|w| unsafe { w.err_warning_limit().bits(96) }); + + this.set_baud_rate(baud_rate); + this + } + + fn regs(&self) -> &RegisterBlock { + self.twai.register_block() + } + + fn internal_set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in Cpu::other() { + crate::interrupt::disable(core, self.twai.interrupt()); + } + crate::interrupt::bind_handler(self.twai.interrupt(), handler); + } + + /// Set the bitrate of the bus. + /// + /// Note: The timings currently assume a APB_CLK of 80MHz. + fn set_baud_rate(&mut self, baud_rate: BaudRate) { + // TWAI is clocked from the APB_CLK according to Table 6-4 [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf) + // Included timings are all for 80MHz so assert that we are running at 80MHz. + #[cfg(not(any(esp32h2, esp32c6)))] + { + let apb_clock = crate::clock::Clocks::get().apb_clock; + assert!(apb_clock.as_mhz() == 80); + } + + // Unpack the baud rate timings and convert them to the values needed for the + // register. Many of the registers have a minimum value of 1 which is + // represented by having zero bits set, therefore many values need to + // have 1 subtracted from them before being stored into the register. + let timing = baud_rate.timing(); + + #[cfg_attr(not(esp32), allow(unused_mut))] + let mut prescaler = timing.baud_rate_prescaler; + + #[cfg(esp32)] + { + // From + // and : + if timing.baud_rate_prescaler > 128 { + // Enable /2 baudrate divider by setting `brp_div`. + // `brp_div` is not an interrupt, it will prescale BRP by 2. Only available on + // ESP32 Revision 2 or later. Reserved otherwise. + self.regs().int_ena().modify(|_, w| w.brp_div().set_bit()); + prescaler = timing.baud_rate_prescaler / 2; + } else { + // Disable /2 baudrate divider by clearing brp_div. + self.regs().int_ena().modify(|_, w| w.brp_div().clear_bit()); + } + } + + let prescale = (prescaler / 2) - 1; + let sjw = timing.sync_jump_width - 1; + let tseg_1 = timing.tseg_1 - 1; + let tseg_2 = timing.tseg_2 - 1; + let triple_sample = timing.triple_sample; + + // Set up the prescaler and sync jump width. + self.regs().bus_timing_0().modify(|_, w| unsafe { + w.baud_presc().bits(prescale as _); + w.sync_jump_width().bits(sjw) + }); + + // Set up the time segment 1, time segment 2, and triple sample. + self.regs().bus_timing_1().modify(|_, w| unsafe { + w.time_seg1().bits(tseg_1); + w.time_seg2().bits(tseg_2); + w.time_samp().bit(triple_sample) + }); + + // disable CLKOUT + self.regs() + .clock_divider() + .modify(|_, w| w.clock_off().set_bit()); + } + + /// Set up the acceptance filter on the device. + /// + /// NOTE: On a bus with mixed 11-bit and 29-bit packet id's, you may + /// experience an 11-bit filter match against a 29-bit frame and vice + /// versa. Your application should check the id again once a frame has + /// been received to make sure it is the expected value. + /// + /// You may use a `const {}` block to ensure that the filter is parsed + /// during program compilation. + /// + /// The filter is not applied to the peripheral until [`Self::start`] is + /// called. + /// + /// [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf#subsubsection.29.4.6) + pub fn set_filter(&mut self, filter: impl Filter) { + // Convert the filter into values for the registers and store them for later + // use. + self.filter = Some((filter.filter_type(), filter.to_registers())); + } + + fn apply_filter(&self) { + let Some((filter_type, registers)) = self.filter.as_ref() else { + return; + }; + + // Set or clear the rx filter mode bit depending on the filter type. + self.regs() + .mode() + .modify(|_, w| w.rx_filter_mode().bit(*filter_type == FilterType::Single)); + + // Copy the filter to the peripheral. + unsafe { + copy_to_data_register(self.regs().data(0).as_ptr(), registers); + } + } + + /// Set the error warning threshold. + /// + /// In the case when any of an error counter value exceeds the threshold, or + /// all the error counter values are below the threshold, an error + /// warning interrupt will be triggered (given the enable signal is + /// valid). + pub fn set_error_warning_limit(&mut self, limit: u8) { + self.regs() + .err_warning_limit() + .write(|w| unsafe { w.err_warning_limit().bits(limit) }); + } + + /// Set the operating mode based on provided option + fn set_mode(&self, mode: TwaiMode) { + self.regs().mode().modify(|_, w| { + // self-test mode turns off acknowledgement requirement + w.self_test_mode().bit(mode == TwaiMode::SelfTest); + w.listen_only_mode().bit(mode == TwaiMode::ListenOnly) + }); + } + + /// Put the peripheral into Operation Mode, allowing the transmission and + /// reception of packets using the new object. + pub fn start(self) -> Twai<'d, Dm> { + self.apply_filter(); + self.set_mode(self.mode); + + // Clear the TEC and REC + self.regs() + .tx_err_cnt() + .write(|w| unsafe { w.tx_err_cnt().bits(0) }); + + let rec = + if cfg!(any(esp32, esp32s2, esp32s3, esp32c3)) && self.mode == TwaiMode::ListenOnly { + // Errata workaround: Prevent transmission of dominant error frame while in + // listen only mode by setting REC to 128 before exiting reset mode. + // This forces the controller to be error passive (thus only transmits recessive + // bits). The TEC/REC remain frozen in listen only mode thus + // ensuring we remain error passive. + 128 + } else { + 0 + }; + self.regs() + .rx_err_cnt() + .write(|w| unsafe { w.rx_err_cnt().bits(rec) }); + + // Clear any interrupts by reading the status register + let _ = self.regs().int_raw().read(); + + // Put the peripheral into operation mode by clearing the reset mode bit. + self.regs().mode().modify(|_, w| w.reset_mode().clear_bit()); + + Twai { + rx: TwaiRx { + twai: unsafe { self.twai.clone_unchecked() }, + phantom: PhantomData, + _guard: PeripheralGuard::new(self.twai.peripheral()), + }, + tx: TwaiTx { + twai: unsafe { self.twai.clone_unchecked() }, + phantom: PhantomData, + _guard: PeripheralGuard::new(self.twai.peripheral()), + }, + twai: unsafe { self.twai.clone_unchecked() }, + phantom: PhantomData, + } + } +} + +impl<'d> TwaiConfiguration<'d, Blocking> { + /// Create a new instance of [TwaiConfiguration] + /// + /// You will need to use a transceiver to connect to the TWAI bus + pub fn new( + peripheral: impl Instance + 'd, + rx_pin: impl PeripheralInput<'d>, + tx_pin: impl PeripheralOutput<'d>, + baud_rate: BaudRate, + mode: TwaiMode, + ) -> Self { + Self::new_internal(peripheral.degrade(), rx_pin, tx_pin, baud_rate, false, mode) + } + + /// Create a new instance of [TwaiConfiguration] meant to connect two ESP32s + /// directly + /// + /// You don't need a transceiver by following the description in the + /// `twai.rs` example + pub fn new_no_transceiver( + peripheral: impl Instance + 'd, + rx_pin: impl PeripheralInput<'d>, + tx_pin: impl PeripheralOutput<'d>, + baud_rate: BaudRate, + mode: TwaiMode, + ) -> Self { + Self::new_internal(peripheral.degrade(), rx_pin, tx_pin, baud_rate, true, mode) + } + + /// Convert the configuration into an async configuration. + pub fn into_async(mut self) -> TwaiConfiguration<'d, Async> { + self.set_interrupt_handler(self.twai.async_handler()); + TwaiConfiguration { + twai: self.twai, + filter: self.filter, + phantom: PhantomData, + mode: self.mode, + _guard: self._guard, + } + } + + /// Registers an interrupt handler for the TWAI peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.internal_set_interrupt_handler(handler); + } +} + +impl<'d> TwaiConfiguration<'d, Async> { + /// Convert the configuration into a blocking configuration. + pub fn into_blocking(self) -> TwaiConfiguration<'d, Blocking> { + use crate::{interrupt, system::Cpu}; + + interrupt::disable(Cpu::current(), self.twai.interrupt()); + + // Re-create in blocking mode + TwaiConfiguration { + twai: self.twai, + filter: self.filter, + phantom: PhantomData, + mode: self.mode, + _guard: self._guard, + } + } +} + +impl crate::private::Sealed for TwaiConfiguration<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for TwaiConfiguration<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.internal_set_interrupt_handler(handler); + } +} + +/// An active TWAI peripheral in Normal Mode. +/// +/// In this mode, the TWAI controller can transmit and receive messages +/// including error signals (such as error and overload frames). +pub struct Twai<'d, Dm: DriverMode> { + twai: AnyTwai<'d>, + tx: TwaiTx<'d, Dm>, + rx: TwaiRx<'d, Dm>, + phantom: PhantomData, +} + +impl<'d, Dm> Twai<'d, Dm> +where + Dm: DriverMode, +{ + fn regs(&self) -> &RegisterBlock { + self.twai.register_block() + } + + fn mode(&self) -> TwaiMode { + let mode = self.regs().mode().read(); + + if mode.self_test_mode().bit_is_set() { + TwaiMode::SelfTest + } else if mode.listen_only_mode().bit_is_set() { + TwaiMode::ListenOnly + } else { + TwaiMode::Normal + } + } + + /// Stop the peripheral, putting it into reset mode and enabling + /// reconfiguration. + pub fn stop(self) -> TwaiConfiguration<'d, Dm> { + // Put the peripheral into reset/configuration mode by setting the reset mode + // bit. + self.regs().mode().modify(|_, w| w.reset_mode().set_bit()); + + let mode = self.mode(); + + let guard = PeripheralGuard::new(self.twai.peripheral()); + TwaiConfiguration { + twai: self.twai, + filter: None, // filter already applied, no need to restore it + phantom: PhantomData, + mode, + _guard: guard, + } + } + + /// Returns the value of the receive error counter. + pub fn receive_error_count(&self) -> u8 { + self.regs().rx_err_cnt().read().rx_err_cnt().bits() + } + + /// Returns the value of the transmit error counter. + pub fn transmit_error_count(&self) -> u8 { + self.regs().tx_err_cnt().read().tx_err_cnt().bits() + } + + /// Check if the controller is in a bus off state. + pub fn is_bus_off(&self) -> bool { + self.regs().status().read().bus_off_st().bit_is_set() + } + + /// Get the number of messages that the peripheral has available in the + /// receive FIFO. + /// + /// Note that this may not be the number of valid messages in the receive + /// FIFO due to fifo overflow/overrun. + pub fn num_available_messages(&self) -> u8 { + self.regs() + .rx_message_cnt() + .read() + .rx_message_counter() + .bits() + } + + /// Clear the receive FIFO, discarding any valid, partial, or invalid + /// packets. + /// + /// This is typically used to clear an overrun receive FIFO. + /// + /// TODO: Not sure if this needs to be guarded against Bus Off or other + /// error states. + pub fn clear_receive_fifo(&self) { + while self.num_available_messages() > 0 { + release_receive_fifo(self.regs()); + } + } + + /// Sends the specified `EspTwaiFrame` over the TWAI bus. + pub fn transmit(&mut self, frame: &EspTwaiFrame) -> nb::Result<(), EspTwaiError> { + self.tx.transmit(frame) + } + + /// Receives a TWAI frame from the TWAI bus. + pub fn receive(&mut self) -> nb::Result { + self.rx.receive() + } + + /// Consumes this `Twai` instance and splits it into transmitting and + /// receiving halves. + pub fn split(self) -> (TwaiRx<'d, Dm>, TwaiTx<'d, Dm>) { + (self.rx, self.tx) + } +} + +/// Interface to the TWAI transmitter part. +pub struct TwaiTx<'d, Dm: DriverMode> { + twai: AnyTwai<'d>, + phantom: PhantomData, + _guard: PeripheralGuard, +} + +impl TwaiTx<'_, Dm> +where + Dm: DriverMode, +{ + fn regs(&self) -> &RegisterBlock { + self.twai.register_block() + } + + /// Transmit a frame. + /// + /// Because of how the TWAI registers are set up, we have to do some + /// assembly of bytes. Note that these registers serve a filter + /// configuration role when the device is in configuration mode so + /// patching the svd files to improve this may be non-trivial. + /// + /// [ESP32C3 Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf#subsubsection.29.4.4.2) + /// + /// NOTE: TODO: This may not work if using the self reception/self test + /// functionality. See notes 1 and 2 in the "Frame Identifier" section + /// of the reference manual. + pub fn transmit(&mut self, frame: &EspTwaiFrame) -> nb::Result<(), EspTwaiError> { + let status = self.regs().status().read(); + + // Check that the peripheral is not in a bus off state. + if status.bus_off_st().bit_is_set() { + return nb::Result::Err(nb::Error::Other(EspTwaiError::BusOff)); + } + // Check that the peripheral is not already transmitting a packet. + if !status.tx_buf_st().bit_is_set() { + return nb::Result::Err(nb::Error::WouldBlock); + } + + write_frame(self.regs(), frame); + + Ok(()) + } +} + +/// Interface to the TWAI receiver part. +pub struct TwaiRx<'d, Dm: DriverMode> { + twai: AnyTwai<'d>, + phantom: PhantomData, + _guard: PeripheralGuard, +} + +impl TwaiRx<'_, Dm> +where + Dm: DriverMode, +{ + fn regs(&self) -> &RegisterBlock { + self.twai.register_block() + } + + /// Receive a frame + pub fn receive(&mut self) -> nb::Result { + let status = self.regs().status().read(); + + // Check that the peripheral is not in a bus off state. + if status.bus_off_st().bit_is_set() { + return nb::Result::Err(nb::Error::Other(EspTwaiError::BusOff)); + } + + // Check that we actually have packets to receive. + if !status.rx_buf_st().bit_is_set() { + return nb::Result::Err(nb::Error::WouldBlock); + } + + // Check if the packet in the receive buffer is valid or overrun. + if status.miss_st().bit_is_set() { + return nb::Result::Err(nb::Error::Other(EspTwaiError::EmbeddedHAL( + ErrorKind::Overrun, + ))); + } + + Ok(read_frame(self.regs())?) + } +} + +/// List of TWAI events. +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum TwaiInterrupt { + /// A frame has been received. + Receive, + /// A frame has been transmitted. + Transmit, + /// An error has occurred on the bus. + BusError, + /// An arbitration lost event has occurred. + ArbitrationLost, + /// The controller has entered an error passive state. + ErrorPassive, +} + +/// Represents errors that can occur in the TWAI driver. +/// This enum defines the possible errors that can be encountered when +/// interacting with the TWAI peripheral. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EspTwaiError { + /// TWAI peripheral has entered a bus-off state. + BusOff, + /// The received frame contains an invalid DLC. + NonCompliantDlc(u8), + /// Encapsulates errors defined by the embedded-hal crate. + EmbeddedHAL(ErrorKind), +} + +#[instability::unstable] +impl embedded_can::Error for EspTwaiError { + fn kind(&self) -> embedded_can::ErrorKind { + if let Self::EmbeddedHAL(kind) = self { + (*kind).into() + } else { + embedded_can::ErrorKind::Other + } + } +} + +/// Copy data from multiple TWAI_DATA_x_REG registers, packing the source into +/// the destination. +/// +/// # Safety +/// This function is marked unsafe because it reads arbitrarily from +/// memory-mapped registers. Specifically, this function is used with the +/// TWAI_DATA_x_REG registers which has different results based on the mode of +/// the peripheral. +#[inline(always)] +unsafe fn copy_from_data_register(dest: &mut [u8], src: *const u32) { + for (i, dest) in dest.iter_mut().enumerate() { + // Perform a volatile read to avoid compiler optimizations. + unsafe { + *dest = src.add(i).read_volatile() as u8; + } + } +} + +/// Copy data to multiple TWAI_DATA_x_REG registers, unpacking the source into +/// the destination. +/// +/// # Safety +/// This function is marked unsafe because it writes arbitrarily to +/// memory-mapped registers. Specifically, this function is used with the +/// TWAI_DATA_x_REG registers which has different results based on the mode of +/// the peripheral. +#[inline(always)] +unsafe fn copy_to_data_register(dest: *mut u32, src: &[u8]) { + for (i, src) in src.iter().enumerate() { + // Perform a volatile write to avoid compiler optimizations. + unsafe { + dest.add(i).write_volatile(*src as u32); + } + } +} + +#[instability::unstable] +impl embedded_can::nb::Can for Twai<'_, Dm> +where + Dm: DriverMode, +{ + type Frame = EspTwaiFrame; + type Error = EspTwaiError; + + /// Transmit a frame. + fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { + self.tx.transmit(frame)?; + + // Success in readying packet for transmit. No packets can be replaced in the + // transmit buffer so return None in accordance with the + // embedded-can/embedded-hal trait. + nb::Result::Ok(None) + } + + /// Return a received frame if there are any available. + fn receive(&mut self) -> nb::Result { + self.rx.receive() + } +} + +/// TWAI peripheral instance. +#[doc(hidden)] +pub trait PrivateInstance: crate::private::Sealed { + /// Returns the system peripheral marker for this instance. + fn peripheral(&self) -> crate::system::Peripheral; + + /// Input signal. + fn input_signal(&self) -> InputSignal; + /// Output signal. + fn output_signal(&self) -> OutputSignal; + /// The interrupt associated with this TWAI instance. + fn interrupt(&self) -> crate::peripherals::Interrupt; + + /// Provides an asynchronous interrupt handler for TWAI instance. + fn async_handler(&self) -> InterruptHandler; + + /// Returns a reference to the register block for TWAI instance. + fn register_block(&self) -> &RegisterBlock; + + /// Enables/disables interrupts for the TWAI peripheral based on the `enable` flag. + fn enable_interrupts(&self, interrupts: EnumSet, enable: bool) { + self.register_block().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + TwaiInterrupt::Receive => w.rx_int_ena().bit(enable), + TwaiInterrupt::Transmit => w.tx_int_ena().bit(enable), + TwaiInterrupt::BusError => w.bus_err_int_ena().bit(enable), + TwaiInterrupt::ArbitrationLost => w.arb_lost_int_ena().bit(enable), + TwaiInterrupt::ErrorPassive => w.err_passive_int_ena().bit(enable), + }; + } + w + }); + } + + /// Listen for given interrupts. + fn listen(&mut self, interrupts: impl Into>) { + self.enable_interrupts(interrupts.into(), true); + } + + /// Unlisten the given interrupts. + fn unlisten(&mut self, interrupts: impl Into>) { + self.enable_interrupts(interrupts.into(), false); + } + /// Returns a reference to the asynchronous state for this TWAI instance. + fn async_state(&self) -> &asynch::TwaiAsyncState; +} + +/// Read a frame from the peripheral. +fn read_frame(register_block: &RegisterBlock) -> Result { + // Read the frame information and extract the frame id format and dlc. + let data_0 = register_block.data(0).read().tx_byte().bits(); + + let is_standard_format = data_0 & (0b1 << 7) == 0; + let is_data_frame = data_0 & (0b1 << 6) == 0; + let self_reception = data_0 & (0b1 << 4) != 0; + let dlc = data_0 & 0b1111; + + if dlc > 8 { + // Release the packet we read from the FIFO, allowing the peripheral to prepare + // the next packet. + release_receive_fifo(register_block); + + return Err(EspTwaiError::NonCompliantDlc(dlc)); + } + let dlc = dlc as usize; + + // Read the payload from the packet and construct a frame. + let (id, data_ptr) = if is_standard_format { + // Frame uses standard 11 bit id. + let data_1 = register_block.data(1).read().tx_byte().bits(); + let data_2 = register_block.data(2).read().tx_byte().bits(); + + let raw_id: u16 = ((data_1 as u16) << 3) | ((data_2 as u16) >> 5); + + let id = Id::from(StandardId::new(raw_id).unwrap()); + (id, register_block.data(3).as_ptr()) + } else { + // Frame uses extended 29 bit id. + let data_1 = register_block.data(1).read().tx_byte().bits(); + let data_2 = register_block.data(2).read().tx_byte().bits(); + let data_3 = register_block.data(3).read().tx_byte().bits(); + let data_4 = register_block.data(4).read().tx_byte().bits(); + + let raw_id: u32 = ((data_1 as u32) << 21) + | ((data_2 as u32) << 13) + | ((data_3 as u32) << 5) + | ((data_4 as u32) >> 3); + + let id = Id::from(ExtendedId::new(raw_id).unwrap()); + (id, register_block.data(5).as_ptr()) + }; + + let mut frame = if is_data_frame { + unsafe { EspTwaiFrame::new_from_data_registers(id, data_ptr, dlc) } + } else { + EspTwaiFrame::new_remote(id, dlc).unwrap() + }; + frame.self_reception = self_reception; + + // Release the packet we read from the FIFO, allowing the peripheral to prepare + // the next packet. + release_receive_fifo(register_block); + + Ok(frame) +} + +/// Release the message in the buffer. This will decrement the received +/// message counter and prepare the next message in the FIFO for +/// reading. +fn release_receive_fifo(register_block: &RegisterBlock) { + register_block.cmd().write(|w| w.release_buf().set_bit()); +} + +/// Write a frame to the peripheral. +fn write_frame(register_block: &RegisterBlock, frame: &EspTwaiFrame) { + // Assemble the frame information into the data_0 byte. + let frame_format: u8 = matches!(frame.id, Id::Extended(_)) as u8; + let self_reception: u8 = frame.self_reception as u8; + let rtr_bit: u8 = frame.is_remote as u8; + let dlc_bits: u8 = frame.dlc as u8 & 0b1111; + + let data_0: u8 = (frame_format << 7) | (rtr_bit << 6) | (self_reception << 4) | dlc_bits; + + register_block + .data(0) + .write(|w| unsafe { w.tx_byte().bits(data_0) }); + + // Assemble the identifier information of the packet and return where the data + // buffer starts. + let data_ptr = match frame.id { + Id::Standard(id) => { + let id = id.as_raw(); + + register_block + .data(1) + .write(|w| unsafe { w.tx_byte().bits((id >> 3) as u8) }); + + register_block + .data(2) + .write(|w| unsafe { w.tx_byte().bits((id << 5) as u8) }); + + register_block.data(3).as_ptr() + } + Id::Extended(id) => { + let id = id.as_raw(); + + register_block + .data(1) + .write(|w| unsafe { w.tx_byte().bits((id >> 21) as u8) }); + register_block + .data(2) + .write(|w| unsafe { w.tx_byte().bits((id >> 13) as u8) }); + register_block + .data(3) + .write(|w| unsafe { w.tx_byte().bits((id >> 5) as u8) }); + register_block + .data(4) + .write(|w| unsafe { w.tx_byte().bits((id << 3) as u8) }); + + register_block.data(5).as_ptr() + } + }; + + // Store the data portion of the packet into the transmit buffer. + unsafe { + copy_to_data_register( + data_ptr, + match frame.is_remote { + true => &[], // RTR frame, so no data is included. + false => &frame.data[0..frame.dlc], + }, + ) + } + + // Trigger the appropriate transmission request based on self_reception flag + if frame.self_reception { + register_block.cmd().write(|w| w.self_rx_req().set_bit()); + } else { + // Set the transmit request command, this will lock the transmit buffer until + // the transmission is complete or aborted. + register_block.cmd().write(|w| w.tx_req().set_bit()); + } +} + +impl PrivateInstance for crate::peripherals::TWAI0<'_> { + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::Twai0 + } + + fn input_signal(&self) -> InputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32c3, esp32s2, esp32s3))] { + InputSignal::TWAI_RX + } else { + InputSignal::TWAI0_RX + } + } + } + + fn output_signal(&self) -> OutputSignal { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32c3, esp32s2, esp32s3))] { + OutputSignal::TWAI_TX + } else { + OutputSignal::TWAI0_TX + } + } + } + + fn interrupt(&self) -> crate::peripherals::Interrupt { + crate::peripherals::Interrupt::TWAI0 + } + + fn async_handler(&self) -> InterruptHandler { + #[handler] + fn twai0() { + let twai = unsafe { crate::peripherals::TWAI0::steal() }; + asynch::handle_interrupt(twai.register_block(), twai.async_state()); + } + + twai0 + } + + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + crate::peripherals::TWAI0::regs() + } + + fn async_state(&self) -> &asynch::TwaiAsyncState { + static STATE: asynch::TwaiAsyncState = asynch::TwaiAsyncState::new(); + &STATE + } +} + +#[cfg(soc_has_twai1)] +impl PrivateInstance for crate::peripherals::TWAI1<'_> { + fn peripheral(&self) -> crate::system::Peripheral { + crate::system::Peripheral::Twai1 + } + + fn input_signal(&self) -> InputSignal { + InputSignal::TWAI1_RX + } + + fn output_signal(&self) -> OutputSignal { + OutputSignal::TWAI1_TX + } + + fn interrupt(&self) -> crate::peripherals::Interrupt { + crate::peripherals::Interrupt::TWAI1 + } + + fn async_handler(&self) -> InterruptHandler { + #[handler] + fn twai1() { + let twai = unsafe { crate::peripherals::TWAI1::steal() }; + asynch::handle_interrupt(twai.register_block(), twai.async_state()); + } + + twai1 + } + + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + crate::peripherals::TWAI1::regs() + } + + fn async_state(&self) -> &asynch::TwaiAsyncState { + static STATE: asynch::TwaiAsyncState = asynch::TwaiAsyncState::new(); + &STATE + } +} + +crate::any_peripheral! { + /// Any TWAI peripheral. + pub peripheral AnyTwai<'d> { + #[cfg(soc_has_twai0)] + Twai0(crate::peripherals::TWAI0<'d>), + #[cfg(soc_has_twai1)] + Twai1(crate::peripherals::TWAI1<'d>), + } +} + +impl PrivateInstance for AnyTwai<'_> { + delegate::delegate! { + to match &self.0 { + #[cfg(soc_has_twai0)] + any::Inner::Twai0(twai) => twai, + #[cfg(soc_has_twai1)] + any::Inner::Twai1(twai) => twai, + } { + fn peripheral(&self) -> crate::system::Peripheral; + fn input_signal(&self) -> InputSignal; + fn output_signal(&self) -> OutputSignal; + fn interrupt(&self) -> crate::peripherals::Interrupt; + fn async_handler(&self) -> InterruptHandler; + fn register_block(&self) -> &RegisterBlock; + fn async_state(&self) -> &asynch::TwaiAsyncState; + } + } +} + +/// A peripheral singleton compatible with the TWAI driver. +pub trait Instance: PrivateInstance + any::Degrade {} + +#[cfg(soc_has_twai0)] +impl Instance for crate::peripherals::TWAI0<'_> {} +#[cfg(soc_has_twai1)] +impl Instance for crate::peripherals::TWAI1<'_> {} +impl Instance for AnyTwai<'_> {} + +mod asynch { + use core::{future::poll_fn, task::Poll}; + + use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, + channel::Channel, + waitqueue::AtomicWaker, + }; + + use super::*; + + pub struct TwaiAsyncState { + pub tx_waker: AtomicWaker, + pub err_waker: AtomicWaker, + pub rx_queue: Channel, 32>, + } + + impl Default for TwaiAsyncState { + fn default() -> Self { + Self::new() + } + } + + impl TwaiAsyncState { + pub const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + err_waker: AtomicWaker::new(), + rx_queue: Channel::new(), + } + } + } + + impl Twai<'_, Async> { + /// Transmits an `EspTwaiFrame` asynchronously over the TWAI bus. + /// + /// The transmission is aborted if the future is dropped. The technical + /// reference manual does not specifiy if aborting the transmission also + /// stops it, in case it is activly transmitting. Therefor it could be + /// the case that even though the future is dropped, the frame was sent + /// anyways. + pub async fn transmit_async(&mut self, frame: &EspTwaiFrame) -> Result<(), EspTwaiError> { + self.tx.transmit_async(frame).await + } + /// Receives an `EspTwaiFrame` asynchronously over the TWAI bus. + pub async fn receive_async(&mut self) -> Result { + self.rx.receive_async().await + } + } + + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct TransmitFuture<'d, 'f> { + twai: AnyTwai<'d>, + frame: &'f EspTwaiFrame, + in_flight: bool, + } + + impl<'d, 'f> TransmitFuture<'d, 'f> { + pub fn new(twai: AnyTwai<'d>, frame: &'f EspTwaiFrame) -> Self { + Self { + twai, + frame, + in_flight: false, + } + } + } + + impl core::future::Future for TransmitFuture<'_, '_> { + type Output = Result<(), EspTwaiError>; + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + self.twai.async_state().tx_waker.register(cx.waker()); + + let regs = self.twai.register_block(); + let status = regs.status().read(); + + // Check that the peripheral is not in a bus off state. + if status.bus_off_st().bit_is_set() { + return Poll::Ready(Err(EspTwaiError::BusOff)); + } + + // Check that the peripheral is not currently transmitting a packet. + if !status.tx_buf_st().bit_is_set() { + return Poll::Pending; + } + + if !self.in_flight { + write_frame(regs, self.frame); + self.in_flight = true; + return Poll::Pending; + } + + Poll::Ready(Ok(())) + } + } + + impl Drop for TransmitFuture<'_, '_> { + fn drop(&mut self) { + self.twai + .register_block() + .cmd() + .write(|w| w.abort_tx().set_bit()); + } + } + + impl TwaiTx<'_, Async> { + /// Transmits an `EspTwaiFrame` asynchronously over the TWAI bus. + /// + /// The transmission is aborted if the future is dropped. The technical + /// reference manual does not specifiy if aborting the transmission also + /// stops it, in case it is actively transmitting. Therefor it could be + /// the case that even though the future is dropped, the frame was sent + /// anyways. + pub async fn transmit_async(&mut self, frame: &EspTwaiFrame) -> Result<(), EspTwaiError> { + self.twai.listen(EnumSet::all()); + TransmitFuture::new(self.twai.reborrow(), frame).await + } + } + + impl TwaiRx<'_, Async> { + /// Receives an `EspTwaiFrame` asynchronously over the TWAI bus. + pub async fn receive_async(&mut self) -> Result { + self.twai.listen(EnumSet::all()); + poll_fn(|cx| { + self.twai.async_state().err_waker.register(cx.waker()); + + if let Poll::Ready(result) = self.twai.async_state().rx_queue.poll_receive(cx) { + return Poll::Ready(result); + } + + let status = self.regs().status().read(); + + // Check that the peripheral is not in a bus off state. + if status.bus_off_st().bit_is_set() { + return Poll::Ready(Err(EspTwaiError::BusOff)); + } + + Poll::Pending + }) + .await + } + } + + pub(super) fn handle_interrupt(register_block: &RegisterBlock, async_state: &TwaiAsyncState) { + let intr_status = register_block.int_raw().read(); + + let int_ena_reg = register_block.int_ena(); + let tx_int_status = intr_status.tx_int_st(); + let rx_int_status = intr_status.rx_int_st(); + + let intr_enable = int_ena_reg.read(); + + if tx_int_status.bit_is_set() { + async_state.tx_waker.wake(); + } + + if rx_int_status.bit_is_set() { + let status = register_block.status().read(); + + let rx_queue = &async_state.rx_queue; + + if status.bus_off_st().bit_is_set() { + let _ = rx_queue.try_send(Err(EspTwaiError::BusOff)); + // Abort transmissions and wake senders if we are in bus-off state. + if !status.tx_buf_st().bit_is_set() { + register_block.cmd().write(|w| w.abort_tx().set_bit()); + async_state.tx_waker.wake(); + } + } + + if status.miss_st().bit_is_set() { + let _ = rx_queue.try_send(Err(EspTwaiError::EmbeddedHAL(ErrorKind::Overrun))); + release_receive_fifo(register_block); + } else { + match read_frame(register_block) { + Ok(frame) => { + let _ = rx_queue.try_send(Ok(frame)); + } + Err(e) => warn!("Error reading frame: {:?}", e), + } + } + } + + if intr_status.bits() & 0b10110100 > 0 { + // We might want to use the error code to gather statistics in the + // future. + let _ = register_block.err_code_cap().read(); + async_state.err_waker.wake(); + } + + // Clear interrupt request bits + unsafe { + int_ena_reg.modify(|_, w| w.bits(intr_enable.bits() & (!intr_status.bits() | 1))); + } + } +} diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs new file mode 100644 index 00000000000..c5ee737c9d1 --- /dev/null +++ b/esp-hal/src/uart/mod.rs @@ -0,0 +1,3861 @@ +//! # Universal Asynchronous Receiver/Transmitter (UART) +//! +//! ## Overview +//! +//! The UART is a hardware peripheral which handles communication using serial +//! communication interfaces, such as RS232 and RS485. This peripheral provides! +//! a cheap and ubiquitous method for full- and half-duplex communication +//! between devices. +//! +//! Depending on your device, two or more UART controllers are available for +//! use, all of which can be configured and used in the same way. All UART +//! controllers are compatible with UART-enabled devices from various +//! manufacturers, and can also support Infrared Data Association (IrDA) +//! protocols. +//! +//! ## Configuration +//! +//! Each UART controller is individually configurable, and the usual setting +//! such as baud rate, data bits, parity, and stop bits can easily be +//! configured. Additionally, the receive (RX) and transmit (TX) pins need to +//! be specified. +//! +//! The UART controller can be configured to invert the polarity of the pins. +//! This is achieved by inverting the desired pins, and then constructing the +//! UART instance using the inverted pins. +//! +//! ## Usage +//! +//! The UART driver implements a number of third-party traits, with the +//! intention of making the HAL inter-compatible with various device drivers +//! from the community. This includes, but is not limited to, the [embedded-hal] +//! and [embedded-io] blocking traits, and the [embedded-hal-async] and +//! [embedded-io-async] asynchronous traits. +//! +//! In addition to the interfaces provided by these traits, native APIs are also +//! available. See the examples below for more information on how to interact +//! with this driver. +//! +//! [embedded-hal]: embedded_hal +//! [embedded-io]: embedded_io_07 +//! [embedded-hal-async]: embedded_hal_async +//! [embedded-io-async]: embedded_io_async_07 + +crate::unstable_driver! { + /// UHCI wrapper around UART + #[cfg(uhci_driver_supported)] + pub mod uhci; +} + +use core::{marker::PhantomData, sync::atomic::Ordering, task::Poll}; + +use enumset::{EnumSet, EnumSetType}; +use portable_atomic::AtomicBool; + +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + gpio::{ + InputConfig, + InputSignal, + OutputConfig, + OutputSignal, + PinGuard, + Pull, + interconnect::{PeripheralInput, PeripheralOutput}, + }, + handler, + interrupt::InterruptHandler, + pac::uart0::RegisterBlock, + private::OnDrop, + ram, + soc::clocks::{self, ClockTree}, + system::PeripheralGuard, +}; + +/// UART RX Error +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum RxError { + /// An RX FIFO overflow happened. + /// + /// This error occurs when RX FIFO is full and a new byte is received. The + /// RX FIFO is then automatically reset by the driver. + FifoOverflowed, + + /// A glitch was detected on the RX line. + /// + /// This error occurs when an unexpected or erroneous signal (glitch) is + /// detected on the UART RX line, which could lead to incorrect data + /// reception. + GlitchOccurred, + + /// A framing error was detected on the RX line. + /// + /// This error occurs when the received data does not conform to the + /// expected UART frame format. + FrameFormatViolated, + + /// A parity error was detected on the RX line. + /// + /// This error occurs when the parity bit in the received data does not + /// match the expected parity configuration. + ParityMismatch, +} + +impl core::error::Error for RxError {} + +impl core::fmt::Display for RxError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + RxError::FifoOverflowed => write!(f, "The RX FIFO overflowed"), + RxError::GlitchOccurred => write!(f, "A glitch was detected on the RX line"), + RxError::FrameFormatViolated => { + write!(f, "A framing error was detected on the RX line") + } + RxError::ParityMismatch => write!(f, "A parity error was detected on the RX line"), + } + } +} + +#[instability::unstable] +impl embedded_io_06::Error for RxError { + fn kind(&self) -> embedded_io_06::ErrorKind { + embedded_io_06::ErrorKind::Other + } +} + +#[instability::unstable] +impl embedded_io_07::Error for RxError { + fn kind(&self) -> embedded_io_07::ErrorKind { + embedded_io_07::ErrorKind::Other + } +} + +/// UART TX Error +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum TxError {} + +impl core::fmt::Display for TxError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Tx error") + } +} + +impl core::error::Error for TxError {} + +#[instability::unstable] +impl embedded_io_06::Error for TxError { + fn kind(&self) -> embedded_io_06::ErrorKind { + embedded_io_06::ErrorKind::Other + } +} +#[instability::unstable] +impl embedded_io_07::Error for TxError { + fn kind(&self) -> embedded_io_07::ErrorKind { + embedded_io_07::ErrorKind::Other + } +} + +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +pub use crate::soc::clocks::Uart0FunctionClockConfig as ClockSource; +#[cfg(not(feature = "unstable"))] +use crate::soc::clocks::Uart0FunctionClockConfig as ClockSource; + +/// Number of data bits +/// +/// This enum represents the various configurations for the number of data +/// bits used in UART communication. The number of data bits defines the +/// length of each transmitted or received data frame. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DataBits { + /// 5 data bits per frame. + _5, + /// 6 data bits per frame. + _6, + /// 7 data bits per frame. + _7, + /// 8 data bits per frame. + #[default] + _8, +} + +/// Parity check +/// +/// Parity is a form of error detection in UART communication, used to +/// ensure that the data has not been corrupted during transmission. The +/// parity bit is added to the data bits to make the number of 1-bits +/// either even or odd. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Parity { + /// No parity bit is used. + #[default] + None, + /// Even parity: the parity bit is set to make the total number of + /// 1-bits even. + Even, + /// Odd parity: the parity bit is set to make the total number of 1-bits + /// odd. + Odd, +} + +/// Number of stop bits +/// +/// The stop bit(s) signal the end of a data packet in UART communication. +/// This enum defines the possible configurations for the number of stop +/// bits. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StopBits { + /// 1 stop bit. + #[default] + _1, + /// 1.5 stop bits. + _1p5, + /// 2 stop bits. + _2, +} + +/// Software flow control settings. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum SwFlowControl { + #[default] + /// Disables software flow control. + Disabled, + /// Enables software flow control with configured parameters + Enabled { + /// Xon flow control byte. + xon_char: u8, + /// Xoff flow control byte. + xoff_char: u8, + /// If the software flow control is enabled and the data amount in + /// rxfifo is less than xon_thrd, an xon_char will be sent. + xon_threshold: u8, + /// If the software flow control is enabled and the data amount in + /// rxfifo is more than xoff_thrd, an xoff_char will be sent + xoff_threshold: u8, + }, +} + +/// Configuration for CTS (Clear To Send) flow control. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum CtsConfig { + /// Enable CTS flow control (TX). + Enabled, + #[default] + /// Disable CTS flow control (TX). + Disabled, +} + +/// Configuration for RTS (Request To Send) flow control. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum RtsConfig { + /// Enable RTS flow control with a FIFO threshold (RX). + Enabled(u8), + #[default] + /// Disable RTS flow control. + Disabled, +} + +/// Hardware flow control configuration. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub struct HwFlowControl { + /// CTS configuration. + pub cts: CtsConfig, + /// RTS configuration. + pub rts: RtsConfig, +} + +/// Defines how strictly the requested baud rate must be met. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +pub enum BaudrateTolerance { + /// Accept the closest achievable baud rate without restriction. + #[default] + Closest, + /// In this setting, the deviation of only 1% from the desired baud value is + /// tolerated. + Exact, + /// Allow a certain percentage of deviation. + ErrorPercent(u8), +} + +/// UART Configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// The baud rate (speed) of the UART communication in bits per second + /// (bps). + baudrate: u32, + /// Determines how close to the desired baud rate value the driver should + /// set the baud rate. + #[builder_lite(unstable)] + baudrate_tolerance: BaudrateTolerance, + /// Number of data bits in each frame (5, 6, 7, or 8 bits). + data_bits: DataBits, + /// Parity setting (None, Even, or Odd). + parity: Parity, + /// Number of stop bits in each frame (1, 1.5, or 2 bits). + stop_bits: StopBits, + /// Software flow control. + #[builder_lite(unstable)] + sw_flow_ctrl: SwFlowControl, + /// Hardware flow control. + #[builder_lite(unstable)] + hw_flow_ctrl: HwFlowControl, + /// Clock source used by the UART peripheral. + #[builder_lite(unstable)] + clock_source: ClockSource, + /// UART Receive part configuration. + rx: RxConfig, + /// UART Transmit part configuration. + tx: TxConfig, +} + +impl Default for Config { + fn default() -> Config { + Config { + rx: RxConfig::default(), + tx: TxConfig::default(), + baudrate: 115_200, + baudrate_tolerance: BaudrateTolerance::default(), + data_bits: Default::default(), + parity: Default::default(), + stop_bits: Default::default(), + sw_flow_ctrl: Default::default(), + hw_flow_ctrl: Default::default(), + clock_source: Default::default(), + } + } +} + +impl Config { + fn validate(&self) -> Result<(), ConfigError> { + if let BaudrateTolerance::ErrorPercent(percentage) = self.baudrate_tolerance { + assert!(percentage > 0 && percentage <= 100); + } + + // Max supported baud rate is 5Mbaud + if self.baudrate == 0 || self.baudrate > 5_000_000 { + return Err(ConfigError::BaudrateNotSupported); + } + Ok(()) + } +} + +/// UART Receive part configuration. +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct RxConfig { + /// Threshold level at which the RX FIFO is considered full. + fifo_full_threshold: u16, + /// Optional timeout value for RX operations. + timeout: Option, +} + +impl Default for RxConfig { + fn default() -> RxConfig { + RxConfig { + // see + fifo_full_threshold: 120, + // see + timeout: Some(10), + } + } +} + +/// UART Transmit part configuration. +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TxConfig { + /// Threshold level at which the TX FIFO is considered empty. + fifo_empty_threshold: u16, +} + +impl Default for TxConfig { + fn default() -> TxConfig { + TxConfig { + // see + fifo_empty_threshold: 10, + } + } +} + +/// Configuration for the AT-CMD detection functionality +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[instability::unstable] +#[non_exhaustive] +pub struct AtCmdConfig { + /// Optional idle time before the AT command detection begins, in clock + /// cycles. + pre_idle_count: Option, + /// Optional idle time after the AT command detection ends, in clock + /// cycles. + post_idle_count: Option, + /// Optional timeout between bytes in the AT command, in clock + /// cycles. + gap_timeout: Option, + /// The byte (character) that triggers the AT command detection. + cmd_char: u8, + /// Optional number of bytes to detect as part of the AT command. + char_num: u8, +} + +impl Default for AtCmdConfig { + fn default() -> Self { + Self { + pre_idle_count: None, + post_idle_count: None, + gap_timeout: None, + cmd_char: b'+', + char_num: 1, + } + } +} + +struct UartBuilder<'d, Dm: DriverMode> { + uart: AnyUart<'d>, + phantom: PhantomData, +} + +impl<'d, Dm> UartBuilder<'d, Dm> +where + Dm: DriverMode, +{ + fn new(uart: impl Instance + 'd) -> Self { + Self { + uart: uart.degrade(), + phantom: PhantomData, + } + } + + fn init(self, config: Config) -> Result, ConfigError> { + let rx_guard = PeripheralGuard::new(self.uart.info().peripheral); + let tx_guard = PeripheralGuard::new(self.uart.info().peripheral); + + let peri_clock_guard = UartClockGuard::new(unsafe { self.uart.clone_unchecked() }); + + let rts_pin = PinGuard::new_unconnected(); + let tx_pin = PinGuard::new_unconnected(); + + let mut serial = Uart { + rx: UartRx { + uart: unsafe { self.uart.clone_unchecked() }, + phantom: PhantomData, + guard: rx_guard, + peri_clock_guard: peri_clock_guard.clone(), + }, + tx: UartTx { + uart: self.uart, + phantom: PhantomData, + guard: tx_guard, + peri_clock_guard, + rts_pin, + tx_pin, + baudrate: config.baudrate, + }, + }; + serial.init(config)?; + + Ok(serial) + } +} + +#[procmacros::doc_replace] +/// UART (Full-duplex) +/// +/// ## Example +/// +/// ```rust, no_run +/// # {before_snippet} +/// use esp_hal::uart::{Config, Uart}; +/// let mut uart = Uart::new(peripherals.UART0, Config::default())? +/// .with_rx(peripherals.GPIO1) +/// .with_tx(peripherals.GPIO2); +/// +/// uart.write(b"Hello world!")?; +/// # {after_snippet} +/// ``` +pub struct Uart<'d, Dm: DriverMode> { + rx: UartRx<'d, Dm>, + tx: UartTx<'d, Dm>, +} + +/// UART (Transmit) +#[instability::unstable] +pub struct UartTx<'d, Dm: DriverMode> { + uart: AnyUart<'d>, + phantom: PhantomData, + guard: PeripheralGuard, + peri_clock_guard: UartClockGuard<'d>, + rts_pin: PinGuard, + tx_pin: PinGuard, + baudrate: u32, +} + +/// UART (Receive) +#[instability::unstable] +pub struct UartRx<'d, Dm: DriverMode> { + uart: AnyUart<'d>, + phantom: PhantomData, + guard: PeripheralGuard, + peri_clock_guard: UartClockGuard<'d>, +} + +/// A configuration error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// The requested baud rate is not achievable. + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + BaudrateNotAchievable, + + /// The requested baud rate is not supported. + /// + /// This error is returned if: + /// * the baud rate exceeds 5MBaud or is equal to zero. + /// * the user has specified an exact baud rate or with some percentage of deviation to the + /// desired value, and the driver cannot reach this speed. + BaudrateNotSupported, + + /// The requested timeout exceeds the maximum value ( + #[cfg_attr(esp32, doc = "127")] + #[cfg_attr(not(esp32), doc = "1023")] + /// ). + TimeoutTooLong, + + /// The requested RX FIFO threshold exceeds the maximum value (127 bytes). + RxFifoThresholdNotSupported, + + /// The requested TX FIFO threshold exceeds the maximum value (127 bytes). + TxFifoThresholdNotSupported, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + #[cfg(feature = "unstable")] + ConfigError::BaudrateNotAchievable => { + write!(f, "The requested baud rate is not achievable") + } + ConfigError::BaudrateNotSupported => { + write!(f, "The requested baud rate is not supported") + } + ConfigError::TimeoutTooLong => write!(f, "The requested timeout is not supported"), + ConfigError::RxFifoThresholdNotSupported => { + write!(f, "The requested RX FIFO threshold is not supported") + } + ConfigError::TxFifoThresholdNotSupported => { + write!(f, "The requested TX FIFO threshold is not supported") + } + } + } +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for Uart<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for UartRx<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.baudrate = config.baudrate; + self.apply_config(config) + } +} + +impl<'d> UartTx<'d, Blocking> { + #[procmacros::doc_replace] + /// Create a new UART TX instance in [`Blocking`] mode. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, UartTx}; + /// let tx = UartTx::new(peripherals.UART0, Config::default())?.with_tx(peripherals.GPIO1); + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub fn new(uart: impl Instance + 'd, config: Config) -> Result { + let (_, uart_tx) = UartBuilder::new(uart).init(config)?.split(); + + Ok(uart_tx) + } + + /// Reconfigures the driver to operate in [`Async`] mode. + #[instability::unstable] + pub fn into_async(self) -> UartTx<'d, Async> { + if !self.uart.state().is_rx_async.load(Ordering::Acquire) { + self.uart + .set_interrupt_handler(self.uart.info().async_handler); + } + self.uart.state().is_tx_async.store(true, Ordering::Release); + + UartTx { + uart: self.uart, + phantom: PhantomData, + guard: self.guard, + peri_clock_guard: self.peri_clock_guard, + rts_pin: self.rts_pin, + tx_pin: self.tx_pin, + baudrate: self.baudrate, + } + } +} + +impl<'d> UartTx<'d, Async> { + /// Reconfigures the driver to operate in [`Blocking`] mode. + #[instability::unstable] + pub fn into_blocking(self) -> UartTx<'d, Blocking> { + self.uart + .state() + .is_tx_async + .store(false, Ordering::Release); + if !self.uart.state().is_rx_async.load(Ordering::Acquire) { + self.uart.disable_peri_interrupt_on_all_cores(); + } + + UartTx { + uart: self.uart, + phantom: PhantomData, + guard: self.guard, + peri_clock_guard: self.peri_clock_guard, + rts_pin: self.rts_pin, + tx_pin: self.tx_pin, + baudrate: self.baudrate, + } + } + + /// Write data into the TX buffer. + /// + /// This function writes the provided buffer `bytes` into the UART transmit + /// buffer. If the buffer is full, the function waits asynchronously for + /// space in the buffer to become available. + /// + /// The function returns the number of bytes written into the buffer. This + /// may be less than the length of the buffer. + /// + /// Upon an error, the function returns immediately and the contents of the + /// internal FIFO are not modified. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + pub async fn write_async(&mut self, bytes: &[u8]) -> Result { + // We need to loop in case the TX empty interrupt was fired but not cleared + // before, but the FIFO itself was filled up by a previous write. + let space = loop { + let tx_fifo_count = self.uart.info().tx_fifo_count(); + let space = Info::UART_FIFO_SIZE - tx_fifo_count; + if space != 0 { + break space; + } + UartTxFuture::new(self.uart.reborrow(), TxEvent::FiFoEmpty).await; + }; + + let free = (space as usize).min(bytes.len()); + + for &byte in &bytes[..free] { + self.uart + .info() + .regs() + .fifo() + .write(|w| unsafe { w.rxfifo_rd_byte().bits(byte) }); + } + + Ok(free) + } + + /// Asynchronously flushes the UART transmit buffer. + /// + /// This function ensures that all pending data in the transmit FIFO has + /// been sent over the UART. If the FIFO contains data, it waits for the + /// transmission to complete before returning. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + pub async fn flush_async(&mut self) -> Result<(), TxError> { + // Nothing is guaranteed to clear the Done status, so let's loop here in case Tx + // was Done before the last write operation that pushed data into the + // FIFO. + while self.uart.info().tx_fifo_count() > 0 { + UartTxFuture::new(self.uart.reborrow(), TxEvent::Done).await; + } + + self.flush_last_byte(); + + Ok(()) + } +} + +impl<'d, Dm> UartTx<'d, Dm> +where + Dm: DriverMode, +{ + /// Configure RTS pin + #[instability::unstable] + pub fn with_rts(mut self, rts: impl PeripheralOutput<'d>) -> Self { + let rts = rts.into(); + + rts.apply_output_config(&OutputConfig::default()); + rts.set_output_enable(true); + + self.rts_pin = rts.connect_with_guard(self.uart.info().rts_signal); + + self + } + + /// Assign the TX pin for UART instance. + /// + /// Sets the specified pin to push-pull output and connects it to the UART + /// TX signal. + /// + /// Disconnects the previous pin that was assigned with `with_tx`. + #[instability::unstable] + pub fn with_tx(mut self, tx: impl PeripheralOutput<'d>) -> Self { + let tx = tx.into(); + + // Make sure we don't cause an unexpected low pulse on the pin. + tx.set_output_high(true); + tx.apply_output_config(&OutputConfig::default()); + tx.set_output_enable(true); + + self.tx_pin = tx.connect_with_guard(self.uart.info().tx_signal); + + self + } + + /// Change the configuration. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.uart + .info() + .set_tx_fifo_empty_threshold(config.tx.fifo_empty_threshold)?; + self.uart.info().txfifo_reset(); + Ok(()) + } + + /// Returns whether the UART buffer is ready to accept more data. + /// + /// If this function returns `true`, [`Self::write`] will not block. + #[instability::unstable] + pub fn write_ready(&mut self) -> bool { + self.uart.info().tx_fifo_count() < Info::UART_FIFO_SIZE + } + + /// Write bytes. + /// + /// This function writes data to the internal TX FIFO of the UART + /// peripheral. The data is then transmitted over the UART TX line. + /// + /// The function returns the number of bytes written to the FIFO. This may + /// be less than the length of the provided data. The function may only + /// return 0 if the provided data is empty. + /// + /// ## Errors + /// + /// This function returns a [`TxError`] if an error occurred during the + /// write operation. + #[instability::unstable] + pub fn write(&mut self, data: &[u8]) -> Result { + self.uart.info().write(data) + } + + fn write_all(&mut self, mut data: &[u8]) -> Result<(), TxError> { + while !data.is_empty() { + let bytes_written = self.write(data)?; + data = &data[bytes_written..]; + } + Ok(()) + } + + /// Flush the transmit buffer. + /// + /// This function blocks until all data in the TX FIFO has been + /// transmitted. + #[instability::unstable] + pub fn flush(&mut self) -> Result<(), TxError> { + while self.uart.info().tx_fifo_count() > 0 {} + self.flush_last_byte(); + Ok(()) + } + + fn flush_last_byte(&mut self) { + // This function handles an edge case that happens when the TX FIFO count + // changes to 0. The FSM is in the Idle state for a short while after + // the last byte is moved out of the FIFO. It is unclear how long this + // takes, but 10us seems to be a good enough duration to wait, for both + // fast and slow baud rates. + crate::rom::ets_delay_us(10); + while !self.is_tx_idle() {} + } + + /// Sends a break signal for a specified duration in bit time. + /// + /// Duration is in bits, the time it takes to transfer one bit at the + /// current baud rate. The delay during the break is just busy-waiting. + #[instability::unstable] + pub fn send_break(&mut self, bits: u32) { + // Read the current TX inversion state + let original_conf0 = self.uart.info().regs().conf0().read(); + let original_txd_inv = original_conf0.txd_inv().bit(); + + // Invert the TX line (toggle the current state) + self.uart + .info() + .regs() + .conf0() + .modify(|_, w| w.txd_inv().bit(!original_txd_inv)); + + #[cfg(any(esp32c3, esp32c5, esp32c6, esp32h2, esp32s3))] + sync_regs(self.uart.info().regs()); + + // Calculate total delay in microseconds: (bits * 1_000_000) / baudrate_bps + // Use u64 to avoid overflow, then convert back to u32 + let total_delay_us = (bits as u64 * 1_000_000) / self.baudrate as u64; + let delay_us = (total_delay_us as u32).max(1); + + crate::rom::ets_delay_us(delay_us); + + // Restore the original register state + self.uart + .info() + .regs() + .conf0() + .write(|w| unsafe { w.bits(original_conf0.bits()) }); + + #[cfg(any(esp32c3, esp32c5, esp32c6, esp32h2, esp32s3))] + sync_regs(self.uart.info().regs()); + } + + /// Checks if the TX line is idle for this UART instance. + /// + /// Returns `true` if the transmit line is idle, meaning no data is + /// currently being transmitted. + fn is_tx_idle(&self) -> bool { + #[cfg(esp32)] + let status = self.regs().status(); + #[cfg(not(esp32))] + let status = self.regs().fsm_status(); + + status.read().st_utx_out().bits() == 0x0 + } + + /// Disables all TX-related interrupts for this UART instance. + /// + /// This function clears and disables the `transmit FIFO empty` interrupt, + /// `transmit break done`, `transmit break idle done`, and `transmit done` + /// interrupts. + fn disable_tx_interrupts(&self) { + self.regs().int_clr().write(|w| { + w.txfifo_empty().clear_bit_by_one(); + w.tx_brk_done().clear_bit_by_one(); + w.tx_brk_idle_done().clear_bit_by_one(); + w.tx_done().clear_bit_by_one() + }); + + self.regs().int_ena().write(|w| { + w.txfifo_empty().clear_bit(); + w.tx_brk_done().clear_bit(); + w.tx_brk_idle_done().clear_bit(); + w.tx_done().clear_bit() + }); + } + + fn regs(&self) -> &RegisterBlock { + self.uart.info().regs() + } +} + +#[inline(always)] +fn sync_regs(_register_block: &RegisterBlock) { + #[cfg(any(esp32c3, esp32c5, esp32c6, esp32h2, esp32s3))] + { + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let update_reg = _register_block.reg_update(); + } else { + let update_reg = _register_block.id(); + } + } + + update_reg.modify(|_, w| w.reg_update().set_bit()); + + while update_reg.read().reg_update().bit_is_set() { + // wait + } + } +} + +impl<'d> UartRx<'d, Blocking> { + #[procmacros::doc_replace] + /// Create a new UART RX instance in [`Blocking`] mode. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, UartRx}; + /// let rx = UartRx::new(peripherals.UART0, Config::default())?.with_rx(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub fn new(uart: impl Instance + 'd, config: Config) -> Result { + let (uart_rx, _) = UartBuilder::new(uart).init(config)?.split(); + + Ok(uart_rx) + } + + /// Waits for a break condition to be detected. + /// + /// This is a blocking function that will continuously check for a break condition. + /// After detection, the break interrupt flag is automatically cleared. + #[instability::unstable] + pub fn wait_for_break(&mut self) { + self.enable_break_detection(); + + while !self.regs().int_raw().read().brk_det().bit_is_set() { + // wait + } + + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); + } + + /// Waits for a break condition to be detected with a timeout. + /// + /// This is a blocking function that will check for a break condition up to + /// the specified timeout. Returns `true` if a break was detected, `false` if + /// the timeout elapsed. After successful detection, the break interrupt flag + /// is automatically cleared. + /// + /// ## Arguments + /// * `timeout` - Maximum time to wait for a break condition + #[instability::unstable] + pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool { + self.enable_break_detection(); + + let start = crate::time::Instant::now(); + + while !self.regs().int_raw().read().brk_det().bit_is_set() { + if crate::time::Instant::now() - start >= timeout { + return false; + } + } + + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); + true + } + + /// Reconfigures the driver to operate in [`Async`] mode. + #[instability::unstable] + pub fn into_async(self) -> UartRx<'d, Async> { + if !self.uart.state().is_tx_async.load(Ordering::Acquire) { + self.uart + .set_interrupt_handler(self.uart.info().async_handler); + } + self.uart.state().is_rx_async.store(true, Ordering::Release); + + UartRx { + uart: self.uart, + phantom: PhantomData, + guard: self.guard, + peri_clock_guard: self.peri_clock_guard, + } + } +} + +impl<'d> UartRx<'d, Async> { + /// Reconfigures the driver to operate in [`Blocking`] mode. + #[instability::unstable] + pub fn into_blocking(self) -> UartRx<'d, Blocking> { + self.uart + .state() + .is_rx_async + .store(false, Ordering::Release); + if !self.uart.state().is_tx_async.load(Ordering::Acquire) { + self.uart.disable_peri_interrupt_on_all_cores(); + } + + UartRx { + uart: self.uart, + phantom: PhantomData, + guard: self.guard, + peri_clock_guard: self.peri_clock_guard, + } + } + + async fn wait_for_buffered_data( + &mut self, + minimum: usize, + max_threshold: usize, + listen_for_timeout: bool, + ) -> Result<(), RxError> { + let current_threshold = self.uart.info().rx_fifo_full_threshold(); + + // User preference takes priority. + let max_threshold = max_threshold.min(current_threshold as usize) as u16; + let minimum = minimum.min(Info::RX_FIFO_MAX_THRHD as usize) as u16; + + // The effective threshold must be >= minimum. We ensure this by lowering the minimum number + // of returnable bytes. + let minimum = minimum.min(max_threshold); + + if self.uart.info().rx_fifo_count() < minimum { + // We're ignoring the user configuration here to ensure that this is not waiting + // for more data than the buffer. We'll restore the original value after the + // future resolved. + let info = self.uart.info(); + unwrap!(info.set_rx_fifo_full_threshold(max_threshold)); + let _guard = OnDrop::new(|| { + unwrap!(info.set_rx_fifo_full_threshold(current_threshold)); + }); + + // Wait for space or event + let mut events = RxEvent::FifoFull + | RxEvent::FifoOvf + | RxEvent::FrameError + | RxEvent::GlitchDetected + | RxEvent::ParityError; + + if self.regs().at_cmd_char().read().char_num().bits() > 0 { + events |= RxEvent::CmdCharDetected; + } + + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let reg_en = self.regs().tout_conf(); + } else { + let reg_en = self.regs().conf1(); + } + }; + if listen_for_timeout && reg_en.read().rx_tout_en().bit_is_set() { + events |= RxEvent::FifoTout; + } + + let events = UartRxFuture::new(self.uart.reborrow(), events).await; + + let result = rx_event_check_for_error(events); + if let Err(error) = result { + if error == RxError::FifoOverflowed { + self.uart.info().rxfifo_reset(); + } + return Err(error); + } + } + + Ok(()) + } + + /// Read data asynchronously. + /// + /// This function reads data from the UART receive buffer into the + /// provided buffer. If the buffer is empty, the function waits + /// asynchronously for data to become available, or for an error to occur. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer. + /// + /// Note that this function may ignore the `rx_fifo_full_threshold` setting + /// to ensure that it does not wait for more data than the buffer can hold. + /// + /// Upon an error, the function returns immediately and the contents of the + /// internal FIFO are not modified. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + pub async fn read_async(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + self.wait_for_buffered_data(1, buf.len(), true).await?; + + self.read_buffered(buf) + } + + /// Fill buffer asynchronously. + /// + /// This function reads data into the provided buffer. If the internal FIFO + /// does not contain enough data, the function waits asynchronously for data + /// to become available, or for an error to occur. + /// + /// Note that this function may ignore the `rx_fifo_full_threshold` setting + /// to ensure that it does not wait for more data than the buffer can hold. + /// + /// ## Cancellation + /// + /// This function is **not** cancellation safe. If the future is dropped + /// before it resolves, or if an error occurs during the read operation, + /// previously read data may be lost. + pub async fn read_exact_async(&mut self, mut buf: &mut [u8]) -> Result<(), RxError> { + if buf.is_empty() { + return Ok(()); + } + + // Drain the buffer first, there's no point in waiting for data we've already received. + let read = self.uart.info().read_buffered(buf)?; + buf = &mut buf[read..]; + + while !buf.is_empty() { + // No point in listening for timeouts, as we're waiting for an exact amount of + // data. On ESP32 and S2, the timeout interrupt can't be cleared unless the FIFO + // is empty, so listening could cause an infinite loop here. + self.wait_for_buffered_data(buf.len(), buf.len(), false) + .await?; + + let read = self.uart.info().read_buffered(buf)?; + buf = &mut buf[read..]; + } + + Ok(()) + } + + /// Waits for a break condition to be detected asynchronously. + /// + /// This is an async function that will await until a break condition is + /// detected on the RX line. After detection, the break interrupt flag is + /// automatically cleared. + #[instability::unstable] + pub async fn wait_for_break_async(&mut self) { + UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await; + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); + } +} + +impl<'d, Dm> UartRx<'d, Dm> +where + Dm: DriverMode, +{ + fn regs(&self) -> &RegisterBlock { + self.uart.info().regs() + } + + /// Assign the CTS pin for UART instance. + /// + /// Sets the specified pin to input and connects it to the UART CTS signal. + #[instability::unstable] + pub fn with_cts(self, cts: impl PeripheralInput<'d>) -> Self { + let cts = cts.into(); + + cts.apply_input_config(&InputConfig::default()); + cts.set_input_enable(true); + + self.uart.info().cts_signal.connect_to(&cts); + + self + } + + /// Assign the RX pin for UART instance. + /// + /// Sets the specified pin to input and connects it to the UART RX signal. + /// + /// Note: when you listen for the output of the UART peripheral, you should + /// configure the driver side (i.e. the TX pin), or ensure that the line is + /// initially high, to avoid receiving a non-data byte caused by an + /// initial low signal level. + #[instability::unstable] + pub fn with_rx(self, rx: impl PeripheralInput<'d>) -> Self { + let rx = rx.into(); + + rx.apply_input_config(&InputConfig::default().with_pull(Pull::Up)); + rx.set_input_enable(true); + + self.uart.info().rx_signal.connect_to(&rx); + + self + } + + /// Enable break detection. + /// + /// This must be called before any breaks are expected to be received. + /// Break detection is enabled automatically by [`Self::wait_for_break`] + /// and [`Self::wait_for_break_with_timeout`], but calling this method + /// explicitly ensures that breaks occurring before the first wait call + /// will be reliably detected. + #[instability::unstable] + pub fn enable_break_detection(&mut self) { + self.uart + .info() + .enable_listen_rx(RxEvent::BreakDetected.into(), true); + + #[cfg(any(esp32c5, esp32c6, esp32h2))] + sync_regs(self.regs()); + } + + /// Change the configuration. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.uart + .info() + .set_rx_fifo_full_threshold(config.rx.fifo_full_threshold)?; + self.uart + .info() + .set_rx_timeout(config.rx.timeout, self.uart.info().current_symbol_length())?; + + self.uart.info().rxfifo_reset(); + Ok(()) + } + + /// Reads and clears errors set by received data. + /// + /// If a FIFO overflow is detected, the RX FIFO is reset. + #[instability::unstable] + pub fn check_for_errors(&mut self) -> Result<(), RxError> { + self.uart.info().check_for_errors() + } + + /// Returns whether the UART buffer has data. + /// + /// If this function returns `true`, [`Self::read`] will not block. + #[instability::unstable] + pub fn read_ready(&mut self) -> bool { + self.uart.info().rx_fifo_count() > 0 + } + + /// Read bytes. + /// + /// The UART hardware continuously receives bytes and stores them in the RX + /// FIFO. This function reads the bytes from the RX FIFO and returns + /// them in the provided buffer. If the hardware buffer is empty, this + /// function will block until data is available. The [`Self::read_ready`] + /// function can be used to check if data is available without blocking. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer. This function only returns 0 + /// if the provided buffer is empty. + /// + /// ## Errors + /// + /// This function returns an [`RxError`] if an error occurred since the last + /// call to [`Self::check_for_errors`], [`Self::read_buffered`], or this + /// function. + /// + /// If the error occurred before this function was called, the contents of + /// the FIFO are not modified. + #[instability::unstable] + pub fn read(&mut self, buf: &mut [u8]) -> Result { + self.uart.info().read(buf) + } + + /// Read already received bytes. + /// + /// This function reads the already received bytes from the FIFO into the + /// provided buffer. The function does not wait for the FIFO to actually + /// contain any bytes. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer, and it may also be 0. + /// + /// ## Errors + /// + /// This function returns an [`RxError`] if an error occurred since the last + /// call to [`Self::check_for_errors`], [`Self::read`], or this + /// function. + /// + /// If the error occurred before this function was called, the contents of + /// the FIFO are not modified. + #[instability::unstable] + pub fn read_buffered(&mut self, buf: &mut [u8]) -> Result { + self.uart.info().read_buffered(buf) + } + + /// Disables all RX-related interrupts for this UART instance. + /// + /// This function clears and disables the `receive FIFO full` interrupt, + /// `receive FIFO overflow`, `receive FIFO timeout`, and `AT command + /// byte detection` interrupts. + fn disable_rx_interrupts(&self) { + self.regs().int_clr().write(|w| { + w.rxfifo_full().clear_bit_by_one(); + w.rxfifo_ovf().clear_bit_by_one(); + w.rxfifo_tout().clear_bit_by_one(); + w.at_cmd_char_det().clear_bit_by_one() + }); + + self.regs().int_ena().write(|w| { + w.rxfifo_full().clear_bit(); + w.rxfifo_ovf().clear_bit(); + w.rxfifo_tout().clear_bit(); + w.at_cmd_char_det().clear_bit() + }); + } +} + +impl<'d> Uart<'d, Blocking> { + #[procmacros::doc_replace] + /// Create a new UART instance in [`Blocking`] mode. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_tx(peripherals.GPIO2); + /// # {after_snippet} + /// ``` + pub fn new(uart: impl Instance + 'd, config: Config) -> Result { + UartBuilder::new(uart).init(config) + } + + /// Reconfigures the driver to operate in [`Async`] mode. + /// + /// See the [`Async`] documentation for an example on how to use this + /// method. + pub fn into_async(self) -> Uart<'d, Async> { + Uart { + rx: self.rx.into_async(), + tx: self.tx.into_async(), + } + } + + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER] + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + // `self.tx.uart` and `self.rx.uart` are the same + self.tx.uart.set_interrupt_handler(handler); + } + + #[procmacros::doc_replace] + /// Listen for the given interrupts + /// + /// ## Example + /// + /// **Note**: In practice a proper serial terminal should be used + /// to connect to the board (espflash won't work) + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::{ + /// delay::Delay, + /// uart::{AtCmdConfig, Config, RxConfig, Uart, UartInterrupt}, + /// }; + /// # let delay = Delay::new(); + /// # let config = Config::default().with_rx( + /// # RxConfig::default().with_fifo_full_threshold(30) + /// # ); + /// # let mut uart = Uart::new( + /// # peripherals.UART0, + /// # config)?; + /// uart.set_interrupt_handler(interrupt_handler); + /// + /// critical_section::with(|cs| { + /// uart.set_at_cmd(AtCmdConfig::default().with_cmd_char(b'#')); + /// uart.listen(UartInterrupt::AtCmd | UartInterrupt::RxFifoFull); + /// + /// SERIAL.borrow_ref_mut(cs).replace(uart); + /// }); + /// + /// loop { + /// println!("Send `#` character or >=30 characters"); + /// delay.delay(Duration::from_secs(1)); + /// } + /// # } + /// + /// use core::cell::RefCell; + /// + /// use critical_section::Mutex; + /// use esp_hal::uart::Uart; + /// static SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); + /// + /// use core::fmt::Write; + /// + /// use esp_hal::uart::UartInterrupt; + /// #[esp_hal::handler] + /// fn interrupt_handler() { + /// critical_section::with(|cs| { + /// let mut serial = SERIAL.borrow_ref_mut(cs); + /// if let Some(serial) = serial.as_mut() { + /// let mut buf = [0u8; 64]; + /// if let Ok(cnt) = serial.read_buffered(&mut buf) { + /// println!("Read {} bytes", cnt); + /// } + /// + /// let pending_interrupts = serial.interrupts(); + /// println!( + /// "Interrupt AT-CMD: {} RX-FIFO-FULL: {}", + /// pending_interrupts.contains(UartInterrupt::AtCmd), + /// pending_interrupts.contains(UartInterrupt::RxFifoFull), + /// ); + /// + /// serial.clear_interrupts(UartInterrupt::AtCmd | UartInterrupt::RxFifoFull); + /// } + /// }); + /// } + /// ``` + #[instability::unstable] + pub fn listen(&mut self, interrupts: impl Into>) { + self.tx.uart.info().enable_listen(interrupts.into(), true) + } + + /// Unlisten the given interrupts + #[instability::unstable] + pub fn unlisten(&mut self, interrupts: impl Into>) { + self.tx.uart.info().enable_listen(interrupts.into(), false) + } + + /// Gets asserted interrupts + #[instability::unstable] + pub fn interrupts(&mut self) -> EnumSet { + self.tx.uart.info().interrupts() + } + + /// Resets asserted interrupts + #[instability::unstable] + pub fn clear_interrupts(&mut self, interrupts: EnumSet) { + self.tx.uart.info().clear_interrupts(interrupts) + } + + /// Waits for a break condition to be detected. + /// + /// This is a blocking function that will continuously check for a break condition. + /// After detection, the break interrupt flag is automatically cleared. + #[instability::unstable] + pub fn wait_for_break(&mut self) { + self.rx.wait_for_break() + } + + /// Waits for a break condition to be detected with a timeout. + /// + /// This is a blocking function that will check for a break condition up to + /// the specified timeout. Returns `true` if a break was detected, `false` if + /// the timeout elapsed. After successful detection, the break interrupt flag + /// is automatically cleared. + /// + /// ## Arguments + /// * `timeout` - Maximum time to wait for a break condition + #[instability::unstable] + pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool { + self.rx.wait_for_break_with_timeout(timeout) + } +} + +impl<'d> Uart<'d, Async> { + /// Reconfigures the driver to operate in [`Blocking`] mode. + /// + /// See the [`Blocking`] documentation for an example on how to use this + /// method. + pub fn into_blocking(self) -> Uart<'d, Blocking> { + Uart { + rx: self.rx.into_blocking(), + tx: self.tx.into_blocking(), + } + } + + #[procmacros::doc_replace] + /// Write data into the TX buffer. + /// + /// This function writes the provided buffer `bytes` into the UART transmit + /// buffer. If the buffer is full, the function waits asynchronously for + /// space in the buffer to become available. + /// + /// The function returns the number of bytes written into the buffer. This + /// may be less than the length of the buffer. + /// + /// Upon an error, the function returns immediately and the contents of the + /// internal FIFO are not modified. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_tx(peripherals.GPIO2) + /// .into_async(); + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write_async(&MESSAGE).await?; + /// # {after_snippet} + /// ``` + pub async fn write_async(&mut self, words: &[u8]) -> Result { + self.tx.write_async(words).await + } + + #[procmacros::doc_replace] + /// Asynchronously flushes the UART transmit buffer. + /// + /// This function ensures that all pending data in the transmit FIFO has + /// been sent over the UART. If the FIFO contains data, it waits for the + /// transmission to complete before returning. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_tx(peripherals.GPIO2) + /// .into_async(); + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write_async(&MESSAGE).await?; + /// uart.flush_async().await?; + /// # {after_snippet} + /// ``` + pub async fn flush_async(&mut self) -> Result<(), TxError> { + self.tx.flush_async().await + } + + #[procmacros::doc_replace] + /// Read data asynchronously. + /// + /// This function reads data from the UART receive buffer into the + /// provided buffer. If the buffer is empty, the function waits + /// asynchronously for data to become available, or for an error to occur. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer. + /// + /// Note that this function may ignore the `rx_fifo_full_threshold` setting + /// to ensure that it does not wait for more data than the buffer can hold. + /// + /// Upon an error, the function returns immediately and the contents of the + /// internal FIFO are not modified. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_tx(peripherals.GPIO2) + /// .into_async(); + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write_async(&MESSAGE).await?; + /// uart.flush_async().await?; + /// + /// let mut buf = [0u8; MESSAGE.len()]; + /// uart.read_async(&mut buf[..]).await.unwrap(); + /// # {after_snippet} + /// ``` + pub async fn read_async(&mut self, buf: &mut [u8]) -> Result { + self.rx.read_async(buf).await + } + + /// Fill buffer asynchronously. + /// + /// This function reads data from the UART receive buffer into the + /// provided buffer. If the buffer is empty, the function waits + /// asynchronously for data to become available, or for an error to occur. + /// + /// Note that this function may ignore the `rx_fifo_full_threshold` setting + /// to ensure that it does not wait for more data than the buffer can hold. + /// + /// ## Cancellation + /// + /// This function is **not** cancellation safe. If the future is dropped + /// before it resolves, or if an error occurs during the read operation, + /// previously read data may be lost. + #[instability::unstable] + pub async fn read_exact_async(&mut self, buf: &mut [u8]) -> Result<(), RxError> { + self.rx.read_exact_async(buf).await + } + + /// Waits for a break condition to be detected asynchronously. + /// + /// This is an async function that will await until a break condition is + /// detected on the RX line. After detection, the break interrupt flag is + /// automatically cleared. + #[instability::unstable] + pub async fn wait_for_break_async(&mut self) { + self.rx.wait_for_break_async().await + } +} + +/// List of exposed UART events. +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum UartInterrupt { + /// Indicates that the received has detected the configured + /// [`Uart::set_at_cmd`] byte. + AtCmd, + + /// The transmitter has finished sending out all data from the FIFO. + TxDone, + + /// Break condition has been detected. + /// Triggered when the receiver detects a NULL character (i.e. logic 0 for + /// one NULL character transmission) after stop bits. + RxBreakDetected, + + /// The receiver has received more data than what + /// [`RxConfig::fifo_full_threshold`] specifies. + RxFifoFull, + + /// The receiver has not received any data for the time + /// [`RxConfig::with_timeout`] specifies. + RxTimeout, +} + +impl<'d, Dm> Uart<'d, Dm> +where + Dm: DriverMode, +{ + #[procmacros::doc_replace] + /// Assign the RX pin for UART instance. + /// + /// Sets the specified pin to input and connects it to the UART RX signal. + /// + /// Note: when you listen for the output of the UART peripheral, you should + /// configure the driver side (i.e. the TX pin), or ensure that the line is + /// initially high, to avoid receiving a non-data byte caused by an + /// initial low signal level. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let uart = Uart::new(peripherals.UART0, Config::default())?.with_rx(peripherals.GPIO1); + /// + /// # {after_snippet} + /// ``` + pub fn with_rx(mut self, rx: impl PeripheralInput<'d>) -> Self { + self.rx = self.rx.with_rx(rx); + self + } + + #[procmacros::doc_replace] + /// Assign the TX pin for UART instance. + /// + /// Sets the specified pin to push-pull output and connects it to the UART + /// TX signal. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let uart = Uart::new(peripherals.UART0, Config::default())?.with_tx(peripherals.GPIO2); + /// + /// # {after_snippet} + /// ``` + pub fn with_tx(mut self, tx: impl PeripheralOutput<'d>) -> Self { + self.tx = self.tx.with_tx(tx); + self + } + + #[procmacros::doc_replace] + /// Configure CTS pin + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_cts(peripherals.GPIO3); + /// + /// # {after_snippet} + /// ``` + pub fn with_cts(mut self, cts: impl PeripheralInput<'d>) -> Self { + self.rx = self.rx.with_cts(cts); + self + } + + #[procmacros::doc_replace] + /// Configure RTS pin + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_tx(peripherals.GPIO2) + /// .with_rts(peripherals.GPIO3); + /// + /// # {after_snippet} + /// ``` + pub fn with_rts(mut self, rts: impl PeripheralOutput<'d>) -> Self { + self.tx = self.tx.with_rts(rts); + self + } + + fn regs(&self) -> &RegisterBlock { + // `self.tx.uart` and `self.rx.uart` are the same + self.tx.uart.info().regs() + } + + #[procmacros::doc_replace] + /// Returns whether the UART TX buffer is ready to accept more data. + /// + /// If this function returns `true`, [`Self::write`] and [`Self::write_async`] + /// will not block. Otherwise, the functions will not return until the buffer is + /// ready. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// if uart.write_ready() { + /// // Because write_ready has returned true, the following call will immediately + /// // copy some bytes into the FIFO and return a non-zero value. + /// let written = uart.write(b"Hello")?; + /// // ... handle written bytes + /// } else { + /// // Calling write would have blocked, but here we can do something useful + /// // instead of waiting for the buffer to become ready. + /// } + /// # {after_snippet} + /// ``` + pub fn write_ready(&mut self) -> bool { + self.tx.write_ready() + } + + #[procmacros::doc_replace] + /// Writes bytes. + /// + /// This function writes data to the internal TX FIFO of the UART + /// peripheral. The data is then transmitted over the UART TX line. + /// + /// The function returns the number of bytes written to the FIFO. This may + /// be less than the length of the provided data. The function may only + /// return 0 if the provided data is empty. + /// + /// ## Errors + /// + /// This function returns a [`TxError`] if an error occurred during the + /// write operation. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write(&MESSAGE)?; + /// # {after_snippet} + /// ``` + pub fn write(&mut self, data: &[u8]) -> Result { + self.tx.write(data) + } + + #[procmacros::doc_replace] + /// Flush the transmit buffer of the UART + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write(&MESSAGE)?; + /// uart.flush()?; + /// # {after_snippet} + /// ``` + pub fn flush(&mut self) -> Result<(), TxError> { + self.tx.flush() + } + + /// Sends a break signal for a specified duration + #[instability::unstable] + pub fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits) + } + + #[procmacros::doc_replace] + /// Returns whether the UART receive buffer has at least one byte of data. + /// + /// If this function returns `true`, [`Self::read`] and [`Self::read_async`] + /// will not block. Otherwise, they will not return until data is available. + /// + /// Data that does not get stored due to an error will be lost and does not count + /// towards the number of bytes in the receive buffer. + // TODO: once we add support for UART_ERR_WR_MASK it needs to be documented here. + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// while !uart.read_ready() { + /// // Do something else while waiting for data to be available. + /// } + /// + /// let mut buf = [0u8; 32]; + /// uart.read(&mut buf[..])?; + /// + /// # {after_snippet} + /// ``` + pub fn read_ready(&mut self) -> bool { + self.rx.read_ready() + } + + #[procmacros::doc_replace] + /// Read received bytes. + /// + /// The UART hardware continuously receives bytes and stores them in the RX + /// FIFO. This function reads the bytes from the RX FIFO and returns + /// them in the provided buffer. If the hardware buffer is empty, this + /// function will block until data is available. The [`Self::read_ready`] + /// function can be used to check if data is available without blocking. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer. This function only returns 0 + /// if the provided buffer is empty. + /// + /// ## Errors + /// + /// This function returns an [`RxError`] if an error occurred since the last + /// check for errors. + /// + /// If the error occurred before this function was called, the contents of + /// the FIFO are not modified. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// const MESSAGE: &[u8] = b"Hello, world!"; + /// uart.write(&MESSAGE)?; + /// uart.flush()?; + /// + /// let mut buf = [0u8; MESSAGE.len()]; + /// uart.read(&mut buf[..])?; + /// + /// # {after_snippet} + /// ``` + pub fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf) + } + + #[procmacros::doc_replace] + /// Change the configuration. + /// + /// ## Errors + /// + /// This function returns a [`ConfigError`] if the configuration is not + /// supported by the hardware. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())?; + /// + /// uart.apply_config(&Config::default().with_baudrate(19_200))?; + /// # {after_snippet} + /// ``` + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + // Must apply the common settings first, as `rx.apply_config` reads back symbol + // size. + self.rx.uart.info().apply_config(config)?; + + self.rx.apply_config(config)?; + self.tx.apply_config(config)?; + Ok(()) + } + + #[procmacros::doc_replace] + /// Split the UART into a transmitter and receiver + /// + /// This is particularly useful when having two tasks correlating to + /// transmitting and receiving. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// use esp_hal::uart::{Config, Uart}; + /// let mut uart = Uart::new(peripherals.UART0, Config::default())? + /// .with_rx(peripherals.GPIO1) + /// .with_tx(peripherals.GPIO2); + /// + /// // The UART can be split into separate Transmit and Receive components: + /// let (mut rx, mut tx) = uart.split(); + /// + /// // Each component can be used individually to interact with the UART: + /// tx.write(&[42u8])?; + /// let mut byte = [0u8; 1]; + /// rx.read(&mut byte); + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub fn split(self) -> (UartRx<'d, Dm>, UartTx<'d, Dm>) { + (self.rx, self.tx) + } + + /// Reads and clears errors set by received data. + #[instability::unstable] + pub fn check_for_rx_errors(&mut self) -> Result<(), RxError> { + self.rx.check_for_errors() + } + + /// Read already received bytes. + /// + /// This function reads the already received bytes from the FIFO into the + /// provided buffer. The function does not wait for the FIFO to actually + /// contain any bytes. + /// + /// The function returns the number of bytes read into the buffer. This may + /// be less than the length of the buffer, and it may also be 0. + /// + /// ## Errors + /// + /// This function returns an [`RxError`] if an error occurred since the last + /// check for errors. + /// + /// If the error occurred before this function was called, the contents of + /// the FIFO are not modified. + #[instability::unstable] + pub fn read_buffered(&mut self, buf: &mut [u8]) -> Result { + self.rx.read_buffered(buf) + } + + /// Configures the AT-CMD detection settings + #[instability::unstable] + pub fn set_at_cmd(&mut self, config: AtCmdConfig) { + #[cfg(not(any(esp32, esp32s2)))] + self.regs() + .clk_conf() + .modify(|_, w| w.sclk_en().clear_bit()); + + self.regs().at_cmd_char().write(|w| unsafe { + w.at_cmd_char().bits(config.cmd_char); + w.char_num().bits(config.char_num) + }); + + if let Some(pre_idle_count) = config.pre_idle_count { + self.regs() + .at_cmd_precnt() + .write(|w| unsafe { w.pre_idle_num().bits(pre_idle_count as _) }); + } + + if let Some(post_idle_count) = config.post_idle_count { + self.regs() + .at_cmd_postcnt() + .write(|w| unsafe { w.post_idle_num().bits(post_idle_count as _) }); + } + + if let Some(gap_timeout) = config.gap_timeout { + self.regs() + .at_cmd_gaptout() + .write(|w| unsafe { w.rx_gap_tout().bits(gap_timeout as _) }); + } + + #[cfg(not(any(esp32, esp32s2)))] + self.regs().clk_conf().modify(|_, w| w.sclk_en().set_bit()); + + sync_regs(self.regs()); + } + + #[inline(always)] + fn init(&mut self, config: Config) -> Result<(), ConfigError> { + self.rx.disable_rx_interrupts(); + self.tx.disable_tx_interrupts(); + + self.apply_config(&config)?; + + // Reset Tx/Rx FIFOs + self.rx.uart.info().rxfifo_reset(); + self.rx.uart.info().txfifo_reset(); + + // Don't wait after transmissions by default, + // so that bytes written to TX FIFO are always immediately transmitted. + self.regs() + .idle_conf() + .modify(|_, w| unsafe { w.tx_idle_num().bits(0) }); + + // Setting err_wr_mask stops uart from storing data when data is wrong according + // to reference manual + self.regs().conf0().modify(|_, w| w.err_wr_mask().set_bit()); + + crate::rom::ets_delay_us(15); + + // Make sure we are starting in a "clean state" - previous operations might have + // run into error conditions + self.regs().int_clr().write(|w| unsafe { w.bits(u32::MAX) }); + + Ok(()) + } +} + +impl crate::private::Sealed for Uart<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for Uart<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + // `self.tx.uart` and `self.rx.uart` are the same + self.tx.uart.set_interrupt_handler(handler); + } +} + +#[instability::unstable] +impl ufmt_write::uWrite for Uart<'_, Dm> +where + Dm: DriverMode, +{ + type Error = TxError; + + #[inline] + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.tx.write_str(s) + } + + #[inline] + fn write_char(&mut self, ch: char) -> Result<(), Self::Error> { + self.tx.write_char(ch) + } +} + +#[instability::unstable] +impl ufmt_write::uWrite for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = TxError; + + #[inline] + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.write_all(s.as_bytes()) + } +} + +impl core::fmt::Write for Uart<'_, Dm> +where + Dm: DriverMode, +{ + #[inline] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.tx.write_str(s) + } +} + +impl core::fmt::Write for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + #[inline] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write_all(s.as_bytes()).map_err(|_| core::fmt::Error) + } +} + +/// UART Tx or Rx Error +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum IoError { + /// UART TX error + Tx(TxError), + /// UART RX error + Rx(RxError), +} + +#[instability::unstable] +impl core::error::Error for IoError {} + +#[instability::unstable] +impl core::fmt::Display for IoError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + IoError::Tx(e) => e.fmt(f), + IoError::Rx(e) => e.fmt(f), + } + } +} + +#[instability::unstable] +impl embedded_io_06::Error for IoError { + fn kind(&self) -> embedded_io_06::ErrorKind { + embedded_io_06::ErrorKind::Other + } +} + +#[instability::unstable] +impl embedded_io_07::Error for IoError { + fn kind(&self) -> embedded_io_07::ErrorKind { + embedded_io_07::ErrorKind::Other + } +} + +#[instability::unstable] +impl From for IoError { + fn from(e: RxError) -> Self { + IoError::Rx(e) + } +} + +#[instability::unstable] +impl From for IoError { + fn from(e: TxError) -> Self { + IoError::Tx(e) + } +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for Uart<'_, Dm> { + type Error = IoError; +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for UartTx<'_, Dm> { + type Error = TxError; +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for UartRx<'_, Dm> { + type Error = RxError; +} + +#[instability::unstable] +impl embedded_io_06::Read for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).map_err(IoError::Rx) + } +} + +#[instability::unstable] +impl embedded_io_06::Read for UartRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf) + } +} + +#[instability::unstable] +impl embedded_io_06::ReadReady for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn read_ready(&mut self) -> Result { + Ok(self.rx.read_ready()) + } +} + +#[instability::unstable] +impl embedded_io_06::ReadReady for UartRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read_ready(&mut self) -> Result { + Ok(self.read_ready()) + } +} + +#[instability::unstable] +impl embedded_io_06::Write for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).map_err(IoError::Tx) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush().map_err(IoError::Tx) + } +} + +#[instability::unstable] +impl embedded_io_06::Write for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush() + } +} + +#[instability::unstable] +impl embedded_io_06::WriteReady for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write_ready(&mut self) -> Result { + Ok(self.write_ready()) + } +} + +#[instability::unstable] +impl embedded_io_06::WriteReady for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn write_ready(&mut self) -> Result { + Ok(self.tx.write_ready()) + } +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for Uart<'_, Dm> { + type Error = IoError; +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for UartTx<'_, Dm> { + type Error = TxError; +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for UartRx<'_, Dm> { + type Error = RxError; +} + +#[instability::unstable] +impl embedded_io_07::Read for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).map_err(IoError::Rx) + } +} + +#[instability::unstable] +impl embedded_io_07::Read for UartRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf) + } +} + +#[instability::unstable] +impl embedded_io_07::ReadReady for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn read_ready(&mut self) -> Result { + Ok(self.rx.read_ready()) + } +} + +#[instability::unstable] +impl embedded_io_07::ReadReady for UartRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read_ready(&mut self) -> Result { + Ok(self.read_ready()) + } +} + +#[instability::unstable] +impl embedded_io_07::Write for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).map_err(IoError::Tx) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush().map_err(IoError::Tx) + } +} + +#[instability::unstable] +impl embedded_io_07::Write for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush() + } +} + +#[instability::unstable] +impl embedded_io_07::WriteReady for UartTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write_ready(&mut self) -> Result { + Ok(self.write_ready()) + } +} + +#[instability::unstable] +impl embedded_io_07::WriteReady for Uart<'_, Dm> +where + Dm: DriverMode, +{ + fn write_ready(&mut self) -> Result { + Ok(self.tx.write_ready()) + } +} + +#[derive(Debug, EnumSetType)] +pub(crate) enum TxEvent { + Done, + FiFoEmpty, +} + +#[derive(Debug, EnumSetType)] +pub(crate) enum RxEvent { + FifoFull, + CmdCharDetected, + FifoOvf, + FifoTout, + GlitchDetected, + FrameError, + ParityError, + BreakDetected, +} + +fn rx_event_check_for_error(events: EnumSet) -> Result<(), RxError> { + for event in events { + match event { + RxEvent::FifoOvf => return Err(RxError::FifoOverflowed), + RxEvent::GlitchDetected => return Err(RxError::GlitchOccurred), + RxEvent::FrameError => return Err(RxError::FrameFormatViolated), + RxEvent::ParityError => return Err(RxError::ParityMismatch), + RxEvent::FifoFull + | RxEvent::CmdCharDetected + | RxEvent::FifoTout + | RxEvent::BreakDetected => continue, + } + } + + Ok(()) +} + +/// A future that resolves when the passed interrupt is triggered, +/// or has been triggered in the meantime (flag set in INT_RAW). +/// Upon construction the future enables the passed interrupt and when it +/// is dropped it disables the interrupt again. The future returns the event +/// that was initially passed, when it resolves. +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct UartRxFuture { + events: EnumSet, + uart: &'static Info, + state: &'static State, + registered: bool, +} + +impl UartRxFuture { + fn new(uart: impl Instance, events: impl Into>) -> Self { + Self { + events: events.into(), + uart: uart.info(), + state: uart.state(), + registered: false, + } + } +} + +impl core::future::Future for UartRxFuture { + type Output = EnumSet; + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + let events = self.uart.rx_events().intersection(self.events); + if !events.is_empty() { + self.uart.clear_rx_events(events); + Poll::Ready(events) + } else { + self.state.rx_waker.register(cx.waker()); + if !self.registered { + self.uart.enable_listen_rx(self.events, true); + self.registered = true; + } + Poll::Pending + } + } +} + +impl Drop for UartRxFuture { + fn drop(&mut self) { + // Although the isr disables the interrupt that occurred directly, we need to + // disable the other interrupts (= the ones that did not occur), as + // soon as this future goes out of scope. + self.uart.enable_listen_rx(self.events, false); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct UartTxFuture { + events: EnumSet, + uart: &'static Info, + state: &'static State, + registered: bool, +} + +impl UartTxFuture { + fn new(uart: impl Instance, events: impl Into>) -> Self { + Self { + events: events.into(), + uart: uart.info(), + state: uart.state(), + registered: false, + } + } +} + +impl core::future::Future for UartTxFuture { + type Output = (); + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + let events = self.uart.tx_events().intersection(self.events); + if !events.is_empty() { + self.uart.clear_tx_events(events); + Poll::Ready(()) + } else { + self.state.tx_waker.register(cx.waker()); + if !self.registered { + self.uart.enable_listen_tx(self.events, true); + self.registered = true; + } + Poll::Pending + } + } +} + +impl Drop for UartTxFuture { + fn drop(&mut self) { + // Although the isr disables the interrupt that occurred directly, we need to + // disable the other interrupts (= the ones that did not occur), as + // soon as this future goes out of scope. + self.uart.enable_listen_tx(self.events, false); + } +} + +#[instability::unstable] +impl embedded_io_async_06::Read for Uart<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await.map_err(IoError::Rx) + } + + async fn read_exact( + &mut self, + buf: &mut [u8], + ) -> Result<(), embedded_io_06::ReadExactError> { + self.read_exact_async(buf) + .await + .map_err(|e| embedded_io_06::ReadExactError::Other(IoError::Rx(e))) + } +} + +#[instability::unstable] +impl embedded_io_async_06::Read for UartRx<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await + } + + async fn read_exact( + &mut self, + buf: &mut [u8], + ) -> Result<(), embedded_io_06::ReadExactError> { + self.read_exact_async(buf) + .await + .map_err(embedded_io_06::ReadExactError::Other) + } +} + +#[instability::unstable] +impl embedded_io_async_06::Write for Uart<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await.map_err(IoError::Tx) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await.map_err(IoError::Tx) + } +} + +#[instability::unstable] +impl embedded_io_async_06::Write for UartTx<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await + } +} + +#[instability::unstable] +impl embedded_io_async_07::Read for Uart<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await.map_err(IoError::Rx) + } + + async fn read_exact( + &mut self, + buf: &mut [u8], + ) -> Result<(), embedded_io_07::ReadExactError> { + self.read_exact_async(buf) + .await + .map_err(|e| embedded_io_07::ReadExactError::Other(IoError::Rx(e))) + } +} + +#[instability::unstable] +impl embedded_io_async_07::Read for UartRx<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await + } + + async fn read_exact( + &mut self, + buf: &mut [u8], + ) -> Result<(), embedded_io_07::ReadExactError> { + self.read_exact_async(buf) + .await + .map_err(embedded_io_07::ReadExactError::Other) + } +} + +#[instability::unstable] +impl embedded_io_async_07::Write for Uart<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await.map_err(IoError::Tx) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await.map_err(IoError::Tx) + } +} + +#[instability::unstable] +impl embedded_io_async_07::Write for UartTx<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await + } +} + +/// Interrupt handler for all UART instances +/// Clears and disables interrupts that have occurred and have their enable +/// bit set. The fact that an interrupt has been disabled is used by the +/// futures to detect that they should indeed resolve after being woken up +#[ram] +pub(super) fn intr_handler(uart: &Info, state: &State) { + let interrupts = uart.regs().int_st().read(); + let interrupt_bits = interrupts.bits(); // = int_raw & int_ena + let rx_wake = interrupts.rxfifo_full().bit_is_set() + | interrupts.rxfifo_ovf().bit_is_set() + | interrupts.rxfifo_tout().bit_is_set() + | interrupts.at_cmd_char_det().bit_is_set() + | interrupts.glitch_det().bit_is_set() + | interrupts.frm_err().bit_is_set() + | interrupts.parity_err().bit_is_set(); + let tx_wake = interrupts.tx_done().bit_is_set() | interrupts.txfifo_empty().bit_is_set(); + + uart.regs() + .int_ena() + .modify(|r, w| unsafe { w.bits(r.bits() & !interrupt_bits) }); + + if tx_wake { + state.tx_waker.wake(); + } + if rx_wake { + state.rx_waker.wake(); + } +} + +/// Low-power UART +#[cfg(lp_uart_driver_supported)] +#[instability::unstable] +pub mod lp_uart { + use crate::{ + gpio::lp_io::{LowPowerInput, LowPowerOutput}, + peripherals::{LP_AON, LP_CLKRST, LP_IO, LP_UART, LPWR}, + soc::clocks::ClockTree, + uart::{DataBits, Parity, StopBits}, + }; + + /// LP-UART Configuration + #[derive(Debug, Clone, Copy, procmacros::BuilderLite)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[non_exhaustive] + pub struct Config { + /// The baud rate (speed) of the UART communication in bits per second + /// (bps). + baudrate: u32, + /// Number of data bits in each frame (5, 6, 7, or 8 bits). + data_bits: DataBits, + /// Parity setting (None, Even, or Odd). + parity: Parity, + /// Number of stop bits in each frame (1, 1.5, or 2 bits). + stop_bits: StopBits, + /// Clock source used by the UART peripheral. + #[builder_lite(unstable)] + clock_source: ClockSource, + } + + impl Default for Config { + fn default() -> Config { + Config { + baudrate: 115_200, + data_bits: Default::default(), + parity: Default::default(), + stop_bits: Default::default(), + clock_source: Default::default(), + } + } + } + + /// LP-UART clock source + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[non_exhaustive] + #[instability::unstable] + pub enum ClockSource { + /// RC_FAST_CLK clock source + RcFast, + + /// XTAL_D2 clock source + #[default] + Xtal, + } + + /// LP-UART driver + pub struct LpUart { + uart: LP_UART<'static>, + } + + impl LpUart { + /// Initialize the UART driver using the provided configuration + // TODO: CTS and RTS pins + pub fn new( + uart: LP_UART<'static>, + config: Config, + _tx: LowPowerOutput<'_, 5>, + _rx: LowPowerInput<'_, 4>, + ) -> Self { + // FIXME: use GPIO APIs to configure pins + LP_AON::regs() + .gpio_mux() + .modify(|r, w| unsafe { w.sel().bits(r.sel().bits() | (1 << 4) | (1 << 5)) }); + + LP_IO::regs() + .gpio(4) + .modify(|_, w| unsafe { w.mcu_sel().bits(1) }); + LP_IO::regs() + .gpio(5) + .modify(|_, w| unsafe { w.mcu_sel().bits(1) }); + + let mut me = Self { uart }; + let uart = me.uart.register_block(); + + // Set UART mode - do nothing for LP + + // Disable UART parity + // 8-bit world + // 1-bit stop bit + uart.conf0().modify(|_, w| unsafe { + w.parity().clear_bit(); + w.parity_en().clear_bit(); + w.bit_num().bits(0x3); + w.stop_bit_num().bits(0x1) + }); + // Set tx idle + uart.idle_conf() + .modify(|_, w| unsafe { w.tx_idle_num().bits(0) }); + // Disable hw-flow control + uart.hwfc_conf().modify(|_, w| w.rx_flow_en().clear_bit()); + + // Get source clock frequency + // default == SOC_MOD_CLK_RTC_FAST == 2 + + // LPWR.lpperi.lp_uart_clk_sel = 0; + LPWR::regs() + .lpperi() + .modify(|_, w| w.lp_uart_clk_sel().clear_bit()); + + // Override protocol parameters from the configuration + // uart_hal_set_baudrate(&hal, cfg->uart_proto_cfg.baud_rate, sclk_freq); + me.change_baud_internal(&config); + // uart_hal_set_parity(&hal, cfg->uart_proto_cfg.parity); + me.change_parity(config.parity); + // uart_hal_set_data_bit_num(&hal, cfg->uart_proto_cfg.data_bits); + me.change_data_bits(config.data_bits); + // uart_hal_set_stop_bits(&hal, cfg->uart_proto_cfg.stop_bits); + me.change_stop_bits(config.stop_bits); + // uart_hal_set_tx_idle_num(&hal, LP_UART_TX_IDLE_NUM_DEFAULT); + me.change_tx_idle(0); // LP_UART_TX_IDLE_NUM_DEFAULT == 0 + + // Reset Tx/Rx FIFOs + me.rxfifo_reset(); + me.txfifo_reset(); + + me + } + + fn rxfifo_reset(&mut self) { + self.uart + .register_block() + .conf0() + .modify(|_, w| w.rxfifo_rst().set_bit()); + self.update(); + + self.uart + .register_block() + .conf0() + .modify(|_, w| w.rxfifo_rst().clear_bit()); + self.update(); + } + + fn txfifo_reset(&mut self) { + self.uart + .register_block() + .conf0() + .modify(|_, w| w.txfifo_rst().set_bit()); + self.update(); + + self.uart + .register_block() + .conf0() + .modify(|_, w| w.txfifo_rst().clear_bit()); + self.update(); + } + + fn update(&mut self) { + let register_block = self.uart.register_block(); + register_block + .reg_update() + .modify(|_, w| w.reg_update().set_bit()); + while register_block.reg_update().read().reg_update().bit_is_set() { + // wait + } + } + + fn change_baud_internal(&mut self, config: &Config) { + let clk = ClockTree::with(|clocks| match config.clock_source { + ClockSource::RcFast => crate::soc::clocks::rc_fast_clk_frequency(clocks), + ClockSource::Xtal => crate::soc::clocks::xtal_d2_clk_frequency(clocks), + }); + + LP_CLKRST::regs().lpperi().modify(|_, w| { + w.lp_uart_clk_sel().bit(match config.clock_source { + ClockSource::RcFast => false, + ClockSource::Xtal => true, + }) + }); + self.uart.register_block().clk_conf().modify(|_, w| { + w.rx_sclk_en().set_bit(); + w.tx_sclk_en().set_bit() + }); + + let divider = clk / config.baudrate; + let divider = divider as u16; + + self.uart + .register_block() + .clkdiv() + .write(|w| unsafe { w.clkdiv().bits(divider).frag().bits(0) }); + + self.update(); + } + + /// Modify UART baud rate and reset TX/RX fifo. + pub fn change_baud(&mut self, config: &Config) { + self.change_baud_internal(config); + self.txfifo_reset(); + self.rxfifo_reset(); + } + + fn change_parity(&mut self, parity: Parity) -> &mut Self { + if parity != Parity::None { + self.uart + .register_block() + .conf0() + .modify(|_, w| w.parity().bit((parity as u8 & 0x1) != 0)); + } + + self.uart + .register_block() + .conf0() + .modify(|_, w| match parity { + Parity::None => w.parity_en().clear_bit(), + Parity::Even => w.parity_en().set_bit().parity().clear_bit(), + Parity::Odd => w.parity_en().set_bit().parity().set_bit(), + }); + + self + } + + fn change_data_bits(&mut self, data_bits: DataBits) -> &mut Self { + self.uart + .register_block() + .conf0() + .modify(|_, w| unsafe { w.bit_num().bits(data_bits as u8) }); + + self.update(); + self + } + + fn change_stop_bits(&mut self, stop_bits: StopBits) -> &mut Self { + self.uart + .register_block() + .conf0() + .modify(|_, w| unsafe { w.stop_bit_num().bits(stop_bits as u8 + 1) }); + + self.update(); + self + } + + fn change_tx_idle(&mut self, idle_num: u16) -> &mut Self { + self.uart + .register_block() + .idle_conf() + .modify(|_, w| unsafe { w.tx_idle_num().bits(idle_num) }); + + self.update(); + self + } + } +} + +/// A peripheral singleton compatible with the UART driver. +pub trait Instance: crate::private::Sealed + any::Degrade { + #[doc(hidden)] + /// Returns the peripheral data and state describing this UART instance. + fn parts(&self) -> (&'static Info, &'static State); + + /// Returns the peripheral data describing this UART instance. + #[inline(always)] + #[doc(hidden)] + fn info(&self) -> &'static Info { + self.parts().0 + } + + /// Returns the peripheral state for this UART instance. + #[inline(always)] + #[doc(hidden)] + fn state(&self) -> &'static State { + self.parts().1 + } +} + +/// Peripheral data describing a particular UART instance. +#[doc(hidden)] +#[non_exhaustive] +#[allow(private_interfaces, reason = "Unstable details")] +pub struct Info { + /// Pointer to the register block for this UART instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, + + /// The system peripheral marker. + pub peripheral: crate::system::Peripheral, + + /// Interrupt handler for the asynchronous operations of this UART instance. + pub async_handler: InterruptHandler, + + /// TX pin + pub tx_signal: OutputSignal, + + /// RX pin + pub rx_signal: InputSignal, + + /// CTS (Clear to Send) pin + pub cts_signal: InputSignal, + + /// RTS (Request to Send) pin + pub rts_signal: OutputSignal, + + pub request_uart_clks: fn(&mut ClockTree), + pub release_uart_clks: fn(&mut ClockTree), + pub configure_sclk: fn(&mut ClockTree, ClockSource), + pub sclk_frequency: fn(&mut ClockTree) -> u32, +} + +/// Peripheral state for a UART instance. +#[doc(hidden)] +#[non_exhaustive] +pub struct State { + /// Waker for the asynchronous RX operations. + pub rx_waker: AtomicWaker, + + /// Waker for the asynchronous TX operations. + pub tx_waker: AtomicWaker, + + /// Stores whether the TX half is configured for async operation. + pub is_rx_async: AtomicBool, + + /// Stores whether the RX half is configured for async operation. + pub is_tx_async: AtomicBool, +} + +impl Info { + // Currently we don't support merging adjacent FIFO memory, so the max size is + // 128 bytes, the max threshold is 127 bytes. + const UART_FIFO_SIZE: u16 = property!("uart.ram_size"); + const RX_FIFO_MAX_THRHD: u16 = Self::UART_FIFO_SIZE - 1; + const TX_FIFO_MAX_THRHD: u16 = Self::RX_FIFO_MAX_THRHD; + + /// Returns the register block for this UART instance. + pub fn regs(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } + + /// Listen for the given interrupts + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + let reg_block = self.regs(); + + reg_block.int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + UartInterrupt::AtCmd => w.at_cmd_char_det().bit(enable), + UartInterrupt::TxDone => w.tx_done().bit(enable), + UartInterrupt::RxBreakDetected => w.brk_det().bit(enable), + UartInterrupt::RxFifoFull => w.rxfifo_full().bit(enable), + UartInterrupt::RxTimeout => w.rxfifo_tout().bit(enable), + }; + } + w + }); + } + + fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + let reg_block = self.regs(); + + let ints = reg_block.int_raw().read(); + + if ints.at_cmd_char_det().bit_is_set() { + res.insert(UartInterrupt::AtCmd); + } + if ints.tx_done().bit_is_set() { + res.insert(UartInterrupt::TxDone); + } + if ints.brk_det().bit_is_set() { + res.insert(UartInterrupt::RxBreakDetected); + } + if ints.rxfifo_full().bit_is_set() { + res.insert(UartInterrupt::RxFifoFull); + } + if ints.rxfifo_tout().bit_is_set() { + res.insert(UartInterrupt::RxTimeout); + } + + res + } + + fn clear_interrupts(&self, interrupts: EnumSet) { + let reg_block = self.regs(); + + reg_block.int_clr().write(|w| { + for interrupt in interrupts { + match interrupt { + UartInterrupt::AtCmd => w.at_cmd_char_det().clear_bit_by_one(), + UartInterrupt::TxDone => w.tx_done().clear_bit_by_one(), + UartInterrupt::RxBreakDetected => w.brk_det().clear_bit_by_one(), + UartInterrupt::RxFifoFull => w.rxfifo_full().clear_bit_by_one(), + UartInterrupt::RxTimeout => w.rxfifo_tout().clear_bit_by_one(), + }; + } + w + }); + } + + fn apply_config(&self, config: &Config) -> Result<(), ConfigError> { + config.validate()?; + self.change_baud(config)?; + self.change_data_bits(config.data_bits); + self.change_parity(config.parity); + self.change_stop_bits(config.stop_bits); + self.change_flow_control(config.sw_flow_ctrl, config.hw_flow_ctrl); + + // Avoid glitch interrupts. + self.regs().int_clr().write(|w| unsafe { w.bits(u32::MAX) }); + + Ok(()) + } + + fn enable_listen_tx(&self, events: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for event in events { + match event { + TxEvent::Done => w.tx_done().bit(enable), + TxEvent::FiFoEmpty => w.txfifo_empty().bit(enable), + }; + } + w + }); + } + + fn tx_events(&self) -> EnumSet { + let pending_interrupts = self.regs().int_raw().read(); + let mut active_events = EnumSet::new(); + + if pending_interrupts.tx_done().bit_is_set() { + active_events |= TxEvent::Done; + } + if pending_interrupts.txfifo_empty().bit_is_set() { + active_events |= TxEvent::FiFoEmpty; + } + + active_events + } + + fn clear_tx_events(&self, events: impl Into>) { + let events = events.into(); + self.regs().int_clr().write(|w| { + for event in events { + match event { + TxEvent::FiFoEmpty => w.txfifo_empty().clear_bit_by_one(), + TxEvent::Done => w.tx_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn enable_listen_rx(&self, events: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for event in events { + match event { + RxEvent::FifoFull => w.rxfifo_full().bit(enable), + RxEvent::BreakDetected => w.brk_det().bit(enable), + RxEvent::CmdCharDetected => w.at_cmd_char_det().bit(enable), + + RxEvent::FifoOvf => w.rxfifo_ovf().bit(enable), + RxEvent::FifoTout => w.rxfifo_tout().bit(enable), + RxEvent::GlitchDetected => w.glitch_det().bit(enable), + RxEvent::FrameError => w.frm_err().bit(enable), + RxEvent::ParityError => w.parity_err().bit(enable), + }; + } + w + }); + } + + fn rx_events(&self) -> EnumSet { + let pending_interrupts = self.regs().int_raw().read(); + let mut active_events = EnumSet::new(); + + if pending_interrupts.rxfifo_full().bit_is_set() { + active_events |= RxEvent::FifoFull; + } + if pending_interrupts.brk_det().bit_is_set() { + active_events |= RxEvent::BreakDetected; + } + if pending_interrupts.at_cmd_char_det().bit_is_set() { + active_events |= RxEvent::CmdCharDetected; + } + if pending_interrupts.rxfifo_ovf().bit_is_set() { + active_events |= RxEvent::FifoOvf; + } + if pending_interrupts.rxfifo_tout().bit_is_set() { + active_events |= RxEvent::FifoTout; + } + if pending_interrupts.glitch_det().bit_is_set() { + active_events |= RxEvent::GlitchDetected; + } + if pending_interrupts.frm_err().bit_is_set() { + active_events |= RxEvent::FrameError; + } + if pending_interrupts.parity_err().bit_is_set() { + active_events |= RxEvent::ParityError; + } + + active_events + } + + fn clear_rx_events(&self, events: impl Into>) { + let events = events.into(); + self.regs().int_clr().write(|w| { + for event in events { + match event { + RxEvent::FifoFull => w.rxfifo_full().clear_bit_by_one(), + RxEvent::BreakDetected => w.brk_det().clear_bit_by_one(), + RxEvent::CmdCharDetected => w.at_cmd_char_det().clear_bit_by_one(), + + RxEvent::FifoOvf => w.rxfifo_ovf().clear_bit_by_one(), + RxEvent::FifoTout => w.rxfifo_tout().clear_bit_by_one(), + RxEvent::GlitchDetected => w.glitch_det().clear_bit_by_one(), + RxEvent::FrameError => w.frm_err().clear_bit_by_one(), + RxEvent::ParityError => w.parity_err().clear_bit_by_one(), + }; + } + w + }); + } + + /// Configures the RX-FIFO threshold + /// + /// ## Errors + /// + /// [ConfigError::UnsupportedRxFifoThreshold] if the provided value exceeds + /// [`Info::RX_FIFO_MAX_THRHD`]. + fn set_rx_fifo_full_threshold(&self, threshold: u16) -> Result<(), ConfigError> { + if threshold > Self::RX_FIFO_MAX_THRHD { + return Err(ConfigError::RxFifoThresholdNotSupported); + } + + self.regs() + .conf1() + .modify(|_, w| unsafe { w.rxfifo_full_thrhd().bits(threshold as _) }); + + Ok(()) + } + + /// Reads the RX-FIFO threshold + #[allow(clippy::useless_conversion)] + fn rx_fifo_full_threshold(&self) -> u16 { + self.regs().conf1().read().rxfifo_full_thrhd().bits().into() + } + + /// Configures the TX-FIFO threshold + /// + /// ## Errors + /// + /// [ConfigError::UnsupportedTxFifoThreshold] if the provided value exceeds + /// [`Info::TX_FIFO_MAX_THRHD`]. + fn set_tx_fifo_empty_threshold(&self, threshold: u16) -> Result<(), ConfigError> { + if threshold > Self::TX_FIFO_MAX_THRHD { + return Err(ConfigError::TxFifoThresholdNotSupported); + } + + self.regs() + .conf1() + .modify(|_, w| unsafe { w.txfifo_empty_thrhd().bits(threshold as _) }); + + Ok(()) + } + + /// Configures the Receive Timeout detection setting + /// + /// ## Arguments + /// + /// `timeout` - the number of symbols ("bytes") to wait for before + /// triggering a timeout. Pass None to disable the timeout. + /// + /// ## Errors + /// + /// [ConfigError::UnsupportedTimeout] if the provided value exceeds + /// the maximum value for SOC: + /// - `esp32`: Symbol size is fixed to 8, do not pass a value > **0x7F**. + /// - `esp32c2`, `esp32c3`, `esp32c6`, `esp32h2`, esp32s2`, esp32s3`: The value you pass times + /// the symbol size must be <= **0x3FF** + fn set_rx_timeout(&self, timeout: Option, _symbol_len: u8) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + const MAX_THRHD: u8 = 0x7F; // 7 bits + } else { + const MAX_THRHD: u16 = 0x3FF; // 10 bits + } + } + + let register_block = self.regs(); + + if let Some(timeout) = timeout { + // the esp32 counts directly in number of symbols (symbol len fixed to 8) + #[cfg(esp32)] + let timeout_reg = timeout; + // all other count in bits, so we need to multiply by the symbol len. + #[cfg(not(esp32))] + let timeout_reg = timeout as u16 * _symbol_len as u16; + + if timeout_reg > MAX_THRHD { + return Err(ConfigError::TimeoutTooLong); + } + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + let reg_thrhd = register_block.conf1(); + } else if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let reg_thrhd = register_block.tout_conf(); + } else { + let reg_thrhd = register_block.mem_conf(); + } + } + reg_thrhd.modify(|_, w| unsafe { w.rx_tout_thrhd().bits(timeout_reg) }); + } + + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let reg_en = register_block.tout_conf(); + } else { + let reg_en = register_block.conf1(); + } + } + reg_en.modify(|_, w| w.rx_tout_en().bit(timeout.is_some())); + + self.sync_regs(); + + Ok(()) + } + + #[cfg(soc_has_pcr)] + fn is_instance(&self, other: impl Instance) -> bool { + self == other.info() + } + + fn sync_regs(&self) { + sync_regs(self.regs()); + } + + fn change_baud(&self, config: &Config) -> Result<(), ConfigError> { + ClockTree::with(|clocks| { + (self.configure_sclk)(clocks, config.clock_source); + let clk = (self.sclk_frequency)(clocks); + + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.regs().conf0().modify(|_, w| { + w.tick_ref_always_on() + .bit(config.clock_source == ClockSource::Apb) + }); + + let divider = (clk << 4) / config.baudrate; + } else { + const MAX_DIV: u32 = 0b1111_1111_1111 - 1; + let clk_div = (clk.div_ceil(MAX_DIV)).div_ceil(config.baudrate); + + // define `conf` in scope for modification below + cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3, esp32s3))] { + let conf = self.regs().clk_conf(); + } else { + // UART clocks are configured via PCR + let pcr = crate::peripherals::PCR::regs(); + let conf = if self.is_instance(unsafe { crate::peripherals::UART0::steal() }) { + pcr.uart(0).clk_conf() + } else { + pcr.uart(1).clk_conf() + }; + } + }; + + // TODO: we should consider a solution that can write this register fully in one go + // as part of the UART_SCLK configuration. Currently this may cause unexpected waveform + // changes in the transmitted data if the clock source is changed, even if the baud + // rate can be kept the same. + conf.modify(|_, w| unsafe { + w.sclk_div_a().bits(0); + w.sclk_div_b().bits(0); + w.sclk_div_num().bits(clk_div as u8 - 1) + }); + + let divider = (clk << 4) / (config.baudrate * clk_div); + } + } + + let divider_integer = divider >> 4; + let divider_frag = (divider & 0xf) as u8; + + self.regs().clkdiv().write(|w| unsafe { + w.clkdiv().bits(divider_integer as _); + w.frag().bits(divider_frag) + }); + + self.sync_regs(); + + #[cfg(feature = "unstable")] + self.verify_baudrate(clk, config)?; + + Ok(()) + }) + } + + fn change_data_bits(&self, data_bits: DataBits) { + self.regs() + .conf0() + .modify(|_, w| unsafe { w.bit_num().bits(data_bits as u8) }); + } + + fn change_parity(&self, parity: Parity) { + self.regs().conf0().modify(|_, w| match parity { + Parity::None => w.parity_en().clear_bit(), + Parity::Even => w.parity_en().set_bit().parity().clear_bit(), + Parity::Odd => w.parity_en().set_bit().parity().set_bit(), + }); + } + + fn change_stop_bits(&self, stop_bits: StopBits) { + #[cfg(esp32)] + { + // workaround for hardware issue, when UART stop bit set as 2-bit mode. + if stop_bits == StopBits::_2 { + self.regs() + .rs485_conf() + .modify(|_, w| w.dl1_en().bit(stop_bits == StopBits::_2)); + + self.regs() + .conf0() + .modify(|_, w| unsafe { w.stop_bit_num().bits(1) }); + } + } + + #[cfg(not(esp32))] + self.regs() + .conf0() + .modify(|_, w| unsafe { w.stop_bit_num().bits(stop_bits as u8 + 1) }); + } + + fn change_flow_control(&self, sw_flow_ctrl: SwFlowControl, hw_flow_ctrl: HwFlowControl) { + // set SW flow control + match sw_flow_ctrl { + SwFlowControl::Enabled { + xon_char, + xoff_char, + xon_threshold, + xoff_threshold, + } => { + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + self.regs().swfc_conf0().modify(|_, w| w.xonoff_del().set_bit().sw_flow_con_en().set_bit()); + self.regs().swfc_conf1().modify(|_, w| unsafe { w.xon_threshold().bits(xon_threshold).xoff_threshold().bits(xoff_threshold)}); + self.regs().swfc_conf0().modify(|_, w| unsafe { w.xon_char().bits(xon_char).xoff_char().bits(xoff_char) }); + } else if #[cfg(esp32)]{ + self.regs().flow_conf().modify(|_, w| w.xonoff_del().set_bit().sw_flow_con_en().set_bit()); + self.regs().swfc_conf().modify(|_, w| unsafe { w.xon_threshold().bits(xon_threshold).xoff_threshold().bits(xoff_threshold) }); + self.regs().swfc_conf().modify(|_, w| unsafe { w.xon_char().bits(xon_char).xoff_char().bits(xoff_char) }); + } else { + self.regs().flow_conf().modify(|_, w| w.xonoff_del().set_bit().sw_flow_con_en().set_bit()); + self.regs().swfc_conf1().modify(|_, w| unsafe { w.xon_threshold().bits(xon_threshold as u16) }); + self.regs().swfc_conf0().modify(|_, w| unsafe { w.xoff_threshold().bits(xoff_threshold as u16) }); + self.regs().swfc_conf1().modify(|_, w| unsafe { w.xon_char().bits(xon_char) }); + self.regs().swfc_conf0().modify(|_, w| unsafe { w.xoff_char().bits(xoff_char) }); + } + } + } + SwFlowControl::Disabled => { + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + let reg = self.regs().swfc_conf0(); + } else { + let reg = self.regs().flow_conf(); + } + } + + reg.modify(|_, w| w.sw_flow_con_en().clear_bit()); + reg.modify(|_, w| w.xonoff_del().clear_bit()); + } + } + + self.regs().conf0().modify(|_, w| { + w.tx_flow_en() + .bit(matches!(hw_flow_ctrl.cts, CtsConfig::Enabled)) + }); + + match hw_flow_ctrl.rts { + RtsConfig::Enabled(threshold) => self.configure_rts_flow_ctrl(true, Some(threshold)), + RtsConfig::Disabled => self.configure_rts_flow_ctrl(false, None), + } + + #[cfg(any(esp32c5, esp32c6, esp32h2))] + sync_regs(self.regs()); + } + + fn configure_rts_flow_ctrl(&self, enable: bool, threshold: Option) { + if let Some(threshold) = threshold { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.regs().conf1().modify(|_, w| unsafe { w.rx_flow_thrhd().bits(threshold) }); + } else if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + self.regs().hwfc_conf().modify(|_, w| unsafe { w.rx_flow_thrhd().bits(threshold) }); + } else { + self.regs().mem_conf().modify(|_, w| unsafe { w.rx_flow_thrhd().bits(threshold as u16) }); + } + } + } + + cfg_if::cfg_if! { + if #[cfg(any(esp32c5, esp32c6, esp32h2))] { + self.regs().hwfc_conf().modify(|_, w| { + w.rx_flow_en().bit(enable) + }); + } else { + self.regs().conf1().modify(|_, w| { + w.rx_flow_en().bit(enable) + }); + } + } + } + + fn rxfifo_reset(&self) { + fn rxfifo_rst(reg_block: &RegisterBlock, enable: bool) { + reg_block.conf0().modify(|_, w| w.rxfifo_rst().bit(enable)); + sync_regs(reg_block); + } + + rxfifo_rst(self.regs(), true); + rxfifo_rst(self.regs(), false); + } + + fn txfifo_reset(&self) { + fn txfifo_rst(reg_block: &RegisterBlock, enable: bool) { + reg_block.conf0().modify(|_, w| w.txfifo_rst().bit(enable)); + sync_regs(reg_block); + } + + txfifo_rst(self.regs(), true); + txfifo_rst(self.regs(), false); + } + + #[cfg(feature = "unstable")] + fn verify_baudrate(&self, clk: u32, config: &Config) -> Result<(), ConfigError> { + // taken from https://github.com/espressif/esp-idf/blob/c5865270b50529cd32353f588d8a917d89f3dba4/components/hal/esp32c6/include/hal/uart_ll.h#L433-L444 + // (it's different for different chips) + let clkdiv_reg = self.regs().clkdiv().read(); + let clkdiv_frag = clkdiv_reg.frag().bits() as u32; + let clkdiv = clkdiv_reg.clkdiv().bits(); + + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + let actual_baud = (clk << 4) / ((clkdiv << 4) | clkdiv_frag); + } else if #[cfg(any(esp32c2, esp32c3, esp32s3))] { + let sclk_div_num = self.regs().clk_conf().read().sclk_div_num().bits() as u32; + let actual_baud = (clk << 4) / ((((clkdiv as u32) << 4) | clkdiv_frag) * (sclk_div_num + 1)); + } else { // esp32c5, esp32c6, esp32h2 + let pcr = crate::peripherals::PCR::regs(); + let conf = if self.is_instance(unsafe { crate::peripherals::UART0::steal() }) { + pcr.uart(0).clk_conf() + } else { + pcr.uart(1).clk_conf() + }; + let sclk_div_num = conf.read().sclk_div_num().bits() as u32; + let actual_baud = (clk << 4) / ((((clkdiv as u32) << 4) | clkdiv_frag) * (sclk_div_num + 1)); + } + }; + + match config.baudrate_tolerance { + BaudrateTolerance::Exact => { + let deviation = ((config.baudrate as i32 - actual_baud as i32).unsigned_abs() + * 100) + / actual_baud; + // We tolerate deviation of 1% from the desired baud value, as it never will be + // exactly the same + if deviation > 1_u32 { + return Err(ConfigError::BaudrateNotAchievable); + } + } + BaudrateTolerance::ErrorPercent(percent) => { + let deviation = ((config.baudrate as i32 - actual_baud as i32).unsigned_abs() + * 100) + / actual_baud; + if deviation > percent as u32 { + return Err(ConfigError::BaudrateNotAchievable); + } + } + _ => {} + } + + Ok(()) + } + + fn current_symbol_length(&self) -> u8 { + let conf0 = self.regs().conf0().read(); + let data_bits = conf0.bit_num().bits() + 5; // 5 data bits are encoded as variant 0 + let parity = conf0.parity_en().bit() as u8; + let mut stop_bits = conf0.stop_bit_num().bits(); + + match stop_bits { + 1 => { + // workaround for hardware issue, when UART stop bit set as 2-bit mode. + #[cfg(esp32)] + if self.regs().rs485_conf().read().dl1_en().bit_is_set() { + stop_bits = 2; + } + } + // esp-idf also counts 2 bits for settings 1.5 and 2 stop bits + _ => stop_bits = 2, + } + + 1 + data_bits + parity + stop_bits + } + + /// Reads one byte from the RX FIFO. + /// + /// If the FIFO is empty, the value of the returned byte is not specified. + fn read_next_from_fifo(&self) -> u8 { + fn access_fifo_register(f: impl Fn() -> R) -> R { + // https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32/03-errata-description/esp32/cpu-subsequent-access-halted-when-get-interrupted.html + cfg_if::cfg_if! { + if #[cfg(esp32)] { + crate::interrupt::free(f) + } else { + f() + } + } + } + + let fifo_reg = self.regs().fifo(); + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + // On the ESP32-S2 we need to use PeriBus2 to read the FIFO: + let fifo_reg = unsafe { + &*fifo_reg.as_ptr().cast::().add(0x20C00000).cast::() + }; + } + } + + access_fifo_register(|| fifo_reg.read().rxfifo_rd_byte().bits()) + } + + #[allow(clippy::useless_conversion)] + fn tx_fifo_count(&self) -> u16 { + u16::from(self.regs().status().read().txfifo_cnt().bits()) + } + + fn write_byte(&self, byte: u8) { + self.regs() + .fifo() + .write(|w| unsafe { w.rxfifo_rd_byte().bits(byte) }); + } + + fn check_for_errors(&self) -> Result<(), RxError> { + let errors = RxEvent::FifoOvf + | RxEvent::FifoTout + | RxEvent::GlitchDetected + | RxEvent::FrameError + | RxEvent::ParityError; + let events = self.rx_events().intersection(errors); + let result = rx_event_check_for_error(events); + if result.is_err() { + self.clear_rx_events(errors); + if events.contains(RxEvent::FifoOvf) { + self.rxfifo_reset(); + } + } + result + } + + #[cfg(not(esp32))] + #[allow(clippy::unnecessary_cast)] + fn rx_fifo_count(&self) -> u16 { + self.regs().status().read().rxfifo_cnt().bits() as u16 + } + + #[cfg(esp32)] + fn rx_fifo_count(&self) -> u16 { + let fifo_cnt = self.regs().status().read().rxfifo_cnt().bits(); + + // Calculate the real count based on the FIFO read and write offset address: + // https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32/03-errata-description/esp32/uart-fifo-cnt-indicates-data-length-incorrectly.html + let status = self.regs().mem_rx_status().read(); + let rd_addr: u16 = status.mem_rx_rd_addr().bits(); + let wr_addr: u16 = status.mem_rx_wr_addr().bits(); + + if wr_addr > rd_addr { + wr_addr - rd_addr + } else if wr_addr < rd_addr { + (wr_addr + Info::UART_FIFO_SIZE) - rd_addr + } else if fifo_cnt > 0 { + Info::UART_FIFO_SIZE + } else { + 0 + } + } + + fn write(&self, data: &[u8]) -> Result { + if data.is_empty() { + return Ok(0); + } + + while self.tx_fifo_count() >= Info::UART_FIFO_SIZE {} + + let space = (Info::UART_FIFO_SIZE - self.tx_fifo_count()) as usize; + let to_write = space.min(data.len()); + for &byte in &data[..to_write] { + self.write_byte(byte); + } + + Ok(to_write) + } + + fn read(&self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + while self.rx_fifo_count() == 0 { + // Block until we received at least one byte + self.check_for_errors()?; + } + + self.read_buffered(buf) + } + + fn read_buffered(&self, buf: &mut [u8]) -> Result { + // Get the count first, to avoid accidentally reading a corrupted byte received + // after the error check. + let to_read = (self.rx_fifo_count() as usize).min(buf.len()); + self.check_for_errors()?; + + for byte_into in buf[..to_read].iter_mut() { + *byte_into = self.read_next_from_fifo(); + } + + // This bit is not cleared until the FIFO actually drops below the threshold. + self.clear_rx_events(RxEvent::FifoFull); + + Ok(to_read) + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + core::ptr::eq(self.register_block, other.register_block) + } +} + +unsafe impl Sync for Info {} + +for_each_uart! { + ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident) => { + impl Instance for crate::peripherals::$inst<'_> { + fn parts(&self) -> (&'static Info, &'static State) { + #[handler] + #[ram] + pub(super) fn irq_handler() { + intr_handler(&PERIPHERAL, &STATE); + } + + static STATE: State = State { + tx_waker: AtomicWaker::new(), + rx_waker: AtomicWaker::new(), + is_rx_async: AtomicBool::new(false), + is_tx_async: AtomicBool::new(false), + }; + + fn request_uart_clks(clocks: &mut ClockTree) { + paste::paste! { + clocks:: [< request_ $inst:lower _function_clock >](clocks); + #[cfg(soc_has_clock_node_uart0_mem_clock)] + clocks:: [< request_ $inst:lower _mem_clock >](clocks); + } + } + + fn release_uart_clks(clocks: &mut ClockTree) { + paste::paste! { + #[cfg(soc_has_clock_node_uart0_mem_clock)] + clocks:: [< release_ $inst:lower _mem_clock >](clocks); + clocks:: [< release_ $inst:lower _function_clock >](clocks); + } + } + + static PERIPHERAL: Info = Info { + register_block: crate::peripherals::$inst::ptr(), + peripheral: crate::system::Peripheral::$peri, + async_handler: irq_handler, + tx_signal: OutputSignal::$txd, + rx_signal: InputSignal::$rxd, + cts_signal: InputSignal::$cts, + rts_signal: OutputSignal::$rts, + request_uart_clks, + release_uart_clks, + configure_sclk: paste::paste!(clocks:: [< configure_ $inst:lower _function_clock >]), + sclk_frequency: paste::paste!(clocks:: [< $inst:lower _function_clock_frequency >]), + }; + (&PERIPHERAL, &STATE) + } + } + }; +} + +crate::any_peripheral! { + /// Any UART peripheral. + pub peripheral AnyUart<'d> { + #[cfg(soc_has_uart0)] + Uart0(crate::peripherals::UART0<'d>), + #[cfg(soc_has_uart1)] + Uart1(crate::peripherals::UART1<'d>), + #[cfg(soc_has_uart2)] + Uart2(crate::peripherals::UART2<'d>), + } +} + +impl Instance for AnyUart<'_> { + #[inline] + fn parts(&self) -> (&'static Info, &'static State) { + any::delegate!(self, uart => { uart.parts() }) + } +} + +impl AnyUart<'_> { + fn bind_peri_interrupt(&self, handler: InterruptHandler) { + any::delegate!(self, uart => { uart.bind_peri_interrupt(handler) }) + } + + fn disable_peri_interrupt_on_all_cores(&self) { + any::delegate!(self, uart => { uart.disable_peri_interrupt_on_all_cores() }) + } + + fn set_interrupt_handler(&self, handler: InterruptHandler) { + self.disable_peri_interrupt_on_all_cores(); + + self.info().enable_listen(EnumSet::all(), false); + self.info().clear_interrupts(EnumSet::all()); + + self.bind_peri_interrupt(handler); + } +} + +struct UartClockGuard<'t> { + uart: AnyUart<'t>, +} + +impl<'t> UartClockGuard<'t> { + fn new(uart: AnyUart<'t>) -> Self { + ClockTree::with(|clocks| { + (uart.info().configure_sclk)(clocks, Default::default()); + (uart.info().request_uart_clks)(clocks); + }); + + Self { uart } + } +} + +impl Clone for UartClockGuard<'_> { + fn clone(&self) -> Self { + Self::new(unsafe { self.uart.clone_unchecked() }) + } +} + +impl Drop for UartClockGuard<'_> { + fn drop(&mut self) { + ClockTree::with(self.uart.info().release_uart_clks); + } +} diff --git a/esp-hal/src/uart/uhci.rs b/esp-hal/src/uart/uhci.rs new file mode 100644 index 00000000000..a523dfbbdda --- /dev/null +++ b/esp-hal/src/uart/uhci.rs @@ -0,0 +1,811 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! ## Usage +//! ```rust, no_run +//! #![no_std] +//! #![no_main] +//! +//! #[panic_handler] +//! fn panic(_: &core::panic::PanicInfo) -> ! { +//! loop {} +//! } +//! +//! use esp_hal::{ +//! clock::CpuClock, +//! dma::{DmaRxBuf, DmaTxBuf}, +//! dma_buffers, +//! main, +//! rom::software_reset, +//! uart, +//! uart::{RxConfig, Uart, uhci, uhci::Uhci}, +//! }; +//! +//! #[main] +//! fn main() -> ! { +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! +//! let config = uart::Config::default() +//! .with_rx(RxConfig::default().with_fifo_full_threshold(64)) +//! .with_baudrate(115200); +//! +//! let uart = Uart::new(peripherals.UART1, config) +//! .unwrap() +//! .with_tx(peripherals.GPIO2) +//! .with_rx(peripherals.GPIO3); +//! +//! let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4092); +//! let dma_rx = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); +//! let mut dma_tx = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); +//! +//! let mut uhci = Uhci::new(uart, peripherals.UHCI0, peripherals.DMA_CH0); +//! uhci.apply_rx_config( +//! &uart::uhci::RxConfig::default().with_chunk_limit(dma_rx.len() as u16), +//! ) +//! .unwrap(); +//! uhci.apply_tx_config(&uart::uhci::TxConfig::default()) +//! .unwrap(); +//! +//! let config = uart::Config::default() +//! .with_rx(RxConfig::default().with_fifo_full_threshold(64)) +//! .with_baudrate(9600); +//! uhci.set_uart_config(&config).unwrap(); +//! +//! let (uhci_rx, uhci_tx) = uhci.split(); +//! // Waiting for message +//! let transfer = uhci_rx +//! .read(dma_rx) +//! .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); +//! let (err, _uhci_rx, dma_rx) = transfer.wait(); +//! err.unwrap(); +//! +//! let received = dma_rx.number_of_received_bytes(); +//! // println!("Received dma bytes: {}", received); +//! +//! let rec_slice = &dma_rx.as_slice()[0..received]; +//! if received > 0 { +//! match core::str::from_utf8(&rec_slice) { +//! Ok(x) => { +//! // println!("Received DMA message: \"{}\"", x); +//! dma_tx.as_mut_slice()[0..received].copy_from_slice(&rec_slice); +//! dma_tx.set_length(received); +//! let transfer = uhci_tx +//! .write(dma_tx) +//! .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); +//! let (err, _uhci, _dma_tx) = transfer.wait(); +//! err.unwrap(); +//! } +//! Err(x) => panic!("Error string: {}", x), +//! } +//! } +//! software_reset() +//! } +//! ``` +// TODO add support for PDMA and multiple UHCI for 32/S2 support + +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use embassy_embedded_hal::SetConfig; + +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + Channel, + ChannelRx, + ChannelTx, + DmaChannelFor, + DmaEligible, + DmaError, + DmaRxBuffer, + DmaTxBuffer, + PeripheralDmaChannel, + PeripheralRxChannel, + PeripheralTxChannel, + asynch::{DmaRxFuture, DmaTxFuture}, + }, + pac::uhci0, + peripherals, + system::{GenericPeripheralGuard, Peripheral}, + uart::{self, TxError, Uart, UartRx, UartTx}, +}; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Uhci specific errors +pub enum Error { + /// DMA originating error + Dma(DmaError), + /// UART Tx originating error + Tx(TxError), +} + +impl From for Error { + fn from(value: DmaError) -> Self { + Error::Dma(value) + } +} + +impl From for Error { + fn from(value: TxError) -> Self { + Error::Tx(value) + } +} + +// TODO: ESP32 and S2 have UHCI1. Revisit when adding support to PDMA devices. +crate::any_peripheral! { + pub peripheral AnyUhci<'d> { + Uhci0(crate::peripherals::UHCI0<'d>), + } +} + +impl<'d> DmaEligible for AnyUhci<'d> { + #[cfg(dma_kind = "gdma")] + type Dma = crate::dma::AnyGdmaChannel<'d>; + + fn dma_peripheral(&self) -> crate::dma::DmaPeripheral { + match &self.0 { + any::Inner::Uhci0(_) => crate::dma::DmaPeripheral::Uhci0, + } + } +} + +impl AnyUhci<'_> { + /// Opens the enum into the peripheral below + fn register_block(&self) -> &uhci0::RegisterBlock { + match &self.0 { + any::Inner::Uhci0(x) => x.register_block(), + } + } +} + +/// A configuration error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// chunk_limit is above 4095, this is not allowed (hardware limit) + AboveReadLimit, +} + +/// UHCI Rx Configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct RxConfig { + /// The limit of how much to read in a single read call. It cannot be higher than the dma + /// buffer size, otherwise uart/dma/uhci will freeze. It cannot exceed 4095 (12 bits), above + /// this value it will simply also split the readings + chunk_limit: u16, +} + +impl RxConfig { + pub(crate) fn apply_config(&self, reg: &uhci0::RegisterBlock) -> Result<(), ConfigError> { + // limit is 12 bits + // Above this value, it will probably split the messages, anyway, the point is below (the + // dma buffer length) it it will not freeze itself + if self.chunk_limit > 4095 { + return Err(ConfigError::AboveReadLimit); + } + reg.pkt_thres() + .write(|w| unsafe { w.bits(self.chunk_limit as u32) }); + + Ok(()) + } +} + +/// UHCI Tx Configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TxConfig { + /// If this is set to true UHCI will end the payload receiving process when UART has been in + /// idle state. + idle_eof: bool, + /// If this is set to true UHCI decoder receiving payload data ends when the receiving + /// byte count has reached the specified value (in len_eof). + /// If this is set to false UHCI decoder receiving payload data is end when 0xc0 is received. + len_eof: bool, +} + +impl TxConfig { + pub(crate) fn apply_config(&self, reg: &uhci0::RegisterBlock) -> Result<(), ConfigError> { + reg.conf0().modify(|_, w| { + w.uart_idle_eof_en().bit(self.idle_eof); + w.len_eof_en().bit(self.len_eof) + }); + + Ok(()) + } +} + +impl Default for RxConfig { + fn default() -> RxConfig { + RxConfig { + // This is the default in the register at boot, still should be changed! + chunk_limit: 128, + } + } +} + +impl Default for TxConfig { + fn default() -> TxConfig { + TxConfig { + // This is the default in the register at boot, still should be changed! + idle_eof: true, + len_eof: true, + } + } +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::AboveReadLimit => { + write!( + f, + "The requested read limit is not possible. The max is 4095 (12 bits)" + ) + } + } + } +} + +/// UHCI (To use with UART over DMA) +pub struct Uhci<'d, Dm> +where + Dm: DriverMode, +{ + uart: Uart<'d, Dm>, + /// Internal UHCI struct. Use it to configure the UHCI peripheral + uhci_per: AnyUhci<'static>, + channel: Channel>>, + // TODO: devices with UHCI1 need the non-generic guard + _guard: GenericPeripheralGuard<{ Peripheral::Uhci0 as u8 }>, +} + +impl<'d, Dm> Uhci<'d, Dm> +where + Dm: DriverMode, +{ + fn init(&self) { + self.clean_turn_on(); + self.reset(); + self.select_uart(); + } + + fn clean_turn_on(&self) { + // General conf registers + let reg = self.uhci_per.register_block(); + reg.conf0().modify(|_, w| w.clk_en().set_bit()); + reg.conf0().write(|w| { + unsafe { w.bits(0) }; + w.clk_en().set_bit() + }); + reg.conf1().modify(|_, w| unsafe { w.bits(0) }); + + // For TX + reg.escape_conf().modify(|_, w| unsafe { w.bits(0) }); + } + + fn reset(&self) { + let reg = self.uhci_per.register_block(); + reg.conf0().modify(|_, w| { + w.rx_rst().set_bit(); + w.tx_rst().set_bit() + }); + reg.conf0().modify(|_, w| { + w.rx_rst().clear_bit(); + w.tx_rst().clear_bit() + }); + } + + fn select_uart(&self) { + let reg = self.uhci_per.register_block(); + + for_each_uart! { + (all $( ($id:literal, $peri:ident, $variant:ident, $($pins:ident),*) ),*) => { + reg.conf0().modify(|_, w| { + cfg_if::cfg_if! { + if #[cfg(uhci_combined_uart_selector_field)] { + unsafe { + w.uart_sel().bits( + match &self.uart.tx.uart.0 { + $(super::any::Inner::$variant(_) => { + debug!("Uhci will use UART{}", stringify!($id)); + $id + })* + } + ) + } + } else { + paste::paste! { + // Clear any previous selection + $( + w.[< $peri:lower _ce >]().clear_bit(); + )* + + // Select UART + match &self.uart.tx.uart.0 { + $(super::any::Inner::$variant(_) => { + debug!("Uhci will use {}", stringify!($peri)); + w.[< $peri:lower _ce >]().set_bit() + })* + } + } + } + } + }); + }; + } + } + + /// Sets the config the the consumed UART + pub fn set_uart_config(&mut self, uart_config: &uart::Config) -> Result<(), uart::ConfigError> { + self.uart.set_config(uart_config) + } + + /// Split the Uhci into UhciRx and UhciTx + pub fn split(self) -> (UhciRx<'d, Dm>, UhciTx<'d, Dm>) { + let (uart_rx, uart_tx) = self.uart.split(); + ( + UhciRx { + uhci_per: unsafe { self.uhci_per.clone_unchecked() }, + uart_rx, + channel_rx: self.channel.rx, + _guard: self._guard.clone(), + }, + UhciTx { + uhci_per: self.uhci_per, + uart_tx, + channel_tx: self.channel.tx, + _guard: self._guard.clone(), + }, + ) + } + + /// Sets the config to the UHCI peripheral, Rx part + pub fn apply_rx_config(&mut self, config: &RxConfig) -> Result<(), ConfigError> { + let reg = self.uhci_per.register_block(); + config.apply_config(reg) + } + + /// Sets the config to the UHCI peripheral, Tx part + pub fn apply_tx_config(&mut self, config: &TxConfig) -> Result<(), ConfigError> { + let reg = self.uhci_per.register_block(); + config.apply_config(reg) + } +} + +impl<'d> Uhci<'d, Blocking> { + /// Creates a new instance of UHCI + pub fn new( + uart: Uart<'d, Blocking>, + uhci: peripherals::UHCI0<'static>, + channel: impl DmaChannelFor>, + ) -> Self { + let guard = GenericPeripheralGuard::new(); + + let channel = Channel::new(channel.degrade()); + channel.runtime_ensure_compatible(&uhci); + + let uhci = Uhci { + uart, + uhci_per: uhci.into(), + channel, + _guard: guard, + }; + + uhci.init(); + uhci + } + + /// Create a new instance in [crate::Async] mode. + pub fn into_async(self) -> Uhci<'d, Async> { + Uhci { + uart: self.uart.into_async(), + uhci_per: self.uhci_per, + channel: self.channel.into_async(), + _guard: self._guard, + } + } +} + +impl<'d> Uhci<'d, Async> { + /// Create a new instance in [crate::Blocking] mode. + pub fn into_blocking(self) -> Uhci<'d, Blocking> { + Uhci { + uart: self.uart.into_blocking(), + uhci_per: self.uhci_per, + channel: self.channel.into_blocking(), + _guard: self._guard, + } + } +} + +/// Splitted Uhci structs, Tx part for sending data +pub struct UhciTx<'d, Dm> +where + Dm: DriverMode, +{ + /// Internal UHCI struct. Use it to configure the UHCI peripheral + uhci_per: AnyUhci<'static>, + /// Tx of the used uart. You can configure it by accessing the value + pub uart_tx: UartTx<'d, Dm>, + channel_tx: ChannelTx>>, + // TODO: devices with UHCI1 need the non-generic guard + _guard: GenericPeripheralGuard<{ Peripheral::Uhci0 as u8 }>, +} + +impl<'d, Dm> UhciTx<'d, Dm> +where + Dm: DriverMode, +{ + /// Starts the write DMA transfer and returns the instance of UhciDmaTxTransfer + pub fn write( + mut self, + mut tx_buffer: Buf, + ) -> Result, (Error, Self, Buf)> { + let res = unsafe { + self.channel_tx + .prepare_transfer(self.uhci_per.dma_peripheral(), &mut tx_buffer) + }; + if let Err(err) = res { + return Err((err.into(), self, tx_buffer)); + } + + let res = self.channel_tx.start_transfer(); + if let Err(err) = res { + return Err((err.into(), self, tx_buffer)); + } + + Ok(UhciDmaTxTransfer::new(self, tx_buffer)) + } + + /// Sets the config to the UHCI peripheral + pub fn apply_config(&mut self, config: &TxConfig) -> Result<(), ConfigError> { + let reg = self.uhci_per.register_block(); + config.apply_config(reg) + } +} + +/// Splitted Uhci structs, Rx part for receiving data +pub struct UhciRx<'d, Dm> +where + Dm: DriverMode, +{ + /// Internal UHCI struct. Use it to configure the UHCI peripheral + uhci_per: AnyUhci<'static>, + /// Rx of the used uart. You can configure it by accessing the value + pub uart_rx: UartRx<'d, Dm>, + channel_rx: ChannelRx>>, + _guard: GenericPeripheralGuard<{ Peripheral::Uhci0 as u8 }>, +} + +impl<'d, Dm> UhciRx<'d, Dm> +where + Dm: DriverMode, +{ + /// Starts the read DMA transfer and returns the instance of UhciDmaRxTransfer + pub fn read( + mut self, + mut rx_buffer: Buf, + ) -> Result, (Error, Self, Buf)> { + { + let res = unsafe { + self.channel_rx + .prepare_transfer(self.uhci_per.dma_peripheral(), &mut rx_buffer) + }; + if let Err(err) = res { + return Err((err.into(), self, rx_buffer)); + } + + let res = self.channel_rx.start_transfer(); + if let Err(err) = res { + return Err((err.into(), self, rx_buffer)); + } + + Ok(UhciDmaRxTransfer::new(self, rx_buffer)) + } + } + + /// Sets the config to the UHCI peripheral + pub fn apply_config(&mut self, config: &RxConfig) -> Result<(), ConfigError> { + let reg = self.uhci_per.register_block(); + config.apply_config(reg) + } +} + +/// A structure representing a DMA transfer for UHCI/UART. +/// +/// This structure holds references to the UHCI instance, DMA buffers, and +/// transfer status. +pub struct UhciDmaTxTransfer<'d, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaTxBuffer, +{ + uhci: ManuallyDrop>, + dma_buf: ManuallyDrop, + done: bool, + saved_err: Result<(), Error>, +} + +impl<'d, Buf: DmaTxBuffer, Dm: DriverMode> UhciDmaTxTransfer<'d, Dm, Buf> { + fn new(uhci: UhciTx<'d, Dm>, dma_buf: Buf) -> Self { + Self { + uhci: ManuallyDrop::new(uhci), + dma_buf: ManuallyDrop::new(dma_buf.into_view()), + done: false, + saved_err: Ok(()), + } + } + + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.uhci.channel_tx.is_done() + } + + /// Cancels the DMA transfer. + pub fn cancel(mut self) -> (UhciTx<'d, Dm>, Buf::Final) { + self.uhci.channel_tx.stop_transfer(); + + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `Uhci` instance and the associated buffer. + pub fn wait(mut self) -> (Result<(), Error>, UhciTx<'d, Dm>, Buf::Final) { + if let Err(err) = self.saved_err { + return ( + Err(err), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + if !self.done { + let res = self.uhci.uart_tx.flush(); + if let Err(err) = res { + return ( + Err(err.into()), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + while !self.is_done() {} + } + + self.uhci.channel_tx.stop_transfer(); + let retval = unsafe { + ( + Result::<(), Error>::Ok(()), + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } +} + +impl<'d, Buf: DmaTxBuffer> UhciDmaTxTransfer<'d, Async, Buf> { + /// Waits for the DMA transfer to complete, but async. After that, you still need to wait() + pub async fn wait_for_done(&mut self) { + // Workaround for an issue when it doesn't actually wait for the transfer to complete. I'm + // lost at this point, this is the only thing that worked + let res = self.uhci.uart_tx.flush_async().await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + let res = DmaTxFuture::new(&mut self.uhci.channel_tx).await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + self.done = true; + } +} + +impl Deref for UhciDmaTxTransfer<'_, Dm, Buf> { + type Target = Buf::View; + + fn deref(&self) -> &Self::Target { + &self.dma_buf + } +} + +impl DerefMut for UhciDmaTxTransfer<'_, Dm, Buf> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.dma_buf + } +} + +impl Drop for UhciDmaTxTransfer<'_, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaTxBuffer, +{ + fn drop(&mut self) { + self.uhci.channel_tx.stop_transfer(); + + unsafe { + ManuallyDrop::drop(&mut self.uhci); + drop(Buf::from_view(ManuallyDrop::take(&mut self.dma_buf))); + } + } +} + +/// A structure representing a DMA transfer for UHCI/UART. +/// +/// This structure holds references to the UHCI instance, DMA buffers, and +/// transfer status. +pub struct UhciDmaRxTransfer<'d, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaRxBuffer, +{ + uhci: ManuallyDrop>, + dma_buf: ManuallyDrop, + done: bool, + saved_err: Result<(), Error>, +} + +impl<'d, Buf: DmaRxBuffer, Dm: DriverMode> UhciDmaRxTransfer<'d, Dm, Buf> { + fn new(uhci: UhciRx<'d, Dm>, dma_buf: Buf) -> Self { + Self { + uhci: ManuallyDrop::new(uhci), + dma_buf: ManuallyDrop::new(dma_buf.into_view()), + done: false, + saved_err: Ok(()), + } + } + + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.uhci.channel_rx.is_done() + } + + /// Cancels the DMA transfer. + pub fn cancel(mut self) -> (UhciRx<'d, Dm>, Buf::Final) { + self.uhci.channel_rx.stop_transfer(); + + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `Uhci` instance and the associated buffer. + pub fn wait(mut self) -> (Result<(), Error>, UhciRx<'d, Dm>, Buf::Final) { + if let Err(err) = self.saved_err { + return ( + Err(err), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + if !self.done { + while !self.is_done() {} + } + self.uhci.channel_rx.stop_transfer(); + + let retval = unsafe { + ( + Result::<(), Error>::Ok(()), + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } +} + +impl<'d, Buf: DmaRxBuffer> UhciDmaRxTransfer<'d, Async, Buf> { + /// Waits for the DMA transfer to complete, but async. After that, you still need to wait() + pub async fn wait_for_done(&mut self) { + let res = DmaRxFuture::new(&mut self.uhci.channel_rx).await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + self.done = true; + } +} + +impl Deref for UhciDmaRxTransfer<'_, Dm, Buf> { + type Target = Buf::View; + + fn deref(&self) -> &Self::Target { + &self.dma_buf + } +} + +impl DerefMut for UhciDmaRxTransfer<'_, Dm, Buf> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.dma_buf + } +} + +impl Drop for UhciDmaRxTransfer<'_, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaRxBuffer, +{ + fn drop(&mut self) { + self.uhci.channel_rx.stop_transfer(); + + unsafe { + ManuallyDrop::drop(&mut self.uhci); + drop(Buf::from_view(ManuallyDrop::take(&mut self.dma_buf))); + } + } +} + +impl embassy_embedded_hal::SetConfig for Uhci<'_, Dm> +where + Dm: DriverMode, +{ + type Config = (RxConfig, TxConfig); + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_rx_config(&config.0)?; + self.apply_tx_config(&config.1) + } +} + +impl embassy_embedded_hal::SetConfig for UhciRx<'_, Dm> +where + Dm: DriverMode, +{ + type Config = RxConfig; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +impl embassy_embedded_hal::SetConfig for UhciTx<'_, Dm> +where + Dm: DriverMode, +{ + type Config = TxConfig; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} diff --git a/esp-hal/src/usb_serial_jtag.rs b/esp-hal/src/usb_serial_jtag.rs new file mode 100644 index 00000000000..2de0bcd1444 --- /dev/null +++ b/esp-hal/src/usb_serial_jtag.rs @@ -0,0 +1,955 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! USB Serial/JTAG Controller (USB_SERIAL_JTAG) +//! +//! ## Overview +//! +//! The USB Serial/JTAG controller can be used to program the SoC's flash, read +//! program output, or attach a debugger to the running program. This is +//! possible for any computer with a USB host (hereafter referred to as 'host'), +//! without any active external components. +//! +//! This peripheral integrates the functionality of both a USB-to-serial +//! converter as well as a USB-to-JTAG adapter. As this device directly +//! interfaces with an external USB host using only the two data lines required +//! by USB 2.0, only two pins are required to be dedicated to this functionality +//! for debugging. +//! +//! The USB Serial/JTAG controller boasts the following features: +//! +//! - Hardwired for CDC-ACM (Communication Device Class - Abstract Control Model) and JTAG adapter +//! functionality +//! - Integrates CDC-ACM adherent serial port emulation (plug-and-play on most modern OSes); +//! supports host controllable chip reset and entry into download mode +//! - Allows fast communication with CPU debugging core using a compact representation of JTAG +//! instructions +//! - Two OUT Endpoints and three IN Endpoints in addition to Control Endpoint 0; Up to 64-byte data +//! payload size +//! - Internal PHY means that very few or no external components needed to connect to a host +//! computer +//! +//! ## Usage +//! +//! The USB Serial/JTAG driver implements a number of third-party traits, with +//! the intention of making the HAL inter-compatible with various device drivers +//! from the community. This includes, but is not limited to, the [embedded-hal] +//! and [embedded-io] blocking traits, and the [embedded-hal-async] +//! and [embedded-io-async] asynchronous traits. +//! +//! In addition to the interfaces provided by these traits, native APIs are also +//! available. See the examples below for more information on how to interact +//! with this driver. +//! +//! ## Examples +//! +//! ### Sending and Receiving Data +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::usb_serial_jtag::UsbSerialJtag; +//! +//! let mut usb_serial = UsbSerialJtag::new(peripherals.USB_DEVICE); +//! +//! // Write bytes out over the USB Serial/JTAG: +//! usb_serial.write(b"Hello, world!")?; +//! # {after_snippet} +//! ``` +//! +//! ### Splitting the USB Serial/JTAG into TX and RX Components +//! ```rust, no_run +//! # {before_snippet} +//! use esp_hal::usb_serial_jtag::UsbSerialJtag; +//! +//! let mut usb_serial = UsbSerialJtag::new(peripherals.USB_DEVICE); +//! // The USB Serial/JTAG can be split into separate Transmit and Receive +//! // components: +//! let (mut rx, mut tx) = usb_serial.split(); +//! +//! // Each component can be used individually to interact with the USB +//! // Serial/JTAG: +//! tx.write(&[42u8])?; +//! let byte = rx.read_byte()?; +//! # {after_snippet} +//! ``` +//! +//! ### How to output text using USB Serial/JTAG. +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::{delay::Delay, usb_serial_jtag::UsbSerialJtag, Blocking}; +//! +//! let delay = Delay::new(); +//! +//! let mut usb_serial = UsbSerialJtag::new(peripherals.USB_DEVICE); +//! usb_serial.set_interrupt_handler(usb_device); +//! usb_serial.listen_rx_packet_recv_interrupt(); +//! +//! critical_section::with(|cs| USB_SERIAL.borrow_ref_mut(cs).replace(usb_serial)); +//! +//! loop { +//! println!("Send keystrokes to see the interrupt trigger"); +//! delay.delay(Duration::from_secs(1)); +//! } +//! # } +//! +//! # use critical_section::Mutex; +//! # use core::{cell::RefCell, fmt::Write}; +//! # use esp_hal::usb_serial_jtag::UsbSerialJtag; +//! static USB_SERIAL: Mutex>>> = +//! Mutex::new(RefCell::new(None)); +//! +//! #[esp_hal::handler] +//! fn usb_device() { +//! critical_section::with(|cs| { +//! let mut usb_serial = USB_SERIAL.borrow_ref_mut(cs); +//! if let Some(usb_serial) = usb_serial.as_mut() { +//! println!("USB serial interrupt"); +//! +//! while let nb::Result::Ok(c) = usb_serial.read_byte() { +//! println!("Read byte: {:02x}", c); +//! } +//! +//! usb_serial.reset_rx_packet_recv_interrupt(); +//! } +//! }); +//! } +//! ``` +//! +//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ +//! [embedded-io]: https://docs.rs/embedded-io/latest/embedded_io/ +//! [embedded-hal-async]: https://docs.rs/embedded-hal-async/latest/embedded_hal_async/ +//! [embedded-io-async]: https://docs.rs/embedded-io-async/latest/embedded_io_async/ + +#[instability::unstable] +use core::task::Poll; +use core::{convert::Infallible, marker::PhantomData}; + +use procmacros::handler; + +use crate::{ + Async, + Blocking, + DriverMode, + asynch::AtomicWaker, + pac::usb_device::RegisterBlock, + peripherals::USB_DEVICE, + system::PeripheralClockControl, +}; + +/// Custom USB serial error type +type Error = Infallible; + +/// USB Serial/JTAG (Full-duplex) +pub struct UsbSerialJtag<'d, Dm: DriverMode> { + rx: UsbSerialJtagRx<'d, Dm>, + tx: UsbSerialJtagTx<'d, Dm>, +} + +/// USB Serial/JTAG (Transmit) +pub struct UsbSerialJtagTx<'d, Dm: DriverMode> { + peripheral: USB_DEVICE<'d>, + phantom: PhantomData, +} + +/// USB Serial/JTAG (Receive) +pub struct UsbSerialJtagRx<'d, Dm: DriverMode> { + peripheral: USB_DEVICE<'d>, + phantom: PhantomData, +} + +impl<'d, Dm> UsbSerialJtagTx<'d, Dm> +where + Dm: DriverMode, +{ + fn new_inner(peripheral: USB_DEVICE<'d>) -> Self { + Self { + peripheral, + phantom: PhantomData, + } + } + + fn regs(&self) -> &RegisterBlock { + self.peripheral.register_block() + } + + /// Write data to the serial output in chunks of up to 64 bytes + pub fn write(&mut self, data: &[u8]) -> Result<(), Error> { + for chunk in data.chunks(64) { + for byte in chunk { + self.regs() + .ep1() + .write(|w| unsafe { w.rdwr_byte().bits(*byte) }); + } + self.regs().ep1_conf().modify(|_, w| w.wr_done().set_bit()); + + // FIXME: raw register access + while self.regs().ep1_conf().read().bits() & 0b011 == 0b000 { + // wait + } + } + + Ok(()) + } + + /// Write data to the serial output in a non-blocking manner + /// Requires manual flushing (automatically flushed every 64 bytes) + pub fn write_byte_nb(&mut self, word: u8) -> nb::Result<(), Error> { + if self + .regs() + .ep1_conf() + .read() + .serial_in_ep_data_free() + .bit_is_set() + { + // the FIFO is not full + unsafe { self.regs().ep1().write(|w| w.rdwr_byte().bits(word)) }; + + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + /// Flush the output FIFO and block until it has been sent + pub fn flush_tx(&mut self) -> Result<(), Error> { + self.regs().ep1_conf().modify(|_, w| w.wr_done().set_bit()); + + // FIXME: raw register access + while self.regs().ep1_conf().read().bits() & 0b011 == 0b000 { + // wait + } + + Ok(()) + } + + /// Flush the output FIFO but don't block if it isn't ready immediately + pub fn flush_tx_nb(&mut self) -> nb::Result<(), Error> { + self.regs().ep1_conf().modify(|_, w| w.wr_done().set_bit()); + + // FIXME: raw register access + if self.regs().ep1_conf().read().bits() & 0b011 == 0b000 { + Err(nb::Error::WouldBlock) + } else { + Ok(()) + } + } +} + +impl<'d, Dm> UsbSerialJtagRx<'d, Dm> +where + Dm: DriverMode, +{ + fn new_inner(peripheral: USB_DEVICE<'d>) -> Self { + Self { + peripheral, + phantom: PhantomData, + } + } + + fn regs(&self) -> &RegisterBlock { + self.peripheral.register_block() + } + + /// Read a byte from the UART in a non-blocking manner + pub fn read_byte(&mut self) -> nb::Result { + // Check if there are any bytes to read + if self + .regs() + .ep1_conf() + .read() + .serial_out_ep_data_avail() + .bit_is_set() + { + let value = self.regs().ep1().read().rdwr_byte().bits(); + + Ok(value) + } else { + Err(nb::Error::WouldBlock) + } + } + + /// Read all available bytes from the RX FIFO into the provided buffer and + /// returns the number of read bytes. Never blocks. May stop early if the + /// number of bytes in the FIFO is larger than `buf`. + pub fn drain_rx_fifo(&mut self, buf: &mut [u8]) -> usize { + let mut count = 0; + while let Ok(value) = self.read_byte() { + buf[count] = value; + count += 1; + if count == buf.len() { + break; + } + } + count + } + + /// Listen for RX-PACKET-RECV interrupts + pub fn listen_rx_packet_recv_interrupt(&mut self) { + self.regs() + .int_ena() + .modify(|_, w| w.serial_out_recv_pkt().set_bit()); + } + + /// Stop listening for RX-PACKET-RECV interrupts + pub fn unlisten_rx_packet_recv_interrupt(&mut self) { + self.regs() + .int_ena() + .modify(|_, w| w.serial_out_recv_pkt().clear_bit()); + } + + /// Checks if RX-PACKET-RECV interrupt is set + pub fn rx_packet_recv_interrupt_set(&mut self) -> bool { + self.regs() + .int_st() + .read() + .serial_out_recv_pkt() + .bit_is_set() + } + + /// Reset RX-PACKET-RECV interrupt + pub fn reset_rx_packet_recv_interrupt(&mut self) { + self.regs() + .int_clr() + .write(|w| w.serial_out_recv_pkt().clear_bit_by_one()); + } +} + +impl<'d> UsbSerialJtag<'d, Blocking> { + /// Create a new USB serial/JTAG instance with defaults + pub fn new(usb_device: USB_DEVICE<'d>) -> Self { + Self::new_inner(usb_device) + } + + /// Reconfigure the USB Serial JTAG peripheral to operate in asynchronous + /// mode. + pub fn into_async(mut self) -> UsbSerialJtag<'d, Async> { + self.set_interrupt_handler(async_interrupt_handler); + + UsbSerialJtag { + rx: UsbSerialJtagRx { + peripheral: self.rx.peripheral, + phantom: PhantomData, + }, + tx: UsbSerialJtagTx { + peripheral: self.tx.peripheral, + phantom: PhantomData, + }, + } + } +} + +impl crate::private::Sealed for UsbSerialJtag<'_, Blocking> {} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for UsbSerialJtag<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +impl<'d, Dm> UsbSerialJtag<'d, Dm> +where + Dm: DriverMode, +{ + fn new_inner(usb_device: USB_DEVICE<'d>) -> Self { + // Do NOT reset the peripheral. Doing so will result in a broken USB JTAG + // connection. + PeripheralClockControl::enable(crate::system::Peripheral::UsbDevice); + + usb_device.disable_tx_interrupts(); + usb_device.disable_rx_interrupts(); + + #[cfg(any(esp32c3, esp32s3))] + { + use crate::efuse::USB_EXCHG_PINS; + + // On the esp32c3, and esp32s3 the USB_EXCHG_PINS efuse is bugged and + // doesn't swap the pullups too, this works around that. + if crate::efuse::read_bit(USB_EXCHG_PINS) { + usb_device.register_block().conf0().modify(|_, w| { + w.pad_pull_override().set_bit(); + w.dm_pullup().clear_bit(); + w.dp_pullup().set_bit() + }); + } + } + + Self { + rx: UsbSerialJtagRx::new_inner(unsafe { usb_device.clone_unchecked() }), + tx: UsbSerialJtagTx::new_inner(usb_device), + } + } + /// Split the USB Serial JTAG peripheral into a transmitter and receiver, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split(self) -> (UsbSerialJtagRx<'d, Dm>, UsbSerialJtagTx<'d, Dm>) { + (self.rx, self.tx) + } + + /// Write data to the serial output in chunks of up to 64 bytes + pub fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.tx.write(data) + } + + /// Write data to the serial output in a non-blocking manner + /// Requires manual flushing (automatically flushed every 64 bytes) + pub fn write_byte_nb(&mut self, word: u8) -> nb::Result<(), Error> { + self.tx.write_byte_nb(word) + } + + /// Flush the output FIFO and block until it has been sent + pub fn flush_tx(&mut self) -> Result<(), Error> { + self.tx.flush_tx() + } + + /// Flush the output FIFO but don't block if it isn't ready immediately + pub fn flush_tx_nb(&mut self) -> nb::Result<(), Error> { + self.tx.flush_tx_nb() + } + + /// Read a single byte but don't block if it isn't ready immediately + pub fn read_byte(&mut self) -> nb::Result { + self.rx.read_byte() + } + + /// Listen for RX-PACKET-RECV interrupts + pub fn listen_rx_packet_recv_interrupt(&mut self) { + self.rx.listen_rx_packet_recv_interrupt() + } + + /// Stop listening for RX-PACKET-RECV interrupts + pub fn unlisten_rx_packet_recv_interrupt(&mut self) { + self.rx.unlisten_rx_packet_recv_interrupt() + } + + /// Checks if RX-PACKET-RECV interrupt is set + pub fn rx_packet_recv_interrupt_set(&mut self) -> bool { + self.rx.rx_packet_recv_interrupt_set() + } + + /// Reset RX-PACKET-RECV interrupt + pub fn reset_rx_packet_recv_interrupt(&mut self) { + self.rx.reset_rx_packet_recv_interrupt() + } + + /// Registers an interrupt handler for the USB Serial JTAG peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + self.rx.peripheral.disable_peri_interrupt_on_all_cores(); + self.rx.peripheral.bind_peri_interrupt(handler); + } +} + +/// USB Serial/JTAG peripheral instance +#[doc(hidden)] +pub trait Instance: crate::private::Sealed { + /// Get a reference to the peripheral's underlying register block + fn register_block(&self) -> &RegisterBlock; + + /// Disable all transmit interrupts for the peripheral + fn disable_tx_interrupts(&self) { + self.register_block() + .int_ena() + .modify(|_, w| w.serial_in_empty().clear_bit()); + + self.register_block() + .int_clr() + .write(|w| w.serial_in_empty().clear_bit_by_one()); + } + + /// Disable all receive interrupts for the peripheral + fn disable_rx_interrupts(&self) { + self.register_block() + .int_ena() + .modify(|_, w| w.serial_out_recv_pkt().clear_bit()); + + self.register_block() + .int_clr() + .write(|w| w.serial_out_recv_pkt().clear_bit_by_one()); + } +} + +impl Instance for USB_DEVICE<'_> { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + USB_DEVICE::regs() + } +} + +impl core::fmt::Write for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + core::fmt::Write::write_str(&mut self.tx, s) + } +} + +impl core::fmt::Write for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write(s.as_bytes()).map_err(|_| core::fmt::Error)?; + Ok(()) + } +} + +#[instability::unstable] +impl ufmt_write::uWrite for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; + + #[inline] + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + ufmt_write::uWrite::write_str(&mut self.tx, s) + } + + #[inline] + fn write_char(&mut self, ch: char) -> Result<(), Self::Error> { + ufmt_write::uWrite::write_char(&mut self.tx, ch) + } +} + +#[instability::unstable] +impl ufmt_write::uWrite for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; + + #[inline] + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.write(s.as_bytes())?; + Ok(()) + } + + #[inline] + fn write_char(&mut self, ch: char) -> Result<(), Self::Error> { + let mut buffer = [0u8; 4]; + self.write(ch.encode_utf8(&mut buffer).as_bytes())?; + + Ok(()) + } +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_06::ErrorType for UsbSerialJtagRx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_06::Read for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + embedded_io_06::Read::read(&mut self.rx, buf) + } +} + +#[instability::unstable] +impl embedded_io_06::Read for UsbSerialJtagRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + loop { + let count = self.drain_rx_fifo(buf); + if count > 0 { + return Ok(count); + } + } + } +} + +#[instability::unstable] +impl embedded_io_06::Write for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + embedded_io_06::Write::write(&mut self.tx, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + embedded_io_06::Write::flush(&mut self.tx) + } +} + +#[instability::unstable] +impl embedded_io_06::Write for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf)?; + + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx() + } +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_07::ErrorType for UsbSerialJtagRx<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl embedded_io_07::Read for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + embedded_io_07::Read::read(&mut self.rx, buf) + } +} + +#[instability::unstable] +impl embedded_io_07::Read for UsbSerialJtagRx<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + loop { + let count = self.drain_rx_fifo(buf); + if count > 0 { + return Ok(count); + } + } + } +} + +#[instability::unstable] +impl embedded_io_07::Write for UsbSerialJtag<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + embedded_io_07::Write::write(&mut self.tx, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + embedded_io_07::Write::flush(&mut self.tx) + } +} + +#[instability::unstable] +impl embedded_io_07::Write for UsbSerialJtagTx<'_, Dm> +where + Dm: DriverMode, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf)?; + + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx() + } +} + +// Static instance of the waker for each component of the peripheral: +static WAKER_TX: AtomicWaker = AtomicWaker::new(); +static WAKER_RX: AtomicWaker = AtomicWaker::new(); + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct UsbSerialJtagWriteFuture<'d> { + peripheral: USB_DEVICE<'d>, +} + +impl<'d> UsbSerialJtagWriteFuture<'d> { + fn new(peripheral: USB_DEVICE<'d>) -> Self { + // Set the interrupt enable bit for the USB_SERIAL_JTAG_SERIAL_IN_EMPTY_INT + // interrupt + peripheral + .register_block() + .int_ena() + .modify(|_, w| w.serial_in_empty().set_bit()); + + Self { peripheral } + } + + fn event_bit_is_clear(&self) -> bool { + self.peripheral + .register_block() + .int_ena() + .read() + .serial_in_empty() + .bit_is_clear() + } +} + +impl core::future::Future for UsbSerialJtagWriteFuture<'_> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + WAKER_TX.register(cx.waker()); + if self.event_bit_is_clear() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct UsbSerialJtagReadFuture<'d> { + peripheral: USB_DEVICE<'d>, +} + +impl<'d> UsbSerialJtagReadFuture<'d> { + fn new(peripheral: USB_DEVICE<'d>) -> Self { + // Set the interrupt enable bit for the USB_SERIAL_JTAG_SERIAL_OUT_RECV_PKT + // interrupt + peripheral + .register_block() + .int_ena() + .modify(|_, w| w.serial_out_recv_pkt().set_bit()); + + Self { peripheral } + } + + fn event_bit_is_clear(&self) -> bool { + self.peripheral + .register_block() + .int_ena() + .read() + .serial_out_recv_pkt() + .bit_is_clear() + } +} + +impl core::future::Future for UsbSerialJtagReadFuture<'_> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + WAKER_RX.register(cx.waker()); + if self.event_bit_is_clear() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl<'d> UsbSerialJtag<'d, Async> { + /// Reconfigure the USB Serial JTAG peripheral to operate in blocking + /// mode. + pub fn into_blocking(self) -> UsbSerialJtag<'d, Blocking> { + self.rx.peripheral.disable_peri_interrupt_on_all_cores(); + UsbSerialJtag { + rx: UsbSerialJtagRx { + peripheral: self.rx.peripheral, + phantom: PhantomData, + }, + tx: UsbSerialJtagTx { + peripheral: self.tx.peripheral, + phantom: PhantomData, + }, + } + } +} + +impl UsbSerialJtagTx<'_, Async> { + async fn write_async(&mut self, words: &[u8]) -> Result<(), Error> { + for chunk in words.chunks(64) { + for byte in chunk { + self.regs() + .ep1() + .write(|w| unsafe { w.rdwr_byte().bits(*byte) }); + } + self.regs().ep1_conf().modify(|_, w| w.wr_done().set_bit()); + + UsbSerialJtagWriteFuture::new(self.peripheral.reborrow()).await; + } + + Ok(()) + } + + async fn flush_tx_async(&mut self) -> Result<(), Error> { + if self + .regs() + .ep1_conf() + .read() + .serial_in_ep_data_free() + .bit_is_clear() + { + UsbSerialJtagWriteFuture::new(self.peripheral.reborrow()).await; + } + + Ok(()) + } +} + +impl UsbSerialJtagRx<'_, Async> { + async fn read_async(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + loop { + let read = self.drain_rx_fifo(buf); + if read > 0 { + return Ok(read); + } + UsbSerialJtagReadFuture::new(self.peripheral.reborrow()).await; + } + } +} + +#[instability::unstable] +impl embedded_io_async_06::Write for UsbSerialJtag<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + embedded_io_async_06::Write::write(&mut self.tx, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + embedded_io_async_06::Write::flush(&mut self.tx).await + } +} + +#[instability::unstable] +impl embedded_io_async_06::Write for UsbSerialJtagTx<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await?; + + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx_async().await + } +} + +#[instability::unstable] +impl embedded_io_async_06::Read for UsbSerialJtag<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + embedded_io_async_06::Read::read(&mut self.rx, buf).await + } +} + +#[instability::unstable] +impl embedded_io_async_06::Read for UsbSerialJtagRx<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await + } +} +#[instability::unstable] +impl embedded_io_async_07::Write for UsbSerialJtag<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + embedded_io_async_07::Write::write(&mut self.tx, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + embedded_io_async_07::Write::flush(&mut self.tx).await + } +} + +#[instability::unstable] +impl embedded_io_async_07::Write for UsbSerialJtagTx<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await?; + + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx_async().await + } +} + +#[instability::unstable] +impl embedded_io_async_07::Read for UsbSerialJtag<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + embedded_io_async_07::Read::read(&mut self.rx, buf).await + } +} + +#[instability::unstable] +impl embedded_io_async_07::Read for UsbSerialJtagRx<'_, Async> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await + } +} + +#[handler] +fn async_interrupt_handler() { + let usb = USB_DEVICE::regs(); + let interrupts = usb.int_st().read(); + + let tx = interrupts.serial_in_empty().bit_is_set(); + let rx = interrupts.serial_out_recv_pkt().bit_is_set(); + + if tx { + usb.int_ena().modify(|_, w| w.serial_in_empty().clear_bit()); + } + if rx { + usb.int_ena() + .modify(|_, w| w.serial_out_recv_pkt().clear_bit()); + } + + usb.int_clr().write(|w| { + w.serial_in_empty() + .clear_bit_by_one() + .serial_out_recv_pkt() + .clear_bit_by_one() + }); + + if rx { + WAKER_RX.wake(); + } + if tx { + WAKER_TX.wake(); + } +} diff --git a/esp-hal/src/work_queue.rs b/esp-hal/src/work_queue.rs new file mode 100644 index 00000000000..894331efc6a --- /dev/null +++ b/esp-hal/src/work_queue.rs @@ -0,0 +1,742 @@ +//! A generic work queue. +//! +//! Work queues are the backbone of cryptographic drivers. They enable asynchronous and +//! blocking operations using shared peripherals, like crypto accelerators. Clients +//! post work items into the work queue, and poll for completion. The act of polling +//! processes the queue, which allows any number of clients to post work without deadlocking. +//! +//! Work queues are configured by backends. Backends register a `process` callback which is +//! called when a client posts a work item or polls for completion. +//! +//! Posting a work item into the queue returns a handle. The handle can be used to poll whether +//! the work item has been processed. Dropping the handle will cancel the work item. +#![cfg_attr(not(feature = "unstable"), allow(unused))] + +use core::{future::poll_fn, marker::PhantomData, ptr::NonNull, task::Context}; + +use embassy_sync::waitqueue::WakerRegistration; +use esp_sync::NonReentrantMutex; + +/// Queue driver operations. +/// +/// Functions in this VTable are provided by drivers that consume work items. +/// These functions may be called both in the context of the queue frontends and drivers. +pub(crate) struct VTable { + /// Starts processing a new work item. + /// + /// The function returns whether the work item was accepted, and its poll status if it was + /// accepted. If there is no driver currently processing the queue, this function will + /// return None to prevent removing the work item from the queue. + /// + /// This function should be as short as possible. + pub(crate) post: fn(NonNull<()>, &mut T) -> Option, + + /// Polls the status of the current work item. + /// + /// The work queue ensures that the item passed here has been first passed to the driver by + /// `post`. + /// + /// This function should be as short as possible. + pub(crate) poll: fn(NonNull<()>, &mut T) -> Poll, + + /// Attempts to abort processing a work item. + /// + /// This function should be as short as possible. + pub(crate) cancel: fn(NonNull<()>, &mut T), + + /// Called when the driver may be stopped. + /// + /// This function should be as short as possible. + pub(crate) stop: fn(NonNull<()>), +} + +impl VTable { + pub(crate) const fn noop() -> Self { + Self { + post: |_, _| None, + poll: |_, _| unreachable!(), + cancel: |_, _| (), + stop: |_| (), + } + } +} + +struct Inner { + head: Option>>, + tail: Option>>, + current: Option>>, + + // The data pointer will be passed to VTable functions, which may be called in any context. + data: NonNull<()>, + vtable: VTable, + + // Counts suspend requests. When this reaches 0 again, the all wakers in the queue need to be + // waken to continue processing. + suspend_count: usize, + // The task waiting for the queue to be suspended. There can be multiple tasks, but that's + // practically rare (in this setup, it needs both HMAC and DSA to want to work at the same + // time). + suspend_waker: WakerRegistration, +} + +unsafe impl Send for Inner {} +unsafe impl Sync for Inner {} + +impl Inner { + /// Places a work item at the end of the queue. + fn enqueue(&mut self, ptr: NonNull>) { + if let Some(tail) = self.tail.as_mut() { + // Queue contains something, append to `tail`. + unsafe { tail.as_mut().next = Some(ptr) }; + } else { + // Queue was empty, set `head` to the first element. + self.head = Some(ptr); + } + + // Move `tail` to the newly inserted item. + self.tail = Some(ptr); + } + + /// Places a work item at the front of the queue. + fn enqueue_front(&mut self, mut ptr: NonNull>) { + // Chain the node into the list. + unsafe { ptr.as_mut().next = self.head }; + + // Adjust list `head` to point at the new-first element. + self.head = Some(ptr); + if self.tail.is_none() { + // The queue was empty, we need to set `tail` to the last element. + self.tail = Some(ptr); + } + } + + /// Runs one processing iteration. + /// + /// This function enqueues a new work item or polls the status of the currently processed one. + /// Returns whether the function should be re-called by the caller. + fn process(&mut self) -> bool { + if let Some(mut current) = self.current { + let poll_result = (self.vtable.poll)(self.data, &mut unsafe { current.as_mut() }.data); + + match poll_result { + Poll::Ready(status) => { + unsafe { current.as_mut() }.complete(status); + self.current = None; + if self.suspend_count > 0 { + // Queue suspended, stop the driver. + (self.vtable.stop)(self.data); + self.suspend_waker.wake(); + false + } else { + self.dequeue_and_post(true) + } + } + Poll::Pending(recall) => recall, + } + } else { + // If the queue is empty, the driver should already have been notified when the queue + // became empty, so we don't notify it here. + self.dequeue_and_post(false) + } + } + + /// Retrieves the next work queue item and sends it to the driver. + /// + /// Returns true if the queue needs to be polled again. + // Note: even if the queue itself may be implemented lock-free, dequeuing and posting to the + // driver must be done atomically to ensure that the queue can be processed fully by any of + // the frontends polling it. + fn dequeue_and_post(&mut self, notify_on_empty: bool) -> bool { + let Some(mut ptr) = self.dequeue() else { + if notify_on_empty { + // There are no more work items. Notify the driver that it can stop. + (self.vtable.stop)(self.data); + } + return false; + }; + + // Start processing a new work item. + + if let Some(poll_status) = (self.vtable.post)(self.data, &mut unsafe { ptr.as_mut() }.data) + { + match poll_status { + Poll::Pending(recall) => { + unsafe { ptr.as_mut().status = Poll::Pending(recall) }; + self.current = Some(ptr); + recall + } + Poll::Ready(status) => { + unsafe { ptr.as_mut() }.complete(status); + // The driver immediately processed the work item. + // Polling again needs to dequeue the next item. + true + } + } + } else { + // If the driver didn't accept the work item, place it back to the front of the + // queue. + self.enqueue_front(ptr); + false + } + } + + /// Pops and returns a work item from the start of the queue. + fn dequeue(&mut self) -> Option>> { + // If the `head` is None, the queue is empty. Return None and do nothing. + let ptr = self.head?; + + self.head = unsafe { ptr.as_ref() }.next; + + // If the new `head` is null, the queue is empty. Clear the `tail` pointer. + if self.head.is_none() { + self.tail = None; + } + + Some(ptr) + } + + /// Cancels a particular work item. + /// + /// If the work item is currently being processed, this function notifies the driver. Otherwise, + /// it tries to remove the pointer from the work queue. + /// + /// The function returns true when the item was immediately cancelled. + /// + /// This function is not `unsafe` because it only dereferences `work_item` if the function has + /// determined that the item belongs to this queue. + fn cancel(&mut self, mut work_item: NonNull>) -> bool { + if self.current == Some(work_item) { + // Cancelling an in-progress item is more complicated than plucking it from the + // queue. Forward the request to the driver to (maybe) cancel the + // operation. + (self.vtable.cancel)( + self.data, + // This is safe to do, because the work item is currently owned by this queue. + &mut unsafe { work_item.as_mut() }.data, + ); + // Queue will need to be polled to query item status. + return false; + } + + if unsafe { work_item.as_ref() }.status.is_ready() { + // Nothing to do. + return true; + } + + // The work item is not the current one, remove it from the queue. This immediately + // cancels the work item. `remove` only uses the address of the work item without + // dereferencing it. + if self.remove(work_item) { + unsafe { work_item.as_mut() }.complete(Status::Cancelled); + // Cancelled immediately, no further polling necessary for this item. + return true; + } + + // In this case the item doesn't belong to this queue, it can be in any state. The item will + // need to be polled, but this also means something may have gone wrong. + false + } + + /// Removes the item from the queue. + /// + /// Returns `true` if the work item was successfully removed, `false` if the work item was not + /// found in the queue. + /// + /// This function is not `unsafe` because it does not dereference `ptr`, so it does not matter + /// that `ptr` may belong to a different work queue. + fn remove(&mut self, ptr: NonNull>) -> bool { + // Walk the queue to find `ptr`. + let mut prev = None; + let mut current = self.head; + while let Some(current_item) = current { + let next = unsafe { current_item.as_ref() }.next; + + if current_item != ptr { + // Not what we're looking for. Move to the next element. + prev = current; + current = next; + continue; + } + + // We've found `ptr`. Remove it from the list. + if Some(ptr) == self.head { + self.head = next; + } else { + // Unwrapping is fine, because if the current pointer is not the `head`, the + // previous pointer must be Some. + unsafe { unwrap!(prev).as_mut() }.next = next; + } + + if Some(ptr) == self.tail { + self.tail = prev; + } + + return true; + } + + // Did not find `ptr`. + false + } + + /// Increases the suspend counter, preventing new work items from starting to be processed. + /// + /// If the current work item finishes processing, the driver is shut down. Call `is_active` to + /// determine when the queue enters suspended state. + fn suspend(&mut self, ctx: Option<&Context<'_>>) { + self.suspend_count += 1; + if let Some(ctx) = ctx { + if self.current.is_some() { + self.suspend_waker.register(ctx.waker()); + } else { + ctx.waker().wake_by_ref(); + } + } + } + + /// Decreases the suspend counter. + /// + /// When it reaches 0, this function wakes async tasks that poll the queue. They need to be + /// waken to ensure that their items don't end up stuck. Blocking pollers will eventually end up + /// looping when their turn comes. + fn resume(&mut self) { + self.suspend_count -= 1; + if self.suspend_count == 0 { + self.wake_polling_tasks(); + } + } + + fn wake_polling_tasks(&mut self) { + if self.data == NonNull::dangling() { + // No VTable means no driver, no need to continue processing. + return; + } + // Walk through the list and wake polling tasks. + let mut current = self.head; + while let Some(mut current_item) = current { + let item = unsafe { current_item.as_mut() }; + + item.waker.wake(); + + current = item.next; + } + } + + fn is_active(&self) -> bool { + self.current.is_some() + } + + unsafe fn configure(&mut self, data: NonNull<()>, vtable: VTable) { + (self.vtable.stop)(self.data); + + self.data = data; + self.vtable = vtable; + + if self.suspend_count == 0 { + self.wake_polling_tasks(); + } + } +} + +/// A generic work queue. +pub(crate) struct WorkQueue { + inner: NonReentrantMutex>, +} + +impl WorkQueue { + /// Creates a new `WorkQueue`. + pub const fn new() -> Self { + Self { + inner: NonReentrantMutex::new(Inner { + head: None, + tail: None, + current: None, + + data: NonNull::dangling(), + vtable: VTable::noop(), + + suspend_count: 0, + suspend_waker: WakerRegistration::new(), + }), + } + } + + /// Configures the queue. + /// + /// The provided data pointer will be passed to the VTable functions. + /// + /// # Safety + /// + /// The `data` pointer must be valid as long as the `WorkQueue` is configured with it. The + /// driver must access the data pointer appropriately (i.e. it must not move !Send data out of + /// it). + pub unsafe fn configure(&self, data: NonNull, vtable: VTable) { + self.inner + .with(|inner| unsafe { inner.configure(data.cast(), vtable) }) + } + + /// Enqueues a work item. + pub fn post_work<'t>(&'t self, work_item: &'t mut WorkItem) -> Handle<'t, T> { + let ptr = unsafe { + // Safety: `Handle` and `work_item` have lifetime 't, which ensures this call + // can't be called on an in-flight work item. As `Handle` (and the underlying driver + // that processed the work queue) does not use the reference, and using the + // reference is not possible while `Handle` exists, this should be safe. + work_item.prepare() + }; + + self.inner.with(|inner| inner.enqueue(ptr)); + + Handle { + queue: self, + work_item: ptr, + _marker: PhantomData, + } + } + + /// Polls the queue once. + /// + /// Returns true if the queue needs to be polled again. + #[allow(unused)] + pub fn process(&self) -> bool { + self.inner.with(|inner| inner.process()) + } + + /// Polls the queue once and returns the status of the given work item. + /// + /// ## Safety + /// + /// The caller must ensure that `item` belongs to the polled queue. An item belongs to the + /// **last queue it was enqueued in**, even if the item is no longer in the queue's linked + /// list. This relationship is broken when the Handle that owns the WorkItem is dropped. + pub unsafe fn poll(&self, item: NonNull>) -> Poll { + self.inner.with(|inner| { + let status = unsafe { &*item.as_ptr() }.status; + if status.is_pending() { + inner.process(); + unsafe { &*item.as_ptr() }.status + } else { + status + } + }) + } + + /// Schedules the work item to be cancelled. + /// + /// The function returns true when the item was immediately cancelled. If the function returns + /// `false`, the item will need to be polled until its status becomes [`Poll::Ready`]. + /// + /// The work item should not be assumed to be immediately cancelled. Polling its handle + /// is necessary to ensure it is no longer being processed by the underlying driver. + pub fn cancel(&self, work_item: NonNull>) -> bool { + self.inner.with(|inner| inner.cancel(work_item)) + } +} + +/// The status of a work item. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Status { + /// The processing has completed. + Completed, + + /// The work item has been cancelled. + Cancelled, +} + +/// A unit of work in the work queue. +pub(crate) struct WorkItem { + next: Option>>, + status: Poll, + data: T, + waker: WakerRegistration, +} + +impl Clone for WorkItem { + fn clone(&self) -> Self { + Self { + // A work item can only be cloned when it's not in a queue. + next: None, + status: Poll::Pending(false), + data: self.data.clone(), + waker: WakerRegistration::new(), + } + } +} + +impl WorkItem { + /// Completes the work item. + /// + /// This function is intended to be called from the underlying drivers. + pub fn complete(&mut self, status: Status) { + self.status = Poll::Ready(status); + self.waker.wake(); + } + + /// Prepares a work item to be enqueued. + /// + /// # Safety: + /// + /// The caller must ensure the reference is not used again while the pointer returned by this + /// function is in use. + unsafe fn prepare(&mut self) -> NonNull { + self.next = None; + self.status = Poll::Pending(false); + + NonNull::from(self) + } +} + +/// The status of a work item posted to a work queue. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum Poll { + /// The work item has not yet been fully processed. Contains whether the caller should poll the + /// queue again. This only has effect on async pollers, which will need to wake their tasks + /// immediately. + Pending(bool), + + /// The work item has been processed. + Ready(Status), +} + +impl Poll { + /// Returns whether the current result is still pending. + pub fn is_pending(self) -> bool { + matches!(self, Self::Pending(_)) + } + + /// Returns whether the current result is ready. + pub fn is_ready(self) -> bool { + !self.is_pending() + } +} + +/// A reference to a posted [`WorkItem`]. +/// +/// This struct ensures that the work item is valid until the item is processed or is removed from +/// the work queue. +/// +/// Dropping the handle cancels the work item, but may block for some time if the work item is +/// already being processed. +pub(crate) struct Handle<'t, T: Sync + Send> { + queue: &'t WorkQueue, + work_item: NonNull>, + // Make sure lifetime is invariant to prevent UB. + _marker: PhantomData<&'t mut WorkItem>, +} + +impl<'t, T: Sync + Send> Handle<'t, T> { + pub(crate) fn from_completed_work_item( + queue: &'t WorkQueue, + work_item: &'t mut WorkItem, + ) -> Self { + // Don't use `complete` here, we don't need to wake anything, just ensure that the item will + // not be put into the queue. + work_item.status = Poll::Ready(Status::Completed); + + Self { + queue, + work_item: NonNull::from(work_item), + _marker: PhantomData, + } + } + + fn poll_inner(&mut self) -> Poll { + unsafe { self.queue.poll(self.work_item) } + } + + /// Returns the status of the work item. + pub fn poll(&mut self) -> bool { + self.poll_inner().is_ready() + } + + /// Polls the work item to completion, by busy-looping. + /// + /// This function returns immediately if `poll` returns `true`. + #[inline] + pub fn wait_blocking(mut self) -> Status { + loop { + if let Poll::Ready(status) = self.poll_inner() { + return status; + } + } + } + + /// Waits until the work item is completed. + pub fn wait(&mut self) -> impl Future { + poll_fn(|ctx| { + unsafe { self.work_item.as_mut() } + .waker + .register(ctx.waker()); + match self.poll_inner() { + Poll::Pending(recall) => { + if recall { + ctx.waker().wake_by_ref(); + } + core::task::Poll::Pending + } + Poll::Ready(status) => core::task::Poll::Ready(status), + } + }) + } + + /// Cancels the work item and asynchronously waits until it is removed from the work queue. + pub async fn cancel(&mut self) { + if !self.queue.cancel(self.work_item) { + self.wait().await; + } + } +} + +impl<'t, T: Sync + Send> Drop for Handle<'t, T> { + fn drop(&mut self) { + if !self.queue.cancel(self.work_item) { + // We must wait for the driver to release our WorkItem. + while self.poll_inner().is_pending() {} + } + } +} + +pub(crate) struct WorkQueueDriver<'t, D, T> +where + D: Sync + Send, + T: Sync + Send, +{ + queue: &'t WorkQueue, + _marker: PhantomData<&'t mut D>, +} + +impl<'t, D, T> WorkQueueDriver<'t, D, T> +where + D: Sync + Send, + T: Sync + Send, +{ + pub fn new(driver: &'t mut D, vtable: VTable, queue: &'t WorkQueue) -> Self { + unsafe { + // Safety: the lifetime 't ensures the pointer remains valid for the lifetime of the + // WorkQueueDriver. The Drop implementation (and the general "Don't forget" clause) + // ensure the pointer is not used after the WQD has been dropped. + queue.configure(NonNull::from(driver), vtable); + } + Self { + queue, + _marker: PhantomData, + } + } + + /// Shuts down the driver. + pub fn stop(self) -> impl Future { + let mut suspended = false; + poll_fn(move |ctx| { + self.queue.inner.with(|inner| { + if !inner.is_active() { + unsafe { + // Safety: the noop VTable functions don't use the pointer at all. + self.queue + .configure(NonNull::::dangling(), VTable::noop()) + }; + // Make sure the queue doesn't remain suspended when the driver is re-started. + if suspended { + inner.resume(); + } + return core::task::Poll::Ready(()); + } + // This may kick out other suspend() callers, but that should be okay. They will + // only be able to do work if the queue is !active, for them it doesn't matter if + // the queue is suspended or stopped completely - just that it isn't running. As for + // the possible waker churn, we can use MultiWakerRegistration with a capacity + // suitable for the number of possible suspenders (2-3 unless the work queue ends up + // being used more widely), if this turns out to be a problem. + inner.suspend_waker.register(ctx.waker()); + if !suspended { + inner.suspend(Some(ctx)); + suspended = true; + } + + core::task::Poll::Pending + }) + }) + } +} + +impl Drop for WorkQueueDriver<'_, D, T> +where + D: Sync + Send, + T: Sync + Send, +{ + fn drop(&mut self) { + let wait_for_suspended = self.queue.inner.with(|inner| { + if inner.is_active() { + inner.suspend(None); + true + } else { + unsafe { inner.configure(NonNull::dangling(), VTable::noop()) }; + false + } + }); + + if !wait_for_suspended { + return; + } + + loop { + let done = self.queue.inner.with(|inner| { + if inner.is_active() { + return false; + } + + unsafe { inner.configure(NonNull::dangling(), VTable::noop()) }; + + inner.resume(); + + true + }); + if done { + break; + } + } + } +} + +/// Used by work queue clients, allows hiding WorkItem. +#[derive(Clone)] +pub(crate) struct WorkQueueFrontend { + work_item: WorkItem, +} + +impl WorkQueueFrontend { + /// Creates a new work queue frontend with the given initial value. + pub fn new(initial: T) -> Self { + Self { + work_item: WorkItem { + next: None, + status: Poll::Pending(false), + data: initial, + waker: WakerRegistration::new(), + }, + } + } + + /// Returns a reference to the work item's data. + #[cfg(ecc_driver_supported)] + pub fn data(&self) -> &T { + &self.work_item.data + } + + /// Returns a mutable reference to the work item's data. + pub fn data_mut(&mut self) -> &mut T { + &mut self.work_item.data + } + + /// Posts the work item to the queue. + /// + /// Returns a [`Handle`] that can be used to wait for the work item to complete. + pub fn post<'t>(&'t mut self, queue: &'t WorkQueue) -> Handle<'t, T> { + queue.post_work(&mut self.work_item) + } + + /// Creates a Handle for a work item that does not need to be put into the queue. + pub fn post_completed<'t>(&'t mut self, queue: &'t WorkQueue) -> Handle<'t, T> { + Handle::from_completed_work_item(queue, &mut self.work_item) + } +} diff --git a/esp-lp-hal/.cargo/config.toml b/esp-lp-hal/.cargo/config.toml new file mode 100644 index 00000000000..3cf0f9207b6 --- /dev/null +++ b/esp-lp-hal/.cargo/config.toml @@ -0,0 +1,16 @@ +[alias] +esp32c6 = "build --release --examples --features=esp32c6 --target riscv32imac-unknown-none-elf" +esp32s2 = "build --release --example=blinky --features=esp32s2 --target riscv32imc-unknown-none-elf" +esp32s3 = "build --release --example=blinky --features=esp32s3 --target riscv32imc-unknown-none-elf" + +[build] +# target = "riscv32imc-unknown-none-elf" # ESP32-S2 + ESP32-S3 +target = "riscv32imac-unknown-none-elf" # ESP32-C6 + +[target.'cfg(target_arch = "riscv32")'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[unstable] +build-std = ["core"] diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md new file mode 100644 index 00000000000..0f4820e409c --- /dev/null +++ b/esp-lp-hal/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + + +### Changed + + +### Fixed + +- Fix panic when handling buffers larger than 7 bytes in `LpI2c.write` and `LpI2c.read` (#4694) + +### Removed + + +## [v0.3.0] - 2025-10-13 + +### Fixed + +- Fixed size of LP_UART's RAM block (#3812) + +## [v0.2.0] - 2025-06-03 + +### Changed + +- Fix gpio `input_state` and `output_state` for the ESP32-S3 and ESP32-S2 (#3191) +- Bump Rust edition to 2024, bump MSRV to 1.86. (#2951, #3391, #3560) + +### Removed + +- Remove embedded-hal 0.2.x impls and dependency from esp-lp-hal package (#2609) + +## 0.1.0 - 2024-07-15 + +### Added + +- Add the `esp32c6-lp-hal` package (#714) +- Add GPIO (output) and delay functionality to `esp32c6-lp-hal` (#715) +- Add GPIO input support and implement additional `embedded-hal` output traits for the C6's LP core (#720) +- Add the `ulp-riscv-hal` package (#840) +- Add LP_UART basic driver (#1113) +- Added basic `LP-I2C` driver for C6 (#1185) +- Add remaining GPIO pins for ESP32-S2/S3 (#1695) +- Add `wake_hp_core` for ESP32-C6 (#1723) +- Implement `embedded-hal@1.x.x` traits by default instead of `embedded-hal@0.2.x` (#1754) +- Implement `embedded-hal-nb` and `embedded-io` traits for UART driver (#1754) +- Add `Delay.delay_millis` function (#1789) +- Make some UART functions public, allowing it to be used without `embedded-hal`/`embedded-io` traits (#1789) + +### Changed + +- Renamed to `esp-ulp-riscv-hal` (#916) +- Remove 2nd level generics from GPIO pin (#1526) +- GPIO Input/Output types have been converted to unit structs (#1754) + +[v0.2.0]: https://github.com/esp-rs/esp-hal/releases/tag/esp-lp-hal-v0.2.0 +[v0.3.0]: https://github.com/esp-rs/esp-hal/compare/esp-lp-hal-v0.2.0...esp-lp-hal-v0.3.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-lp-hal-v0.3.0...HEAD diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml new file mode 100644 index 00000000000..789776284ca --- /dev/null +++ b/esp-lp-hal/Cargo.toml @@ -0,0 +1,100 @@ +[package] +name = "esp-lp-hal" +version = "0.3.0" +edition = "2024" +rust-version = "1.86.0" +description = "HAL for low-power RISC-V coprocessors found in ESP32 devices" +documentation = "https://docs.espressif.com/projects/rust/esp-lp-hal/latest/" +keywords = ["embedded", "embedded-hal", "esp32", "espressif", "hal"] +categories = ["embedded", "hardware-support", "no-std"] +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.docs.rs] +default-target = "riscv32imac-unknown-none-elf" +features = ["esp32c6"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.espressif] +targets_lp_core = true +doc-config = { features = ["embedded-hal"], append = [ + { features = ["embedded-io"], if = 'chip_has("lp_core")' }, +] } +check-configs = [ + { features = [] }, + { features = ["embedded-hal"] }, + { features = ["embedded-io"] }, + { features = ["embedded-hal", "embedded-io"] }, +] +clippy-configs = [ + { features = ["embedded-hal", "embedded-io"] }, +] + +[lib] +bench = false +test = false + +[dependencies] +cfg-if = "1" +document-features = "0.2" +embedded-hal = { version = "1.0.0", optional = true } +embedded-hal-nb = { version = "1.0.0", optional = true } +embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true } +embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } +esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true } +nb = { version = "1.1.0", optional = true } +procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } +riscv = { version = "0.15", features = ["critical-section-single-hart"] } +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } + +[dev-dependencies] +panic-halt = "0.2.0" + +[build-dependencies] +esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"] } + +[features] +default = ["embedded-hal"] + +## Enable debug features in the HAL (used for development). +debug = [ + "esp32c6-lp?/impl-register-debug", + "esp32s2-ulp?/impl-register-debug", + "esp32s3-ulp?/impl-register-debug", +] + +# Chip Support Feature Flags +# Target the ESP32-C6. +esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb"] +# Target the ESP32-S2. +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core"] +# Target the ESP32-S3. +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core"] + +#! ### Trait Implementation Feature Flags +## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and +## `embedded-hal-nb` for the relevant peripherals. +embedded-hal = ["dep:embedded-hal", "dep:embedded-hal-nb"] +## Implement the traits defined in `embedded-io` for the relevant peripherals. +embedded-io = ["dep:embedded-io-06", "dep:embedded-io-07"] + +[[example]] +name = "blinky" +required-features = [] + +[[example]] +name = "i2c" +required-features = ["esp32c6"] + +[[example]] +name = "i2c_sht30" +required-features = ["esp32c6"] + +[[example]] +name = "uart" +required-features = ["esp32c6"] + +[lints.rust] +unexpected_cfgs = "allow" diff --git a/esp-lp-hal/README.md b/esp-lp-hal/README.md new file mode 100644 index 00000000000..c66b0f55baf --- /dev/null +++ b/esp-lp-hal/README.md @@ -0,0 +1,52 @@ +# esp-lp-hal + +[![Crates.io](https://img.shields.io/crates/v/esp-lp-hal?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-lp-hal) +[![docs.rs](https://img.shields.io/docsrs/esp-lp-hal?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-lp-hal/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-lp-hal?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +Bare-metal (`no_std`) hardware abstraction layer for the low-power RISC-V coprocessors found in the ESP32-C6, ESP32-S2, and ESP32-S3 from Espressif. + +Implements a number of blocking and, where applicable, async traits from the various packages in the [embedded-hal] repository. + +For help getting started with this HAL, please refer to [The Rust on ESP Book] and the [documentation]. + +[embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ +[the rust on esp book]: https://docs.espressif.com/projects/rust/book/ + +## [Documentation](https://docs.espressif.com/projects/rust/esp-lp-hal/latest/) + +## Supported Devices + +| Chip | Datasheet | Technical Reference Manual | Target | +| :------: | :----------------------: | :------------------------: | :----------------------------: | +| ESP32-C6 | [ESP32-C6][c6-datasheet] | [ESP32-C6][c6-trm] | `riscv32imac-unknown-none-elf` | +| ESP32-S2 | [ESP32-S2][s2-datasheet] | [ESP32-S2][s2-trm] | `riscv32imc-unknown-none-elf` | +| ESP32-S3 | [ESP32-S3][s3-datasheet] | [ESP32-S3][s3-trm] | `riscv32imc-unknown-none-elf` | + +[c6-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf +[s2-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf +[s3-datasheet]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf +[c6-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf +[s2-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf +[s3-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs new file mode 100644 index 00000000000..f07dca41629 --- /dev/null +++ b/esp-lp-hal/build.rs @@ -0,0 +1,27 @@ +use std::{env, error::Error, fs, path::PathBuf}; + +use esp_metadata_generated::Chip; + +fn main() -> Result<(), Box> { + // Determine the name of the configured device: + let chip = Chip::from_cargo_feature()?; + + // Define all necessary configuration symbols for the configured device: + chip.define_cfgs(); + + // Copy the required linker script to the `out` directory: + let source_file = match chip { + Chip::Esp32c6 => "ld/link-lp.x", + Chip::Esp32s2 | Chip::Esp32s3 => "ld/link-ulp.x", + _ => unreachable!(), + }; + + // Put the linker script somewhere the linker can find it: + let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + fs::copy(source_file, out.join("link.x"))?; + println!("cargo:rerun-if-changed=ld/link-ulp.x"); + + // Done! + Ok(()) +} diff --git a/esp-lp-hal/examples/blinky.rs b/esp-lp-hal/examples/blinky.rs new file mode 100644 index 00000000000..dfd12d10947 --- /dev/null +++ b/esp-lp-hal/examples/blinky.rs @@ -0,0 +1,43 @@ +//! Counts a 32 bit value at a known point in memory, and blink GPIO1. +//! +//! When using the ESP32-C6's LP core, this address in memory is `0x5000_2000`. +//! +//! When using the ESP32-S2 or ESP32-S3's ULP core, this address in memory is +//! `0x5000_0400` (but is `0x400`` from the ULP's point of view!). +//! +//! Make sure the LP RAM is cleared before loading the code. + +#![no_std] +#![no_main] + +use embedded_hal::{delay::DelayNs, digital::OutputPin}; +use esp_lp_hal::{delay::Delay, gpio::Output, prelude::*}; +use panic_halt as _; + +cfg_if::cfg_if! { + if #[cfg(feature = "esp32c6")] { + const ADDRESS: u32 = 0x5000_2000; + } else if #[cfg(any(feature = "esp32s2", feature = "esp32s3"))] { + const ADDRESS: u32 = 0x400; + } +} + +#[entry] +fn main(mut gpio1: Output<1>) -> ! { + let mut i: u32 = 0; + + let ptr = ADDRESS as *mut u32; + + loop { + i = i.wrapping_add(1u32); + unsafe { + ptr.write_volatile(i); + } + + gpio1.set_high().unwrap(); + Delay.delay_ms(500); + + gpio1.set_low().unwrap(); + Delay.delay_ms(500); + } +} diff --git a/esp-lp-hal/examples/i2c.rs b/esp-lp-hal/examples/i2c.rs new file mode 100644 index 00000000000..65e854d8bd5 --- /dev/null +++ b/esp-lp-hal/examples/i2c.rs @@ -0,0 +1,26 @@ +//! Uses `LP_I2C` and reads calibration data from BMP180 sensor. +//! +//! This example dumps the calibration data from a BMP180 sensor, to view them, +//! logic analyzer or oscilloscope is required. +//! +//! The following wiring is assumed: +//! - SDA => GPIO6 +//! - SCL => GPIO7 + +//% CHIPS: esp32c6 + +#![no_std] +#![no_main] + +use esp_lp_hal::{i2c::LpI2c, prelude::*}; +use panic_halt as _; + +#[entry] +fn main(mut i2c: LpI2c) -> ! { + let _peripherals = esp32c6_lp::Peripherals::take().unwrap(); + + loop { + let mut data = [0u8; 22]; + i2c.write_read(0x77, &[0xaa], &mut data).ok(); + } +} diff --git a/esp-lp-hal/examples/i2c_sht30.rs b/esp-lp-hal/examples/i2c_sht30.rs new file mode 100644 index 00000000000..878762bfb88 --- /dev/null +++ b/esp-lp-hal/examples/i2c_sht30.rs @@ -0,0 +1,64 @@ +//! Uses `LP_I2C` and reads temperature and humidity from SHT30 sensor. +//! +//! This code runs on the LP core and writes the sensor data to LP memory, +//! which can then be accessed by the HP core. +//! +//! The following wiring is assumed: +//! - SDA => GPIO6 +//! - SCL => GPIO7 + +//% CHIPS: esp32c6 + +#![no_std] +#![no_main] + +use embedded_hal::delay::DelayNs; +use esp_lp_hal::{ + delay::Delay, + i2c::{Error, LpI2c}, + prelude::*, +}; +use panic_halt as _; + +// LP SRAM addresses used as a simple shared-memory interface between the LP and HP cores. +// These addresses must match the locations that the HP core expects to read from. +// - 0x5000_2000: f32 temperature value +// - 0x5000_2004: f32 humidity value (4 bytes after temperature to hold a second f32) +// Adjust with care: changing these requires updating the corresponding HP-core code and +// ensuring the chosen addresses stay within a valid, reserved LP memory region. +const TEMP_ADDRESS: u32 = 0x5000_2000; +const HUMID_ADDRESS: u32 = 0x5000_2004; + +// I2C address of the SHT30 temperature and humidity sensor. +const DEV_ADDR: u8 = 0x44; +// SHT30 command for single-shot measurement, clock stretching disabled, high repeatability. +const CMD_READ_ONESHOT: [u8; 2] = [0x2C, 0x06]; + +fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { + let mut buffer = [0u8; 6]; + // Send single-shot measurement command. + i2c.write(DEV_ADDR, &CMD_READ_ONESHOT)?; + // Wait for the measurement to complete (up to 15 ms per datasheet). + Delay.delay_ms(15); + // Read measurement results. + i2c.read(DEV_ADDR, &mut buffer)?; + let temp_raw = u16::from_be_bytes([buffer[0], buffer[1]]); + let hum_raw = u16::from_be_bytes([buffer[3], buffer[4]]); + let temperature = -45.0 + (175.0 * (temp_raw as f32) / 65535.0); + let humidity = 100.0 * (hum_raw as f32) / 65535.0; + Ok((temperature, humidity)) +} + +#[entry] +fn main(mut i2c: LpI2c) -> ! { + let temp_ptr = TEMP_ADDRESS as *mut f32; + let hum_ptr = HUMID_ADDRESS as *mut f32; + loop { + let (temp, humid) = read_temp_humid(&mut i2c).unwrap_or((f32::NAN, f32::NAN)); + unsafe { + temp_ptr.write_volatile(temp); + hum_ptr.write_volatile(humid); + } + Delay.delay_ms(1000); + } +} diff --git a/esp-lp-hal/examples/uart.rs b/esp-lp-hal/examples/uart.rs new file mode 100644 index 00000000000..bce4da8817b --- /dev/null +++ b/esp-lp-hal/examples/uart.rs @@ -0,0 +1,25 @@ +//! Uses `LP_UART` and logs "Hello World from LP Core". +//! +//! Uses GPIO4 for RX and GPIO5 for TX. GPIOs can't be changed. +//! +//! It is neccessary to use Serial-Uart bridge connected to TX and RX to see +//! logs from LP_UART. Make sure the LP RAM is cleared before loading the code. + +//% CHIPS: esp32c6 + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use embedded_hal::delay::DelayNs; +use esp_lp_hal::{delay::Delay, prelude::*, uart::LpUart}; +use panic_halt as _; + +#[entry] +fn main(mut uart: LpUart) -> ! { + loop { + writeln!(uart, "Hello World from LP Core").unwrap(); + Delay.delay_ms(1000); + } +} diff --git a/esp-lp-hal/ld/link-lp.x b/esp-lp-hal/ld/link-lp.x new file mode 100644 index 00000000000..f0471d64e06 --- /dev/null +++ b/esp-lp-hal/ld/link-lp.x @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +ENTRY(reset_vector) + +VECTOR_TABLE_LENGTH = 0x80; +CONFIG_ULP_COPROC_RESERVE_MEM = 1024 * 16; +CONFIG_ULP_SHARED_MEM = 0; +RAM_LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM - VECTOR_TABLE_LENGTH - CONFIG_ULP_SHARED_MEM; + +MEMORY +{ + /*first 128byte for exception/interrupt vectors*/ + vector_table(RX) : ORIGIN = 0x50000000, LENGTH = VECTOR_TABLE_LENGTH + ram(RWX) : ORIGIN = 0x50000080, LENGTH = RAM_LENGTH +} + +SECTIONS +{ + .vector.text : + { + /* Exception/interrupt vectors */ + __mtvec_base = .; + KEEP (*(.init.vector)) + __mtvec_end = .; + } > vector_table + + . = ORIGIN(ram); + + .text ALIGN(4): + { + *(.text.vectors) /* Default reset vector must link to offset 0x80 */ + + KEEP(*(.init)); + KEEP(*(.init.rust)); + *(.text) + *(.text*) + } > ram + + .rodata ALIGN(4): + { + *(.rodata) + *(.rodata*) + } > ram + + .data ALIGN(4): + { + *(.data) + *(.data*) + *(.sdata) + *(.sdata*) + } > ram + + .bss ALIGN(4) : + { + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + PROVIDE(end = .); + } > ram + + __stack_top = ORIGIN(ram) + LENGTH(ram); +} diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x new file mode 100644 index 00000000000..7e6926a7fa1 --- /dev/null +++ b/esp-lp-hal/ld/link-ulp.x @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +ENTRY(reset_vector) + +CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; + +MEMORY +{ + ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM +} + +SECTIONS +{ + . = ORIGIN(ram); + + .text : + { + *(.text.vectors) /* Default reset vector must link to offset 0x0 */ + + KEEP(*(.init)); + KEEP(*(.init.rust)); + *(.text) + *(.text*) + } >ram + + .rodata ALIGN(4): + { + *(.rodata) + *(.rodata*) + } > ram + + .data ALIGN(4): + { + *(.data) + *(.data*) + *(.sdata) + *(.sdata*) + } > ram + + .bss ALIGN(4) : + { + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + } >ram + + __stack_top = ORIGIN(ram) + LENGTH(ram); +} diff --git a/esp-lp-hal/src/delay.rs b/esp-lp-hal/src/delay.rs new file mode 100644 index 00000000000..89c8bcbe43f --- /dev/null +++ b/esp-lp-hal/src/delay.rs @@ -0,0 +1,83 @@ +//! # Delay driver +//! +//! ## Overview +//! +//! The delay driver provides blocking delay functionality. The driver +//! implements the relevant traits from `embedded-hal`. +//! +//! ## Examples +//! +//! ```rust,no_run +//! esp_lp_hal::delay::Delay.delay_millis(500); +//! ``` + +/// Delay driver +#[derive(Debug, Clone, Copy)] +pub struct Delay; + +impl Delay { + /// Delay for at least the number of specific milliseconds. + pub fn delay_millis(&self, mut ms: u32) { + const MICROS_PER_MILLI: u32 = 1_000; + const MAX_MILLIS: u32 = u32::MAX / MICROS_PER_MILLI; + + // Avoid potential overflow if milli -> micro conversion is too large + while ms > MAX_MILLIS { + ms -= MAX_MILLIS; + self.delay_micros(MAX_MILLIS * MICROS_PER_MILLI); + } + + self.delay_micros(ms * MICROS_PER_MILLI); + } + + /// Delay for at least the number of specific microseconds. + pub fn delay_micros(&self, mut us: u32) { + const NANOS_PER_MICRO: u32 = 1_000; + const MAX_MICROS: u32 = u32::MAX / NANOS_PER_MICRO; + + // Avoid potential overflow if micro -> nano conversion is too large + while us > MAX_MICROS { + us -= MAX_MICROS; + self.delay_nanos(MAX_MICROS * NANOS_PER_MICRO); + } + + self.delay_nanos(us * NANOS_PER_MICRO); + } + + /// Delay for at least the number of specific nanoseconds. + pub fn delay_nanos(&self, ns: u32) { + let ticks_seconds = unsafe { crate::CPU_CLOCK }; + let clock = (ns as u64 * (ticks_seconds as u64)) / 1_000_000_000u64; + let t0 = cycles(); + + while cycles().wrapping_sub(t0) <= clock {} + } +} + +#[cfg(esp32c6)] +#[inline(always)] +fn cycles() -> u64 { + riscv::register::mcycle::read64() +} + +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +fn cycles() -> u64 { + let mut cycles: u32; + unsafe { + core::arch::asm!( + "rdcycle {cycles}", + cycles = out(reg) cycles, + ) + } + + cycles as u64 +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::delay::DelayNs for Delay { + #[inline(always)] + fn delay_ns(&mut self, ns: u32) { + self.delay_nanos(ns); + } +} diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs new file mode 100644 index 00000000000..4f87e95c85e --- /dev/null +++ b/esp-lp-hal/src/gpio.rs @@ -0,0 +1,148 @@ +//! # General Purpose Input/Output +//! +//! ## Overview +//! +//! It's assumed that GPIOs are already configured correctly by the HP core. +//! +//! This driver supports various operations on GPIO pins, primarily manipulating +//! the pin state (setting high/low, toggling). +//! +//! This module also implements a number of traits from `embedded-hal` to +//! provide a common interface for GPIO pins. +//! +//! ## Examples +//! +//! ```rust,no_run +//! fn main(gpio0: Input<0>, gpio1: Output<1>) -> ! { +//! loop { +//! let input_state: bool = gpio0.input_state(); +//! gpio.set_output(input_state); +//! +//! esp_lp_hal::delay::Delay.delay_millis(50); +//! } +//! } +//! ``` + +cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + type LpIo = crate::pac::LP_IO; + const MAX_GPIO_PIN: u8 = 7; + } else { + type LpIo = crate::pac::RTC_IO; + const MAX_GPIO_PIN: u8 = 21; + } +} + +/// GPIO input driver +pub struct Input; + +impl Input { + /// Read the input state/level of the pin. + pub fn input_state(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + (unsafe { &*LpIo::PTR }.in_().read().bits() >> PIN) & 0x1 != 0 + } else if #[cfg(esp32s2)] { + (unsafe { &*LpIo::PTR }.in_().read().gpio_in_next().bits() >> PIN) & 0x1 != 0 + } else if #[cfg(esp32s3)] { + (unsafe { &*LpIo::PTR }.in_().read().next().bits() >> PIN) & 0x1 != 0 + } + } + } +} + +/// GPIO output driver +pub struct Output; + +impl Output { + /// Read the output state/level of the pin. + pub fn output_state(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + (unsafe { &*LpIo::PTR }.out().read().bits() >> PIN) & 0x1 != 0 + } else if #[cfg(esp32s2)] { + (unsafe { &*LpIo::PTR }.out().read().gpio_out_data().bits() >> PIN) & 0x1 != 0 + } else if #[cfg(esp32s3)] { + (unsafe { &*LpIo::PTR }.out().read().data().bits() >> PIN) & 0x1 != 0 + } + } + } + + /// Set the output state/level of the pin. + pub fn set_output(&mut self, on: bool) { + if on { + unsafe { &*LpIo::PTR } + .out_w1ts() + .write(|w| unsafe { w.out_data_w1ts().bits(1 << PIN) }); + } else { + unsafe { &*LpIo::PTR } + .out_w1tc() + .write(|w| unsafe { w.out_data_w1tc().bits(1 << PIN) }); + } + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_output() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Output) + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_input() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Input) + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::digital::ErrorType for Input { + type Error = core::convert::Infallible; +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::digital::ErrorType for Output { + type Error = core::convert::Infallible; +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::digital::InputPin for Input { + fn is_high(&mut self) -> Result { + Ok(self.input_state()) + } + + fn is_low(&mut self) -> Result { + Ok(!self.is_high()?) + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::digital::OutputPin for Output { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_output(false); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_output(true); + Ok(()) + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::digital::StatefulOutputPin for Output { + fn is_set_high(&mut self) -> Result { + Ok(self.output_state()) + } + + fn is_set_low(&mut self) -> Result { + Ok(!self.is_set_high()?) + } +} diff --git a/esp-lp-hal/src/i2c.rs b/esp-lp-hal/src/i2c.rs new file mode 100644 index 00000000000..24dfa92331d --- /dev/null +++ b/esp-lp-hal/src/i2c.rs @@ -0,0 +1,470 @@ +//! # Inter-Integrated Circuit (I2C) + +#![allow(unused)] // TODO: Remove me when `embedded_hal::i2c::I2c` is implemented + +use crate::pac::{LP_I2C0, lp_i2c0::COMD}; + +const LP_I2C_TRANS_COMPLETE_INT_ST_S: u32 = 7; +const LP_I2C_END_DETECT_INT_ST_S: u32 = 3; +const LP_I2C_NACK_INT_ST_S: u32 = 10; + +const I2C_LL_INTR_MASK: u32 = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) + | (1 << LP_I2C_END_DETECT_INT_ST_S) + | (1 << LP_I2C_NACK_INT_ST_S); + +const LP_I2C_FIFO_LEN: u32 = property!("lp_i2c_master.fifo_size"); + +#[doc(hidden)] +pub unsafe fn conjure() -> LpI2c { + unsafe { + LpI2c { + i2c: LP_I2C0::steal(), + } + } +} + +// TODO: Document enum variants +/// I2C-specific transmission errors +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + ExceedingFifo, + AckCheckFailed, + TimeOut, + ArbitrationLost, + ExecIncomplete, + CommandNrExceeded, + InvalidResponse, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum OperationType { + Write = 0, + Read = 1, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Ack { + Ack, + Nack, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Opcode { + RStart = 6, + Write = 1, + Read = 3, + Stop = 2, + End = 4, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Command { + Start, + Stop, + End, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte has + /// been received. + ack_value: Ack, + /// Length of data (in bytes) to be read. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, +} + +impl From for u16 { + fn from(c: Command) -> u16 { + let opcode = match c { + Command::Start => Opcode::RStart, + Command::Stop => Opcode::Stop, + Command::End => Opcode::End, + Command::Write { .. } => Opcode::Write, + Command::Read { .. } => Opcode::Read, + }; + + let length = match c { + Command::Start | Command::Stop | Command::End => 0, + Command::Write { length: l, .. } | Command::Read { length: l, .. } => l, + }; + + let ack_exp = match c { + Command::Start | Command::Stop | Command::End | Command::Read { .. } => Ack::Nack, + Command::Write { ack_exp: exp, .. } => exp, + }; + + let ack_check_en = match c { + Command::Start | Command::Stop | Command::End | Command::Read { .. } => false, + Command::Write { + ack_check_en: en, .. + } => en, + }; + + let ack_value = match c { + Command::Start | Command::Stop | Command::End | Command::Write { .. } => Ack::Nack, + Command::Read { ack_value: ack, .. } => ack, + }; + + let mut cmd: u16 = length.into(); + + if ack_check_en { + cmd |= 1 << 8; + } else { + cmd &= !(1 << 8); + } + + if ack_exp == Ack::Nack { + cmd |= 1 << 9; + } else { + cmd &= !(1 << 9); + } + + if ack_value == Ack::Nack { + cmd |= 1 << 10; + } else { + cmd &= !(1 << 10); + } + + cmd |= (opcode as u16) << 11; + + cmd + } +} + +impl From for u32 { + fn from(c: Command) -> u32 { + u16::from(c) as u32 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CommandRegister { + COMD0, + COMD1, + COMD2, + COMD3, + COMD4, + COMD5, + COMD6, + COMD7, +} + +impl CommandRegister { + fn advance(&mut self) { + *self = match *self { + CommandRegister::COMD0 => CommandRegister::COMD1, + CommandRegister::COMD1 => CommandRegister::COMD2, + CommandRegister::COMD2 => CommandRegister::COMD3, + CommandRegister::COMD3 => CommandRegister::COMD4, + CommandRegister::COMD4 => CommandRegister::COMD5, + CommandRegister::COMD5 => CommandRegister::COMD6, + CommandRegister::COMD6 => CommandRegister::COMD7, + CommandRegister::COMD7 => panic!("Cannot advance beyond COMD7"), + } + } +} + +// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122 +// TX/RX RAM size is 16*8 bit +// TX RX FIFO has 16 bit depth +// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST. +// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK. +// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source, +// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source. +// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK. +// Adjust the timing registers accordingly when the clock frequency changes. + +/// LP-I2C driver +pub struct LpI2c { + i2c: LP_I2C0, +} + +impl LpI2c { + /// Writes bytes to slave with given `address` + pub fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> { + let mut cmd_iterator = CommandRegister::COMD0; + + // If SCL is busy, reset the Master FSM + if self.i2c.sr().read().bus_busy().bit_is_set() { + self.i2c.ctr().modify(|_, w| w.fsm_rst().set_bit()); + } + + if bytes.len() > 255 { + return Err(Error::ExceedingFifo); + } + + // Reset FIFO and command list + self.reset_fifo(); + + self.add_cmd_lp(&mut cmd_iterator, Command::Start)?; + + // Load device address and R/W bit into FIFO + self.write_fifo((address << 1) | OperationType::Write as u8); + + self.add_cmd_lp( + &mut cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 1_u8, + }, + )?; + + self.enable_listen(I2C_LL_INTR_MASK); + + let mut data_idx = 0; + let mut remaining_bytes = bytes.len() as u32; + + let mut fifo_available = LP_I2C_FIFO_LEN - 1; + + while remaining_bytes > 0 { + let fifo_size = remaining_bytes.min(fifo_available); + remaining_bytes -= fifo_size; + + // Write data to the FIFO + for &byte in &bytes[data_idx as usize..(data_idx as usize) + fifo_size as usize] { + self.write_fifo(byte); + } + + // Add a Write command with the specified length + self.add_cmd_lp( + &mut cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: fifo_size as u8, + }, + )?; + + // Check if this is the last chunk + let cmd = if remaining_bytes == 0 { + Command::Stop + } else { + Command::End + }; + + // Add the Stop/End command + self.add_cmd_lp(&mut cmd_iterator, cmd)?; + cmd_iterator = CommandRegister::COMD0; + + // Start the I2C transaction + self.lp_i2c_update(); + self.i2c.ctr().modify(|_, w| w.trans_start().set_bit()); + + // Wait for the transaction to complete + self.wait_for_completion()?; + + // Update the index for the next data chunk + data_idx += fifo_size; + + fifo_available = LP_I2C_FIFO_LEN; + } + + Ok(()) + } + + /// Reads enough bytes from slave with given `address` to fill `buffer` + pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + // Check size constraints + if buffer.len() > 254 { + return Err(Error::ExceedingFifo); + } + + let mut cmd_iterator = CommandRegister::COMD0; + + self.add_cmd_lp(&mut cmd_iterator, Command::Start)?; + + // Load device address + self.write_fifo((address << 1) | OperationType::Read as u8); + + self.add_cmd_lp( + &mut cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 1_u8, + }, + )?; + + self.enable_listen( + (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S), + ); + + let mut remaining_bytes = buffer.len(); + + while remaining_bytes > 0 { + let fifo_size = remaining_bytes.min(LP_I2C_FIFO_LEN as usize); + remaining_bytes -= fifo_size; + + if fifo_size == 1 { + // Read one byte and send NACK + self.add_cmd_lp( + &mut cmd_iterator, + Command::Read { + ack_value: Ack::Nack, + length: 1, // which is `fifo_size` + }, + )?; + // Send STOP command after reading + self.add_cmd_lp(&mut cmd_iterator, Command::Stop)?; + } else if fifo_size > 1 && remaining_bytes == 0 { + // This means it is the last transaction + // Read all but the last byte and send ACKs + self.add_cmd_lp( + &mut cmd_iterator, + Command::Read { + ack_value: Ack::Ack, + length: (fifo_size - 1) as u8, + }, + )?; + // Read the last byte and send NACK + self.add_cmd_lp( + &mut cmd_iterator, + Command::Read { + ack_value: Ack::Nack, + length: 1, + }, + )?; + // Send STOP command after reading + self.add_cmd_lp(&mut cmd_iterator, Command::Stop)?; + } else { + // This means we have to read data more than we can fit into the Rx FIFO + // Read fifo_size bytes and send ACKs + self.add_cmd_lp( + &mut cmd_iterator, + Command::Read { + ack_value: Ack::Ack, + length: fifo_size as u8, + }, + )?; + // Send END command signaling more data to come + self.add_cmd_lp(&mut cmd_iterator, Command::End)?; + cmd_iterator = CommandRegister::COMD0; + } + + self.lp_i2c_update(); + + // Initiate I2C transfer + self.i2c.ctr().modify(|_, w| w.trans_start().set_bit()); + + // Await for completion (This function or mechanism should handle waiting for + // the I2C transfer to complete) + self.wait_for_completion()?; + + // Read from FIFO into the current chunk + for byte in buffer.iter_mut() { + *byte = self.read_fifo(); + } + } + Ok(()) + } + + /// Writes bytes to slave with given `address` and then reads enough bytes + /// to fill `buffer` *in a single transaction* + pub fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Error> { + // It would be possible to combine the write and read in one transaction, but + // filling the tx fifo with the current code is somewhat slow even in release + // mode which can cause issues. + self.write(address, bytes)?; + self.read(address, buffer)?; + + Ok(()) + } + + fn lp_i2c_update(&self) { + self.i2c.ctr().modify(|_, w| w.conf_upgate().set_bit()); + } + + fn reset_fifo(&self) { + self.i2c + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().set_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().clear_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().set_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().clear_bit()); + } + + fn wait_for_completion(&self) -> Result<(), Error> { + loop { + let interrupts = self.i2c.int_st().read(); + + // Handle completion cases + // A full transmission was completed + if interrupts.nack().bit_is_set() { + self.i2c + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + return Err(Error::InvalidResponse); + } else if interrupts.trans_complete().bit_is_set() { + self.disable_interrupts(); + + self.i2c + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + break; + } else if interrupts.end_detect().bit_is_set() { + self.i2c + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + break; + } + } + + Ok(()) + } + + fn enable_listen(&self, mask: u32) { + self.i2c.int_ena().write(|w| unsafe { w.bits(mask) }); + } + + fn disable_interrupts(&self) { + self.i2c.int_ena().write(|w| unsafe { w.bits(0) }); + } + + fn write_fifo(&self, data: u8) { + self.i2c + .data() + .write(|w| unsafe { w.fifo_rdata().bits(data) }); + } + + fn read_fifo(&self) -> u8 { + self.i2c.data().read().fifo_rdata().bits() + } + + fn add_cmd_lp( + &self, + command_register: &mut CommandRegister, + command: Command, + ) -> Result<(), Error> { + self.i2c + .comd(*command_register as usize) + .write(|w| unsafe { w.command().bits(command.into()) }); + + command_register.advance(); + + Ok(()) + } +} diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs new file mode 100644 index 00000000000..7c4e979130a --- /dev/null +++ b/esp-lp-hal/src/lib.rs @@ -0,0 +1,179 @@ +#![cfg_attr( + all(docsrs, not(not_really_docsrs)), + doc = "\n\n
    \n\n" +)] +//! Bare-metal (`no_std`) HAL for the low power and ultra-low power cores found +//! in some Espressif devices. Where applicable, drivers implement the +//! [embedded-hal] traits. +//! +//! ## Choosing a device +//! +//! Depending on your target device, you need to enable the chip feature +//! for that device. +//! +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![allow(asm_sub_register)] +#![deny(missing_docs)] +#![no_std] + +#[allow(unused_imports, reason = "Only used for some MCUs currently")] +#[macro_use] +extern crate esp_metadata_generated; + +use core::arch::global_asm; + +pub mod delay; +pub mod gpio; +#[cfg(lp_i2c_master_driver_supported)] +pub mod i2c; +#[cfg(lp_uart_driver_supported)] +pub mod uart; + +#[cfg(esp32c6)] +pub use esp32c6_lp as pac; +#[cfg(esp32s2)] +pub use esp32s2_ulp as pac; +#[cfg(esp32s3)] +pub use esp32s3_ulp as pac; + +/// The prelude +pub mod prelude { + pub use procmacros::entry; +} + +cfg_if::cfg_if! { + if #[cfg(esp32c6)] { + // LP_FAST_CLK is not very accurate, for now use a rough estimate + const LP_FAST_CLK_HZ: u32 = 16_000_000; + const XTAL_D2_CLK_HZ: u32 = 20_000_000; + } else if #[cfg(esp32s2)] { + const LP_FAST_CLK_HZ: u32 = 8_000_000; + } else if #[cfg(esp32s3)] { + const LP_FAST_CLK_HZ: u32 = 17_500_000; + } +} + +pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; + +/// Wake up the HP core +#[cfg(esp32c6)] +pub fn wake_hp_core() { + unsafe { &*esp32c6_lp::PMU::PTR } + .hp_lp_cpu_comm() + .write(|w| w.lp_trigger_hp().set_bit()); +} + +#[cfg(esp32c6)] +global_asm!( + r#" + .section .init.vector, "ax" + /* This is the vector table. It is currently empty, but will be populated + * with exception and interrupt handlers when this is supported + */ + + .align 0x4, 0xff + .global _vector_table + .type _vector_table, @function +_vector_table: + .option push + .option norvc + + .rept 32 + nop + .endr + + .option pop + .size _vector_table, .-_vector_table + + .section .init, "ax" + .global reset_vector + +/* The reset vector, jumps to startup code */ +reset_vector: + j __start + +__start: + /* setup the stack pointer */ + la sp, __stack_top + call rust_main +loop: + j loop +"# +); + +#[cfg(any(esp32s2, esp32s3))] +global_asm!( + r#" + .section .text.vectors + .global irq_vector + .global reset_vector + +/* The reset vector, jumps to startup code */ +reset_vector: + j __start + +/* Interrupt handler */ +.balign 16 +irq_vector: + ret + + .section .text + +__start: + /* setup the stack pointer */ + la sp, __stack_top + + call ulp_riscv_rescue_from_monitor + call rust_main + call ulp_riscv_halt +loop: + j loop +"# +); + +#[unsafe(link_section = ".init.rust")] +#[unsafe(export_name = "rust_main")] +unsafe extern "C" fn lp_core_startup() -> ! { + unsafe { + unsafe extern "Rust" { + fn main() -> !; + } + + #[cfg(esp32c6)] + if (*pac::LP_CLKRST::PTR) + .lp_clk_conf() + .read() + .fast_clk_sel() + .bit_is_set() + { + CPU_CLOCK = XTAL_D2_CLK_HZ; + } + + main(); + } +} + +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(link_section = ".init.rust")] +#[unsafe(no_mangle)] +unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { + // Rescue RISC-V core from monitor state. + unsafe { &*pac::RTC_CNTL::PTR } + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done().clear_bit().cocpu_shut_reset_en().clear_bit()); +} + +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(link_section = ".init.rust")] +#[unsafe(no_mangle)] +unsafe extern "C" fn ulp_riscv_halt() { + unsafe { &*pac::RTC_CNTL::PTR } + .cocpu_ctrl() + .modify(|_, w| unsafe { w.cocpu_shut_2_clk_dis().bits(0x3f).cocpu_done().set_bit() }); + + loop { + riscv::asm::wfi(); + } +} diff --git a/esp-lp-hal/src/uart.rs b/esp-lp-hal/src/uart.rs new file mode 100644 index 00000000000..49758976724 --- /dev/null +++ b/esp-lp-hal/src/uart.rs @@ -0,0 +1,380 @@ +//! # Universal Asynchronous Receiver/Transmitter (UART) +//! +//! ## Overview +//! +//! The UART is a hardware peripheral which handles communication using serial +//! interfaces. This peripheral provides a cheap and ubiquitous method for full- +//! and half-duplex communication between devices. +//! +//! ## Configuration +//! +//! The usual setting such as baud rate, data bits, parity, and stop bits can +//! easily be configured. See the [config] module documentation for more +//! information. +//! +//! ## Usage +//! +//! The UART driver implements a number of third-party traits, with the +//! intention of making the HAL inter-compatible with various device drivers +//! from the community. This includes the [embedded-hal], [embedded-hal-nb], and +//! [embedded-io] traits. +//! +//! ## Examples +//! +//! ```rust,no_run +//! fn main(mut uart: LpUart) -> ! { +//! loop { +//! writeln!(uart, "Hello, world!").ok(); +//! esp_lp_hal::delay::Delay.delay_ms(1000); +//! } +//! } +//! ``` +//! +//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/ +//! [embedded-hal-nb]: https://docs.rs/embedded-hal-nb/latest/embedded_hal_nb/ +//! [embedded-io]: https://docs.rs/embedded-io/latest/embedded_io/ + +use crate::pac::LP_UART; + +const UART_FIFO_SIZE: u16 = property!("lp_uart.ram_size"); + +#[doc(hidden)] +pub unsafe fn conjure() -> LpUart { + unsafe { + LpUart { + uart: LP_UART::steal(), + } + } +} + +/// UART Error +#[derive(Debug)] +pub enum Error {} + +#[cfg(feature = "embedded-io")] +impl core::error::Error for Error {} + +#[cfg(feature = "embedded-io")] +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "UART error") + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + embedded_hal_nb::serial::ErrorKind::Other + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_06::Error for Error { + fn kind(&self) -> embedded_io_06::ErrorKind { + embedded_io_06::ErrorKind::Other + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_07::Error for Error { + fn kind(&self) -> embedded_io_07::ErrorKind { + embedded_io_07::ErrorKind::Other + } +} + +/// UART configuration +pub mod config { + /// Number of data bits + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + pub enum DataBits { + /// 5 data bits + DataBits5 = 0, + /// 6 data bits + DataBits6 = 1, + /// 7 data bits + DataBits7 = 2, + /// 8 data bits + #[default] + DataBits8 = 3, + } + + /// Parity check + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + pub enum Parity { + /// No parity + #[default] + ParityNone = 0, + /// Even parity + ParityEven = 1, + /// Odd parity + ParityOdd = 2, + } + + /// Number of stop bits + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + pub enum StopBits { + /// 1 stop bit + #[default] + Stop1 = 1, + /// 1.5 stop bits + Stop1p5 = 2, + /// 2 stop bits + Stop2 = 3, + } + + /// UART configuration + #[derive(Debug, Clone, Copy)] + pub struct Config { + baudrate: u32, + data_bits: DataBits, + parity: Parity, + stop_bits: StopBits, + } + + impl Config { + /// Configure the UART's baud rate + pub fn baudrate(mut self, baudrate: u32) -> Self { + self.baudrate = baudrate; + self + } + + /// Configure the UART to use no parity + pub fn parity_none(mut self) -> Self { + self.parity = Parity::ParityNone; + self + } + + /// Configure the UART to use even parity + pub fn parity_even(mut self) -> Self { + self.parity = Parity::ParityEven; + self + } + + /// Configure the UART to use odd parity + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::ParityOdd; + self + } + + /// Configure the UART's data bits + pub fn data_bits(mut self, data_bits: DataBits) -> Self { + self.data_bits = data_bits; + self + } + + /// Configure the UART's stop bits + pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { + self.stop_bits = stop_bits; + self + } + } + + impl Default for Config { + fn default() -> Config { + Config { + baudrate: 115_200, + data_bits: Default::default(), + parity: Default::default(), + stop_bits: Default::default(), + } + } + } +} + +/// LP-UART driver +pub struct LpUart { + uart: LP_UART, +} + +impl LpUart { + /// Read a single byte from the UART in a non-blocking manner. + pub fn read_byte(&mut self) -> nb::Result { + if self.rx_fifo_count() > 0 { + let byte = self.uart.fifo().read().rxfifo_rd_byte().bits(); + Ok(byte) + } else { + Err(nb::Error::WouldBlock) + } + } + + /// Write a single byte to the UART in a non-blocking manner. + pub fn write_byte(&mut self, byte: u8) -> nb::Result<(), Error> { + if self.tx_fifo_count() < UART_FIFO_SIZE { + self.uart + .fifo() + .write(|w| unsafe { w.rxfifo_rd_byte().bits(byte) }); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + /// Write one or more byte to the UART, blocking until the write has + /// completed. + pub fn write_bytes(&mut self, data: &[u8]) -> Result { + let count = data.len(); + + data.iter() + .try_for_each(|c| nb::block!(self.write_byte(*c)))?; + + Ok(count) + } + + /// Flush the UART's transmit buffer in a non-blocking manner. + pub fn flush_tx(&mut self) -> nb::Result<(), Error> { + if self.is_tx_idle() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn rx_fifo_count(&mut self) -> u16 { + self.uart.status().read().rxfifo_cnt().bits().into() + } + + fn tx_fifo_count(&mut self) -> u16 { + self.uart.status().read().txfifo_cnt().bits().into() + } + + fn is_tx_idle(&self) -> bool { + self.uart.fsm_status().read().st_utx_out().bits() == 0 + } +} + +impl core::fmt::Write for LpUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write_bytes(s.as_bytes()) + .map_err(|_| core::fmt::Error)?; + Ok(()) + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal_nb::serial::ErrorType for LpUart { + type Error = Error; +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal_nb::serial::Read for LpUart { + fn read(&mut self) -> nb::Result { + self.read_byte() + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal_nb::serial::Write for LpUart { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.write_byte(word) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.flush_tx() + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_06::ErrorType for LpUart { + type Error = Error; +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_06::Read for LpUart { + fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + while self.rx_fifo_count() == 0 { + // Block until we have received at least one byte + } + + let mut count = 0; + while self.rx_fifo_count() > 0 && count < buf.len() { + buf[count] = self.uart.fifo().read().rxfifo_rd_byte().bits(); + count += 1; + } + + Ok(count) + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_06::ReadReady for LpUart { + fn read_ready(&mut self) -> Result { + Ok(self.rx_fifo_count() > 0) + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_06::Write for LpUart { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_bytes(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + loop { + match self.flush_tx() { + Ok(_) => break, + Err(nb::Error::WouldBlock) => { /* Wait */ } + #[allow(unreachable_patterns)] + Err(nb::Error::Other(e)) => return Err(e), + } + } + + Ok(()) + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_07::ErrorType for LpUart { + type Error = Error; +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_07::Read for LpUart { + fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + while self.rx_fifo_count() == 0 { + // Block until we have received at least one byte + } + + let mut count = 0; + while self.rx_fifo_count() > 0 && count < buf.len() { + buf[count] = self.uart.fifo().read().rxfifo_rd_byte().bits(); + count += 1; + } + + Ok(count) + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_07::ReadReady for LpUart { + fn read_ready(&mut self) -> Result { + Ok(self.rx_fifo_count() > 0) + } +} + +#[cfg(feature = "embedded-io")] +impl embedded_io_07::Write for LpUart { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_bytes(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + loop { + match self.flush_tx() { + Ok(_) => break, + Err(nb::Error::WouldBlock) => { /* Wait */ } + #[allow(unreachable_patterns)] + Err(nb::Error::Other(e)) => return Err(e), + } + } + + Ok(()) + } +} diff --git a/esp-metadata-generated/Cargo.toml b/esp-metadata-generated/Cargo.toml new file mode 100644 index 00000000000..9981f3754ea --- /dev/null +++ b/esp-metadata-generated/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "esp-metadata-generated" +version = "0.3.0" +edition = "2024" +rust-version = "1.86.0" +description = "Generated metadata for Espressif devices" +documentation = "https://docs.espressif.com/projects/rust/esp-metadata-generated/latest/" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +check-configs = [ + { features = [] }, + { features = ["build-script"] }, +] +clippy-configs = [] # don't waste time on this + +[dependencies] + +[features] +build-script = [] +_device-selected = [] +esp32 = ["_device-selected"] +esp32c2 = ["_device-selected"] +esp32c3 = ["_device-selected"] +esp32c5 = ["_device-selected"] +esp32c6 = ["_device-selected"] +esp32h2 = ["_device-selected"] +esp32s2 = ["_device-selected"] +esp32s3 = ["_device-selected"] diff --git a/esp-metadata-generated/README.md b/esp-metadata-generated/README.md new file mode 100644 index 00000000000..e5b60e32af2 --- /dev/null +++ b/esp-metadata-generated/README.md @@ -0,0 +1,34 @@ +# esp-metadata + +[![Crates.io](https://img.shields.io/crates/v/esp-metadata?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-metadata) +[![docs.rs](https://img.shields.io/docsrs/esp-metadata?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-metadata/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-metadata?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +The generated output of `esp-metadata`, intended for use in [build scripts] and HAL crates alike. + +The contents of this crate have been generated by running `cargo xtask update-metadata`. Do not edit by hand. + +[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html + +## [Documentation](https://docs.espressif.com/projects/rust/esp-metadata-generated/latest/) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-metadata-generated/rustfmt.toml b/esp-metadata-generated/rustfmt.toml new file mode 100644 index 00000000000..5cc205161df --- /dev/null +++ b/esp-metadata-generated/rustfmt.toml @@ -0,0 +1,20 @@ +edition = "2024" + +# Comments +format_code_in_doc_comments = true +normalize_comments = true +normalize_doc_attributes = true +wrap_comments = true + +# Code in comments +doc_comment_code_block_width = 100 +comment_width = 100 +format_strings = true + +# Enums +enum_discrim_align_threshold = 35 + +# Imports +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +imports_layout = "HorizontalVertical" diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs new file mode 100644 index 00000000000..af333b3c6cc --- /dev/null +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -0,0 +1,5419 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +use core::ops::Range; +extern crate alloc; +#[cfg(docsrs)] +macro_rules! println { + ($($any:tt)*) => {}; +} +#[doc(hidden)] +#[macro_export] +macro_rules! __assert_features_logic { + ($op:tt, $limit:expr, $msg:literal, $($feature:literal),+ $(,)?) => { + { let enabled : Vec < & str > = [$(if cfg!(feature = $feature) { Some($feature) } + else { None },)+].into_iter().flatten().collect(); assert!(enabled.len() $op + $limit, concat!($msg, + ": {}.\nCurrently enabled: {}. This might be caused by enabled default features.\n"), + [$($feature),+].join(", "), if enabled.is_empty() { "none".to_string() } else { + enabled.join(", ") }); } + }; +} +#[macro_export] +macro_rules! assert_unique_features { + ($($f:literal),+ $(,)?) => { + $crate::__assert_features_logic!(<=, 1, + "\nAt most one of the following features must be enabled", $($f),+); + }; +} +#[macro_export] +macro_rules! assert_unique_used_features { + ($($f:literal),+ $(,)?) => { + $crate::__assert_features_logic!(==, 1, + "\nExactly one of the following features must be enabled", $($f),+); + }; +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(docsrs, doc(cfg(feature = "build-script")))] +pub enum Chip { + Esp32, + Esp32c2, + Esp32c3, + Esp32c5, + Esp32c6, + Esp32h2, + Esp32s2, + Esp32s3, +} +impl core::str::FromStr for Chip { + type Err = alloc::string::String; + fn from_str(s: &str) -> Result { + match s { + "esp32" => Ok(Self::Esp32), + "esp32c2" => Ok(Self::Esp32c2), + "esp32c3" => Ok(Self::Esp32c3), + "esp32c5" => Ok(Self::Esp32c5), + "esp32c6" => Ok(Self::Esp32c6), + "esp32h2" => Ok(Self::Esp32h2), + "esp32s2" => Ok(Self::Esp32s2), + "esp32s3" => Ok(Self::Esp32s3), + _ => Err(alloc::format!( + "Unknown chip {s}. Possible options: esp32, esp32c2, esp32c3, esp32c5, esp32c6, \ + esp32h2, esp32s2, esp32s3" + )), + } + } +} +impl Chip { + /// Tries to extract the active chip from the active cargo features. + /// + /// Exactly one device feature must be enabled for this function to succeed. + pub fn from_cargo_feature() -> Result { + let all_chips = [ + ("CARGO_FEATURE_ESP32", Self::Esp32), + ("CARGO_FEATURE_ESP32C2", Self::Esp32c2), + ("CARGO_FEATURE_ESP32C3", Self::Esp32c3), + ("CARGO_FEATURE_ESP32C5", Self::Esp32c5), + ("CARGO_FEATURE_ESP32C6", Self::Esp32c6), + ("CARGO_FEATURE_ESP32H2", Self::Esp32h2), + ("CARGO_FEATURE_ESP32S2", Self::Esp32s2), + ("CARGO_FEATURE_ESP32S3", Self::Esp32s3), + ]; + let mut chip = None; + for (env, c) in all_chips { + if std::env::var(env).is_ok() { + if chip.is_some() { + return Err( + "Expected exactly one of the following features to be enabled: esp32, \ + esp32c2, esp32c3, esp32c5, esp32c6, esp32h2, esp32s2, esp32s3", + ); + } + chip = Some(c); + } + } + match chip { + Some(chip) => Ok(chip), + None => Err( + "Expected exactly one of the following features to be enabled: esp32, esp32c2, \ + esp32c3, esp32c5, esp32c6, esp32h2, esp32s2, esp32s3", + ), + } + } + /// Returns whether the current chip uses the Tensilica Xtensa ISA. + pub fn is_xtensa(self) -> bool { + self.config().architecture == "xtensa" + } + /// The target triple of the current chip. + pub fn target(self) -> &'static str { + self.config().target + } + /// The simple name of the current chip. + /// + /// ## Example + /// + /// ```rust,no_run + /// assert_eq!(Chip::Esp32s3.name(), "esp32s3"); + /// ``` + pub fn name(self) -> &'static str { + match self { + Self::Esp32 => "esp32", + Self::Esp32c2 => "esp32c2", + Self::Esp32c3 => "esp32c3", + Self::Esp32c5 => "esp32c5", + Self::Esp32c6 => "esp32c6", + Self::Esp32h2 => "esp32h2", + Self::Esp32s2 => "esp32s2", + Self::Esp32s3 => "esp32s3", + } + } + /// Returns whether the chip configuration contains the given symbol. + /// + /// This function is a short-hand for `self.all_symbols().contains(&symbol)`. + /// + /// ## Example + /// + /// ```rust,no_run + /// assert!(Chip::Esp32s3.contains("soc_has_pcnt")); + /// ``` + pub fn contains(self, symbol: &str) -> bool { + self.all_symbols().contains(&symbol) + } + /// Calling this function will define all cfg symbols for the firmware crate to use. + pub fn define_cfgs(self) { + self.config().define_cfgs() + } + /// Returns all symbols as a big slice. + /// + /// ## Example + /// + /// ```rust,no_run + /// assert!(Chip::Esp32s3.all_symbols().contains("soc_has_pcnt")); + /// ``` + pub fn all_symbols(&self) -> &'static [&'static str] { + self.config().symbols + } + /// Returns memory layout information. + pub fn memory_layout(&self) -> &'static MemoryLayout { + self.config().memory_layout + } + /// Returns information about all pins. + pub fn pins(&self) -> &'static [PinInfo] { + self.config().pins + } + /// Returns an iterator over all chips. + /// + /// ## Example + /// + /// ```rust,no_run + /// assert!(Chip::iter().any(|c| c == Chip::Esp32)); + /// ``` + pub fn iter() -> impl Iterator { + [ + Self::Esp32, + Self::Esp32c2, + Self::Esp32c3, + Self::Esp32c5, + Self::Esp32c6, + Self::Esp32h2, + Self::Esp32s2, + Self::Esp32s3, + ] + .into_iter() + } + fn config(self) -> Config { + match self { + Self::Esp32 => Config { + architecture: "xtensa", + target: "xtensa-esp32-none-elf", + symbols: &[ + "esp32", + "xtensa", + "multi_core", + "soc_has_aes", + "soc_has_apb_ctrl", + "soc_has_bb", + "soc_has_dport", + "soc_has_system", + "soc_has_efuse", + "soc_has_emac_dma", + "soc_has_emac_ext", + "soc_has_emac_mac", + "soc_has_flash_encryption", + "soc_has_frc_timer", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hinf", + "soc_has_i2c0", + "soc_has_i2c1", + "soc_has_i2s0", + "soc_has_i2s1", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_mcpwm0", + "soc_has_mcpwm1", + "soc_has_nrx", + "soc_has_pcnt", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_lpwr", + "soc_has_rtc_i2c", + "soc_has_rtc_io", + "soc_has_sdhost", + "soc_has_sens", + "soc_has_sha", + "soc_has_slc", + "soc_has_slchost", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_spi3", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_twai0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uart2", + "soc_has_uhci0", + "soc_has_uhci1", + "soc_has_wifi", + "soc_has_dma_spi2", + "soc_has_dma_spi3", + "soc_has_dma_i2s0", + "soc_has_dma_i2s1", + "soc_has_adc1", + "soc_has_adc2", + "soc_has_bt", + "soc_has_cpu_ctrl", + "soc_has_dac1", + "soc_has_dac2", + "soc_has_flash", + "soc_has_psram", + "soc_has_sw_interrupt", + "soc_has_touch", + "phy", + "psram", + "touch", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "ulp_supported", + "adc_driver_supported", + "aes_driver_supported", + "bt_driver_supported", + "dac_driver_supported", + "dma_driver_supported", + "gpio_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "mcpwm_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "psram_driver_supported", + "rgb_display_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sd_host_driver_supported", + "sd_slave_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "touch_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "ulp_fsm_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "adc_adc2", + "dac_dac1", + "dac_dac2", + "i2c_master_i2c0", + "i2c_master_i2c1", + "spi_master_spi2", + "spi_master_spi3", + "spi_slave_spi2", + "spi_slave_spi3", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "uart_uart2", + "aes_endianness_configurable", + "bt_controller=\"btdm\"", + "dma_kind=\"pdma\"", + "gpio_has_bank_1", + "gpio_gpio_function=\"2\"", + "gpio_constant_0_input=\"48\"", + "gpio_constant_1_input=\"56\"", + "gpio_remap_iomux_pin_registers", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"206\"", + "gpio_output_signal_max=\"256\"", + "i2c_master_separate_filter_config_registers", + "i2c_master_i2c0_data_register_ahb_address=\"1610690588\"", + "i2c_master_i2c0_data_register_ahb_address_is_set", + "i2c_master_max_bus_timeout=\"1048575\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"3\"", + "interrupt_controller=\"xtensa\"", + "phy_combo_module", + "rmt_ram_start=\"1073047552\"", + "rmt_channel_ram_size=\"64\"", + "rmt_has_per_channel_clock", + "rmt_supports_reftick_clock", + "rmt_supports_apb_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"512\"", + "rsa_memory_size_bytes=\"512\"", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_multi_core_enabled", + "soc_rc_fast_clk_default=\"8500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_apll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_pll_f160m_clk", + "soc_has_clock_node_cpu_pll_div_in", + "soc_has_clock_node_cpu_pll_div", + "soc_has_clock_node_syscon_pre_div_in", + "soc_has_clock_node_syscon_pre_div", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_apb_clk_cpu_div2", + "soc_has_clock_node_apb_clk_80m", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_ref_tick_pll", + "soc_has_clock_node_ref_tick_apll", + "soc_has_clock_node_ref_tick_xtal", + "soc_has_clock_node_ref_tick_fosc", + "soc_has_clock_node_ref_tick", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_rc_fast_div_clk", + "soc_has_clock_node_xtal_div_clk", + "soc_has_clock_node_rtc_slow_clk", + "soc_has_clock_node_rtc_fast_clk", + "soc_has_clock_node_uart_mem_clk", + "soc_has_clock_node_mcpwm0_function_clock", + "soc_has_clock_node_mcpwm1_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart0_mem_clock", + "soc_has_clock_node_uart1_function_clock", + "soc_has_clock_node_uart1_mem_clock", + "soc_has_clock_node_uart2_function_clock", + "soc_has_clock_node_uart2_mem_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_slave_supports_dma", + "timergroup_timg_has_timer1", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "wifi_mac_version=\"1\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32", + "cargo:rustc-cfg=xtensa", + "cargo:rustc-cfg=multi_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_ctrl", + "cargo:rustc-cfg=soc_has_bb", + "cargo:rustc-cfg=soc_has_dport", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_emac_dma", + "cargo:rustc-cfg=soc_has_emac_ext", + "cargo:rustc-cfg=soc_has_emac_mac", + "cargo:rustc-cfg=soc_has_flash_encryption", + "cargo:rustc-cfg=soc_has_frc_timer", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hinf", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2c1", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_i2s1", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_mcpwm0", + "cargo:rustc-cfg=soc_has_mcpwm1", + "cargo:rustc-cfg=soc_has_nrx", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_rtc_i2c", + "cargo:rustc-cfg=soc_has_rtc_io", + "cargo:rustc-cfg=soc_has_sdhost", + "cargo:rustc-cfg=soc_has_sens", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_slc", + "cargo:rustc-cfg=soc_has_slchost", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_spi3", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uart2", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_uhci1", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=soc_has_dma_spi2", + "cargo:rustc-cfg=soc_has_dma_spi3", + "cargo:rustc-cfg=soc_has_dma_i2s0", + "cargo:rustc-cfg=soc_has_dma_i2s1", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_adc2", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_cpu_ctrl", + "cargo:rustc-cfg=soc_has_dac1", + "cargo:rustc-cfg=soc_has_dac2", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_psram", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_touch", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=psram", + "cargo:rustc-cfg=touch", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=pm_support_ext0_wakeup", + "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=pm_support_touch_sensor_wakeup", + "cargo:rustc-cfg=ulp_supported", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dac_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=mcpwm_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=psram_driver_supported", + "cargo:rustc-cfg=rgb_display_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sd_host_driver_supported", + "cargo:rustc-cfg=sd_slave_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=touch_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=ulp_fsm_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=adc_adc2", + "cargo:rustc-cfg=dac_dac1", + "cargo:rustc-cfg=dac_dac2", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=i2c_master_i2c1", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_master_spi3", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=spi_slave_spi3", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=uart_uart2", + "cargo:rustc-cfg=aes_endianness_configurable", + "cargo:rustc-cfg=bt_controller=\"btdm\"", + "cargo:rustc-cfg=dma_kind=\"pdma\"", + "cargo:rustc-cfg=gpio_has_bank_1", + "cargo:rustc-cfg=gpio_gpio_function=\"2\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"48\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"56\"", + "cargo:rustc-cfg=gpio_remap_iomux_pin_registers", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"206\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"256\"", + "cargo:rustc-cfg=i2c_master_separate_filter_config_registers", + "cargo:rustc-cfg=i2c_master_i2c0_data_register_ahb_address=\"1610690588\"", + "cargo:rustc-cfg=i2c_master_i2c0_data_register_ahb_address_is_set", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"1048575\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"3\"", + "cargo:rustc-cfg=interrupt_controller=\"xtensa\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=rmt_ram_start=\"1073047552\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"64\"", + "cargo:rustc-cfg=rmt_has_per_channel_clock", + "cargo:rustc-cfg=rmt_supports_reftick_clock", + "cargo:rustc-cfg=rmt_supports_apb_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"512\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"512\"", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_multi_core_enabled", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"8500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_apll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f160m_clk", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div_in", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div", + "cargo:rustc-cfg=soc_has_clock_node_syscon_pre_div_in", + "cargo:rustc-cfg=soc_has_clock_node_syscon_pre_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk_cpu_div2", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk_80m", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_pll", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_apll", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_xtal", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_fosc", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_uart_mem_clk", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart2_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart2_mem_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_timer1", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=wifi_mac_version=\"1\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x3FFAE000..0x40000000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x3FFE7E30..0x40000000, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &["strapping"], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &["strapping"], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &["strapping"], + }, + PinInfo { + pin: 6, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 7, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 8, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 9, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 10, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 11, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 12, + limitations: &["strapping"], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 15, + limitations: &["strapping"], + }, + PinInfo { + pin: 16, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 17, + limitations: &["spi_psram"], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &["esp32_pico_v3"], + }, + PinInfo { + pin: 21, + limitations: &[], + }, + PinInfo { + pin: 22, + limitations: &[], + }, + PinInfo { + pin: 23, + limitations: &[], + }, + PinInfo { + pin: 25, + limitations: &[], + }, + PinInfo { + pin: 26, + limitations: &[], + }, + PinInfo { + pin: 27, + limitations: &[], + }, + PinInfo { + pin: 32, + limitations: &[], + }, + PinInfo { + pin: 33, + limitations: &[], + }, + PinInfo { + pin: 34, + limitations: &["input_only"], + }, + PinInfo { + pin: 35, + limitations: &["input_only"], + }, + PinInfo { + pin: 36, + limitations: &["input_only"], + }, + PinInfo { + pin: 37, + limitations: &["input_only"], + }, + PinInfo { + pin: 38, + limitations: &["input_only"], + }, + PinInfo { + pin: 39, + limitations: &["input_only"], + }, + ], + }, + Self::Esp32c2 => Config { + architecture: "riscv", + target: "riscv32imc-unknown-none-elf", + symbols: &[ + "esp32c2", + "riscv", + "single_core", + "soc_has_apb_ctrl", + "soc_has_apb_saradc", + "soc_has_bb", + "soc_has_assist_debug", + "soc_has_dma", + "soc_has_ecc", + "soc_has_efuse", + "soc_has_extmem", + "soc_has_gpio", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_interrupt_core0", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_rng", + "soc_has_lpwr", + "soc_has_modem_clkrst", + "soc_has_sensitive", + "soc_has_sha", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_system", + "soc_has_systimer", + "soc_has_timg0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_xts_aes", + "soc_has_dma_ch0", + "soc_has_adc1", + "soc_has_bt", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_sw_interrupt", + "soc_has_wifi", + "phy", + "swd", + "rom_crc_le", + "rom_crc_be", + "rom_md5_mbedtls", + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "gpio_support_deepsleep_wakeup", + "adc_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "ecc_driver_supported", + "gpio_driver_supported", + "i2c_master_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "phy_driver_supported", + "rng_driver_supported", + "lp_timer_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "uart_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "i2c_master_i2c0", + "spi_master_spi2", + "spi_slave_spi2", + "timergroup_timg0", + "uart_uart0", + "uart_uart1", + "assist_debug_has_sp_monitor", + "bt_controller=\"npl\"", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_max_priority=\"9\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"1\"", + "dma_gdma_version_is_set", + "ecc_zero_extend_writes", + "ecc_has_finite_field_division", + "ecc_has_curve_p192", + "ecc_has_curve_p256", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"31\"", + "gpio_constant_1_input=\"30\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"100\"", + "gpio_output_signal_max=\"128\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_hw_bus_clear", + "i2c_master_has_bus_timeout_enable", + "i2c_master_has_conf_update", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"16\"", + "interrupts_status_registers=\"2\"", + "interrupt_controller=\"riscv_basic\"", + "phy_combo_module", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_cpu_has_csr_pc", + "soc_rc_fast_clk_default=\"17500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_osc_slow_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_rc_fast_div_clk", + "soc_has_clock_node_system_pre_div_in", + "soc_has_clock_node_system_pre_div", + "soc_has_clock_node_cpu_pll_div", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_pll_40m", + "soc_has_clock_node_pll_60m", + "soc_has_clock_node_pll_80m", + "soc_has_clock_node_cpu_div2", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_crypto_clk", + "soc_has_clock_node_mspi_clk", + "soc_has_clock_node_rc_fast_clk_div_n", + "soc_has_clock_node_xtal_div_clk", + "soc_has_clock_node_rtc_slow_clk", + "soc_has_clock_node_rtc_fast_clk", + "soc_has_clock_node_low_power_clk", + "soc_has_clock_node_uart_mem_clk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg0_wdt_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart0_mem_clock", + "soc_has_clock_node_uart1_function_clock", + "soc_has_clock_node_uart1_mem_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_divcnt_rst", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "wifi_mac_version=\"1\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32c2", + "cargo:rustc-cfg=riscv", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_apb_ctrl", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_bb", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ecc", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_extmem", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_modem_clkrst", + "cargo:rustc-cfg=soc_has_sensitive", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_xts_aes", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=swd", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_mbedtls", + "cargo:rustc-cfg=pm_support_wifi_wakeup", + "cargo:rustc-cfg=pm_support_bt_wakeup", + "cargo:rustc-cfg=uart_support_wakeup_int", + "cargo:rustc-cfg=gpio_support_deepsleep_wakeup", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=ecc_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=assist_debug_has_sp_monitor", + "cargo:rustc-cfg=bt_controller=\"npl\"", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_max_priority=\"9\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"1\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=ecc_zero_extend_writes", + "cargo:rustc-cfg=ecc_has_finite_field_division", + "cargo:rustc-cfg=ecc_has_curve_p192", + "cargo:rustc-cfg=ecc_has_curve_p256", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"31\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"30\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"100\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"128\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_hw_bus_clear", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"16\"", + "cargo:rustc-cfg=interrupts_status_registers=\"2\"", + "cargo:rustc-cfg=interrupt_controller=\"riscv_basic\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_cpu_has_csr_pc", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"17500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_osc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div_in", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_40m", + "cargo:rustc-cfg=soc_has_clock_node_pll_60m", + "cargo:rustc-cfg=soc_has_clock_node_pll_80m", + "cargo:rustc-cfg=soc_has_clock_node_cpu_div2", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_crypto_clk", + "cargo:rustc-cfg=soc_has_clock_node_mspi_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk_div_n", + "cargo:rustc-cfg=soc_has_clock_node_xtal_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_low_power_clk", + "cargo:rustc-cfg=soc_has_clock_node_uart_mem_clk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_mem_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_divcnt_rst", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=wifi_mac_version=\"1\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x3FCA0000..0x3FCE0000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x3FCCE800..0x3FCDEB70, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &[], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &[], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &[], + }, + PinInfo { + pin: 8, + limitations: &["strapping"], + }, + PinInfo { + pin: 9, + limitations: &["strapping"], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 12, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 13, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 14, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 15, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 16, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 17, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &[], + }, + ], + }, + Self::Esp32c3 => Config { + architecture: "riscv", + target: "riscv32imc-unknown-none-elf", + symbols: &[ + "esp32c3", + "riscv", + "single_core", + "soc_has_aes", + "soc_has_apb_ctrl", + "soc_has_apb_saradc", + "soc_has_assist_debug", + "soc_has_bb", + "soc_has_dma", + "soc_has_ds", + "soc_has_efuse", + "soc_has_extmem", + "soc_has_fe", + "soc_has_fe2", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hmac", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2s0", + "soc_has_interrupt_core0", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_nrx", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_lpwr", + "soc_has_sensitive", + "soc_has_sha", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_system", + "soc_has_systimer", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_twai0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uhci0", + "soc_has_usb_device", + "soc_has_xts_aes", + "soc_has_dma_ch0", + "soc_has_dma_ch1", + "soc_has_dma_ch2", + "soc_has_adc1", + "soc_has_adc2", + "soc_has_bt", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_sw_interrupt", + "soc_has_tsens", + "soc_has_wifi", + "phy", + "swd", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "gpio_support_deepsleep_wakeup", + "adc_driver_supported", + "aes_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "gpio_driver_supported", + "hmac_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "phy_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "uhci_driver_supported", + "usb_serial_jtag_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "adc_adc2", + "i2c_master_i2c0", + "spi_master_spi2", + "spi_slave_spi2", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_has_split_text_registers", + "assist_debug_has_sp_monitor", + "assist_debug_has_region_monitor", + "bt_controller=\"btdm\"", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_max_priority=\"9\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"1\"", + "dma_gdma_version_is_set", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"31\"", + "gpio_constant_1_input=\"30\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"100\"", + "gpio_output_signal_max=\"128\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_hw_bus_clear", + "i2c_master_has_bus_timeout_enable", + "i2c_master_has_conf_update", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"2\"", + "interrupt_controller=\"riscv_basic\"", + "phy_combo_module", + "phy_backed_up_digital_register_count=\"21\"", + "phy_backed_up_digital_register_count_is_set", + "rmt_ram_start=\"1610703872\"", + "rmt_channel_ram_size=\"48\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_wrap", + "rmt_has_rx_demodulation", + "rmt_supports_none_clock", + "rmt_supports_apb_clock", + "rmt_supports_rcfast_clock", + "rmt_supports_xtal_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"384\"", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_cpu_has_csr_pc", + "soc_rc_fast_clk_default=\"17500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_rc_fast_div_clk", + "soc_has_clock_node_system_pre_div_in", + "soc_has_clock_node_system_pre_div", + "soc_has_clock_node_cpu_pll_div_out", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_pll_80m", + "soc_has_clock_node_pll_160m", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_crypto_clk", + "soc_has_clock_node_rc_fast_clk_div_n", + "soc_has_clock_node_xtal_div_clk", + "soc_has_clock_node_rtc_slow_clk", + "soc_has_clock_node_rtc_fast_clk", + "soc_has_clock_node_low_power_clk", + "soc_has_clock_node_uart_mem_clk", + "soc_has_clock_node_rmt_sclk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg0_wdt_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_timg1_wdt_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart0_mem_clock", + "soc_has_clock_node_uart1_function_clock", + "soc_has_clock_node_uart1_mem_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_divcnt_rst", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "wifi_mac_version=\"1\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32c3", + "cargo:rustc-cfg=riscv", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_ctrl", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_bb", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_extmem", + "cargo:rustc-cfg=soc_has_fe", + "cargo:rustc-cfg=soc_has_fe2", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_nrx", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_sensitive", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb_device", + "cargo:rustc-cfg=soc_has_xts_aes", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_dma_ch1", + "cargo:rustc-cfg=soc_has_dma_ch2", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_adc2", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_tsens", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=swd", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=pm_support_wifi_wakeup", + "cargo:rustc-cfg=pm_support_bt_wakeup", + "cargo:rustc-cfg=uart_support_wakeup_int", + "cargo:rustc-cfg=gpio_support_deepsleep_wakeup", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=hmac_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=uhci_driver_supported", + "cargo:rustc-cfg=usb_serial_jtag_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=adc_adc2", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=assist_debug_has_sp_monitor", + "cargo:rustc-cfg=assist_debug_has_region_monitor", + "cargo:rustc-cfg=bt_controller=\"btdm\"", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_max_priority=\"9\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"1\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"31\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"30\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"100\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"128\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_hw_bus_clear", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"2\"", + "cargo:rustc-cfg=interrupt_controller=\"riscv_basic\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=phy_backed_up_digital_register_count=\"21\"", + "cargo:rustc-cfg=phy_backed_up_digital_register_count_is_set", + "cargo:rustc-cfg=rmt_ram_start=\"1610703872\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_wrap", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_supports_none_clock", + "cargo:rustc-cfg=rmt_supports_apb_clock", + "cargo:rustc-cfg=rmt_supports_rcfast_clock", + "cargo:rustc-cfg=rmt_supports_xtal_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"384\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_cpu_has_csr_pc", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"17500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div_in", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div_out", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_80m", + "cargo:rustc-cfg=soc_has_clock_node_pll_160m", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_crypto_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk_div_n", + "cargo:rustc-cfg=soc_has_clock_node_xtal_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_low_power_clk", + "cargo:rustc-cfg=soc_has_clock_node_uart_mem_clk", + "cargo:rustc-cfg=soc_has_clock_node_rmt_sclk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_mem_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_divcnt_rst", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=wifi_mac_version=\"1\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x3FC80000..0x3FCE0000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x3FCCE400..0x3FCDE710, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &[], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &["strapping"], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &[], + }, + PinInfo { + pin: 8, + limitations: &["strapping"], + }, + PinInfo { + pin: 9, + limitations: &["strapping"], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 12, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 13, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 14, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 15, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 16, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 17, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &[], + }, + PinInfo { + pin: 21, + limitations: &[], + }, + ], + }, + Self::Esp32c5 => Config { + architecture: "riscv", + target: "riscv32imac-unknown-none-elf", + symbols: &[ + "esp32c5", + "riscv", + "single_core", + "soc_has_aes", + "soc_has_assist_debug", + "soc_has_apb_saradc", + "soc_has_clint", + "soc_has_dma", + "soc_has_ds", + "soc_has_ecc", + "soc_has_ecdsa", + "soc_has_efuse", + "soc_has_etm", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hmac", + "soc_has_hp_apm", + "soc_has_hp_sys", + "soc_has_huk", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2s0", + "soc_has_ieee802154", + "soc_has_interrupt_core0", + "soc_has_intpri", + "soc_has_io_mux", + "soc_has_keymng", + "soc_has_lp_ana", + "soc_has_lp_aon", + "soc_has_lp_apm0", + "soc_has_lp_clkrst", + "soc_has_lp_i2c_ana_mst", + "soc_has_lp_io_mux", + "soc_has_lp_peri", + "soc_has_lp_tee", + "soc_has_lp_timer", + "soc_has_lp_uart", + "soc_has_lp_wdt", + "soc_has_lpwr", + "soc_has_mcpwm0", + "soc_has_mem_monitor", + "soc_has_modem_lpcon", + "soc_has_modem_syscon", + "soc_has_parl_io", + "soc_has_pau", + "soc_has_pcnt", + "soc_has_pcr", + "soc_has_pmu", + "soc_has_pvt_monitor", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_sha", + "soc_has_slc", + "soc_has_spi2", + "soc_has_system", + "soc_has_systimer", + "soc_has_tee", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uhci0", + "soc_has_usb_device", + "soc_has_dma_ch0", + "soc_has_dma_ch1", + "soc_has_dma_ch2", + "soc_has_bt", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_lp_core", + "soc_has_sw_interrupt", + "soc_has_wifi", + "soc_has_mem2mem0", + "soc_has_mem2mem1", + "soc_has_mem2mem2", + "soc_has_mem2mem3", + "soc_has_mem2mem4", + "soc_has_mem2mem5", + "soc_has_mem2mem6", + "soc_has_mem2mem7", + "soc_has_mem2mem8", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "aes_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "ecc_driver_supported", + "gpio_driver_supported", + "i2c_master_driver_supported", + "ieee802154_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "lp_i2c_master_driver_supported", + "parl_io_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "sha_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "timergroup_driver_supported", + "uart_driver_supported", + "uhci_driver_supported", + "usb_serial_jtag_driver_supported", + "wifi_driver_supported", + "i2c_master_i2c0", + "spi_master_spi2", + "spi_slave_spi2", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_has_split_text_registers", + "assist_debug_has_sp_monitor", + "assist_debug_has_region_monitor", + "bt_controller=\"npl\"", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_separate_in_out_interrupts", + "dma_max_priority=\"5\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"2\"", + "dma_gdma_version_is_set", + "ecc_separate_jacobian_point_memory", + "ecc_has_memory_clock_gate", + "ecc_supports_enhanced_security", + "ecc_has_modular_arithmetic", + "ecc_has_point_addition", + "ecc_has_curve_p192", + "ecc_has_curve_p256", + "ecc_has_curve_p384", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"96\"", + "gpio_constant_1_input=\"64\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"116\"", + "gpio_output_signal_max=\"256\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_bus_timeout_enable", + "i2c_master_can_estimate_nack_reason", + "i2c_master_has_conf_update", + "i2c_master_has_reliable_fsm_reset", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"3\"", + "interrupt_controller=\"clic\"", + "lp_i2c_master_fifo_size=\"16\"", + "lp_uart_ram_size=\"32\"", + "parl_io_version=\"2\"", + "phy_combo_module", + "rmt_ram_start=\"1610638336\"", + "rmt_channel_ram_size=\"48\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_loop_auto_stop", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_wrap", + "rmt_has_rx_demodulation", + "rmt_supports_xtal_clock", + "rmt_supports_rcfast_clock", + "rmt_supports_pll80mhz_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"384\"", + "sha_dma", + "soc_cpu_has_branch_predictor", + "soc_cpu_csr_prv_mode=\"2064\"", + "soc_cpu_csr_prv_mode_is_set", + "soc_rc_fast_clk_default=\"17500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_osc_slow_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_pll_f12m", + "soc_has_clock_node_pll_f20m", + "soc_has_clock_node_pll_f40m", + "soc_has_clock_node_pll_f48m", + "soc_has_clock_node_pll_f60m", + "soc_has_clock_node_pll_f80m", + "soc_has_clock_node_pll_f120m", + "soc_has_clock_node_pll_f160m", + "soc_has_clock_node_pll_f240m", + "soc_has_clock_node_hp_root_clk", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_ahb_clk", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_xtal_d2_clk", + "soc_has_clock_node_lp_fast_clk", + "soc_has_clock_node_lp_slow_clk", + "soc_has_clock_node_crypto_clk", + "soc_has_clock_node_timg_calibration_clock", + "soc_has_clock_node_parlio_rx_clock", + "soc_has_clock_node_parlio_tx_clock", + "soc_has_clock_node_rmt_sclk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_wdt_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_wdt_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart1_function_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_master_has_clk_pre_div", + "timergroup_timg_has_divcnt_rst", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "uart_peripheral_controls_mem_clk", + "uhci_combined_uart_selector_field", + "wifi_mac_version=\"3\"", + "wifi_has_5g", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32c5", + "cargo:rustc-cfg=riscv", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_clint", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_ecc", + "cargo:rustc-cfg=soc_has_ecdsa", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_etm", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_hp_apm", + "cargo:rustc-cfg=soc_has_hp_sys", + "cargo:rustc-cfg=soc_has_huk", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_ieee802154", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_intpri", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_keymng", + "cargo:rustc-cfg=soc_has_lp_ana", + "cargo:rustc-cfg=soc_has_lp_aon", + "cargo:rustc-cfg=soc_has_lp_apm0", + "cargo:rustc-cfg=soc_has_lp_clkrst", + "cargo:rustc-cfg=soc_has_lp_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_lp_io_mux", + "cargo:rustc-cfg=soc_has_lp_peri", + "cargo:rustc-cfg=soc_has_lp_tee", + "cargo:rustc-cfg=soc_has_lp_timer", + "cargo:rustc-cfg=soc_has_lp_uart", + "cargo:rustc-cfg=soc_has_lp_wdt", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_mcpwm0", + "cargo:rustc-cfg=soc_has_mem_monitor", + "cargo:rustc-cfg=soc_has_modem_lpcon", + "cargo:rustc-cfg=soc_has_modem_syscon", + "cargo:rustc-cfg=soc_has_parl_io", + "cargo:rustc-cfg=soc_has_pau", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_pcr", + "cargo:rustc-cfg=soc_has_pmu", + "cargo:rustc-cfg=soc_has_pvt_monitor", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_slc", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_tee", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb_device", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_dma_ch1", + "cargo:rustc-cfg=soc_has_dma_ch2", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_lp_core", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=soc_has_mem2mem0", + "cargo:rustc-cfg=soc_has_mem2mem1", + "cargo:rustc-cfg=soc_has_mem2mem2", + "cargo:rustc-cfg=soc_has_mem2mem3", + "cargo:rustc-cfg=soc_has_mem2mem4", + "cargo:rustc-cfg=soc_has_mem2mem5", + "cargo:rustc-cfg=soc_has_mem2mem6", + "cargo:rustc-cfg=soc_has_mem2mem7", + "cargo:rustc-cfg=soc_has_mem2mem8", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=ecc_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=ieee802154_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=lp_i2c_master_driver_supported", + "cargo:rustc-cfg=parl_io_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=uhci_driver_supported", + "cargo:rustc-cfg=usb_serial_jtag_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=assist_debug_has_sp_monitor", + "cargo:rustc-cfg=assist_debug_has_region_monitor", + "cargo:rustc-cfg=bt_controller=\"npl\"", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_separate_in_out_interrupts", + "cargo:rustc-cfg=dma_max_priority=\"5\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"2\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=ecc_separate_jacobian_point_memory", + "cargo:rustc-cfg=ecc_has_memory_clock_gate", + "cargo:rustc-cfg=ecc_supports_enhanced_security", + "cargo:rustc-cfg=ecc_has_modular_arithmetic", + "cargo:rustc-cfg=ecc_has_point_addition", + "cargo:rustc-cfg=ecc_has_curve_p192", + "cargo:rustc-cfg=ecc_has_curve_p256", + "cargo:rustc-cfg=ecc_has_curve_p384", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"96\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"64\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"116\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"256\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_can_estimate_nack_reason", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_reliable_fsm_reset", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"3\"", + "cargo:rustc-cfg=interrupt_controller=\"clic\"", + "cargo:rustc-cfg=lp_i2c_master_fifo_size=\"16\"", + "cargo:rustc-cfg=lp_uart_ram_size=\"32\"", + "cargo:rustc-cfg=parl_io_version=\"2\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=rmt_ram_start=\"1610638336\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_loop_auto_stop", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_wrap", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_supports_xtal_clock", + "cargo:rustc-cfg=rmt_supports_rcfast_clock", + "cargo:rustc-cfg=rmt_supports_pll80mhz_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"384\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=soc_cpu_has_branch_predictor", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode=\"2064\"", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode_is_set", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"17500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_osc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f12m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f20m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f40m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f48m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f60m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f80m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f120m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f160m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f240m", + "cargo:rustc-cfg=soc_has_clock_node_hp_root_clk", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_ahb_clk", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal_d2_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_crypto_clk", + "cargo:rustc-cfg=soc_has_clock_node_timg_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_rx_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_tx_clock", + "cargo:rustc-cfg=soc_has_clock_node_rmt_sclk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_master_has_clk_pre_div", + "cargo:rustc-cfg=timergroup_timg_has_divcnt_rst", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=uart_peripheral_controls_mem_clk", + "cargo:rustc-cfg=uhci_combined_uart_selector_field", + "cargo:rustc-cfg=wifi_mac_version=\"3\"", + "cargo:rustc-cfg=wifi_has_5g", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x40800000..0x40860000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x0..0x4085E5A0, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &[], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &["strapping"], + }, + PinInfo { + pin: 3, + limitations: &["strapping"], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &["strapping"], + }, + PinInfo { + pin: 8, + limitations: &[], + }, + PinInfo { + pin: 9, + limitations: &[], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &[], + }, + PinInfo { + pin: 12, + limitations: &[], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 23, + limitations: &[], + }, + PinInfo { + pin: 24, + limitations: &[], + }, + PinInfo { + pin: 25, + limitations: &["strapping"], + }, + PinInfo { + pin: 26, + limitations: &["strapping"], + }, + PinInfo { + pin: 27, + limitations: &["strapping"], + }, + PinInfo { + pin: 28, + limitations: &["strapping"], + }, + ], + }, + Self::Esp32c6 => Config { + architecture: "riscv", + target: "riscv32imac-unknown-none-elf", + symbols: &[ + "esp32c6", + "riscv", + "single_core", + "soc_has_aes", + "soc_has_apb_saradc", + "soc_has_assist_debug", + "soc_has_atomic", + "soc_has_dma", + "soc_has_ds", + "soc_has_ecc", + "soc_has_efuse", + "soc_has_extmem", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hinf", + "soc_has_hmac", + "soc_has_hp_apm", + "soc_has_hp_sys", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2s0", + "soc_has_ieee802154", + "soc_has_interrupt_core0", + "soc_has_intpri", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_lp_ana", + "soc_has_lp_aon", + "soc_has_lp_apm", + "soc_has_lp_apm0", + "soc_has_lp_clkrst", + "soc_has_lp_i2c0", + "soc_has_lp_i2c_ana_mst", + "soc_has_lp_io", + "soc_has_lp_peri", + "soc_has_lp_tee", + "soc_has_lp_timer", + "soc_has_lp_uart", + "soc_has_lp_wdt", + "soc_has_lpwr", + "soc_has_mcpwm0", + "soc_has_mem_monitor", + "soc_has_modem_lpcon", + "soc_has_modem_syscon", + "soc_has_otp_debug", + "soc_has_parl_io", + "soc_has_pau", + "soc_has_pcnt", + "soc_has_pcr", + "soc_has_plic_mx", + "soc_has_pmu", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_sha", + "soc_has_slchost", + "soc_has_etm", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_system", + "soc_has_systimer", + "soc_has_tee", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_trace0", + "soc_has_twai0", + "soc_has_twai1", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uhci0", + "soc_has_usb_device", + "soc_has_dma_ch0", + "soc_has_dma_ch1", + "soc_has_dma_ch2", + "soc_has_adc1", + "soc_has_bt", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_lp_core", + "soc_has_sw_interrupt", + "soc_has_tsens", + "soc_has_wifi", + "soc_has_mem2mem0", + "soc_has_mem2mem1", + "soc_has_mem2mem2", + "soc_has_mem2mem3", + "soc_has_mem2mem4", + "soc_has_mem2mem5", + "soc_has_mem2mem6", + "soc_has_mem2mem7", + "soc_has_mem2mem8", + "phy", + "lp_core", + "swd", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "pm_support_wifi_wakeup", + "pm_support_beacon_wakeup", + "pm_support_bt_wakeup", + "gpio_support_deepsleep_wakeup", + "uart_support_wakeup_int", + "pm_support_ext1_wakeup", + "adc_driver_supported", + "aes_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "ecc_driver_supported", + "etm_driver_supported", + "gpio_driver_supported", + "hmac_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "ieee802154_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "lp_i2c_master_driver_supported", + "lp_uart_driver_supported", + "mcpwm_driver_supported", + "parl_io_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sd_slave_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "uhci_driver_supported", + "ulp_riscv_driver_supported", + "usb_serial_jtag_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "i2c_master_i2c0", + "spi_master_spi2", + "spi_slave_spi2", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_has_split_text_registers", + "assist_debug_has_sp_monitor", + "assist_debug_has_region_monitor", + "bt_controller=\"npl\"", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_separate_in_out_interrupts", + "dma_max_priority=\"9\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"1\"", + "dma_gdma_version_is_set", + "ecc_zero_extend_writes", + "ecc_has_memory_clock_gate", + "ecc_has_curve_p192", + "ecc_has_curve_p256", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"60\"", + "gpio_constant_1_input=\"56\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"124\"", + "gpio_output_signal_max=\"128\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_hw_bus_clear", + "i2c_master_has_bus_timeout_enable", + "i2c_master_can_estimate_nack_reason", + "i2c_master_has_conf_update", + "i2c_master_has_reliable_fsm_reset", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"3\"", + "interrupt_controller=\"plic\"", + "lp_i2c_master_fifo_size=\"16\"", + "lp_uart_ram_size=\"32\"", + "parl_io_version=\"1\"", + "phy_combo_module", + "rmt_ram_start=\"1610638336\"", + "rmt_channel_ram_size=\"48\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_loop_auto_stop", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_wrap", + "rmt_has_rx_demodulation", + "rmt_supports_none_clock", + "rmt_supports_pll80mhz_clock", + "rmt_supports_rcfast_clock", + "rmt_supports_xtal_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"384\"", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_cpu_has_csr_pc", + "soc_cpu_csr_prv_mode=\"3088\"", + "soc_cpu_csr_prv_mode_is_set", + "soc_rc_fast_clk_default=\"17500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_osc_slow_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_soc_root_clk", + "soc_has_clock_node_hp_root_clk", + "soc_has_clock_node_cpu_hs_div", + "soc_has_clock_node_cpu_ls_div", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_ahb_hs_div", + "soc_has_clock_node_ahb_ls_div", + "soc_has_clock_node_ahb_clk", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_mspi_fast_hs_clk", + "soc_has_clock_node_mspi_fast_ls_clk", + "soc_has_clock_node_mspi_fast_clk", + "soc_has_clock_node_pll_f48m", + "soc_has_clock_node_pll_f80m", + "soc_has_clock_node_pll_f160m", + "soc_has_clock_node_pll_f240m", + "soc_has_clock_node_ledc_sclk", + "soc_has_clock_node_xtal_d2_clk", + "soc_has_clock_node_lp_fast_clk", + "soc_has_clock_node_lp_slow_clk", + "soc_has_clock_node_mcpwm0_function_clock", + "soc_has_clock_node_parlio_rx_clock", + "soc_has_clock_node_parlio_tx_clock", + "soc_has_clock_node_rmt_sclk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg0_wdt_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_timg1_wdt_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart1_function_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_divcnt_rst", + "timergroup_rc_fast_calibration_divider", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "uart_peripheral_controls_mem_clk", + "wifi_has_wifi6", + "wifi_mac_version=\"2\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32c6", + "cargo:rustc-cfg=riscv", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_atomic", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_ecc", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_extmem", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hinf", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_hp_apm", + "cargo:rustc-cfg=soc_has_hp_sys", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_ieee802154", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_intpri", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_lp_ana", + "cargo:rustc-cfg=soc_has_lp_aon", + "cargo:rustc-cfg=soc_has_lp_apm", + "cargo:rustc-cfg=soc_has_lp_apm0", + "cargo:rustc-cfg=soc_has_lp_clkrst", + "cargo:rustc-cfg=soc_has_lp_i2c0", + "cargo:rustc-cfg=soc_has_lp_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_lp_io", + "cargo:rustc-cfg=soc_has_lp_peri", + "cargo:rustc-cfg=soc_has_lp_tee", + "cargo:rustc-cfg=soc_has_lp_timer", + "cargo:rustc-cfg=soc_has_lp_uart", + "cargo:rustc-cfg=soc_has_lp_wdt", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_mcpwm0", + "cargo:rustc-cfg=soc_has_mem_monitor", + "cargo:rustc-cfg=soc_has_modem_lpcon", + "cargo:rustc-cfg=soc_has_modem_syscon", + "cargo:rustc-cfg=soc_has_otp_debug", + "cargo:rustc-cfg=soc_has_parl_io", + "cargo:rustc-cfg=soc_has_pau", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_pcr", + "cargo:rustc-cfg=soc_has_plic_mx", + "cargo:rustc-cfg=soc_has_pmu", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_slchost", + "cargo:rustc-cfg=soc_has_etm", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_tee", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_trace0", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_twai1", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb_device", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_dma_ch1", + "cargo:rustc-cfg=soc_has_dma_ch2", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_lp_core", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_tsens", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=soc_has_mem2mem0", + "cargo:rustc-cfg=soc_has_mem2mem1", + "cargo:rustc-cfg=soc_has_mem2mem2", + "cargo:rustc-cfg=soc_has_mem2mem3", + "cargo:rustc-cfg=soc_has_mem2mem4", + "cargo:rustc-cfg=soc_has_mem2mem5", + "cargo:rustc-cfg=soc_has_mem2mem6", + "cargo:rustc-cfg=soc_has_mem2mem7", + "cargo:rustc-cfg=soc_has_mem2mem8", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=lp_core", + "cargo:rustc-cfg=swd", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=pm_support_wifi_wakeup", + "cargo:rustc-cfg=pm_support_beacon_wakeup", + "cargo:rustc-cfg=pm_support_bt_wakeup", + "cargo:rustc-cfg=gpio_support_deepsleep_wakeup", + "cargo:rustc-cfg=uart_support_wakeup_int", + "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=ecc_driver_supported", + "cargo:rustc-cfg=etm_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=hmac_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=ieee802154_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=lp_i2c_master_driver_supported", + "cargo:rustc-cfg=lp_uart_driver_supported", + "cargo:rustc-cfg=mcpwm_driver_supported", + "cargo:rustc-cfg=parl_io_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sd_slave_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=uhci_driver_supported", + "cargo:rustc-cfg=ulp_riscv_driver_supported", + "cargo:rustc-cfg=usb_serial_jtag_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=assist_debug_has_sp_monitor", + "cargo:rustc-cfg=assist_debug_has_region_monitor", + "cargo:rustc-cfg=bt_controller=\"npl\"", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_separate_in_out_interrupts", + "cargo:rustc-cfg=dma_max_priority=\"9\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"1\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=ecc_zero_extend_writes", + "cargo:rustc-cfg=ecc_has_memory_clock_gate", + "cargo:rustc-cfg=ecc_has_curve_p192", + "cargo:rustc-cfg=ecc_has_curve_p256", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"60\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"56\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"124\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"128\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_hw_bus_clear", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_can_estimate_nack_reason", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_reliable_fsm_reset", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"3\"", + "cargo:rustc-cfg=interrupt_controller=\"plic\"", + "cargo:rustc-cfg=lp_i2c_master_fifo_size=\"16\"", + "cargo:rustc-cfg=lp_uart_ram_size=\"32\"", + "cargo:rustc-cfg=parl_io_version=\"1\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=rmt_ram_start=\"1610638336\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_loop_auto_stop", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_wrap", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_supports_none_clock", + "cargo:rustc-cfg=rmt_supports_pll80mhz_clock", + "cargo:rustc-cfg=rmt_supports_rcfast_clock", + "cargo:rustc-cfg=rmt_supports_xtal_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"384\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_cpu_has_csr_pc", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode=\"3088\"", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode_is_set", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"17500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_osc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_soc_root_clk", + "cargo:rustc-cfg=soc_has_clock_node_hp_root_clk", + "cargo:rustc-cfg=soc_has_clock_node_cpu_hs_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_ls_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_ahb_hs_div", + "cargo:rustc-cfg=soc_has_clock_node_ahb_ls_div", + "cargo:rustc-cfg=soc_has_clock_node_ahb_clk", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_mspi_fast_hs_clk", + "cargo:rustc-cfg=soc_has_clock_node_mspi_fast_ls_clk", + "cargo:rustc-cfg=soc_has_clock_node_mspi_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f48m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f80m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f160m", + "cargo:rustc-cfg=soc_has_clock_node_pll_f240m", + "cargo:rustc-cfg=soc_has_clock_node_ledc_sclk", + "cargo:rustc-cfg=soc_has_clock_node_xtal_d2_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_rx_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_tx_clock", + "cargo:rustc-cfg=soc_has_clock_node_rmt_sclk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_divcnt_rst", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_divider", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=uart_peripheral_controls_mem_clk", + "cargo:rustc-cfg=wifi_has_wifi6", + "cargo:rustc-cfg=wifi_mac_version=\"2\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x40800000..0x40880000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x4086E610..0x4087E610, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &[], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &[], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &["strapping"], + }, + PinInfo { + pin: 5, + limitations: &["strapping"], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &[], + }, + PinInfo { + pin: 8, + limitations: &["strapping"], + }, + PinInfo { + pin: 9, + limitations: &["strapping"], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &[], + }, + PinInfo { + pin: 12, + limitations: &[], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 15, + limitations: &["strapping"], + }, + PinInfo { + pin: 16, + limitations: &[], + }, + PinInfo { + pin: 17, + limitations: &[], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &[], + }, + PinInfo { + pin: 21, + limitations: &[], + }, + PinInfo { + pin: 22, + limitations: &[], + }, + PinInfo { + pin: 23, + limitations: &[], + }, + PinInfo { + pin: 24, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 25, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 26, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 27, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 28, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 29, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 30, + limitations: &["spi_flash"], + }, + ], + }, + Self::Esp32h2 => Config { + architecture: "riscv", + target: "riscv32imac-unknown-none-elf", + symbols: &[ + "esp32h2", + "riscv", + "single_core", + "soc_has_aes", + "soc_has_apb_saradc", + "soc_has_assist_debug", + "soc_has_dma", + "soc_has_ds", + "soc_has_ecc", + "soc_has_efuse", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hmac", + "soc_has_hp_apm", + "soc_has_hp_sys", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2c1", + "soc_has_i2s0", + "soc_has_ieee802154", + "soc_has_interrupt_core0", + "soc_has_intpri", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_lpwr", + "soc_has_lp_ana", + "soc_has_lp_aon", + "soc_has_lp_apm", + "soc_has_lp_apm0", + "soc_has_lp_clkrst", + "soc_has_lp_peri", + "soc_has_lp_timer", + "soc_has_lp_wdt", + "soc_has_mcpwm0", + "soc_has_mem_monitor", + "soc_has_modem_lpcon", + "soc_has_modem_syscon", + "soc_has_otp_debug", + "soc_has_parl_io", + "soc_has_pau", + "soc_has_pcnt", + "soc_has_pcr", + "soc_has_plic_mx", + "soc_has_pmu", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_sha", + "soc_has_etm", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_system", + "soc_has_systimer", + "soc_has_tee", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_trace0", + "soc_has_twai0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uhci0", + "soc_has_usb_device", + "soc_has_dma_ch0", + "soc_has_dma_ch1", + "soc_has_dma_ch2", + "soc_has_adc1", + "soc_has_bt", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_sw_interrupt", + "soc_has_mem2mem0", + "soc_has_mem2mem1", + "soc_has_mem2mem2", + "soc_has_mem2mem3", + "soc_has_mem2mem4", + "soc_has_mem2mem5", + "soc_has_mem2mem6", + "soc_has_mem2mem7", + "soc_has_mem2mem8", + "phy", + "swd", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "adc_driver_supported", + "aes_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "ecc_driver_supported", + "etm_driver_supported", + "gpio_driver_supported", + "hmac_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "ieee802154_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "mcpwm_driver_supported", + "parl_io_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "uhci_driver_supported", + "usb_serial_jtag_driver_supported", + "adc_adc1", + "i2c_master_i2c0", + "i2c_master_i2c1", + "spi_master_spi2", + "spi_slave_spi2", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_has_split_text_registers", + "assist_debug_has_sp_monitor", + "assist_debug_has_region_monitor", + "bt_controller=\"npl\"", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_separate_in_out_interrupts", + "dma_max_priority=\"5\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"1\"", + "dma_gdma_version_is_set", + "ecc_zero_extend_writes", + "ecc_separate_jacobian_point_memory", + "ecc_has_memory_clock_gate", + "ecc_supports_enhanced_security", + "ecc_has_modular_arithmetic", + "ecc_has_point_addition", + "ecc_has_curve_p192", + "ecc_has_curve_p256", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"60\"", + "gpio_constant_1_input=\"56\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"124\"", + "gpio_output_signal_max=\"128\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_hw_bus_clear", + "i2c_master_has_bus_timeout_enable", + "i2c_master_can_estimate_nack_reason", + "i2c_master_has_conf_update", + "i2c_master_has_reliable_fsm_reset", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"2\"", + "interrupt_controller=\"plic\"", + "parl_io_version=\"2\"", + "rmt_ram_start=\"1610642432\"", + "rmt_channel_ram_size=\"48\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_loop_auto_stop", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_wrap", + "rmt_has_rx_demodulation", + "rmt_supports_xtal_clock", + "rmt_supports_rcfast_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"384\"", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_cpu_has_csr_pc", + "soc_cpu_csr_prv_mode=\"3088\"", + "soc_cpu_csr_prv_mode_is_set", + "soc_rc_fast_clk_default=\"8500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_f96m_clk", + "soc_has_clock_node_pll_f64m_clk", + "soc_has_clock_node_pll_f48m_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_osc_slow_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_pll_lp_clk", + "soc_has_clock_node_hp_root_clk", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_ahb_clk", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_xtal_d2_clk", + "soc_has_clock_node_lp_fast_clk", + "soc_has_clock_node_lp_slow_clk", + "soc_has_clock_node_mcpwm0_function_clock", + "soc_has_clock_node_parlio_rx_clock", + "soc_has_clock_node_parlio_tx_clock", + "soc_has_clock_node_rmt_sclk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg0_wdt_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_timg1_wdt_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart1_function_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_divcnt_rst", + "timergroup_rc_fast_calibration_divider", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "uart_peripheral_controls_mem_clk", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32h2", + "cargo:rustc-cfg=riscv", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_ecc", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_hp_apm", + "cargo:rustc-cfg=soc_has_hp_sys", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2c1", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_ieee802154", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_intpri", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_lp_ana", + "cargo:rustc-cfg=soc_has_lp_aon", + "cargo:rustc-cfg=soc_has_lp_apm", + "cargo:rustc-cfg=soc_has_lp_apm0", + "cargo:rustc-cfg=soc_has_lp_clkrst", + "cargo:rustc-cfg=soc_has_lp_peri", + "cargo:rustc-cfg=soc_has_lp_timer", + "cargo:rustc-cfg=soc_has_lp_wdt", + "cargo:rustc-cfg=soc_has_mcpwm0", + "cargo:rustc-cfg=soc_has_mem_monitor", + "cargo:rustc-cfg=soc_has_modem_lpcon", + "cargo:rustc-cfg=soc_has_modem_syscon", + "cargo:rustc-cfg=soc_has_otp_debug", + "cargo:rustc-cfg=soc_has_parl_io", + "cargo:rustc-cfg=soc_has_pau", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_pcr", + "cargo:rustc-cfg=soc_has_plic_mx", + "cargo:rustc-cfg=soc_has_pmu", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_etm", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_tee", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_trace0", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb_device", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_dma_ch1", + "cargo:rustc-cfg=soc_has_dma_ch2", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_mem2mem0", + "cargo:rustc-cfg=soc_has_mem2mem1", + "cargo:rustc-cfg=soc_has_mem2mem2", + "cargo:rustc-cfg=soc_has_mem2mem3", + "cargo:rustc-cfg=soc_has_mem2mem4", + "cargo:rustc-cfg=soc_has_mem2mem5", + "cargo:rustc-cfg=soc_has_mem2mem6", + "cargo:rustc-cfg=soc_has_mem2mem7", + "cargo:rustc-cfg=soc_has_mem2mem8", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=swd", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=ecc_driver_supported", + "cargo:rustc-cfg=etm_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=hmac_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=ieee802154_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=mcpwm_driver_supported", + "cargo:rustc-cfg=parl_io_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=uhci_driver_supported", + "cargo:rustc-cfg=usb_serial_jtag_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=i2c_master_i2c1", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=assist_debug_has_sp_monitor", + "cargo:rustc-cfg=assist_debug_has_region_monitor", + "cargo:rustc-cfg=bt_controller=\"npl\"", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_separate_in_out_interrupts", + "cargo:rustc-cfg=dma_max_priority=\"5\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"1\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=ecc_zero_extend_writes", + "cargo:rustc-cfg=ecc_separate_jacobian_point_memory", + "cargo:rustc-cfg=ecc_has_memory_clock_gate", + "cargo:rustc-cfg=ecc_supports_enhanced_security", + "cargo:rustc-cfg=ecc_has_modular_arithmetic", + "cargo:rustc-cfg=ecc_has_point_addition", + "cargo:rustc-cfg=ecc_has_curve_p192", + "cargo:rustc-cfg=ecc_has_curve_p256", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"60\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"56\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"124\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"128\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_hw_bus_clear", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_can_estimate_nack_reason", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_reliable_fsm_reset", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"2\"", + "cargo:rustc-cfg=interrupt_controller=\"plic\"", + "cargo:rustc-cfg=parl_io_version=\"2\"", + "cargo:rustc-cfg=rmt_ram_start=\"1610642432\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_loop_auto_stop", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_wrap", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_supports_xtal_clock", + "cargo:rustc-cfg=rmt_supports_rcfast_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"384\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_cpu_has_csr_pc", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode=\"3088\"", + "cargo:rustc-cfg=soc_cpu_csr_prv_mode_is_set", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"8500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f96m_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f64m_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_f48m_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_osc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_lp_clk", + "cargo:rustc-cfg=soc_has_clock_node_hp_root_clk", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_ahb_clk", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal_d2_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_lp_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_rx_clock", + "cargo:rustc-cfg=soc_has_clock_node_parlio_tx_clock", + "cargo:rustc-cfg=soc_has_clock_node_rmt_sclk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_wdt_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_divcnt_rst", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_divider", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=uart_peripheral_controls_mem_clk", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x40800000..0x40850000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x4083EFD0..0x4084FEE0, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &[], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &[], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 7, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 8, + limitations: &["strapping"], + }, + PinInfo { + pin: 9, + limitations: &["strapping"], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &[], + }, + PinInfo { + pin: 12, + limitations: &[], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 22, + limitations: &[], + }, + PinInfo { + pin: 23, + limitations: &[], + }, + PinInfo { + pin: 24, + limitations: &[], + }, + PinInfo { + pin: 25, + limitations: &["strapping"], + }, + PinInfo { + pin: 26, + limitations: &[], + }, + PinInfo { + pin: 27, + limitations: &[], + }, + ], + }, + Self::Esp32s2 => Config { + architecture: "xtensa", + target: "xtensa-esp32s2-none-elf", + symbols: &[ + "esp32s2", + "xtensa", + "single_core", + "soc_has_aes", + "soc_has_apb_saradc", + "soc_has_dedicated_gpio", + "soc_has_ds", + "soc_has_efuse", + "soc_has_extmem", + "soc_has_fe", + "soc_has_fe2", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hmac", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2c1", + "soc_has_i2s0", + "soc_has_interrupt_core0", + "soc_has_io_mux", + "soc_has_ledc", + "soc_has_nrx", + "soc_has_pcnt", + "soc_has_pms", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_lpwr", + "soc_has_rtc_i2c", + "soc_has_rtc_io", + "soc_has_sens", + "soc_has_sha", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_spi3", + "soc_has_syscon", + "soc_has_system", + "soc_has_systimer", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_twai0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uhci0", + "soc_has_usb0", + "soc_has_usb_wrap", + "soc_has_xts_aes", + "soc_has_wifi", + "soc_has_dma_spi2", + "soc_has_dma_spi3", + "soc_has_dma_i2s0", + "soc_has_dma_crypto", + "soc_has_dma_copy", + "soc_has_adc1", + "soc_has_adc2", + "soc_has_dac1", + "soc_has_dac2", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_psram", + "soc_has_sw_interrupt", + "soc_has_ulp_riscv_core", + "phy", + "psram", + "psram_dma", + "ulp_riscv_core", + "rom_crc_le", + "rom_md5_bsd", + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "pm_support_wifi_wakeup", + "uart_support_wakeup_int", + "ulp_supported", + "riscv_coproc_supported", + "adc_driver_supported", + "aes_driver_supported", + "dac_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "gpio_driver_supported", + "hmac_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "psram_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "ulp_fsm_driver_supported", + "ulp_riscv_driver_supported", + "usb_otg_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "adc_adc2", + "dac_dac1", + "dac_dac2", + "i2c_master_i2c0", + "i2c_master_i2c1", + "spi_master_spi2", + "spi_master_spi3", + "spi_slave_spi2", + "spi_slave_spi3", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_dma_mode_gcm", + "aes_has_split_text_registers", + "aes_endianness_configurable", + "dedicated_gpio_needs_initialization", + "dma_kind=\"pdma\"", + "dma_supports_mem2mem", + "gpio_has_bank_1", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"60\"", + "gpio_constant_1_input=\"56\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"242\"", + "gpio_output_signal_max=\"256\"", + "i2c_master_has_bus_timeout_enable", + "i2c_master_separate_filter_config_registers", + "i2c_master_has_arbitration_en", + "i2c_master_i2c0_data_register_ahb_address=\"1610690588\"", + "i2c_master_i2c0_data_register_ahb_address_is_set", + "i2c_master_max_bus_timeout=\"16777215\"", + "i2c_master_ll_intr_mask=\"131071\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"3\"", + "interrupt_controller=\"xtensa\"", + "rmt_ram_start=\"1061250048\"", + "rmt_channel_ram_size=\"64\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_demodulation", + "rmt_has_per_channel_clock", + "rmt_supports_reftick_clock", + "rmt_supports_apb_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"512\"", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_rc_fast_clk_default=\"8500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_apll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_cpu_pll_div_in", + "soc_has_clock_node_cpu_pll_div", + "soc_has_clock_node_system_pre_div_in", + "soc_has_clock_node_system_pre_div", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_apb_clk_cpu_div2", + "soc_has_clock_node_apb_clk_80m", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_ref_tick_xtal", + "soc_has_clock_node_ref_tick_ck8m", + "soc_has_clock_node_ref_tick", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_rc_fast_div_clk", + "soc_has_clock_node_xtal_div_clk", + "soc_has_clock_node_rtc_slow_clk", + "soc_has_clock_node_rtc_fast_clk", + "soc_has_clock_node_uart_mem_clk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart0_mem_clock", + "soc_has_clock_node_uart1_function_clock", + "soc_has_clock_node_uart1_mem_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_octal", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_timer1", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "wifi_mac_version=\"1\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32s2", + "cargo:rustc-cfg=xtensa", + "cargo:rustc-cfg=single_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_dedicated_gpio", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_extmem", + "cargo:rustc-cfg=soc_has_fe", + "cargo:rustc-cfg=soc_has_fe2", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2c1", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_nrx", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_pms", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_rtc_i2c", + "cargo:rustc-cfg=soc_has_rtc_io", + "cargo:rustc-cfg=soc_has_sens", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_spi3", + "cargo:rustc-cfg=soc_has_syscon", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb0", + "cargo:rustc-cfg=soc_has_usb_wrap", + "cargo:rustc-cfg=soc_has_xts_aes", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=soc_has_dma_spi2", + "cargo:rustc-cfg=soc_has_dma_spi3", + "cargo:rustc-cfg=soc_has_dma_i2s0", + "cargo:rustc-cfg=soc_has_dma_crypto", + "cargo:rustc-cfg=soc_has_dma_copy", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_adc2", + "cargo:rustc-cfg=soc_has_dac1", + "cargo:rustc-cfg=soc_has_dac2", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_psram", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_ulp_riscv_core", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=psram", + "cargo:rustc-cfg=psram_dma", + "cargo:rustc-cfg=ulp_riscv_core", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=pm_support_ext0_wakeup", + "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=pm_support_touch_sensor_wakeup", + "cargo:rustc-cfg=pm_support_wifi_wakeup", + "cargo:rustc-cfg=uart_support_wakeup_int", + "cargo:rustc-cfg=ulp_supported", + "cargo:rustc-cfg=riscv_coproc_supported", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=dac_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=hmac_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=psram_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=ulp_fsm_driver_supported", + "cargo:rustc-cfg=ulp_riscv_driver_supported", + "cargo:rustc-cfg=usb_otg_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=adc_adc2", + "cargo:rustc-cfg=dac_dac1", + "cargo:rustc-cfg=dac_dac2", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=i2c_master_i2c1", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_master_spi3", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=spi_slave_spi3", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_dma_mode_gcm", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=aes_endianness_configurable", + "cargo:rustc-cfg=dedicated_gpio_needs_initialization", + "cargo:rustc-cfg=dma_kind=\"pdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=gpio_has_bank_1", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"60\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"56\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"242\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"256\"", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_separate_filter_config_registers", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_i2c0_data_register_ahb_address=\"1610690588\"", + "cargo:rustc-cfg=i2c_master_i2c0_data_register_ahb_address_is_set", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"16777215\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"131071\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"3\"", + "cargo:rustc-cfg=interrupt_controller=\"xtensa\"", + "cargo:rustc-cfg=rmt_ram_start=\"1061250048\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"64\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_has_per_channel_clock", + "cargo:rustc-cfg=rmt_supports_reftick_clock", + "cargo:rustc-cfg=rmt_supports_apb_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"512\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"8500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_apll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div_in", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div_in", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk_cpu_div2", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk_80m", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_xtal", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick_ck8m", + "cargo:rustc-cfg=soc_has_clock_node_ref_tick", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_uart_mem_clk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_mem_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_octal", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_timer1", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=wifi_mac_version=\"1\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x3FFB0000..0x40000000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x3FFDE000..0x40000000, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &["strapping"], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &[], + }, + PinInfo { + pin: 3, + limitations: &[], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &[], + }, + PinInfo { + pin: 8, + limitations: &[], + }, + PinInfo { + pin: 9, + limitations: &[], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &[], + }, + PinInfo { + pin: 12, + limitations: &[], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 15, + limitations: &[], + }, + PinInfo { + pin: 16, + limitations: &[], + }, + PinInfo { + pin: 17, + limitations: &[], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &[], + }, + PinInfo { + pin: 21, + limitations: &[], + }, + PinInfo { + pin: 26, + limitations: &["spi_psram"], + }, + PinInfo { + pin: 27, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 28, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 29, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 30, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 31, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 32, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 33, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 34, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 35, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 36, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 37, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 38, + limitations: &[], + }, + PinInfo { + pin: 39, + limitations: &[], + }, + PinInfo { + pin: 40, + limitations: &[], + }, + PinInfo { + pin: 41, + limitations: &[], + }, + PinInfo { + pin: 42, + limitations: &[], + }, + PinInfo { + pin: 43, + limitations: &[], + }, + PinInfo { + pin: 44, + limitations: &[], + }, + PinInfo { + pin: 45, + limitations: &["strapping"], + }, + PinInfo { + pin: 46, + limitations: &["strapping"], + }, + ], + }, + Self::Esp32s3 => Config { + architecture: "xtensa", + target: "xtensa-esp32s3-none-elf", + symbols: &[ + "esp32s3", + "xtensa", + "multi_core", + "soc_has_aes", + "soc_has_apb_ctrl", + "soc_has_apb_saradc", + "soc_has_assist_debug", + "soc_has_dma", + "soc_has_ds", + "soc_has_efuse", + "soc_has_extmem", + "soc_has_gpio", + "soc_has_gpio_sd", + "soc_has_hmac", + "soc_has_i2c_ana_mst", + "soc_has_i2c0", + "soc_has_i2c1", + "soc_has_i2s0", + "soc_has_i2s1", + "soc_has_interrupt_core0", + "soc_has_interrupt_core1", + "soc_has_io_mux", + "soc_has_lcd_cam", + "soc_has_ledc", + "soc_has_lpwr", + "soc_has_mcpwm0", + "soc_has_mcpwm1", + "soc_has_pcnt", + "soc_has_peri_backup", + "soc_has_rmt", + "soc_has_rng", + "soc_has_rsa", + "soc_has_rtc_cntl", + "soc_has_rtc_i2c", + "soc_has_rtc_io", + "soc_has_sdhost", + "soc_has_sens", + "soc_has_sensitive", + "soc_has_sha", + "soc_has_spi0", + "soc_has_spi1", + "soc_has_spi2", + "soc_has_spi3", + "soc_has_system", + "soc_has_systimer", + "soc_has_timg0", + "soc_has_timg1", + "soc_has_twai0", + "soc_has_uart0", + "soc_has_uart1", + "soc_has_uart2", + "soc_has_uhci0", + "soc_has_usb0", + "soc_has_usb_device", + "soc_has_usb_wrap", + "soc_has_wcl", + "soc_has_xts_aes", + "soc_has_dma_ch0", + "soc_has_dma_ch1", + "soc_has_dma_ch2", + "soc_has_dma_ch3", + "soc_has_dma_ch4", + "soc_has_adc1", + "soc_has_adc2", + "soc_has_bt", + "soc_has_cpu_ctrl", + "soc_has_flash", + "soc_has_gpio_dedicated", + "soc_has_psram", + "soc_has_sw_interrupt", + "soc_has_ulp_riscv_core", + "soc_has_wifi", + "phy", + "psram", + "psram_dma", + "octal_psram", + "swd", + "ulp_riscv_core", + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "ulp_supported", + "riscv_coproc_supported", + "adc_driver_supported", + "aes_driver_supported", + "assist_debug_driver_supported", + "bt_driver_supported", + "camera_driver_supported", + "dedicated_gpio_driver_supported", + "dma_driver_supported", + "gpio_driver_supported", + "hmac_driver_supported", + "i2c_master_driver_supported", + "i2s_driver_supported", + "interrupts_driver_supported", + "io_mux_driver_supported", + "ledc_driver_supported", + "mcpwm_driver_supported", + "pcnt_driver_supported", + "phy_driver_supported", + "psram_driver_supported", + "rgb_display_driver_supported", + "rmt_driver_supported", + "rng_driver_supported", + "rsa_driver_supported", + "lp_timer_driver_supported", + "sd_host_driver_supported", + "sha_driver_supported", + "sleep_driver_supported", + "soc_driver_supported", + "spi_master_driver_supported", + "spi_slave_driver_supported", + "systimer_driver_supported", + "temp_sensor_driver_supported", + "timergroup_driver_supported", + "twai_driver_supported", + "uart_driver_supported", + "uhci_driver_supported", + "ulp_fsm_driver_supported", + "ulp_riscv_driver_supported", + "usb_otg_driver_supported", + "usb_serial_jtag_driver_supported", + "wifi_driver_supported", + "adc_adc1", + "adc_adc2", + "i2c_master_i2c0", + "i2c_master_i2c1", + "spi_master_spi2", + "spi_master_spi3", + "spi_slave_spi2", + "spi_slave_spi3", + "timergroup_timg0", + "timergroup_timg1", + "uart_uart0", + "uart_uart1", + "uart_uart2", + "aes_dma", + "aes_dma_mode_ecb", + "aes_dma_mode_cbc", + "aes_dma_mode_ofb", + "aes_dma_mode_ctr", + "aes_dma_mode_cfb8", + "aes_dma_mode_cfb128", + "aes_has_split_text_registers", + "assist_debug_has_region_monitor", + "bt_controller=\"btdm\"", + "dedicated_gpio_needs_initialization", + "dma_kind=\"gdma\"", + "dma_supports_mem2mem", + "dma_separate_in_out_interrupts", + "dma_max_priority=\"9\"", + "dma_max_priority_is_set", + "dma_gdma_version=\"1\"", + "dma_gdma_version_is_set", + "gpio_has_bank_1", + "gpio_gpio_function=\"1\"", + "gpio_constant_0_input=\"60\"", + "gpio_constant_1_input=\"56\"", + "gpio_func_in_sel_offset=\"0\"", + "gpio_input_signal_max=\"255\"", + "gpio_output_signal_max=\"256\"", + "i2c_master_has_fsm_timeouts", + "i2c_master_has_bus_timeout_enable", + "i2c_master_can_estimate_nack_reason", + "i2c_master_has_conf_update", + "i2c_master_has_arbitration_en", + "i2c_master_has_tx_fifo_watermark", + "i2c_master_bus_timeout_is_exponential", + "i2c_master_max_bus_timeout=\"31\"", + "i2c_master_ll_intr_mask=\"262143\"", + "i2c_master_fifo_size=\"32\"", + "interrupts_status_registers=\"4\"", + "interrupt_controller=\"xtensa\"", + "phy_combo_module", + "phy_backed_up_digital_register_count=\"21\"", + "phy_backed_up_digital_register_count_is_set", + "rmt_ram_start=\"1610704896\"", + "rmt_channel_ram_size=\"48\"", + "rmt_has_tx_immediate_stop", + "rmt_has_tx_loop_count", + "rmt_has_tx_loop_auto_stop", + "rmt_has_tx_carrier_data_only", + "rmt_has_tx_sync", + "rmt_has_rx_wrap", + "rmt_has_rx_demodulation", + "rmt_has_dma", + "rmt_supports_none_clock", + "rmt_supports_apb_clock", + "rmt_supports_rcfast_clock", + "rmt_supports_xtal_clock", + "rng_apb_cycle_wait_num=\"16\"", + "rng_trng_supported", + "rsa_size_increment=\"32\"", + "rsa_memory_size_bytes=\"512\"", + "sha_dma", + "sleep_light_sleep", + "sleep_deep_sleep", + "soc_multi_core_enabled", + "soc_rc_fast_clk_default=\"17500000\"", + "soc_rc_fast_clk_default_is_set", + "soc_has_clock_node_xtal_clk", + "soc_has_clock_node_pll_clk", + "soc_has_clock_node_rc_fast_clk", + "soc_has_clock_node_xtal32k_clk", + "soc_has_clock_node_rc_slow_clk", + "soc_has_clock_node_rc_fast_div_clk", + "soc_has_clock_node_system_pre_div_in", + "soc_has_clock_node_system_pre_div", + "soc_has_clock_node_cpu_pll_div_out", + "soc_has_clock_node_cpu_clk", + "soc_has_clock_node_pll_d2", + "soc_has_clock_node_pll_160m", + "soc_has_clock_node_apb_80m", + "soc_has_clock_node_apb_clk", + "soc_has_clock_node_crypto_pwm_clk", + "soc_has_clock_node_rc_fast_clk_div_n", + "soc_has_clock_node_xtal_div_clk", + "soc_has_clock_node_rtc_slow_clk", + "soc_has_clock_node_rtc_fast_clk", + "soc_has_clock_node_low_power_clk", + "soc_has_clock_node_uart_mem_clk", + "soc_has_clock_node_mcpwm0_function_clock", + "soc_has_clock_node_mcpwm1_function_clock", + "soc_has_clock_node_rmt_sclk", + "soc_has_clock_node_timg0_function_clock", + "soc_has_clock_node_timg0_calibration_clock", + "soc_has_clock_node_timg1_function_clock", + "soc_has_clock_node_timg1_calibration_clock", + "soc_has_clock_node_uart0_function_clock", + "soc_has_clock_node_uart0_mem_clock", + "soc_has_clock_node_uart1_function_clock", + "soc_has_clock_node_uart1_mem_clock", + "soc_has_clock_node_uart2_function_clock", + "soc_has_clock_node_uart2_mem_clock", + "has_dram_region", + "has_dram2_uninit_region", + "spi_master_supports_dma", + "spi_master_has_octal", + "spi_master_has_app_interrupts", + "spi_master_has_dma_segmented_transfer", + "spi_slave_supports_dma", + "timergroup_timg_has_timer1", + "timergroup_rc_fast_calibration_is_set", + "uart_ram_size=\"128\"", + "wifi_mac_version=\"1\"", + ], + cfgs: &[ + "cargo:rustc-cfg=esp32s3", + "cargo:rustc-cfg=xtensa", + "cargo:rustc-cfg=multi_core", + "cargo:rustc-cfg=soc_has_aes", + "cargo:rustc-cfg=soc_has_apb_ctrl", + "cargo:rustc-cfg=soc_has_apb_saradc", + "cargo:rustc-cfg=soc_has_assist_debug", + "cargo:rustc-cfg=soc_has_dma", + "cargo:rustc-cfg=soc_has_ds", + "cargo:rustc-cfg=soc_has_efuse", + "cargo:rustc-cfg=soc_has_extmem", + "cargo:rustc-cfg=soc_has_gpio", + "cargo:rustc-cfg=soc_has_gpio_sd", + "cargo:rustc-cfg=soc_has_hmac", + "cargo:rustc-cfg=soc_has_i2c_ana_mst", + "cargo:rustc-cfg=soc_has_i2c0", + "cargo:rustc-cfg=soc_has_i2c1", + "cargo:rustc-cfg=soc_has_i2s0", + "cargo:rustc-cfg=soc_has_i2s1", + "cargo:rustc-cfg=soc_has_interrupt_core0", + "cargo:rustc-cfg=soc_has_interrupt_core1", + "cargo:rustc-cfg=soc_has_io_mux", + "cargo:rustc-cfg=soc_has_lcd_cam", + "cargo:rustc-cfg=soc_has_ledc", + "cargo:rustc-cfg=soc_has_lpwr", + "cargo:rustc-cfg=soc_has_mcpwm0", + "cargo:rustc-cfg=soc_has_mcpwm1", + "cargo:rustc-cfg=soc_has_pcnt", + "cargo:rustc-cfg=soc_has_peri_backup", + "cargo:rustc-cfg=soc_has_rmt", + "cargo:rustc-cfg=soc_has_rng", + "cargo:rustc-cfg=soc_has_rsa", + "cargo:rustc-cfg=soc_has_rtc_cntl", + "cargo:rustc-cfg=soc_has_rtc_i2c", + "cargo:rustc-cfg=soc_has_rtc_io", + "cargo:rustc-cfg=soc_has_sdhost", + "cargo:rustc-cfg=soc_has_sens", + "cargo:rustc-cfg=soc_has_sensitive", + "cargo:rustc-cfg=soc_has_sha", + "cargo:rustc-cfg=soc_has_spi0", + "cargo:rustc-cfg=soc_has_spi1", + "cargo:rustc-cfg=soc_has_spi2", + "cargo:rustc-cfg=soc_has_spi3", + "cargo:rustc-cfg=soc_has_system", + "cargo:rustc-cfg=soc_has_systimer", + "cargo:rustc-cfg=soc_has_timg0", + "cargo:rustc-cfg=soc_has_timg1", + "cargo:rustc-cfg=soc_has_twai0", + "cargo:rustc-cfg=soc_has_uart0", + "cargo:rustc-cfg=soc_has_uart1", + "cargo:rustc-cfg=soc_has_uart2", + "cargo:rustc-cfg=soc_has_uhci0", + "cargo:rustc-cfg=soc_has_usb0", + "cargo:rustc-cfg=soc_has_usb_device", + "cargo:rustc-cfg=soc_has_usb_wrap", + "cargo:rustc-cfg=soc_has_wcl", + "cargo:rustc-cfg=soc_has_xts_aes", + "cargo:rustc-cfg=soc_has_dma_ch0", + "cargo:rustc-cfg=soc_has_dma_ch1", + "cargo:rustc-cfg=soc_has_dma_ch2", + "cargo:rustc-cfg=soc_has_dma_ch3", + "cargo:rustc-cfg=soc_has_dma_ch4", + "cargo:rustc-cfg=soc_has_adc1", + "cargo:rustc-cfg=soc_has_adc2", + "cargo:rustc-cfg=soc_has_bt", + "cargo:rustc-cfg=soc_has_cpu_ctrl", + "cargo:rustc-cfg=soc_has_flash", + "cargo:rustc-cfg=soc_has_gpio_dedicated", + "cargo:rustc-cfg=soc_has_psram", + "cargo:rustc-cfg=soc_has_sw_interrupt", + "cargo:rustc-cfg=soc_has_ulp_riscv_core", + "cargo:rustc-cfg=soc_has_wifi", + "cargo:rustc-cfg=phy", + "cargo:rustc-cfg=psram", + "cargo:rustc-cfg=psram_dma", + "cargo:rustc-cfg=octal_psram", + "cargo:rustc-cfg=swd", + "cargo:rustc-cfg=ulp_riscv_core", + "cargo:rustc-cfg=rom_crc_le", + "cargo:rustc-cfg=rom_crc_be", + "cargo:rustc-cfg=rom_md5_bsd", + "cargo:rustc-cfg=pm_support_ext0_wakeup", + "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=pm_support_touch_sensor_wakeup", + "cargo:rustc-cfg=pm_support_wifi_wakeup", + "cargo:rustc-cfg=pm_support_bt_wakeup", + "cargo:rustc-cfg=uart_support_wakeup_int", + "cargo:rustc-cfg=ulp_supported", + "cargo:rustc-cfg=riscv_coproc_supported", + "cargo:rustc-cfg=adc_driver_supported", + "cargo:rustc-cfg=aes_driver_supported", + "cargo:rustc-cfg=assist_debug_driver_supported", + "cargo:rustc-cfg=bt_driver_supported", + "cargo:rustc-cfg=camera_driver_supported", + "cargo:rustc-cfg=dedicated_gpio_driver_supported", + "cargo:rustc-cfg=dma_driver_supported", + "cargo:rustc-cfg=gpio_driver_supported", + "cargo:rustc-cfg=hmac_driver_supported", + "cargo:rustc-cfg=i2c_master_driver_supported", + "cargo:rustc-cfg=i2s_driver_supported", + "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=io_mux_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", + "cargo:rustc-cfg=mcpwm_driver_supported", + "cargo:rustc-cfg=pcnt_driver_supported", + "cargo:rustc-cfg=phy_driver_supported", + "cargo:rustc-cfg=psram_driver_supported", + "cargo:rustc-cfg=rgb_display_driver_supported", + "cargo:rustc-cfg=rmt_driver_supported", + "cargo:rustc-cfg=rng_driver_supported", + "cargo:rustc-cfg=rsa_driver_supported", + "cargo:rustc-cfg=lp_timer_driver_supported", + "cargo:rustc-cfg=sd_host_driver_supported", + "cargo:rustc-cfg=sha_driver_supported", + "cargo:rustc-cfg=sleep_driver_supported", + "cargo:rustc-cfg=soc_driver_supported", + "cargo:rustc-cfg=spi_master_driver_supported", + "cargo:rustc-cfg=spi_slave_driver_supported", + "cargo:rustc-cfg=systimer_driver_supported", + "cargo:rustc-cfg=temp_sensor_driver_supported", + "cargo:rustc-cfg=timergroup_driver_supported", + "cargo:rustc-cfg=twai_driver_supported", + "cargo:rustc-cfg=uart_driver_supported", + "cargo:rustc-cfg=uhci_driver_supported", + "cargo:rustc-cfg=ulp_fsm_driver_supported", + "cargo:rustc-cfg=ulp_riscv_driver_supported", + "cargo:rustc-cfg=usb_otg_driver_supported", + "cargo:rustc-cfg=usb_serial_jtag_driver_supported", + "cargo:rustc-cfg=wifi_driver_supported", + "cargo:rustc-cfg=adc_adc1", + "cargo:rustc-cfg=adc_adc2", + "cargo:rustc-cfg=i2c_master_i2c0", + "cargo:rustc-cfg=i2c_master_i2c1", + "cargo:rustc-cfg=spi_master_spi2", + "cargo:rustc-cfg=spi_master_spi3", + "cargo:rustc-cfg=spi_slave_spi2", + "cargo:rustc-cfg=spi_slave_spi3", + "cargo:rustc-cfg=timergroup_timg0", + "cargo:rustc-cfg=timergroup_timg1", + "cargo:rustc-cfg=uart_uart0", + "cargo:rustc-cfg=uart_uart1", + "cargo:rustc-cfg=uart_uart2", + "cargo:rustc-cfg=aes_dma", + "cargo:rustc-cfg=aes_dma_mode_ecb", + "cargo:rustc-cfg=aes_dma_mode_cbc", + "cargo:rustc-cfg=aes_dma_mode_ofb", + "cargo:rustc-cfg=aes_dma_mode_ctr", + "cargo:rustc-cfg=aes_dma_mode_cfb8", + "cargo:rustc-cfg=aes_dma_mode_cfb128", + "cargo:rustc-cfg=aes_has_split_text_registers", + "cargo:rustc-cfg=assist_debug_has_region_monitor", + "cargo:rustc-cfg=bt_controller=\"btdm\"", + "cargo:rustc-cfg=dedicated_gpio_needs_initialization", + "cargo:rustc-cfg=dma_kind=\"gdma\"", + "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=dma_separate_in_out_interrupts", + "cargo:rustc-cfg=dma_max_priority=\"9\"", + "cargo:rustc-cfg=dma_max_priority_is_set", + "cargo:rustc-cfg=dma_gdma_version=\"1\"", + "cargo:rustc-cfg=dma_gdma_version_is_set", + "cargo:rustc-cfg=gpio_has_bank_1", + "cargo:rustc-cfg=gpio_gpio_function=\"1\"", + "cargo:rustc-cfg=gpio_constant_0_input=\"60\"", + "cargo:rustc-cfg=gpio_constant_1_input=\"56\"", + "cargo:rustc-cfg=gpio_func_in_sel_offset=\"0\"", + "cargo:rustc-cfg=gpio_input_signal_max=\"255\"", + "cargo:rustc-cfg=gpio_output_signal_max=\"256\"", + "cargo:rustc-cfg=i2c_master_has_fsm_timeouts", + "cargo:rustc-cfg=i2c_master_has_bus_timeout_enable", + "cargo:rustc-cfg=i2c_master_can_estimate_nack_reason", + "cargo:rustc-cfg=i2c_master_has_conf_update", + "cargo:rustc-cfg=i2c_master_has_arbitration_en", + "cargo:rustc-cfg=i2c_master_has_tx_fifo_watermark", + "cargo:rustc-cfg=i2c_master_bus_timeout_is_exponential", + "cargo:rustc-cfg=i2c_master_max_bus_timeout=\"31\"", + "cargo:rustc-cfg=i2c_master_ll_intr_mask=\"262143\"", + "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", + "cargo:rustc-cfg=interrupts_status_registers=\"4\"", + "cargo:rustc-cfg=interrupt_controller=\"xtensa\"", + "cargo:rustc-cfg=phy_combo_module", + "cargo:rustc-cfg=phy_backed_up_digital_register_count=\"21\"", + "cargo:rustc-cfg=phy_backed_up_digital_register_count_is_set", + "cargo:rustc-cfg=rmt_ram_start=\"1610704896\"", + "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", + "cargo:rustc-cfg=rmt_has_tx_immediate_stop", + "cargo:rustc-cfg=rmt_has_tx_loop_count", + "cargo:rustc-cfg=rmt_has_tx_loop_auto_stop", + "cargo:rustc-cfg=rmt_has_tx_carrier_data_only", + "cargo:rustc-cfg=rmt_has_tx_sync", + "cargo:rustc-cfg=rmt_has_rx_wrap", + "cargo:rustc-cfg=rmt_has_rx_demodulation", + "cargo:rustc-cfg=rmt_has_dma", + "cargo:rustc-cfg=rmt_supports_none_clock", + "cargo:rustc-cfg=rmt_supports_apb_clock", + "cargo:rustc-cfg=rmt_supports_rcfast_clock", + "cargo:rustc-cfg=rmt_supports_xtal_clock", + "cargo:rustc-cfg=rng_apb_cycle_wait_num=\"16\"", + "cargo:rustc-cfg=rng_trng_supported", + "cargo:rustc-cfg=rsa_size_increment=\"32\"", + "cargo:rustc-cfg=rsa_memory_size_bytes=\"512\"", + "cargo:rustc-cfg=sha_dma", + "cargo:rustc-cfg=sleep_light_sleep", + "cargo:rustc-cfg=sleep_deep_sleep", + "cargo:rustc-cfg=soc_multi_core_enabled", + "cargo:rustc-cfg=soc_rc_fast_clk_default=\"17500000\"", + "cargo:rustc-cfg=soc_rc_fast_clk_default_is_set", + "cargo:rustc-cfg=soc_has_clock_node_xtal_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_xtal32k_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div_in", + "cargo:rustc-cfg=soc_has_clock_node_system_pre_div", + "cargo:rustc-cfg=soc_has_clock_node_cpu_pll_div_out", + "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", + "cargo:rustc-cfg=soc_has_clock_node_pll_d2", + "cargo:rustc-cfg=soc_has_clock_node_pll_160m", + "cargo:rustc-cfg=soc_has_clock_node_apb_80m", + "cargo:rustc-cfg=soc_has_clock_node_apb_clk", + "cargo:rustc-cfg=soc_has_clock_node_crypto_pwm_clk", + "cargo:rustc-cfg=soc_has_clock_node_rc_fast_clk_div_n", + "cargo:rustc-cfg=soc_has_clock_node_xtal_div_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_slow_clk", + "cargo:rustc-cfg=soc_has_clock_node_rtc_fast_clk", + "cargo:rustc-cfg=soc_has_clock_node_low_power_clk", + "cargo:rustc-cfg=soc_has_clock_node_uart_mem_clk", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_mcpwm1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_rmt_sclk", + "cargo:rustc-cfg=soc_has_clock_node_timg0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg0_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_timg1_calibration_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart0_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart1_mem_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart2_function_clock", + "cargo:rustc-cfg=soc_has_clock_node_uart2_mem_clock", + "cargo:rustc-cfg=has_dram_region", + "cargo:rustc-cfg=has_dram2_uninit_region", + "cargo:rustc-cfg=spi_master_supports_dma", + "cargo:rustc-cfg=spi_master_has_octal", + "cargo:rustc-cfg=spi_master_has_app_interrupts", + "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", + "cargo:rustc-cfg=spi_slave_supports_dma", + "cargo:rustc-cfg=timergroup_timg_has_timer1", + "cargo:rustc-cfg=timergroup_rc_fast_calibration_is_set", + "cargo:rustc-cfg=uart_ram_size=\"128\"", + "cargo:rustc-cfg=wifi_mac_version=\"1\"", + ], + memory_layout: &MemoryLayout { + regions: &[ + ( + "dram", + MemoryRegion { + address_range: 0x3FC88000..0x3FD00000, + }, + ), + ( + "dram2_uninit", + MemoryRegion { + address_range: 0x3FCDB700..0x3FCED710, + }, + ), + ], + }, + pins: &[ + PinInfo { + pin: 0, + limitations: &["strapping"], + }, + PinInfo { + pin: 1, + limitations: &[], + }, + PinInfo { + pin: 2, + limitations: &[], + }, + PinInfo { + pin: 3, + limitations: &["strapping"], + }, + PinInfo { + pin: 4, + limitations: &[], + }, + PinInfo { + pin: 5, + limitations: &[], + }, + PinInfo { + pin: 6, + limitations: &[], + }, + PinInfo { + pin: 7, + limitations: &[], + }, + PinInfo { + pin: 8, + limitations: &[], + }, + PinInfo { + pin: 9, + limitations: &[], + }, + PinInfo { + pin: 10, + limitations: &[], + }, + PinInfo { + pin: 11, + limitations: &[], + }, + PinInfo { + pin: 12, + limitations: &[], + }, + PinInfo { + pin: 13, + limitations: &[], + }, + PinInfo { + pin: 14, + limitations: &[], + }, + PinInfo { + pin: 15, + limitations: &[], + }, + PinInfo { + pin: 16, + limitations: &[], + }, + PinInfo { + pin: 17, + limitations: &[], + }, + PinInfo { + pin: 18, + limitations: &[], + }, + PinInfo { + pin: 19, + limitations: &[], + }, + PinInfo { + pin: 20, + limitations: &[], + }, + PinInfo { + pin: 21, + limitations: &[], + }, + PinInfo { + pin: 26, + limitations: &["spi_psram"], + }, + PinInfo { + pin: 27, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 28, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 29, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 30, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 31, + limitations: &["spi_flash", "spi_psram"], + }, + PinInfo { + pin: 32, + limitations: &["spi_flash"], + }, + PinInfo { + pin: 33, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 34, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 35, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 36, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 37, + limitations: &["octal_flash", "octal_psram"], + }, + PinInfo { + pin: 38, + limitations: &[], + }, + PinInfo { + pin: 39, + limitations: &[], + }, + PinInfo { + pin: 40, + limitations: &[], + }, + PinInfo { + pin: 41, + limitations: &[], + }, + PinInfo { + pin: 42, + limitations: &[], + }, + PinInfo { + pin: 43, + limitations: &[], + }, + PinInfo { + pin: 44, + limitations: &[], + }, + PinInfo { + pin: 45, + limitations: &["strapping"], + }, + PinInfo { + pin: 46, + limitations: &["strapping"], + }, + PinInfo { + pin: 47, + limitations: &[], + }, + PinInfo { + pin: 48, + limitations: &[], + }, + ], + }, + } + } +} +/// Information about a memory region. +pub struct MemoryRegion { + address_range: Range, +} +impl MemoryRegion { + /// Returns the address range of the memory region. + pub fn range(&self) -> Range { + self.address_range.clone() + } + /// Returns the size of the memory region in bytes. + pub fn size(&self) -> u32 { + self.address_range.end - self.address_range.start + } +} +/// Information about the memory layout of a chip. +pub struct MemoryLayout { + regions: &'static [(&'static str, MemoryRegion)], +} +impl MemoryLayout { + /// Returns the memory region with the given name. + pub fn region(&self, name: &str) -> Option<&'static MemoryRegion> { + self.regions + .iter() + .find_map(|(n, r)| if *n == name { Some(r) } else { None }) + } +} +/// Information about a specific pin. +#[non_exhaustive] +pub struct PinInfo { + /// The pin number. + pub pin: usize, + /// The list of possible restriction categories for this pin. + /// + /// This can include "strapping", "spi_psram", etc. + pub limitations: &'static [&'static str], +} +struct Config { + architecture: &'static str, + target: &'static str, + symbols: &'static [&'static str], + cfgs: &'static [&'static str], + memory_layout: &'static MemoryLayout, + pins: &'static [PinInfo], +} +impl Config { + fn define_cfgs(&self) { + emit_check_cfg_directives(); + for cfg in self.cfgs { + println!("{cfg}"); + } + } +} +/// Prints `cargo:rustc-check-cfg` lines. +pub fn emit_check_cfg_directives() { + println!("cargo:rustc-check-cfg=cfg(not_really_docsrs)"); + println!("cargo:rustc-check-cfg=cfg(semver_checks)"); + println!("cargo:rustc-check-cfg=cfg(esp32)"); + println!("cargo:rustc-check-cfg=cfg(xtensa)"); + println!("cargo:rustc-check-cfg=cfg(multi_core)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_aes)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_apb_ctrl)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_bb)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dport)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_system)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_efuse)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_emac_dma)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_emac_ext)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_emac_mac)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_flash_encryption)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_frc_timer)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_gpio)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_gpio_sd)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_hinf)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_i2c0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_i2c1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_i2s0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_i2s1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_io_mux)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ledc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mcpwm0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mcpwm1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_nrx)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pcnt)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rmt)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rng)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rsa)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lpwr)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rtc_i2c)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rtc_io)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_sdhost)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_sens)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_sha)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_slc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_slchost)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_spi0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_spi1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_spi2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_spi3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_timg0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_timg1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_twai0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_uart0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_uart1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_uart2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_uhci0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_uhci1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_wifi)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_spi2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_spi3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_i2s0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_i2s1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_adc1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_adc2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_bt)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_cpu_ctrl)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dac1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dac2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_flash)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_psram)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_sw_interrupt)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_touch)"); + println!("cargo:rustc-check-cfg=cfg(phy)"); + println!("cargo:rustc-check-cfg=cfg(psram)"); + println!("cargo:rustc-check-cfg=cfg(touch)"); + println!("cargo:rustc-check-cfg=cfg(rom_crc_le)"); + println!("cargo:rustc-check-cfg=cfg(rom_crc_be)"); + println!("cargo:rustc-check-cfg=cfg(rom_md5_bsd)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_ext0_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_ext1_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_touch_sensor_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(ulp_supported)"); + println!("cargo:rustc-check-cfg=cfg(adc_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(aes_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(bt_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(dac_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(dma_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(gpio_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(i2s_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(interrupts_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(io_mux_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(ledc_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(mcpwm_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(pcnt_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(phy_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(psram_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(rgb_display_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(rmt_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(rng_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(rsa_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(lp_timer_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(sd_host_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(sd_slave_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(sha_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(sleep_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(soc_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(spi_slave_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(temp_sensor_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(touch_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(twai_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(uart_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(ulp_fsm_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(wifi_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(adc_adc1)"); + println!("cargo:rustc-check-cfg=cfg(adc_adc2)"); + println!("cargo:rustc-check-cfg=cfg(dac_dac1)"); + println!("cargo:rustc-check-cfg=cfg(dac_dac2)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_i2c0)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_i2c1)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_spi2)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_spi3)"); + println!("cargo:rustc-check-cfg=cfg(spi_slave_spi2)"); + println!("cargo:rustc-check-cfg=cfg(spi_slave_spi3)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_timg0)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_timg1)"); + println!("cargo:rustc-check-cfg=cfg(uart_uart0)"); + println!("cargo:rustc-check-cfg=cfg(uart_uart1)"); + println!("cargo:rustc-check-cfg=cfg(uart_uart2)"); + println!("cargo:rustc-check-cfg=cfg(aes_endianness_configurable)"); + println!("cargo:rustc-check-cfg=cfg(gpio_has_bank_1)"); + println!("cargo:rustc-check-cfg=cfg(gpio_remap_iomux_pin_registers)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_separate_filter_config_registers)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_i2c0_data_register_ahb_address_is_set)"); + println!("cargo:rustc-check-cfg=cfg(phy_combo_module)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_per_channel_clock)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_reftick_clock)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_apb_clock)"); + println!("cargo:rustc-check-cfg=cfg(rng_trng_supported)"); + println!("cargo:rustc-check-cfg=cfg(sleep_light_sleep)"); + println!("cargo:rustc-check-cfg=cfg(sleep_deep_sleep)"); + println!("cargo:rustc-check-cfg=cfg(soc_multi_core_enabled)"); + println!("cargo:rustc-check-cfg=cfg(soc_rc_fast_clk_default_is_set)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_xtal_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_apll_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rc_fast_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f160m_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_pll_div_in)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_pll_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_syscon_pre_div_in)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_syscon_pre_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_apb_clk_cpu_div2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_apb_clk_80m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_apb_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_pll)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_apll)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_xtal)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_fosc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_xtal32k_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rc_slow_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rc_fast_div_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_xtal_div_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rtc_slow_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rtc_fast_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart_mem_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mcpwm0_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mcpwm1_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg0_calibration_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg1_calibration_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart0_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart0_mem_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart1_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart1_mem_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart2_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_uart2_mem_clock)"); + println!("cargo:rustc-check-cfg=cfg(has_dram_region)"); + println!("cargo:rustc-check-cfg=cfg(has_dram2_uninit_region)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_supports_dma)"); + println!("cargo:rustc-check-cfg=cfg(spi_slave_supports_dma)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_timg_has_timer1)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_rc_fast_calibration_is_set)"); + println!("cargo:rustc-check-cfg=cfg(esp32c2)"); + println!("cargo:rustc-check-cfg=cfg(riscv)"); + println!("cargo:rustc-check-cfg=cfg(single_core)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_apb_saradc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_assist_debug)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ecc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_extmem)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_i2c_ana_mst)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_interrupt_core0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_modem_clkrst)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_sensitive)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_systimer)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_xts_aes)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_gpio_dedicated)"); + println!("cargo:rustc-check-cfg=cfg(swd)"); + println!("cargo:rustc-check-cfg=cfg(rom_md5_mbedtls)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_wifi_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_bt_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(uart_support_wakeup_int)"); + println!("cargo:rustc-check-cfg=cfg(gpio_support_deepsleep_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(assist_debug_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(dedicated_gpio_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(ecc_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(systimer_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(assist_debug_has_sp_monitor)"); + println!("cargo:rustc-check-cfg=cfg(dma_supports_mem2mem)"); + println!("cargo:rustc-check-cfg=cfg(dma_max_priority_is_set)"); + println!("cargo:rustc-check-cfg=cfg(dma_gdma_version_is_set)"); + println!("cargo:rustc-check-cfg=cfg(ecc_zero_extend_writes)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_finite_field_division)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_curve_p192)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_curve_p256)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_fsm_timeouts)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_hw_bus_clear)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_bus_timeout_enable)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_conf_update)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_arbitration_en)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_tx_fifo_watermark)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_bus_timeout_is_exponential)"); + println!("cargo:rustc-check-cfg=cfg(sha_dma)"); + println!("cargo:rustc-check-cfg=cfg(soc_cpu_has_csr_pc)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_osc_slow_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_system_pre_div_in)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_system_pre_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_40m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_60m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_80m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_div2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_crypto_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rc_fast_clk_div_n)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_low_power_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg0_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg0_wdt_clock)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_has_app_interrupts)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_has_dma_segmented_transfer)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_timg_has_divcnt_rst)"); + println!("cargo:rustc-check-cfg=cfg(esp32c3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ds)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_fe)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_fe2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_hmac)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_usb_device)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_tsens)"); + println!("cargo:rustc-check-cfg=cfg(hmac_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(uhci_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(usb_serial_jtag_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_ecb)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_cbc)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_ofb)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_ctr)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_cfb8)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_cfb128)"); + println!("cargo:rustc-check-cfg=cfg(aes_has_split_text_registers)"); + println!("cargo:rustc-check-cfg=cfg(assist_debug_has_region_monitor)"); + println!("cargo:rustc-check-cfg=cfg(phy_backed_up_digital_register_count_is_set)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_immediate_stop)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_loop_count)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_carrier_data_only)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_sync)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_rx_wrap)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_rx_demodulation)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_none_clock)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_rcfast_clock)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_xtal_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_pll_div_out)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_160m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_rmt_sclk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg1_function_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg1_wdt_clock)"); + println!("cargo:rustc-check-cfg=cfg(esp32c5)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clint)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ecdsa)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_etm)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_hp_apm)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_hp_sys)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_huk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ieee802154)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_intpri)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_keymng)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_ana)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_aon)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_apm0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_clkrst)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_i2c_ana_mst)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_io_mux)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_peri)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_tee)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_timer)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_uart)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_wdt)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem_monitor)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_modem_lpcon)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_modem_syscon)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_parl_io)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pau)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pcr)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pmu)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pvt_monitor)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_tee)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_core)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem4)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem5)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem6)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem7)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_mem2mem8)"); + println!("cargo:rustc-check-cfg=cfg(ieee802154_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(lp_i2c_master_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(parl_io_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(dma_separate_in_out_interrupts)"); + println!("cargo:rustc-check-cfg=cfg(ecc_separate_jacobian_point_memory)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_memory_clock_gate)"); + println!("cargo:rustc-check-cfg=cfg(ecc_supports_enhanced_security)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_modular_arithmetic)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_point_addition)"); + println!("cargo:rustc-check-cfg=cfg(ecc_has_curve_p384)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_can_estimate_nack_reason)"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_has_reliable_fsm_reset)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_loop_auto_stop)"); + println!("cargo:rustc-check-cfg=cfg(rmt_supports_pll80mhz_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_cpu_has_branch_predictor)"); + println!("cargo:rustc-check-cfg=cfg(soc_cpu_csr_prv_mode_is_set)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f12m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f20m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f40m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f48m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f60m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f80m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f120m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f160m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f240m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_hp_root_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ahb_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_xtal_d2_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_lp_fast_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_lp_slow_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg_calibration_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_parlio_rx_clock)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_parlio_tx_clock)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_has_clk_pre_div)"); + println!("cargo:rustc-check-cfg=cfg(uart_peripheral_controls_mem_clk)"); + println!("cargo:rustc-check-cfg=cfg(uhci_combined_uart_selector_field)"); + println!("cargo:rustc-check-cfg=cfg(wifi_has_5g)"); + println!("cargo:rustc-check-cfg=cfg(esp32c6)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_atomic)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_apm)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_i2c0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lp_io)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_otp_debug)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_plic_mx)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_trace0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_twai1)"); + println!("cargo:rustc-check-cfg=cfg(lp_core)"); + println!("cargo:rustc-check-cfg=cfg(pm_support_beacon_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(etm_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(lp_uart_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(ulp_riscv_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_soc_root_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_hs_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_cpu_ls_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ahb_hs_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ahb_ls_div)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_hs_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_ls_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ledc_sclk)"); + println!("cargo:rustc-check-cfg=cfg(timergroup_rc_fast_calibration_divider)"); + println!("cargo:rustc-check-cfg=cfg(wifi_has_wifi6)"); + println!("cargo:rustc-check-cfg=cfg(esp32h2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f96m_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f64m_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f48m_clk)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_lp_clk)"); + println!("cargo:rustc-check-cfg=cfg(esp32s2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dedicated_gpio)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_pms)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_syscon)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_usb0)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_usb_wrap)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_crypto)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_copy)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_ulp_riscv_core)"); + println!("cargo:rustc-check-cfg=cfg(psram_dma)"); + println!("cargo:rustc-check-cfg=cfg(ulp_riscv_core)"); + println!("cargo:rustc-check-cfg=cfg(riscv_coproc_supported)"); + println!("cargo:rustc-check-cfg=cfg(usb_otg_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_gcm)"); + println!("cargo:rustc-check-cfg=cfg(dedicated_gpio_needs_initialization)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_ck8m)"); + println!("cargo:rustc-check-cfg=cfg(spi_master_has_octal)"); + println!("cargo:rustc-check-cfg=cfg(esp32s3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_interrupt_core1)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_lcd_cam)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_peri_backup)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_rtc_cntl)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_wcl)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch3)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch4)"); + println!("cargo:rustc-check-cfg=cfg(octal_psram)"); + println!("cargo:rustc-check-cfg=cfg(camera_driver_supported)"); + println!("cargo:rustc-check-cfg=cfg(rmt_has_dma)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_d2)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_apb_80m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_crypto_pwm_clk)"); + println!("cargo:rustc-check-cfg=cfg(bt_controller, values(\"btdm\",\"npl\"))"); + println!("cargo:rustc-check-cfg=cfg(dma_kind, values(\"pdma\",\"gdma\"))"); + println!("cargo:rustc-check-cfg=cfg(gpio_gpio_function, values(\"2\",\"1\"))"); + println!( + "cargo:rustc-check-cfg=cfg(gpio_constant_0_input, values(\"48\",\"31\",\"96\",\"60\"))" + ); + println!("cargo:rustc-check-cfg=cfg(gpio_constant_1_input, values(\"56\",\"30\",\"64\"))"); + println!("cargo:rustc-check-cfg=cfg(gpio_func_in_sel_offset, values(\"0\"))"); + println!( + "cargo:rustc-check-cfg=cfg(gpio_input_signal_max, \ + values(\"206\",\"100\",\"116\",\"124\",\"242\",\"255\"))" + ); + println!("cargo:rustc-check-cfg=cfg(gpio_output_signal_max, values(\"256\",\"128\"))"); + println!( + "cargo:rustc-check-cfg=cfg(i2c_master_i2c0_data_register_ahb_address, \ + values(\"1610690588\"))" + ); + println!( + "cargo:rustc-check-cfg=cfg(i2c_master_max_bus_timeout, \ + values(\"1048575\",\"31\",\"16777215\"))" + ); + println!("cargo:rustc-check-cfg=cfg(i2c_master_ll_intr_mask, values(\"262143\",\"131071\"))"); + println!("cargo:rustc-check-cfg=cfg(i2c_master_fifo_size, values(\"32\",\"16\"))"); + println!("cargo:rustc-check-cfg=cfg(interrupts_status_registers, values(\"3\",\"2\",\"4\"))"); + println!( + "cargo:rustc-check-cfg=cfg(interrupt_controller, \ + values(\"xtensa\",\"riscv_basic\",\"clic\",\"plic\"))" + ); + println!( + "cargo:rustc-check-cfg=cfg(rmt_ram_start, \ + values(\"1073047552\",\"1610703872\",\"1610638336\",\"1610642432\",\"1061250048\",\"\ + 1610704896\"))" + ); + println!("cargo:rustc-check-cfg=cfg(rmt_channel_ram_size, values(\"64\",\"48\"))"); + println!("cargo:rustc-check-cfg=cfg(rng_apb_cycle_wait_num, values(\"16\"))"); + println!("cargo:rustc-check-cfg=cfg(rsa_size_increment, values(\"512\",\"32\"))"); + println!("cargo:rustc-check-cfg=cfg(rsa_memory_size_bytes, values(\"512\",\"384\"))"); + println!( + "cargo:rustc-check-cfg=cfg(soc_rc_fast_clk_default, values(\"8500000\",\"17500000\"))" + ); + println!("cargo:rustc-check-cfg=cfg(uart_ram_size, values(\"128\"))"); + println!("cargo:rustc-check-cfg=cfg(wifi_mac_version, values(\"1\",\"3\",\"2\"))"); + println!("cargo:rustc-check-cfg=cfg(dma_max_priority, values(\"9\",\"5\"))"); + println!("cargo:rustc-check-cfg=cfg(dma_gdma_version, values(\"1\",\"2\"))"); + println!("cargo:rustc-check-cfg=cfg(phy_backed_up_digital_register_count, values(\"21\"))"); + println!("cargo:rustc-check-cfg=cfg(lp_i2c_master_fifo_size, values(\"16\"))"); + println!("cargo:rustc-check-cfg=cfg(lp_uart_ram_size, values(\"32\"))"); + println!("cargo:rustc-check-cfg=cfg(parl_io_version, values(\"2\",\"1\"))"); + println!("cargo:rustc-check-cfg=cfg(soc_cpu_csr_prv_mode, values(\"2064\",\"3088\"))"); +} diff --git a/esp-metadata-generated/src/_generated_esp32.rs b/esp-metadata-generated/src/_generated_esp32.rs new file mode 100644 index 00000000000..a013e049fc7 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -0,0 +1,4585 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32" + }; + ("arch") => { + "xtensa" + }; + ("cores") => { + 2 + }; + ("cores", str) => { + stringify!(2) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + false + }; + ("aes.has_split_text_registers") => { + false + }; + ("aes.endianness_configurable") => { + true + }; + ("bt.controller") => { + "btdm" + }; + ("dma.kind") => { + "pdma" + }; + ("dma.supports_mem2mem") => { + false + }; + ("dma.separate_in_out_interrupts") => { + false + }; + ("gpio.has_bank_1") => { + true + }; + ("gpio.gpio_function") => { + 2 + }; + ("gpio.gpio_function", str) => { + stringify!(2) + }; + ("gpio.constant_0_input") => { + 48 + }; + ("gpio.constant_0_input", str) => { + stringify!(48) + }; + ("gpio.constant_1_input") => { + 56 + }; + ("gpio.constant_1_input", str) => { + stringify!(56) + }; + ("gpio.remap_iomux_pin_registers") => { + true + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 206 + }; + ("gpio.input_signal_max", str) => { + stringify!(206) + }; + ("gpio.output_signal_max") => { + 256 + }; + ("gpio.output_signal_max", str) => { + stringify!(256) + }; + ("i2c_master.has_fsm_timeouts") => { + false + }; + ("i2c_master.has_hw_bus_clear") => { + false + }; + ("i2c_master.has_bus_timeout_enable") => { + false + }; + ("i2c_master.separate_filter_config_registers") => { + true + }; + ("i2c_master.can_estimate_nack_reason") => { + false + }; + ("i2c_master.has_conf_update") => { + false + }; + ("i2c_master.has_reliable_fsm_reset") => { + false + }; + ("i2c_master.has_arbitration_en") => { + false + }; + ("i2c_master.has_tx_fifo_watermark") => { + false + }; + ("i2c_master.bus_timeout_is_exponential") => { + false + }; + ("i2c_master.i2c0_data_register_ahb_address") => { + 1610690588 + }; + ("i2c_master.i2c0_data_register_ahb_address", str) => { + stringify!(1610690588) + }; + ("i2c_master.max_bus_timeout") => { + 1048575 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(1048575) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 3 + }; + ("interrupts.status_registers", str) => { + stringify!(3) + }; + ("phy.combo_module") => { + true + }; + ("rmt.ram_start") => { + 1073047552 + }; + ("rmt.ram_start", str) => { + stringify!(1073047552) + }; + ("rmt.channel_ram_size") => { + 64 + }; + ("rmt.channel_ram_size", str) => { + stringify!(64) + }; + ("rmt.has_tx_immediate_stop") => { + false + }; + ("rmt.has_tx_loop_count") => { + false + }; + ("rmt.has_tx_loop_auto_stop") => { + false + }; + ("rmt.has_tx_carrier_data_only") => { + false + }; + ("rmt.has_tx_sync") => { + false + }; + ("rmt.has_rx_wrap") => { + false + }; + ("rmt.has_rx_demodulation") => { + false + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + true + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 512 + }; + ("rsa.size_increment", str) => { + stringify!(512) + }; + ("rsa.memory_size_bytes") => { + 512 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(512) + }; + ("sha.dma") => { + false + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + false + }; + ("soc.multi_core_enabled") => { + true + }; + ("soc.rc_fast_clk_default") => { + 8500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(8500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + false + }; + ("spi_master.has_dma_segmented_transfer") => { + false + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + true + }; + ("timergroup.timg_has_divcnt_rst") => { + false + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + false + }; + ("uhci.combined_uart_selector_field") => { + false + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 1 + }; + ("wifi.mac_version", str) => { + stringify!(1) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((192)); _for_each_inner_aes_key_length!((256)); + _for_each_inner_aes_key_length!((128, 0, 4)); + _for_each_inner_aes_key_length!((192, 1, 5)); + _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (192), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (192, 1, 5), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe {} + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((4)); _for_each_inner_rmt_channel!((5)); + _for_each_inner_rmt_channel!((6)); _for_each_inner_rmt_channel!((7)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 2)); _for_each_inner_rmt_channel!((3, 3)); + _for_each_inner_rmt_channel!((4, 4)); _for_each_inner_rmt_channel!((5, 5)); + _for_each_inner_rmt_channel!((6, 6)); _for_each_inner_rmt_channel!((7, 7)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 2)); _for_each_inner_rmt_channel!((3, 3)); + _for_each_inner_rmt_channel!((4, 4)); _for_each_inner_rmt_channel!((5, 5)); + _for_each_inner_rmt_channel!((6, 6)); _for_each_inner_rmt_channel!((7, 7)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3), (4), (5), (6), (7))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), + (6, 6), (7, 7))); _for_each_inner_rmt_channel!((rx(0, 0), (1, 1), (2, 2), (3, 3), + (4, 4), (5, 5), (6, 6), (7, 7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((RefTick, 0)); + _for_each_inner_rmt_clock_source!((Apb, 1)); + _for_each_inner_rmt_clock_source!((Apb)); + _for_each_inner_rmt_clock_source!((all(RefTick, 0), (Apb, 1))); + _for_each_inner_rmt_clock_source!((default(Apb))); + _for_each_inner_rmt_clock_source!((is_boolean)); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((3584)); + _for_each_inner_rsa_exponentiation!((4096)); + _for_each_inner_rsa_exponentiation!((all(512), (1024), (1536), (2048), (2560), + (3072), (3584), (4096))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((2048)); + _for_each_inner_rsa_multiplication!((all(512), (1024), (1536), (2048))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha384, "SHA-384"(sizes : 128, 48, 16) + (insecure_against :), 0)); _for_each_inner_sha_algorithm!((Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 0)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha256, + "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), 0), + (Sha384, "SHA-384"(sizes : 128, 48, 16) (insecure_against :), 0), (Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 0))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { +/// todo!() +/// } +/// +/// // APLL_CLK +/// +/// fn enable_apll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apll_clk_impl(_clocks: &mut ClockTree, _config: ApllClkConfig) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F160M_CLK +/// +/// fn enable_pll_f160m_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV_IN +/// +/// fn enable_cpu_pll_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuPllDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV +/// +/// fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { +/// todo!() +/// } +/// +/// // SYSCON_PRE_DIV_IN +/// +/// fn enable_syscon_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_syscon_pre_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SysconPreDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // SYSCON_PRE_DIV +/// +/// fn enable_syscon_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_syscon_pre_div_impl(_clocks: &mut ClockTree, _new_config: SysconPreDivConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ApbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // REF_TICK +/// +/// fn enable_ref_tick_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RefTickConfig, +/// ) { +/// todo!() +/// } +/// +/// // REF_TICK_XTAL +/// +/// fn enable_ref_tick_xtal_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_xtal_impl(_clocks: &mut ClockTree, _new_config: RefTickXtalConfig) { +/// todo!() +/// } +/// +/// // REF_TICK_FOSC +/// +/// fn enable_ref_tick_fosc_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_fosc_impl(_clocks: &mut ClockTree, _new_config: RefTickFoscConfig) { +/// todo!() +/// } +/// +/// // REF_TICK_APLL +/// +/// fn enable_ref_tick_apll_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_apll_impl(_clocks: &mut ClockTree, _new_config: RefTickApllConfig) { +/// todo!() +/// } +/// +/// // REF_TICK_PLL +/// +/// fn enable_ref_tick_pll_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_pll_impl(_clocks: &mut ClockTree, _new_config: RefTickPllConfig) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // APB_CLK_CPU_DIV2 +/// +/// fn enable_apb_clk_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // APB_CLK_80M +/// +/// fn enable_apb_clk_80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_DIV_CLK +/// +/// fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL_DIV_CLK +/// +/// fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RTC_SLOW_CLK +/// +/// fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // RTC_FAST_CLK +/// +/// fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART_MEM_CLK +/// +/// fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // MCPWM0_FUNCTION_CLOCK +/// +/// fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // MCPWM1_FUNCTION_CLOCK +/// +/// fn enable_mcpwm1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_MEM_CLOCK +/// +/// fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_MEM_CLOCK +/// +/// fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART2_FUNCTION_CLOCK +/// +/// fn enable_uart2_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart2_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART2_MEM_CLOCK +/// +/// fn enable_uart2_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart2_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 26 MHz + _26, + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_26 => 26000000, + XtalClkConfig::_40 => 40000000, + } + } + } + /// Selects the output frequency of `PLL_CLK`. Depends on `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PllClkConfig { + /// 320 MHz + _320, + /// 480 MHz + _480, + } + impl PllClkConfig { + pub fn value(&self) -> u32 { + match self { + PllClkConfig::_320 => 320000000, + PllClkConfig::_480 => 480000000, + } + } + } + /// The target frequency of the `APLL_CLK` clock source. Depends on `PLL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct ApllClkConfig(u32); + impl ApllClkConfig { + /// Creates a new clock source configuration. + /// + /// # Panics + /// + /// Panics if the output frequency value is outside the + /// valid range (16 MHz - 128 MHz). + pub const fn new(frequency: u32) -> Self { + ::core::assert!( + frequency >= 16000000u32 && frequency <= 128000000u32, + "`APLL_CLK` output frequency value must be between 16000000 and 128000000 \ + (inclusive)." + ); + Self(frequency) + } + } + impl ApllClkConfig { + pub fn value(&self) -> u32 { + self.0 + } + } + /// The list of clock signals that the `CPU_PLL_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivInConfig { + /// Selects `PLL_CLK`. + Pll, + /// Selects `APLL_CLK`. + Apll, + } + /// Configures the `CPU_PLL_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = CPU_PLL_DIV_IN / divisor`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivConfig { + /// Selects `divisor = 2`. + _2 = 2, + /// Selects `divisor = 4`. + _4 = 4, + } + impl CpuPllDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 2 => CpuPllDivConfig::_2, + 4 => CpuPllDivConfig::_4, + _ => ::core::panic!("Invalid CPU_PLL_DIV divisor value"), + } + } + } + impl CpuPllDivConfig { + fn divisor(self) -> u32 { + match self { + CpuPllDivConfig::_2 => 2, + CpuPllDivConfig::_4 => 4, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `SYSCON_PRE_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SysconPreDivInConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `SYSCON_PRE_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = SYSCON_PRE_DIV_IN / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SysconPreDivConfig { + divisor: u32, + } + impl SysconPreDivConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 1023). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 1023u32, + "`SYSCON_PRE_DIV` divisor value must be between 0 and 1023 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `APB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `APB_CLK_80M`. + Pll80m, + /// Selects `APB_CLK_CPU_DIV2`. + CpuDiv2, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `REF_TICK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RefTickConfig { + /// Selects `REF_TICK_PLL`. + Pll, + /// Selects `REF_TICK_APLL`. + Apll, + /// Selects `REF_TICK_XTAL`. + Xtal, + /// Selects `REF_TICK_FOSC`. + Fosc, + } + /// Configures the `REF_TICK_XTAL` clock divider. + /// + /// The output is calculated as `OUTPUT = APB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickXtalConfig { + divisor: u32, + } + impl RefTickXtalConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_XTAL` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `REF_TICK_FOSC` clock divider. + /// + /// The output is calculated as `OUTPUT = APB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickFoscConfig { + divisor: u32, + } + impl RefTickFoscConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_FOSC` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `REF_TICK_APLL` clock divider. + /// + /// The output is calculated as `OUTPUT = APB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickApllConfig { + divisor: u32, + } + impl RefTickApllConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_APLL` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `REF_TICK_PLL` clock divider. + /// + /// The output is calculated as `OUTPUT = APB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickPllConfig { + divisor: u32, + } + impl RefTickPllConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_PLL` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `SYSCON_PRE_DIV`. + Xtal, + /// Selects `SYSCON_PRE_DIV`. + RcFast, + /// Selects `CPU_PLL_DIV`. + Apll, + /// Selects `CPU_PLL_DIV`. + Pll, + } + /// The list of clock signals that the `RTC_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `RC_FAST_DIV_CLK`. + RcFast, + } + /// The list of clock signals that the `RTC_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcFastClkConfig { + /// Selects `XTAL_DIV_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + Rc, + } + /// The list of clock signals that the `MCPWM0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Mcpwm0FunctionClockConfig { + #[default] + /// Selects `PLL_F160M_CLK`. + PllF160m, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + #[default] + /// Selects `RC_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_DIV_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + #[default] + /// Selects `APB_CLK`. + Apb, + /// Selects `REF_TICK`. + RefTick, + } + /// The list of clock signals that the `UART0_MEM_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0MemClockConfig { + #[default] + /// Selects `UART_MEM_CLK`. + Mem, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + pll_clk: Option, + apll_clk: Option, + cpu_pll_div_in: Option, + cpu_pll_div: Option, + syscon_pre_div_in: Option, + syscon_pre_div: Option, + apb_clk: Option, + ref_tick: Option, + ref_tick_xtal: Option, + ref_tick_fosc: Option, + ref_tick_apll: Option, + ref_tick_pll: Option, + cpu_clk: Option, + rtc_slow_clk: Option, + rtc_fast_clk: Option, + mcpwm0_function_clock: Option, + mcpwm1_function_clock: Option, + timg0_calibration_clock: Option, + timg1_calibration_clock: Option, + uart0_function_clock: Option, + uart0_mem_clock: Option, + uart1_function_clock: Option, + uart1_mem_clock: Option, + uart2_function_clock: Option, + uart2_mem_clock: Option, + pll_clk_refcount: u32, + rc_fast_clk_refcount: u32, + pll_f160m_clk_refcount: u32, + apb_clk_refcount: u32, + ref_tick_refcount: u32, + xtal32k_clk_refcount: u32, + rc_slow_clk_refcount: u32, + rc_fast_div_clk_refcount: u32, + rtc_slow_clk_refcount: u32, + rtc_fast_clk_refcount: u32, + uart_mem_clk_refcount: u32, + mcpwm0_function_clock_refcount: u32, + mcpwm1_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart0_mem_clock_refcount: u32, + uart1_function_clock_refcount: u32, + uart1_mem_clock_refcount: u32, + uart2_function_clock_refcount: u32, + uart2_mem_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the PLL_CLK clock tree node + pub fn pll_clk(&self) -> Option { + self.pll_clk + } + /// Returns the current configuration of the APLL_CLK clock tree node + pub fn apll_clk(&self) -> Option { + self.apll_clk + } + /// Returns the current configuration of the CPU_PLL_DIV_IN clock tree node + pub fn cpu_pll_div_in(&self) -> Option { + self.cpu_pll_div_in + } + /// Returns the current configuration of the CPU_PLL_DIV clock tree node + pub fn cpu_pll_div(&self) -> Option { + self.cpu_pll_div + } + /// Returns the current configuration of the SYSCON_PRE_DIV_IN clock tree node + pub fn syscon_pre_div_in(&self) -> Option { + self.syscon_pre_div_in + } + /// Returns the current configuration of the SYSCON_PRE_DIV clock tree node + pub fn syscon_pre_div(&self) -> Option { + self.syscon_pre_div + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the REF_TICK clock tree node + pub fn ref_tick(&self) -> Option { + self.ref_tick + } + /// Returns the current configuration of the REF_TICK_XTAL clock tree node + pub fn ref_tick_xtal(&self) -> Option { + self.ref_tick_xtal + } + /// Returns the current configuration of the REF_TICK_FOSC clock tree node + pub fn ref_tick_fosc(&self) -> Option { + self.ref_tick_fosc + } + /// Returns the current configuration of the REF_TICK_APLL clock tree node + pub fn ref_tick_apll(&self) -> Option { + self.ref_tick_apll + } + /// Returns the current configuration of the REF_TICK_PLL clock tree node + pub fn ref_tick_pll(&self) -> Option { + self.ref_tick_pll + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the RTC_SLOW_CLK clock tree node + pub fn rtc_slow_clk(&self) -> Option { + self.rtc_slow_clk + } + /// Returns the current configuration of the RTC_FAST_CLK clock tree node + pub fn rtc_fast_clk(&self) -> Option { + self.rtc_fast_clk + } + /// Returns the current configuration of the MCPWM0_FUNCTION_CLOCK clock tree node + pub fn mcpwm0_function_clock(&self) -> Option { + self.mcpwm0_function_clock + } + /// Returns the current configuration of the MCPWM1_FUNCTION_CLOCK clock tree node + pub fn mcpwm1_function_clock(&self) -> Option { + self.mcpwm1_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART0_MEM_CLOCK clock tree node + pub fn uart0_mem_clock(&self) -> Option { + self.uart0_mem_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + /// Returns the current configuration of the UART1_MEM_CLOCK clock tree node + pub fn uart1_mem_clock(&self) -> Option { + self.uart1_mem_clock + } + /// Returns the current configuration of the UART2_FUNCTION_CLOCK clock tree node + pub fn uart2_function_clock(&self) -> Option { + self.uart2_function_clock + } + /// Returns the current configuration of the UART2_MEM_CLOCK clock tree node + pub fn uart2_mem_clock(&self) -> Option { + self.uart2_mem_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + pll_clk: None, + apll_clk: None, + cpu_pll_div_in: None, + cpu_pll_div: None, + syscon_pre_div_in: None, + syscon_pre_div: None, + apb_clk: None, + ref_tick: None, + ref_tick_xtal: None, + ref_tick_fosc: None, + ref_tick_apll: None, + ref_tick_pll: None, + cpu_clk: None, + rtc_slow_clk: None, + rtc_fast_clk: None, + mcpwm0_function_clock: None, + mcpwm1_function_clock: None, + timg0_calibration_clock: None, + timg1_calibration_clock: None, + uart0_function_clock: None, + uart0_mem_clock: None, + uart1_function_clock: None, + uart1_mem_clock: None, + uart2_function_clock: None, + uart2_mem_clock: None, + pll_clk_refcount: 0, + rc_fast_clk_refcount: 0, + pll_f160m_clk_refcount: 0, + apb_clk_refcount: 0, + ref_tick_refcount: 0, + xtal32k_clk_refcount: 0, + rc_slow_clk_refcount: 0, + rc_fast_div_clk_refcount: 0, + rtc_slow_clk_refcount: 0, + rtc_fast_clk_refcount: 0, + uart_mem_clk_refcount: 0, + mcpwm0_function_clock_refcount: 0, + mcpwm1_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart0_mem_clock_refcount: 0, + uart1_function_clock_refcount: 0, + uart1_mem_clock_refcount: 0, + uart2_function_clock_refcount: 0, + uart2_mem_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn configure_pll_clk(clocks: &mut ClockTree, config: PllClkConfig) { + if let Some(cpu_pll_div) = clocks.cpu_pll_div { + assert!(!((config.value() == 480000000) && (cpu_pll_div.value() == 4))); + } + clocks.pll_clk = Some(config); + configure_pll_clk_impl(clocks, config); + } + pub fn pll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.pll_clk + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + if increment_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + if decrement_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.pll_clk).value() + } + pub fn configure_apll_clk(clocks: &mut ClockTree, config: ApllClkConfig) { + clocks.apll_clk = Some(config); + configure_apll_clk_impl(clocks, config); + } + pub fn apll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apll_clk + } + pub fn request_apll_clk(clocks: &mut ClockTree) { + trace!("Requesting APLL_CLK"); + trace!("Enabling APLL_CLK"); + request_pll_clk(clocks); + enable_apll_clk_impl(clocks, true); + } + pub fn release_apll_clk(clocks: &mut ClockTree) { + trace!("Releasing APLL_CLK"); + trace!("Disabling APLL_CLK"); + enable_apll_clk_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn apll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.apll_clk).value() + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 8000000 + } + pub fn request_pll_f160m_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_F160M_CLK"); + if increment_reference_count(&mut clocks.pll_f160m_clk_refcount) { + trace!("Enabling PLL_F160M_CLK"); + request_pll_clk(clocks); + enable_pll_f160m_clk_impl(clocks, true); + } + } + pub fn release_pll_f160m_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_F160M_CLK"); + if decrement_reference_count(&mut clocks.pll_f160m_clk_refcount) { + trace!("Disabling PLL_F160M_CLK"); + enable_pll_f160m_clk_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f160m_clk_frequency(clocks: &mut ClockTree) -> u32 { + 160000000 + } + pub fn configure_cpu_pll_div_in(clocks: &mut ClockTree, new_selector: CpuPllDivInConfig) { + let old_selector = clocks.cpu_pll_div_in.replace(new_selector); + match new_selector { + CpuPllDivInConfig::Pll => request_pll_clk(clocks), + CpuPllDivInConfig::Apll => request_apll_clk(clocks), + } + configure_cpu_pll_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuPllDivInConfig::Pll => release_pll_clk(clocks), + CpuPllDivInConfig::Apll => release_apll_clk(clocks), + } + } + } + pub fn cpu_pll_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div_in + } + pub fn request_cpu_pll_div_in(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV_IN"); + trace!("Enabling CPU_PLL_DIV_IN"); + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => request_pll_clk(clocks), + CpuPllDivInConfig::Apll => request_apll_clk(clocks), + } + enable_cpu_pll_div_in_impl(clocks, true); + } + pub fn release_cpu_pll_div_in(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV_IN"); + trace!("Disabling CPU_PLL_DIV_IN"); + enable_cpu_pll_div_in_impl(clocks, false); + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => release_pll_clk(clocks), + CpuPllDivInConfig::Apll => release_apll_clk(clocks), + } + } + pub fn cpu_pll_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => pll_clk_frequency(clocks), + CpuPllDivInConfig::Apll => apll_clk_frequency(clocks), + } + } + pub fn configure_cpu_pll_div(clocks: &mut ClockTree, config: CpuPllDivConfig) { + if let Some(pll_clk) = clocks.pll_clk { + assert!(!((pll_clk.value() == 480000000) && (config.divisor() == 4))); + } + clocks.cpu_pll_div = Some(config); + configure_cpu_pll_div_impl(clocks, config); + } + pub fn cpu_pll_div_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div + } + pub fn request_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV"); + trace!("Enabling CPU_PLL_DIV"); + request_cpu_pll_div_in(clocks); + enable_cpu_pll_div_impl(clocks, true); + } + pub fn release_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV"); + trace!("Disabling CPU_PLL_DIV"); + enable_cpu_pll_div_impl(clocks, false); + release_cpu_pll_div_in(clocks); + } + pub fn cpu_pll_div_frequency(clocks: &mut ClockTree) -> u32 { + (cpu_pll_div_in_frequency(clocks) / unwrap!(clocks.cpu_pll_div).divisor()) + } + pub fn configure_syscon_pre_div_in( + clocks: &mut ClockTree, + new_selector: SysconPreDivInConfig, + ) { + let old_selector = clocks.syscon_pre_div_in.replace(new_selector); + match new_selector { + SysconPreDivInConfig::Xtal => request_xtal_clk(clocks), + SysconPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_syscon_pre_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SysconPreDivInConfig::Xtal => release_xtal_clk(clocks), + SysconPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn syscon_pre_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.syscon_pre_div_in + } + pub fn request_syscon_pre_div_in(clocks: &mut ClockTree) { + trace!("Requesting SYSCON_PRE_DIV_IN"); + trace!("Enabling SYSCON_PRE_DIV_IN"); + match unwrap!(clocks.syscon_pre_div_in) { + SysconPreDivInConfig::Xtal => request_xtal_clk(clocks), + SysconPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_syscon_pre_div_in_impl(clocks, true); + } + pub fn release_syscon_pre_div_in(clocks: &mut ClockTree) { + trace!("Releasing SYSCON_PRE_DIV_IN"); + trace!("Disabling SYSCON_PRE_DIV_IN"); + enable_syscon_pre_div_in_impl(clocks, false); + match unwrap!(clocks.syscon_pre_div_in) { + SysconPreDivInConfig::Xtal => release_xtal_clk(clocks), + SysconPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + pub fn syscon_pre_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.syscon_pre_div_in) { + SysconPreDivInConfig::Xtal => xtal_clk_frequency(clocks), + SysconPreDivInConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_syscon_pre_div(clocks: &mut ClockTree, config: SysconPreDivConfig) { + clocks.syscon_pre_div = Some(config); + configure_syscon_pre_div_impl(clocks, config); + } + pub fn syscon_pre_div_config(clocks: &mut ClockTree) -> Option { + clocks.syscon_pre_div + } + pub fn request_syscon_pre_div(clocks: &mut ClockTree) { + trace!("Requesting SYSCON_PRE_DIV"); + trace!("Enabling SYSCON_PRE_DIV"); + request_syscon_pre_div_in(clocks); + enable_syscon_pre_div_impl(clocks, true); + } + pub fn release_syscon_pre_div(clocks: &mut ClockTree) { + trace!("Releasing SYSCON_PRE_DIV"); + trace!("Disabling SYSCON_PRE_DIV"); + enable_syscon_pre_div_impl(clocks, false); + release_syscon_pre_div_in(clocks); + } + pub fn syscon_pre_div_frequency(clocks: &mut ClockTree) -> u32 { + (syscon_pre_div_in_frequency(clocks) / (unwrap!(clocks.syscon_pre_div).divisor() + 1)) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, new_selector: ApbClkConfig) { + let old_selector = clocks.apb_clk.replace(new_selector); + if clocks.apb_clk_refcount > 0 { + match new_selector { + ApbClkConfig::Pll80m => request_apb_clk_80m(clocks), + ApbClkConfig::CpuDiv2 => request_apb_clk_cpu_div2(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_apb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ApbClkConfig::Pll80m => release_apb_clk_80m(clocks), + ApbClkConfig::CpuDiv2 => release_apb_clk_cpu_div2(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_apb_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => request_apb_clk_80m(clocks), + ApbClkConfig::CpuDiv2 => request_apb_clk_cpu_div2(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => release_apb_clk_80m(clocks), + ApbClkConfig::CpuDiv2 => release_apb_clk_cpu_div2(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => apb_clk_80m_frequency(clocks), + ApbClkConfig::CpuDiv2 => apb_clk_cpu_div2_frequency(clocks), + ApbClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_ref_tick(clocks: &mut ClockTree, new_selector: RefTickConfig) { + let old_selector = clocks.ref_tick.replace(new_selector); + if clocks.ref_tick_refcount > 0 { + match new_selector { + RefTickConfig::Pll => request_ref_tick_pll(clocks), + RefTickConfig::Apll => request_ref_tick_apll(clocks), + RefTickConfig::Xtal => request_ref_tick_xtal(clocks), + RefTickConfig::Fosc => request_ref_tick_fosc(clocks), + } + configure_ref_tick_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RefTickConfig::Pll => release_ref_tick_pll(clocks), + RefTickConfig::Apll => release_ref_tick_apll(clocks), + RefTickConfig::Xtal => release_ref_tick_xtal(clocks), + RefTickConfig::Fosc => release_ref_tick_fosc(clocks), + } + } + } else { + configure_ref_tick_impl(clocks, old_selector, new_selector); + } + } + pub fn ref_tick_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick + } + pub fn request_ref_tick(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK"); + if increment_reference_count(&mut clocks.ref_tick_refcount) { + trace!("Enabling REF_TICK"); + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => request_ref_tick_pll(clocks), + RefTickConfig::Apll => request_ref_tick_apll(clocks), + RefTickConfig::Xtal => request_ref_tick_xtal(clocks), + RefTickConfig::Fosc => request_ref_tick_fosc(clocks), + } + enable_ref_tick_impl(clocks, true); + } + } + pub fn release_ref_tick(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK"); + if decrement_reference_count(&mut clocks.ref_tick_refcount) { + trace!("Disabling REF_TICK"); + enable_ref_tick_impl(clocks, false); + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => release_ref_tick_pll(clocks), + RefTickConfig::Apll => release_ref_tick_apll(clocks), + RefTickConfig::Xtal => release_ref_tick_xtal(clocks), + RefTickConfig::Fosc => release_ref_tick_fosc(clocks), + } + } + } + pub fn ref_tick_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => ref_tick_pll_frequency(clocks), + RefTickConfig::Apll => ref_tick_apll_frequency(clocks), + RefTickConfig::Xtal => ref_tick_xtal_frequency(clocks), + RefTickConfig::Fosc => ref_tick_fosc_frequency(clocks), + } + } + pub fn configure_ref_tick_xtal(clocks: &mut ClockTree, config: RefTickXtalConfig) { + clocks.ref_tick_xtal = Some(config); + configure_ref_tick_xtal_impl(clocks, config); + } + pub fn ref_tick_xtal_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_xtal + } + pub fn request_ref_tick_xtal(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_XTAL"); + trace!("Enabling REF_TICK_XTAL"); + request_apb_clk(clocks); + enable_ref_tick_xtal_impl(clocks, true); + } + pub fn release_ref_tick_xtal(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_XTAL"); + trace!("Disabling REF_TICK_XTAL"); + enable_ref_tick_xtal_impl(clocks, false); + release_apb_clk(clocks); + } + pub fn ref_tick_xtal_frequency(clocks: &mut ClockTree) -> u32 { + (apb_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_xtal).divisor() + 1)) + } + pub fn configure_ref_tick_fosc(clocks: &mut ClockTree, config: RefTickFoscConfig) { + clocks.ref_tick_fosc = Some(config); + configure_ref_tick_fosc_impl(clocks, config); + } + pub fn ref_tick_fosc_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_fosc + } + pub fn request_ref_tick_fosc(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_FOSC"); + trace!("Enabling REF_TICK_FOSC"); + request_apb_clk(clocks); + enable_ref_tick_fosc_impl(clocks, true); + } + pub fn release_ref_tick_fosc(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_FOSC"); + trace!("Disabling REF_TICK_FOSC"); + enable_ref_tick_fosc_impl(clocks, false); + release_apb_clk(clocks); + } + pub fn ref_tick_fosc_frequency(clocks: &mut ClockTree) -> u32 { + (apb_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_fosc).divisor() + 1)) + } + pub fn configure_ref_tick_apll(clocks: &mut ClockTree, config: RefTickApllConfig) { + clocks.ref_tick_apll = Some(config); + configure_ref_tick_apll_impl(clocks, config); + } + pub fn ref_tick_apll_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_apll + } + pub fn request_ref_tick_apll(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_APLL"); + trace!("Enabling REF_TICK_APLL"); + request_apb_clk(clocks); + enable_ref_tick_apll_impl(clocks, true); + } + pub fn release_ref_tick_apll(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_APLL"); + trace!("Disabling REF_TICK_APLL"); + enable_ref_tick_apll_impl(clocks, false); + release_apb_clk(clocks); + } + pub fn ref_tick_apll_frequency(clocks: &mut ClockTree) -> u32 { + (apb_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_apll).divisor() + 1)) + } + pub fn configure_ref_tick_pll(clocks: &mut ClockTree, config: RefTickPllConfig) { + clocks.ref_tick_pll = Some(config); + configure_ref_tick_pll_impl(clocks, config); + } + pub fn ref_tick_pll_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_pll + } + pub fn request_ref_tick_pll(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_PLL"); + trace!("Enabling REF_TICK_PLL"); + request_apb_clk(clocks); + enable_ref_tick_pll_impl(clocks, true); + } + pub fn release_ref_tick_pll(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_PLL"); + trace!("Disabling REF_TICK_PLL"); + enable_ref_tick_pll_impl(clocks, false); + release_apb_clk(clocks); + } + pub fn ref_tick_pll_frequency(clocks: &mut ClockTree) -> u32 { + (apb_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_pll).divisor() + 1)) + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Xtal => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_syscon_pre_div_in(clocks, SysconPreDivInConfig::Xtal); + configure_ref_tick(clocks, RefTickConfig::Xtal); + let config_value = + RefTickXtalConfig::new(((apb_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_xtal(clocks, config_value); + } + CpuClkConfig::RcFast => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_syscon_pre_div_in(clocks, SysconPreDivInConfig::RcFast); + configure_ref_tick(clocks, RefTickConfig::Fosc); + let config_value = + RefTickFoscConfig::new(((apb_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_fosc(clocks, config_value); + } + CpuClkConfig::Apll => { + configure_apb_clk(clocks, ApbClkConfig::CpuDiv2); + configure_cpu_pll_div_in(clocks, CpuPllDivInConfig::Apll); + configure_ref_tick(clocks, RefTickConfig::Apll); + let config_value = + RefTickApllConfig::new(((apb_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_apll(clocks, config_value); + } + CpuClkConfig::Pll => { + configure_apb_clk(clocks, ApbClkConfig::Pll80m); + configure_cpu_pll_div_in(clocks, CpuPllDivInConfig::Pll); + configure_ref_tick(clocks, RefTickConfig::Pll); + let config_value = + RefTickPllConfig::new(((apb_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_pll(clocks, config_value); + } + } + match new_selector { + CpuClkConfig::Xtal => request_syscon_pre_div(clocks), + CpuClkConfig::RcFast => request_syscon_pre_div(clocks), + CpuClkConfig::Apll => request_cpu_pll_div(clocks), + CpuClkConfig::Pll => request_cpu_pll_div(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Xtal => release_syscon_pre_div(clocks), + CpuClkConfig::RcFast => release_syscon_pre_div(clocks), + CpuClkConfig::Apll => release_cpu_pll_div(clocks), + CpuClkConfig::Pll => release_cpu_pll_div(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Xtal => syscon_pre_div_frequency(clocks), + CpuClkConfig::RcFast => syscon_pre_div_frequency(clocks), + CpuClkConfig::Apll => cpu_pll_div_frequency(clocks), + CpuClkConfig::Pll => cpu_pll_div_frequency(clocks), + } + } + pub fn request_apb_clk_cpu_div2(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK_CPU_DIV2"); + trace!("Enabling APB_CLK_CPU_DIV2"); + request_cpu_clk(clocks); + enable_apb_clk_cpu_div2_impl(clocks, true); + } + pub fn release_apb_clk_cpu_div2(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK_CPU_DIV2"); + trace!("Disabling APB_CLK_CPU_DIV2"); + enable_apb_clk_cpu_div2_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn apb_clk_cpu_div2_frequency(clocks: &mut ClockTree) -> u32 { + (cpu_clk_frequency(clocks) / 2) + } + pub fn request_apb_clk_80m(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK_80M"); + trace!("Enabling APB_CLK_80M"); + request_cpu_clk(clocks); + enable_apb_clk_80m_impl(clocks, true); + } + pub fn release_apb_clk_80m(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK_80M"); + trace!("Disabling APB_CLK_80M"); + enable_apb_clk_80m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn apb_clk_80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 150000 + } + pub fn request_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_DIV_CLK"); + if increment_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Enabling RC_FAST_DIV_CLK"); + request_rc_fast_clk(clocks); + enable_rc_fast_div_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_DIV_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Disabling RC_FAST_DIV_CLK"); + enable_rc_fast_div_clk_impl(clocks, false); + release_rc_fast_clk(clocks); + } + } + pub fn rc_fast_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / 256) + } + pub fn request_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_DIV_CLK"); + trace!("Enabling XTAL_DIV_CLK"); + request_xtal_clk(clocks); + enable_xtal_div_clk_impl(clocks, true); + } + pub fn release_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_DIV_CLK"); + trace!("Disabling XTAL_DIV_CLK"); + enable_xtal_div_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 4) + } + pub fn configure_rtc_slow_clk(clocks: &mut ClockTree, new_selector: RtcSlowClkConfig) { + let old_selector = clocks.rtc_slow_clk.replace(new_selector); + if clocks.rtc_slow_clk_refcount > 0 { + match new_selector { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } else { + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_slow_clk + } + pub fn request_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rtc_slow_clk_refcount) { + trace!("Enabling RTC_SLOW_CLK"); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + enable_rtc_slow_clk_impl(clocks, true); + } + } + pub fn release_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rtc_slow_clk_refcount) { + trace!("Disabling RTC_SLOW_CLK"); + enable_rtc_slow_clk_impl(clocks, false); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } + pub fn rtc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + RtcSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcFast => rc_fast_div_clk_frequency(clocks), + } + } + pub fn configure_rtc_fast_clk(clocks: &mut ClockTree, new_selector: RtcFastClkConfig) { + let old_selector = clocks.rtc_fast_clk.replace(new_selector); + if clocks.rtc_fast_clk_refcount > 0 { + match new_selector { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk(clocks), + } + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk(clocks), + } + } + } else { + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_fast_clk + } + pub fn request_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_FAST_CLK"); + if increment_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Enabling RTC_FAST_CLK"); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk(clocks), + } + enable_rtc_fast_clk_impl(clocks, true); + } + } + pub fn release_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Disabling RTC_FAST_CLK"); + enable_rtc_fast_clk_impl(clocks, false); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk(clocks), + } + } + } + pub fn rtc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => xtal_div_clk_frequency(clocks), + RtcFastClkConfig::Rc => rc_fast_clk_frequency(clocks), + } + } + pub fn request_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Requesting UART_MEM_CLK"); + if increment_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Enabling UART_MEM_CLK"); + request_xtal_clk(clocks); + enable_uart_mem_clk_impl(clocks, true); + } + } + pub fn release_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Releasing UART_MEM_CLK"); + if decrement_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Disabling UART_MEM_CLK"); + enable_uart_mem_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn uart_mem_clk_frequency(clocks: &mut ClockTree) -> u32 { + xtal_clk_frequency(clocks) + } + pub fn configure_mcpwm0_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm0_function_clock.replace(new_selector); + if clocks.mcpwm0_function_clock_refcount > 0 { + request_pll_f160m_clk(clocks); + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_pll_f160m_clk(clocks); + } + } else { + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm0_function_clock + } + pub fn request_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Enabling MCPWM0_FUNCTION_CLOCK"); + request_pll_f160m_clk(clocks); + enable_mcpwm0_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Disabling MCPWM0_FUNCTION_CLOCK"); + enable_mcpwm0_function_clock_impl(clocks, false); + release_pll_f160m_clk(clocks); + } + } + pub fn mcpwm0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + pll_f160m_clk_frequency(clocks) + } + pub fn configure_mcpwm1_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm1_function_clock.replace(new_selector); + if clocks.mcpwm1_function_clock_refcount > 0 { + request_pll_f160m_clk(clocks); + configure_mcpwm1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_pll_f160m_clk(clocks); + } + } else { + configure_mcpwm1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm1_function_clock + } + pub fn request_mcpwm1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm1_function_clock_refcount) { + trace!("Enabling MCPWM1_FUNCTION_CLOCK"); + request_pll_f160m_clk(clocks); + enable_mcpwm1_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm1_function_clock_refcount) { + trace!("Disabling MCPWM1_FUNCTION_CLOCK"); + enable_mcpwm1_function_clock_impl(clocks, false); + release_pll_f160m_clk(clocks); + } + } + pub fn mcpwm1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + pll_f160m_clk_frequency(clocks) + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RefTick => ref_tick_frequency(clocks), + } + } + pub fn configure_uart0_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart0_mem_clock.replace(new_selector); + if clocks.uart0_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart0_mem_clock + } + pub fn request_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Enabling UART0_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart0_mem_clock_impl(clocks, true); + } + } + pub fn release_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Disabling UART0_MEM_CLOCK"); + enable_uart0_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart0_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RefTick => ref_tick_frequency(clocks), + } + } + pub fn configure_uart1_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart1_mem_clock.replace(new_selector); + if clocks.uart1_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart1_mem_clock + } + pub fn request_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Enabling UART1_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart1_mem_clock_impl(clocks, true); + } + } + pub fn release_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Disabling UART1_MEM_CLOCK"); + enable_uart1_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart1_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart2_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart2_function_clock.replace(new_selector); + if clocks.uart2_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + configure_uart2_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } else { + configure_uart2_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart2_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart2_function_clock + } + pub fn request_uart2_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART2_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart2_function_clock_refcount) { + trace!("Enabling UART2_FUNCTION_CLOCK"); + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + enable_uart2_function_clock_impl(clocks, true); + } + } + pub fn release_uart2_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART2_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart2_function_clock_refcount) { + trace!("Disabling UART2_FUNCTION_CLOCK"); + enable_uart2_function_clock_impl(clocks, false); + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } + pub fn uart2_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RefTick => ref_tick_frequency(clocks), + } + } + pub fn configure_uart2_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart2_mem_clock.replace(new_selector); + if clocks.uart2_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart2_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart2_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart2_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart2_mem_clock + } + pub fn request_uart2_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART2_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart2_mem_clock_refcount) { + trace!("Enabling UART2_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart2_mem_clock_impl(clocks, true); + } + } + pub fn release_uart2_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART2_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart2_mem_clock_refcount) { + trace!("Disabling UART2_MEM_CLOCK"); + enable_uart2_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart2_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `PLL_CLK` configuration. + pub pll_clk: Option, + /// `APLL_CLK` configuration. + pub apll_clk: Option, + /// `CPU_PLL_DIV` configuration. + pub cpu_pll_div: Option, + /// `SYSCON_PRE_DIV` configuration. + pub syscon_pre_div: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `RTC_SLOW_CLK` configuration. + pub rtc_slow_clk: Option, + /// `RTC_FAST_CLK` configuration. + pub rtc_fast_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.pll_clk { + configure_pll_clk(clocks, config); + } + if let Some(config) = self.apll_clk { + configure_apll_clk(clocks, config); + } + if let Some(config) = self.cpu_pll_div { + configure_cpu_pll_div(clocks, config); + } + if let Some(config) = self.syscon_pre_div { + configure_syscon_pre_div(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.rtc_slow_clk { + configure_rtc_slow_clk(clocks, config); + } + if let Some(config) = self.rtc_fast_clk { + configure_rtc_fast_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2C_EXT1 peripheral clock signal + I2cExt1, + /// I2S0 peripheral clock signal + I2s0, + /// I2S1 peripheral clock signal + I2s1, + /// LEDC peripheral clock signal + Ledc, + /// MCPWM0 peripheral clock signal + Mcpwm0, + /// MCPWM1 peripheral clock signal + Mcpwm1, + /// PCNT peripheral clock signal + Pcnt, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SPI3 peripheral clock signal + Spi3, + /// SPI_DMA peripheral clock signal + SpiDma, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TWAI0 peripheral clock signal + Twai0, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UART2 peripheral clock signal + Uart2, + /// UART_MEM peripheral clock signal + UartMem, + /// UHCI0 peripheral clock signal + Uhci0, + /// UHCI1 peripheral clock signal + Uhci1, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = &[Self::Timg0, Self::Uart0, Self::UartMem]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::I2cExt0, + Self::I2cExt1, + Self::I2s0, + Self::I2s1, + Self::Ledc, + Self::Mcpwm0, + Self::Mcpwm1, + Self::Pcnt, + Self::Rmt, + Self::Rsa, + Self::Sha, + Self::Spi2, + Self::Spi3, + Self::SpiDma, + Self::Timg0, + Self::Timg1, + Self::Twai0, + Self::Uart0, + Self::Uart1, + Self::Uart2, + Self::UartMem, + Self::Uhci0, + Self::Uhci1, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .peri_clk_en() + .modify(|_, w| w.crypto_aes_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.i2c_ext0_clk_en().bit(enable)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.i2c_ext1_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.i2s0_clk_en().bit(enable)); + } + Peripheral::I2s1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.i2s1_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.pwm0_clk_en().bit(enable)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.pwm1_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .peri_clk_en() + .modify(|_, w| w.crypto_rsa_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .peri_clk_en() + .modify(|_, w| w.crypto_sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.spi3_clk_en().bit(enable)); + } + Peripheral::SpiDma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.spi_dma_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.timergroup_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.timergroup1_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.twai_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uart_clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uart1_clk_en().bit(enable)); + } + Peripheral::Uart2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uart2_clk_en().bit(enable)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uart_mem_clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uhci0_clk_en().bit(enable)); + } + Peripheral::Uhci1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en() + .modify(|_, w| w.uhci1_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .peri_rst_en() + .modify(|_, w| w.crypto_aes_rst().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.i2c_ext0_rst().bit(reset)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.i2c_ext1_rst().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.i2s0_rst().bit(reset)); + } + Peripheral::I2s1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.i2s1_rst().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.ledc_rst().bit(reset)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.pwm0_rst().bit(reset)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.pwm1_rst().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.pcnt_rst().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.rmt_rst().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .peri_rst_en() + .modify(|_, w| w.crypto_rsa_rst().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .peri_rst_en() + .modify(|_, w| w.crypto_sha_rst().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.spi2_rst().bit(reset)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.spi3_rst().bit(reset)); + } + Peripheral::SpiDma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.spi_dma_rst().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.timergroup_rst().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.timergroup1_rst().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.twai_rst().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uart_rst().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uart1_rst().bit(reset)); + } + Peripheral::Uart2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uart2_rst().bit(reset)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uart_mem_rst().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uhci0_rst().bit(reset)); + } + Peripheral::Uhci1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en() + .modify(|_, w| w.uhci1_rst().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x3FFAE000..0x40000000 + }; + (size as str, "DRAM") => { + "335872" + }; + ("DRAM2_UNINIT") => { + 0x3FFE7E30..0x40000000 + }; + (size as str, "DRAM2_UNINIT") => { + "98768" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((1, I2C1, I2cExt1, I2CEXT1_SCL, + I2CEXT1_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA), (1, I2C1, I2cExt1, I2CEXT1_SCL, I2CEXT1_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((2, UART2, Uart2, U2RXD, U2TXD, U2CTS, U2RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS), (2, UART2, Uart2, U2RXD, U2TXD, U2CTS, + U2RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, HSPICLK[HSPICS0, HSPICS1, + HSPICS2] [HSPID, HSPIQ, HSPIWP, HSPIHD], true)); + _for_each_inner_spi_master!((SPI3, Spi3, VSPICLK[VSPICS0, VSPICS1, VSPICS2] + [VSPID, VSPIQ, VSPIWP, VSPIHD], true)); _for_each_inner_spi_master!((all(SPI2, + Spi2, HSPICLK[HSPICS0, HSPICS1, HSPICS2] [HSPID, HSPIQ, HSPIWP, HSPIHD], true), + (SPI3, Spi3, VSPICLK[VSPICS0, VSPICS1, VSPICS2] [VSPID, VSPIQ, VSPIWP, VSPIHD], + true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, HSPICLK, HSPID, HSPIQ, HSPICS0)); + _for_each_inner_spi_slave!((SPI3, Spi3, VSPICLK, VSPID, VSPIQ, VSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, HSPICLK, HSPID, HSPIQ, HSPICS0), + (SPI3, Spi3, VSPICLK, VSPID, VSPIQ, VSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO1 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO1 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO3 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO4 peripheral singleton"] + GPIO4 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO10 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO10 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO12 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO14 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO15 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO18 peripheral singleton"] + GPIO18 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin is only available on ESP32-PICO-V3.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO20 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO21 peripheral singleton"] + GPIO21 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO22 peripheral singleton"] GPIO22 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO23 peripheral singleton"] + GPIO23 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO25 peripheral singleton"] GPIO25 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO26 peripheral singleton"] + GPIO26 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton"] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO32 peripheral singleton"] + GPIO32 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO33 peripheral singleton"] GPIO33 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO34 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO35 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO36 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO37 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO38 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO38 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO39 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "BB peripheral singleton"] BB <= + BB() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DPORT peripheral singleton"] DPORT <= DPORT() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTEM peripheral singleton"] + SYSTEM <= DPORT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EMAC_DMA peripheral singleton"] + EMAC_DMA <= EMAC_DMA() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "EMAC_EXT peripheral singleton"] EMAC_EXT <= EMAC_EXT() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EMAC_MAC peripheral singleton"] + EMAC_MAC <= EMAC_MAC() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "FLASH_ENCRYPTION peripheral singleton"] FLASH_ENCRYPTION <= + FLASH_ENCRYPTION() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FRC_TIMER peripheral singleton"] FRC_TIMER <= FRC_TIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HINF peripheral singleton"] + HINF <= HINF() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C1 peripheral singleton"] + I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2S1 peripheral singleton"] + I2S1 <= I2S1(I2S1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MCPWM1 peripheral singleton"] + MCPWM1 <= MCPWM1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "NRX peripheral singleton"] NRX <= NRX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PCNT peripheral singleton"] + PCNT <= PCNT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RMT peripheral singleton"] RMT <= RMT() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RNG peripheral singleton"] RNG + <= RNG() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RSA peripheral singleton"] RSA <= RSA(RSA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LPWR peripheral singleton"] + LPWR <= RTC_CNTL() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RTC_I2C peripheral singleton"] RTC_I2C <= RTC_I2C() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RTC_IO peripheral singleton"] + RTC_IO <= RTC_IO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SDHOST peripheral singleton"] SDHOST <= SDHOST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SENS peripheral singleton"] + SENS <= SENS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SHA peripheral singleton"] SHA <= SHA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SLC peripheral singleton"] SLC + <= SLC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SLCHOST peripheral singleton"] SLCHOST <= SLCHOST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI0 peripheral singleton"] + SPI0 <= SPI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI1 peripheral singleton"] SPI1 <= SPI1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI2 peripheral singleton"] + SPI2 <= SPI2(SPI2_DMA : { bind_dma_interrupt, enable_dma_interrupt, + disable_dma_interrupt }, SPI2 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI3 peripheral singleton"] SPI3 <= SPI3(SPI3_DMA : { bind_dma_interrupt, + enable_dma_interrupt, disable_dma_interrupt }, SPI3 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG0 peripheral singleton"] + TIMG0 <= TIMG0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TWAI0 peripheral singleton"] + TWAI0 <= TWAI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART2 peripheral singleton"] UART2 <= UART2(UART2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI1 peripheral singleton"] UHCI1 <= UHCI1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WIFI peripheral singleton"] + WIFI <= WIFI(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_SPI2 peripheral singleton"] DMA_SPI2 <= SPI2() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_SPI3 peripheral singleton"] + DMA_SPI3 <= SPI3() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_I2S0 peripheral singleton"] DMA_I2S0 <= I2S0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_I2S1 peripheral singleton"] + DMA_I2S1 <= I2S1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC2 peripheral singleton"] + ADC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BT peripheral singleton"] BT <= virtual(BT_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, RWBLE : { bind_rwble_interrupt, + enable_rwble_interrupt, disable_rwble_interrupt }, RWBT : { bind_rwbt_interrupt, + enable_rwbt_interrupt, disable_rwbt_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "CPU_CTRL peripheral singleton"] + CPU_CTRL <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "DAC1 peripheral singleton"] DAC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DAC2 peripheral singleton"] + DAC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FLASH peripheral singleton"] FLASH <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PSRAM peripheral singleton"] + PSRAM <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TOUCH peripheral singleton"] + TOUCH <= virtual() (unstable))); _for_each_inner_peripheral!((GPIO0)); + _for_each_inner_peripheral!((GPIO1)); _for_each_inner_peripheral!((GPIO2)); + _for_each_inner_peripheral!((GPIO3)); _for_each_inner_peripheral!((GPIO4)); + _for_each_inner_peripheral!((GPIO5)); _for_each_inner_peripheral!((GPIO6)); + _for_each_inner_peripheral!((GPIO7)); _for_each_inner_peripheral!((GPIO8)); + _for_each_inner_peripheral!((GPIO9)); _for_each_inner_peripheral!((GPIO10)); + _for_each_inner_peripheral!((GPIO11)); _for_each_inner_peripheral!((GPIO12)); + _for_each_inner_peripheral!((GPIO13)); _for_each_inner_peripheral!((GPIO14)); + _for_each_inner_peripheral!((GPIO15)); _for_each_inner_peripheral!((GPIO16)); + _for_each_inner_peripheral!((GPIO17)); _for_each_inner_peripheral!((GPIO18)); + _for_each_inner_peripheral!((GPIO19)); _for_each_inner_peripheral!((GPIO20)); + _for_each_inner_peripheral!((GPIO21)); _for_each_inner_peripheral!((GPIO22)); + _for_each_inner_peripheral!((GPIO23)); _for_each_inner_peripheral!((GPIO25)); + _for_each_inner_peripheral!((GPIO26)); _for_each_inner_peripheral!((GPIO27)); + _for_each_inner_peripheral!((GPIO32)); _for_each_inner_peripheral!((GPIO33)); + _for_each_inner_peripheral!((GPIO34)); _for_each_inner_peripheral!((GPIO35)); + _for_each_inner_peripheral!((GPIO36)); _for_each_inner_peripheral!((GPIO37)); + _for_each_inner_peripheral!((GPIO38)); _for_each_inner_peripheral!((GPIO39)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_CTRL(unstable))); + _for_each_inner_peripheral!((BB(unstable))); + _for_each_inner_peripheral!((DPORT(unstable))); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((EMAC_DMA(unstable))); + _for_each_inner_peripheral!((EMAC_EXT(unstable))); + _for_each_inner_peripheral!((EMAC_MAC(unstable))); + _for_each_inner_peripheral!((FLASH_ENCRYPTION(unstable))); + _for_each_inner_peripheral!((FRC_TIMER(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HINF(unstable))); + _for_each_inner_peripheral!((I2C0)); _for_each_inner_peripheral!((I2C1)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((I2S1(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((MCPWM0(unstable))); + _for_each_inner_peripheral!((MCPWM1(unstable))); + _for_each_inner_peripheral!((NRX(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((RTC_I2C(unstable))); + _for_each_inner_peripheral!((RTC_IO(unstable))); + _for_each_inner_peripheral!((SDHOST(unstable))); + _for_each_inner_peripheral!((SENS(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SLC(unstable))); + _for_each_inner_peripheral!((SLCHOST(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); _for_each_inner_peripheral!((SPI3)); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UART2)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((UHCI1(unstable))); + _for_each_inner_peripheral!((WIFI)); + _for_each_inner_peripheral!((DMA_SPI2(unstable))); + _for_each_inner_peripheral!((DMA_SPI3(unstable))); + _for_each_inner_peripheral!((DMA_I2S0(unstable))); + _for_each_inner_peripheral!((DMA_I2S1(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((ADC2(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((CPU_CTRL(unstable))); + _for_each_inner_peripheral!((DAC1(unstable))); + _for_each_inner_peripheral!((DAC2(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((PSRAM(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((TOUCH(unstable))); + _for_each_inner_peripheral!((I2S0, I2s0, 0)); _for_each_inner_peripheral!((I2S1, + I2s1, 1)); _for_each_inner_peripheral!((SPI2, Spi2, 2)); + _for_each_inner_peripheral!((SPI3, Spi3, 3)); _for_each_inner_peripheral!((UHCI0, + Uhci0, 4)); _for_each_inner_peripheral!((UHCI1, Uhci1, 5)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton"] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin is only available on ESP32-PICO-V3.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO20 <= virtual()), (@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual()), (@ peri_type #[doc = + "GPIO22 peripheral singleton"] GPIO22 <= virtual()), (@ peri_type #[doc = + "GPIO23 peripheral singleton"] GPIO23 <= virtual()), (@ peri_type #[doc = + "GPIO25 peripheral singleton"] GPIO25 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton"] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton"] GPIO27 <= virtual()), (@ peri_type #[doc = + "GPIO32 peripheral singleton"] GPIO32 <= virtual()), (@ peri_type #[doc = + "GPIO33 peripheral singleton"] GPIO33 <= virtual()), (@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO34 <= virtual()), (@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO35 <= virtual()), (@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO36 <= virtual()), (@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO37 <= virtual()), (@ peri_type #[doc = + "GPIO38 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO38 <= virtual()), (@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = "
    • This pin can only be used as an input.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO39 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES() (unstable)), (@ peri_type #[doc = + "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable)), (@ peri_type + #[doc = "BB peripheral singleton"] BB <= BB() (unstable)), (@ peri_type #[doc = + "DPORT peripheral singleton"] DPORT <= DPORT() (unstable)), (@ peri_type #[doc = + "SYSTEM peripheral singleton"] SYSTEM <= DPORT() (unstable)), (@ peri_type #[doc + = "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable)), (@ peri_type #[doc + = "EMAC_DMA peripheral singleton"] EMAC_DMA <= EMAC_DMA() (unstable)), (@ + peri_type #[doc = "EMAC_EXT peripheral singleton"] EMAC_EXT <= EMAC_EXT() + (unstable)), (@ peri_type #[doc = "EMAC_MAC peripheral singleton"] EMAC_MAC <= + EMAC_MAC() (unstable)), (@ peri_type #[doc = + "FLASH_ENCRYPTION peripheral singleton"] FLASH_ENCRYPTION <= FLASH_ENCRYPTION() + (unstable)), (@ peri_type #[doc = "FRC_TIMER peripheral singleton"] FRC_TIMER <= + FRC_TIMER() (unstable)), (@ peri_type #[doc = "GPIO peripheral singleton"] GPIO + <= GPIO() (unstable)), (@ peri_type #[doc = "GPIO_SD peripheral singleton"] + GPIO_SD <= GPIO_SD() (unstable)), (@ peri_type #[doc = + "HINF peripheral singleton"] HINF <= HINF() (unstable)), (@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "I2S1 peripheral singleton"] I2S1 <= I2S1(I2S1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type + #[doc = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable)), (@ peri_type + #[doc = "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable)), (@ + peri_type #[doc = "MCPWM1 peripheral singleton"] MCPWM1 <= MCPWM1() (unstable)), + (@ peri_type #[doc = "NRX peripheral singleton"] NRX <= NRX() (unstable)), (@ + peri_type #[doc = "PCNT peripheral singleton"] PCNT <= PCNT() (unstable)), (@ + peri_type #[doc = "RMT peripheral singleton"] RMT <= RMT() (unstable)), (@ + peri_type #[doc = "RNG peripheral singleton"] RNG <= RNG() (unstable)), (@ + peri_type #[doc = "RSA peripheral singleton"] RSA <= RSA(RSA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "LPWR peripheral singleton"] LPWR <= RTC_CNTL() + (unstable)), (@ peri_type #[doc = "RTC_I2C peripheral singleton"] RTC_I2C <= + RTC_I2C() (unstable)), (@ peri_type #[doc = "RTC_IO peripheral singleton"] RTC_IO + <= RTC_IO() (unstable)), (@ peri_type #[doc = "SDHOST peripheral singleton"] + SDHOST <= SDHOST() (unstable)), (@ peri_type #[doc = "SENS peripheral singleton"] + SENS <= SENS() (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA + <= SHA() (unstable)), (@ peri_type #[doc = "SLC peripheral singleton"] SLC <= + SLC() (unstable)), (@ peri_type #[doc = "SLCHOST peripheral singleton"] SLCHOST + <= SLCHOST() (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 + <= SPI0() (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= + SPI1() (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= + SPI2(SPI2_DMA : { bind_dma_interrupt, enable_dma_interrupt, disable_dma_interrupt + }, SPI2 : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + })), (@ peri_type #[doc = "SPI3 peripheral singleton"] SPI3 <= SPI3(SPI3_DMA : { + bind_dma_interrupt, enable_dma_interrupt, disable_dma_interrupt }, SPI3 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable)), (@ + peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() (unstable)), (@ + peri_type #[doc = "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() (unstable)), (@ + peri_type #[doc = "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "UART1 peripheral singleton"] UART1 <= UART1(UART1 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "UART2 peripheral singleton"] UART2 <= UART2(UART2 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable)), (@ + peri_type #[doc = "UHCI1 peripheral singleton"] UHCI1 <= UHCI1() (unstable)), (@ + peri_type #[doc = "WIFI peripheral singleton"] WIFI <= WIFI(WIFI_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt })), (@ peri_type + #[doc = "DMA_SPI2 peripheral singleton"] DMA_SPI2 <= SPI2() (unstable)), (@ + peri_type #[doc = "DMA_SPI3 peripheral singleton"] DMA_SPI3 <= SPI3() + (unstable)), (@ peri_type #[doc = "DMA_I2S0 peripheral singleton"] DMA_I2S0 <= + I2S0() (unstable)), (@ peri_type #[doc = "DMA_I2S1 peripheral singleton"] + DMA_I2S1 <= I2S1() (unstable)), (@ peri_type #[doc = "ADC1 peripheral singleton"] + ADC1 <= virtual() (unstable)), (@ peri_type #[doc = "ADC2 peripheral singleton"] + ADC2 <= virtual() (unstable)), (@ peri_type #[doc = "BT peripheral singleton"] BT + <= virtual(BT_BB : { bind_bb_interrupt, enable_bb_interrupt, disable_bb_interrupt + }, RWBLE : { bind_rwble_interrupt, enable_rwble_interrupt, + disable_rwble_interrupt }, RWBT : { bind_rwbt_interrupt, enable_rwbt_interrupt, + disable_rwbt_interrupt }) (unstable)), (@ peri_type #[doc = + "CPU_CTRL peripheral singleton"] CPU_CTRL <= virtual() (unstable)), (@ peri_type + #[doc = "DAC1 peripheral singleton"] DAC1 <= virtual() (unstable)), (@ peri_type + #[doc = "DAC2 peripheral singleton"] DAC2 <= virtual() (unstable)), (@ peri_type + #[doc = "FLASH peripheral singleton"] FLASH <= virtual() (unstable)), (@ + peri_type #[doc = "PSRAM peripheral singleton"] PSRAM <= virtual() (unstable)), + (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= + virtual() (unstable)), (@ peri_type #[doc = "TOUCH peripheral singleton"] TOUCH + <= virtual() (unstable)))); _for_each_inner_peripheral!((singletons(GPIO0), + (GPIO1), (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), + (GPIO10), (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), + (GPIO18), (GPIO19), (GPIO20), (GPIO21), (GPIO22), (GPIO23), (GPIO25), (GPIO26), + (GPIO27), (GPIO32), (GPIO33), (GPIO34), (GPIO35), (GPIO36), (GPIO37), (GPIO38), + (GPIO39), (AES(unstable)), (APB_CTRL(unstable)), (BB(unstable)), + (DPORT(unstable)), (SYSTEM(unstable)), (EMAC_DMA(unstable)), + (EMAC_EXT(unstable)), (EMAC_MAC(unstable)), (FLASH_ENCRYPTION(unstable)), + (FRC_TIMER(unstable)), (GPIO(unstable)), (GPIO_SD(unstable)), (HINF(unstable)), + (I2C0), (I2C1), (I2S0(unstable)), (I2S1(unstable)), (IO_MUX(unstable)), + (LEDC(unstable)), (MCPWM0(unstable)), (MCPWM1(unstable)), (NRX(unstable)), + (PCNT(unstable)), (RMT(unstable)), (RNG(unstable)), (RSA(unstable)), + (LPWR(unstable)), (RTC_I2C(unstable)), (RTC_IO(unstable)), (SDHOST(unstable)), + (SENS(unstable)), (SHA(unstable)), (SLC(unstable)), (SLCHOST(unstable)), + (SPI0(unstable)), (SPI1(unstable)), (SPI2), (SPI3), (TIMG0(unstable)), + (TIMG1(unstable)), (TWAI0(unstable)), (UART0), (UART1), (UART2), + (UHCI0(unstable)), (UHCI1(unstable)), (WIFI), (DMA_SPI2(unstable)), + (DMA_SPI3(unstable)), (DMA_I2S0(unstable)), (DMA_I2S1(unstable)), + (ADC1(unstable)), (ADC2(unstable)), (BT(unstable)), (CPU_CTRL(unstable)), + (DAC1(unstable)), (DAC2(unstable)), (FLASH(unstable)), (PSRAM(unstable)), + (SW_INTERRUPT(unstable)), (TOUCH(unstable)))); + _for_each_inner_peripheral!((dma_eligible(I2S0, I2s0, 0), (I2S1, I2s1, 1), (SPI2, + Spi2, 2), (SPI3, Spi3, 3), (UHCI0, Uhci0, 4), (UHCI1, Uhci1, 5))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0(_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => + EMAC_TX_CLK) ([Input] [Output]))); _for_each_inner_gpio!((1, GPIO1(_5 => + EMAC_RXD2) (_0 => U0TXD _1 => CLK_OUT3) ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2(_1 => HSPIWP _3 => HS2_DATA0 _4 => SD_DATA0) (_1 + => HSPIWP _3 => HS2_DATA0 _4 => SD_DATA0) ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3(_0 => U0RXD) (_1 => CLK_OUT2) ([Input] + [Output]))); _for_each_inner_gpio!((4, GPIO4(_1 => HSPIHD _3 => HS2_DATA1 _4 => + SD_DATA1 _5 => EMAC_TX_ER) (_1 => HSPIHD _3 => HS2_DATA1 _4 => SD_DATA1 _5 => + EMAC_TX_ER) ([Input] [Output]))); _for_each_inner_gpio!((5, GPIO5(_1 => VSPICS0 + _3 => HS1_DATA6 _5 => EMAC_RX_CLK) (_1 => VSPICS0 _3 => HS1_DATA6) ([Input] + [Output]))); _for_each_inner_gpio!((6, GPIO6(_1 => SPICLK _4 => U1CTS) (_0 => + SD_CLK _1 => SPICLK _3 => HS1_CLK) ([Input] [Output]))); + _for_each_inner_gpio!((7, GPIO7(_0 => SD_DATA0 _1 => SPIQ _3 => HS1_DATA0) (_0 => + SD_DATA0 _1 => SPIQ _3 => HS1_DATA0 _4 => U2RTS) ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8(_0 => SD_DATA1 _1 => SPID _3 => HS1_DATA1 _4 => + U2CTS) (_0 => SD_DATA1 _1 => SPID _3 => HS1_DATA1) ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9(_0 => SD_DATA2 _1 => SPIHD _3 => HS1_DATA2 _4 => + U1RXD) (_0 => SD_DATA2 _1 => SPIHD _3 => HS1_DATA2) ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10(_0 => SD_DATA3 _1 => SPIWP _3 => HS1_DATA3) (_0 + => SD_DATA3 _1 => SPIWP _3 => HS1_DATA3 _4 => U1TXD) ([Input] [Output]))); + _for_each_inner_gpio!((11, GPIO11(_0 => SD_CMD _1 => SPICS0) (_0 => SD_CMD _1 => + SPICS0 _3 => HS1_CMD _4 => U1RTS) ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12(_0 => MTDI _1 => HSPIQ _3 => HS2_DATA2 _4 => + SD_DATA2) (_1 => HSPIQ _3 => HS2_DATA2 _4 => SD_DATA2 _5 => EMAC_TXD3) ([Input] + [Output]))); _for_each_inner_gpio!((13, GPIO13(_0 => MTCK _1 => HSPID _3 => + HS2_DATA3 _4 => SD_DATA3 _5 => EMAC_RX_ER) (_1 => HSPID _3 => HS2_DATA3 _4 => + SD_DATA3 _5 => EMAC_RX_ER) ([Input] [Output]))); _for_each_inner_gpio!((14, + GPIO14(_0 => MTMS _1 => HSPICLK) (_1 => HSPICLK _3 => HS2_CLK _4 => SD_CLK _5 => + EMAC_TXD2) ([Input] [Output]))); _for_each_inner_gpio!((15, GPIO15(_1 => HSPICS0 + _4 => SD_CMD _5 => EMAC_RXD3) (_0 => MTDO _1 => HSPICS0 _3 => HS2_CMD _4 => + SD_CMD) ([Input] [Output]))); _for_each_inner_gpio!((16, GPIO16(_3 => HS1_DATA4 + _4 => U2RXD) (_3 => HS1_DATA4 _5 => EMAC_CLK_OUT) ([Input] [Output]))); + _for_each_inner_gpio!((17, GPIO17(_3 => HS1_DATA5) (_3 => HS1_DATA5 _4 => U2TXD + _5 => EMAC_CLK_180) ([Input] [Output]))); _for_each_inner_gpio!((18, GPIO18(_1 => + VSPICLK _3 => HS1_DATA7) (_1 => VSPICLK _3 => HS1_DATA7) ([Input] [Output]))); + _for_each_inner_gpio!((19, GPIO19(_1 => VSPIQ _3 => U0CTS) (_1 => VSPIQ _5 => + EMAC_TXD0) ([Input] [Output]))); _for_each_inner_gpio!((20, GPIO20() () ([Input] + [Output]))); _for_each_inner_gpio!((21, GPIO21(_1 => VSPIHD) (_1 => VSPIHD _5 => + EMAC_TX_EN) ([Input] [Output]))); _for_each_inner_gpio!((22, GPIO22(_1 => VSPIWP) + (_1 => VSPIWP _3 => U0RTS _5 => EMAC_TXD1) ([Input] [Output]))); + _for_each_inner_gpio!((23, GPIO23(_1 => VSPID) (_1 => VSPID _3 => HS1_STROBE) + ([Input] [Output]))); _for_each_inner_gpio!((25, GPIO25(_5 => EMAC_RXD0) () + ([Input] [Output]))); _for_each_inner_gpio!((26, GPIO26(_5 => EMAC_RXD1) () + ([Input] [Output]))); _for_each_inner_gpio!((27, GPIO27(_5 => EMAC_RX_DV) () + ([Input] [Output]))); _for_each_inner_gpio!((32, GPIO32() () ([Input] + [Output]))); _for_each_inner_gpio!((33, GPIO33() () ([Input] [Output]))); + _for_each_inner_gpio!((34, GPIO34() () ([Input] []))); _for_each_inner_gpio!((35, + GPIO35() () ([Input] []))); _for_each_inner_gpio!((36, GPIO36() () ([Input] + []))); _for_each_inner_gpio!((37, GPIO37() () ([Input] []))); + _for_each_inner_gpio!((38, GPIO38() () ([Input] []))); _for_each_inner_gpio!((39, + GPIO39() () ([Input] []))); _for_each_inner_gpio!((all(0, GPIO0(_5 => + EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output])), (1, GPIO1(_5 + => EMAC_RXD2) (_0 => U0TXD _1 => CLK_OUT3) ([Input] [Output])), (2, GPIO2(_1 => + HSPIWP _3 => HS2_DATA0 _4 => SD_DATA0) (_1 => HSPIWP _3 => HS2_DATA0 _4 => + SD_DATA0) ([Input] [Output])), (3, GPIO3(_0 => U0RXD) (_1 => CLK_OUT2) ([Input] + [Output])), (4, GPIO4(_1 => HSPIHD _3 => HS2_DATA1 _4 => SD_DATA1 _5 => + EMAC_TX_ER) (_1 => HSPIHD _3 => HS2_DATA1 _4 => SD_DATA1 _5 => EMAC_TX_ER) + ([Input] [Output])), (5, GPIO5(_1 => VSPICS0 _3 => HS1_DATA6 _5 => EMAC_RX_CLK) + (_1 => VSPICS0 _3 => HS1_DATA6) ([Input] [Output])), (6, GPIO6(_1 => SPICLK _4 => + U1CTS) (_0 => SD_CLK _1 => SPICLK _3 => HS1_CLK) ([Input] [Output])), (7, + GPIO7(_0 => SD_DATA0 _1 => SPIQ _3 => HS1_DATA0) (_0 => SD_DATA0 _1 => SPIQ _3 => + HS1_DATA0 _4 => U2RTS) ([Input] [Output])), (8, GPIO8(_0 => SD_DATA1 _1 => SPID + _3 => HS1_DATA1 _4 => U2CTS) (_0 => SD_DATA1 _1 => SPID _3 => HS1_DATA1) ([Input] + [Output])), (9, GPIO9(_0 => SD_DATA2 _1 => SPIHD _3 => HS1_DATA2 _4 => U1RXD) (_0 + => SD_DATA2 _1 => SPIHD _3 => HS1_DATA2) ([Input] [Output])), (10, GPIO10(_0 => + SD_DATA3 _1 => SPIWP _3 => HS1_DATA3) (_0 => SD_DATA3 _1 => SPIWP _3 => HS1_DATA3 + _4 => U1TXD) ([Input] [Output])), (11, GPIO11(_0 => SD_CMD _1 => SPICS0) (_0 => + SD_CMD _1 => SPICS0 _3 => HS1_CMD _4 => U1RTS) ([Input] [Output])), (12, + GPIO12(_0 => MTDI _1 => HSPIQ _3 => HS2_DATA2 _4 => SD_DATA2) (_1 => HSPIQ _3 => + HS2_DATA2 _4 => SD_DATA2 _5 => EMAC_TXD3) ([Input] [Output])), (13, GPIO13(_0 => + MTCK _1 => HSPID _3 => HS2_DATA3 _4 => SD_DATA3 _5 => EMAC_RX_ER) (_1 => HSPID _3 + => HS2_DATA3 _4 => SD_DATA3 _5 => EMAC_RX_ER) ([Input] [Output])), (14, GPIO14(_0 + => MTMS _1 => HSPICLK) (_1 => HSPICLK _3 => HS2_CLK _4 => SD_CLK _5 => EMAC_TXD2) + ([Input] [Output])), (15, GPIO15(_1 => HSPICS0 _4 => SD_CMD _5 => EMAC_RXD3) (_0 + => MTDO _1 => HSPICS0 _3 => HS2_CMD _4 => SD_CMD) ([Input] [Output])), (16, + GPIO16(_3 => HS1_DATA4 _4 => U2RXD) (_3 => HS1_DATA4 _5 => EMAC_CLK_OUT) ([Input] + [Output])), (17, GPIO17(_3 => HS1_DATA5) (_3 => HS1_DATA5 _4 => U2TXD _5 => + EMAC_CLK_180) ([Input] [Output])), (18, GPIO18(_1 => VSPICLK _3 => HS1_DATA7) (_1 + => VSPICLK _3 => HS1_DATA7) ([Input] [Output])), (19, GPIO19(_1 => VSPIQ _3 => + U0CTS) (_1 => VSPIQ _5 => EMAC_TXD0) ([Input] [Output])), (20, GPIO20() () + ([Input] [Output])), (21, GPIO21(_1 => VSPIHD) (_1 => VSPIHD _5 => EMAC_TX_EN) + ([Input] [Output])), (22, GPIO22(_1 => VSPIWP) (_1 => VSPIWP _3 => U0RTS _5 => + EMAC_TXD1) ([Input] [Output])), (23, GPIO23(_1 => VSPID) (_1 => VSPID _3 => + HS1_STROBE) ([Input] [Output])), (25, GPIO25(_5 => EMAC_RXD0) () ([Input] + [Output])), (26, GPIO26(_5 => EMAC_RXD1) () ([Input] [Output])), (27, GPIO27(_5 + => EMAC_RX_DV) () ([Input] [Output])), (32, GPIO32() () ([Input] [Output])), (33, + GPIO33() () ([Input] [Output])), (34, GPIO34() () ([Input] [])), (35, GPIO35() () + ([Input] [])), (36, GPIO36() () ([Input] [])), (37, GPIO37() () ([Input] [])), + (38, GPIO38() () ([Input] [])), (39, GPIO39() () ([Input] [])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((ADC2_CH1, GPIO0)); + _for_each_inner_analog_function!((TOUCH1, GPIO0)); + _for_each_inner_analog_function!((ADC2_CH2, GPIO2)); + _for_each_inner_analog_function!((TOUCH2, GPIO2)); + _for_each_inner_analog_function!((ADC2_CH0, GPIO4)); + _for_each_inner_analog_function!((TOUCH0, GPIO4)); + _for_each_inner_analog_function!((ADC2_CH5, GPIO12)); + _for_each_inner_analog_function!((TOUCH5, GPIO12)); + _for_each_inner_analog_function!((ADC2_CH4, GPIO13)); + _for_each_inner_analog_function!((TOUCH4, GPIO13)); + _for_each_inner_analog_function!((ADC2_CH6, GPIO14)); + _for_each_inner_analog_function!((TOUCH6, GPIO14)); + _for_each_inner_analog_function!((ADC2_CH3, GPIO15)); + _for_each_inner_analog_function!((TOUCH3, GPIO15)); + _for_each_inner_analog_function!((DAC1, GPIO25)); + _for_each_inner_analog_function!((ADC2_CH8, GPIO25)); + _for_each_inner_analog_function!((DAC2, GPIO26)); + _for_each_inner_analog_function!((ADC2_CH9, GPIO26)); + _for_each_inner_analog_function!((ADC2_CH7, GPIO27)); + _for_each_inner_analog_function!((TOUCH7, GPIO27)); + _for_each_inner_analog_function!((XTAL_32K_P, GPIO32)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO32)); + _for_each_inner_analog_function!((TOUCH9, GPIO32)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO33)); + _for_each_inner_analog_function!((ADC1_CH5, GPIO33)); + _for_each_inner_analog_function!((TOUCH8, GPIO33)); + _for_each_inner_analog_function!((ADC1_CH6, GPIO34)); + _for_each_inner_analog_function!((ADC1_CH7, GPIO35)); + _for_each_inner_analog_function!((ADC_H, GPIO36)); + _for_each_inner_analog_function!((ADC1_CH0, GPIO36)); + _for_each_inner_analog_function!((ADC_H, GPIO37)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO37)); + _for_each_inner_analog_function!((ADC_H, GPIO38)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO38)); + _for_each_inner_analog_function!((ADC_H, GPIO39)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO39)); + _for_each_inner_analog_function!(((ADC2_CH1, ADCn_CHm, 2, 1), GPIO0)); + _for_each_inner_analog_function!(((TOUCH1, TOUCHn, 1), GPIO0)); + _for_each_inner_analog_function!(((ADC2_CH2, ADCn_CHm, 2, 2), GPIO2)); + _for_each_inner_analog_function!(((TOUCH2, TOUCHn, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC2_CH0, ADCn_CHm, 2, 0), GPIO4)); + _for_each_inner_analog_function!(((TOUCH0, TOUCHn, 0), GPIO4)); + _for_each_inner_analog_function!(((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)); + _for_each_inner_analog_function!(((TOUCH5, TOUCHn, 5), GPIO12)); + _for_each_inner_analog_function!(((ADC2_CH4, ADCn_CHm, 2, 4), GPIO13)); + _for_each_inner_analog_function!(((TOUCH4, TOUCHn, 4), GPIO13)); + _for_each_inner_analog_function!(((ADC2_CH6, ADCn_CHm, 2, 6), GPIO14)); + _for_each_inner_analog_function!(((TOUCH6, TOUCHn, 6), GPIO14)); + _for_each_inner_analog_function!(((ADC2_CH3, ADCn_CHm, 2, 3), GPIO15)); + _for_each_inner_analog_function!(((TOUCH3, TOUCHn, 3), GPIO15)); + _for_each_inner_analog_function!(((DAC1, DACn, 1), GPIO25)); + _for_each_inner_analog_function!(((ADC2_CH8, ADCn_CHm, 2, 8), GPIO25)); + _for_each_inner_analog_function!(((DAC2, DACn, 2), GPIO26)); + _for_each_inner_analog_function!(((ADC2_CH9, ADCn_CHm, 2, 9), GPIO26)); + _for_each_inner_analog_function!(((ADC2_CH7, ADCn_CHm, 2, 7), GPIO27)); + _for_each_inner_analog_function!(((TOUCH7, TOUCHn, 7), GPIO27)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO32)); + _for_each_inner_analog_function!(((TOUCH9, TOUCHn, 9), GPIO32)); + _for_each_inner_analog_function!(((ADC1_CH5, ADCn_CHm, 1, 5), GPIO33)); + _for_each_inner_analog_function!(((TOUCH8, TOUCHn, 8), GPIO33)); + _for_each_inner_analog_function!(((ADC1_CH6, ADCn_CHm, 1, 6), GPIO34)); + _for_each_inner_analog_function!(((ADC1_CH7, ADCn_CHm, 1, 7), GPIO35)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO36)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO37)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO38)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO39)); + _for_each_inner_analog_function!((all(ADC2_CH1, GPIO0), (TOUCH1, GPIO0), + (ADC2_CH2, GPIO2), (TOUCH2, GPIO2), (ADC2_CH0, GPIO4), (TOUCH0, GPIO4), + (ADC2_CH5, GPIO12), (TOUCH5, GPIO12), (ADC2_CH4, GPIO13), (TOUCH4, GPIO13), + (ADC2_CH6, GPIO14), (TOUCH6, GPIO14), (ADC2_CH3, GPIO15), (TOUCH3, GPIO15), + (DAC1, GPIO25), (ADC2_CH8, GPIO25), (DAC2, GPIO26), (ADC2_CH9, GPIO26), + (ADC2_CH7, GPIO27), (TOUCH7, GPIO27), (XTAL_32K_P, GPIO32), (ADC1_CH4, GPIO32), + (TOUCH9, GPIO32), (XTAL_32K_N, GPIO33), (ADC1_CH5, GPIO33), (TOUCH8, GPIO33), + (ADC1_CH6, GPIO34), (ADC1_CH7, GPIO35), (ADC_H, GPIO36), (ADC1_CH0, GPIO36), + (ADC_H, GPIO37), (ADC1_CH1, GPIO37), (ADC_H, GPIO38), (ADC1_CH2, GPIO38), (ADC_H, + GPIO39), (ADC1_CH3, GPIO39))); + _for_each_inner_analog_function!((all_expanded((ADC2_CH1, ADCn_CHm, 2, 1), + GPIO0), ((TOUCH1, TOUCHn, 1), GPIO0), ((ADC2_CH2, ADCn_CHm, 2, 2), GPIO2), + ((TOUCH2, TOUCHn, 2), GPIO2), ((ADC2_CH0, ADCn_CHm, 2, 0), GPIO4), ((TOUCH0, + TOUCHn, 0), GPIO4), ((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12), ((TOUCH5, TOUCHn, 5), + GPIO12), ((ADC2_CH4, ADCn_CHm, 2, 4), GPIO13), ((TOUCH4, TOUCHn, 4), GPIO13), + ((ADC2_CH6, ADCn_CHm, 2, 6), GPIO14), ((TOUCH6, TOUCHn, 6), GPIO14), ((ADC2_CH3, + ADCn_CHm, 2, 3), GPIO15), ((TOUCH3, TOUCHn, 3), GPIO15), ((DAC1, DACn, 1), + GPIO25), ((ADC2_CH8, ADCn_CHm, 2, 8), GPIO25), ((DAC2, DACn, 2), GPIO26), + ((ADC2_CH9, ADCn_CHm, 2, 9), GPIO26), ((ADC2_CH7, ADCn_CHm, 2, 7), GPIO27), + ((TOUCH7, TOUCHn, 7), GPIO27), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO32), ((TOUCH9, + TOUCHn, 9), GPIO32), ((ADC1_CH5, ADCn_CHm, 1, 5), GPIO33), ((TOUCH8, TOUCHn, 8), + GPIO33), ((ADC1_CH6, ADCn_CHm, 1, 6), GPIO34), ((ADC1_CH7, ADCn_CHm, 1, 7), + GPIO35), ((ADC1_CH0, ADCn_CHm, 1, 0), GPIO36), ((ADC1_CH1, ADCn_CHm, 1, 1), + GPIO37), ((ADC1_CH2, ADCn_CHm, 1, 2), GPIO38), ((ADC1_CH3, ADCn_CHm, 1, 3), + GPIO39))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((RTC_GPIO11, GPIO0)); + _for_each_inner_lp_function!((SAR_I2C_SDA, GPIO0)); + _for_each_inner_lp_function!((RTC_GPIO12, GPIO2)); + _for_each_inner_lp_function!((SAR_I2C_SCL, GPIO2)); + _for_each_inner_lp_function!((RTC_GPIO10, GPIO4)); + _for_each_inner_lp_function!((SAR_I2C_SCL, GPIO4)); + _for_each_inner_lp_function!((RTC_GPIO15, GPIO12)); + _for_each_inner_lp_function!((RTC_GPIO14, GPIO13)); + _for_each_inner_lp_function!((RTC_GPIO16, GPIO14)); + _for_each_inner_lp_function!((RTC_GPIO13, GPIO15)); + _for_each_inner_lp_function!((SAR_I2C_SDA, GPIO15)); + _for_each_inner_lp_function!((RTC_GPIO6, GPIO25)); + _for_each_inner_lp_function!((RTC_GPIO7, GPIO26)); + _for_each_inner_lp_function!((RTC_GPIO17, GPIO27)); + _for_each_inner_lp_function!((RTC_GPIO9, GPIO32)); + _for_each_inner_lp_function!((RTC_GPIO8, GPIO33)); + _for_each_inner_lp_function!((RTC_GPIO4, GPIO34)); + _for_each_inner_lp_function!((RTC_GPIO5, GPIO35)); + _for_each_inner_lp_function!((RTC_GPIO0, GPIO36)); + _for_each_inner_lp_function!((RTC_GPIO1, GPIO37)); + _for_each_inner_lp_function!((RTC_GPIO2, GPIO38)); + _for_each_inner_lp_function!((RTC_GPIO3, GPIO39)); + _for_each_inner_lp_function!(((RTC_GPIO11, RTC_GPIOn, 11), GPIO0)); + _for_each_inner_lp_function!(((RTC_GPIO12, RTC_GPIOn, 12), GPIO2)); + _for_each_inner_lp_function!(((RTC_GPIO10, RTC_GPIOn, 10), GPIO4)); + _for_each_inner_lp_function!(((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)); + _for_each_inner_lp_function!(((RTC_GPIO14, RTC_GPIOn, 14), GPIO13)); + _for_each_inner_lp_function!(((RTC_GPIO16, RTC_GPIOn, 16), GPIO14)); + _for_each_inner_lp_function!(((RTC_GPIO13, RTC_GPIOn, 13), GPIO15)); + _for_each_inner_lp_function!(((RTC_GPIO6, RTC_GPIOn, 6), GPIO25)); + _for_each_inner_lp_function!(((RTC_GPIO7, RTC_GPIOn, 7), GPIO26)); + _for_each_inner_lp_function!(((RTC_GPIO17, RTC_GPIOn, 17), GPIO27)); + _for_each_inner_lp_function!(((RTC_GPIO9, RTC_GPIOn, 9), GPIO32)); + _for_each_inner_lp_function!(((RTC_GPIO8, RTC_GPIOn, 8), GPIO33)); + _for_each_inner_lp_function!(((RTC_GPIO4, RTC_GPIOn, 4), GPIO34)); + _for_each_inner_lp_function!(((RTC_GPIO5, RTC_GPIOn, 5), GPIO35)); + _for_each_inner_lp_function!(((RTC_GPIO0, RTC_GPIOn, 0), GPIO36)); + _for_each_inner_lp_function!(((RTC_GPIO1, RTC_GPIOn, 1), GPIO37)); + _for_each_inner_lp_function!(((RTC_GPIO2, RTC_GPIOn, 2), GPIO38)); + _for_each_inner_lp_function!(((RTC_GPIO3, RTC_GPIOn, 3), GPIO39)); + _for_each_inner_lp_function!((all(RTC_GPIO11, GPIO0), (SAR_I2C_SDA, GPIO0), + (RTC_GPIO12, GPIO2), (SAR_I2C_SCL, GPIO2), (RTC_GPIO10, GPIO4), (SAR_I2C_SCL, + GPIO4), (RTC_GPIO15, GPIO12), (RTC_GPIO14, GPIO13), (RTC_GPIO16, GPIO14), + (RTC_GPIO13, GPIO15), (SAR_I2C_SDA, GPIO15), (RTC_GPIO6, GPIO25), (RTC_GPIO7, + GPIO26), (RTC_GPIO17, GPIO27), (RTC_GPIO9, GPIO32), (RTC_GPIO8, GPIO33), + (RTC_GPIO4, GPIO34), (RTC_GPIO5, GPIO35), (RTC_GPIO0, GPIO36), (RTC_GPIO1, + GPIO37), (RTC_GPIO2, GPIO38), (RTC_GPIO3, GPIO39))); + _for_each_inner_lp_function!((all_expanded((RTC_GPIO11, RTC_GPIOn, 11), GPIO0), + ((RTC_GPIO12, RTC_GPIOn, 12), GPIO2), ((RTC_GPIO10, RTC_GPIOn, 10), GPIO4), + ((RTC_GPIO15, RTC_GPIOn, 15), GPIO12), ((RTC_GPIO14, RTC_GPIOn, 14), GPIO13), + ((RTC_GPIO16, RTC_GPIOn, 16), GPIO14), ((RTC_GPIO13, RTC_GPIOn, 13), GPIO15), + ((RTC_GPIO6, RTC_GPIOn, 6), GPIO25), ((RTC_GPIO7, RTC_GPIOn, 7), GPIO26), + ((RTC_GPIO17, RTC_GPIOn, 17), GPIO27), ((RTC_GPIO9, RTC_GPIOn, 9), GPIO32), + ((RTC_GPIO8, RTC_GPIOn, 8), GPIO33), ((RTC_GPIO4, RTC_GPIOn, 4), GPIO34), + ((RTC_GPIO5, RTC_GPIOn, 5), GPIO35), ((RTC_GPIO0, RTC_GPIOn, 0), GPIO36), + ((RTC_GPIO1, RTC_GPIOn, 1), GPIO37), ((RTC_GPIO2, RTC_GPIOn, 2), GPIO38), + ((RTC_GPIO3, RTC_GPIOn, 3), GPIO39))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + SPICLK = 0, + SPIQ = 1, + SPID = 2, + SPIHD = 3, + SPIWP = 4, + SPICS0 = 5, + SPICS1 = 6, + SPICS2 = 7, + HSPICLK = 8, + HSPIQ = 9, + HSPID = 10, + HSPICS0 = 11, + HSPIHD = 12, + HSPIWP = 13, + U0RXD = 14, + U0CTS = 15, + U0DSR = 16, + U1RXD = 17, + U1CTS = 18, + I2S0O_BCK = 23, + I2S1O_BCK = 24, + I2S0O_WS = 25, + I2S1O_WS = 26, + I2S0I_BCK = 27, + I2S0I_WS = 28, + I2CEXT0_SCL = 29, + I2CEXT0_SDA = 30, + PWM0_SYNC0 = 31, + PWM0_SYNC1 = 32, + PWM0_SYNC2 = 33, + PWM0_F0 = 34, + PWM0_F1 = 35, + PWM0_F2 = 36, + PCNT0_SIG_CH0 = 39, + PCNT0_SIG_CH1 = 40, + PCNT0_CTRL_CH0 = 41, + PCNT0_CTRL_CH1 = 42, + PCNT1_SIG_CH0 = 43, + PCNT1_SIG_CH1 = 44, + PCNT1_CTRL_CH0 = 45, + PCNT1_CTRL_CH1 = 46, + PCNT2_SIG_CH0 = 47, + PCNT2_SIG_CH1 = 48, + PCNT2_CTRL_CH0 = 49, + PCNT2_CTRL_CH1 = 50, + PCNT3_SIG_CH0 = 51, + PCNT3_SIG_CH1 = 52, + PCNT3_CTRL_CH0 = 53, + PCNT3_CTRL_CH1 = 54, + PCNT4_SIG_CH0 = 55, + PCNT4_SIG_CH1 = 56, + PCNT4_CTRL_CH0 = 57, + PCNT4_CTRL_CH1 = 58, + HSPICS1 = 61, + HSPICS2 = 62, + VSPICLK = 63, + VSPIQ = 64, + VSPID = 65, + VSPIHD = 66, + VSPIWP = 67, + VSPICS0 = 68, + VSPICS1 = 69, + VSPICS2 = 70, + PCNT5_SIG_CH0 = 71, + PCNT5_SIG_CH1 = 72, + PCNT5_CTRL_CH0 = 73, + PCNT5_CTRL_CH1 = 74, + PCNT6_SIG_CH0 = 75, + PCNT6_SIG_CH1 = 76, + PCNT6_CTRL_CH0 = 77, + PCNT6_CTRL_CH1 = 78, + PCNT7_SIG_CH0 = 79, + PCNT7_SIG_CH1 = 80, + PCNT7_CTRL_CH0 = 81, + PCNT7_CTRL_CH1 = 82, + RMT_SIG_0 = 83, + RMT_SIG_1 = 84, + RMT_SIG_2 = 85, + RMT_SIG_3 = 86, + RMT_SIG_4 = 87, + RMT_SIG_5 = 88, + RMT_SIG_6 = 89, + RMT_SIG_7 = 90, + TWAI_RX = 94, + I2CEXT1_SCL = 95, + I2CEXT1_SDA = 96, + HOST_CARD_DETECT_N_1 = 97, + HOST_CARD_DETECT_N_2 = 98, + HOST_CARD_WRITE_PRT_1 = 99, + HOST_CARD_WRITE_PRT_2 = 100, + HOST_CARD_INT_N_1 = 101, + HOST_CARD_INT_N_2 = 102, + PWM1_SYNC0 = 103, + PWM1_SYNC1 = 104, + PWM1_SYNC2 = 105, + PWM1_F0 = 106, + PWM1_F1 = 107, + PWM1_F2 = 108, + PWM0_CAP0 = 109, + PWM0_CAP1 = 110, + PWM0_CAP2 = 111, + PWM1_CAP0 = 112, + PWM1_CAP1 = 113, + PWM1_CAP2 = 114, + I2S0I_DATA_0 = 140, + I2S0I_DATA_1 = 141, + I2S0I_DATA_2 = 142, + I2S0I_DATA_3 = 143, + I2S0I_DATA_4 = 144, + I2S0I_DATA_5 = 145, + I2S0I_DATA_6 = 146, + I2S0I_DATA_7 = 147, + I2S0I_DATA_8 = 148, + I2S0I_DATA_9 = 149, + I2S0I_DATA_10 = 150, + I2S0I_DATA_11 = 151, + I2S0I_DATA_12 = 152, + I2S0I_DATA_13 = 153, + I2S0I_DATA_14 = 154, + I2S0I_DATA_15 = 155, + I2S1I_BCK = 164, + I2S1I_WS = 165, + I2S1I_DATA_0 = 166, + I2S1I_DATA_1 = 167, + I2S1I_DATA_2 = 168, + I2S1I_DATA_3 = 169, + I2S1I_DATA_4 = 170, + I2S1I_DATA_5 = 171, + I2S1I_DATA_6 = 172, + I2S1I_DATA_7 = 173, + I2S1I_DATA_8 = 174, + I2S1I_DATA_9 = 175, + I2S1I_DATA_10 = 176, + I2S1I_DATA_11 = 177, + I2S1I_DATA_12 = 178, + I2S1I_DATA_13 = 179, + I2S1I_DATA_14 = 180, + I2S1I_DATA_15 = 181, + I2S0I_H_SYNC = 190, + I2S0I_V_SYNC = 191, + I2S0I_H_ENABLE = 192, + I2S1I_H_SYNC = 193, + I2S1I_V_SYNC = 194, + I2S1I_H_ENABLE = 195, + U2RXD = 198, + U2CTS = 199, + EMAC_MDC = 200, + EMAC_MDI = 201, + EMAC_CRS = 202, + EMAC_COL = 203, + PCMFSYNC = 204, + PCMCLK = 205, + PCMDIN = 206, + SD_CMD, + SD_DATA0, + SD_DATA1, + SD_DATA2, + SD_DATA3, + HS1_DATA0, + HS1_DATA1, + HS1_DATA2, + HS1_DATA3, + HS1_DATA4, + HS1_DATA5, + HS1_DATA6, + HS1_DATA7, + HS2_DATA0, + HS2_DATA1, + HS2_DATA2, + HS2_DATA3, + EMAC_TX_CLK, + EMAC_RXD2, + EMAC_TX_ER, + EMAC_RX_CLK, + EMAC_RX_ER, + EMAC_RXD3, + EMAC_RXD0, + EMAC_RXD1, + EMAC_RX_DV, + MTDI, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + SPICLK = 0, + SPIQ = 1, + SPID = 2, + SPIHD = 3, + SPIWP = 4, + SPICS0 = 5, + SPICS1 = 6, + SPICS2 = 7, + HSPICLK = 8, + HSPIQ = 9, + HSPID = 10, + HSPICS0 = 11, + HSPIHD = 12, + HSPIWP = 13, + U0TXD = 14, + U0RTS = 15, + U0DTR = 16, + U1TXD = 17, + U1RTS = 18, + I2S0O_BCK = 23, + I2S1O_BCK = 24, + I2S0O_WS = 25, + I2S1O_WS = 26, + I2S0I_BCK = 27, + I2S0I_WS = 28, + I2CEXT0_SCL = 29, + I2CEXT0_SDA = 30, + SDIO_TOHOSTT = 31, + PWM0_0A = 32, + PWM0_0B = 33, + PWM0_1A = 34, + PWM0_1B = 35, + PWM0_2A = 36, + PWM0_2B = 37, + HSPICS1 = 61, + HSPICS2 = 62, + VSPICLK = 63, + VSPIQ = 64, + VSPID = 65, + VSPIHD = 66, + VSPIWP = 67, + VSPICS0 = 68, + VSPICS1 = 69, + VSPICS2 = 70, + LEDC_HS_SIG0 = 71, + LEDC_HS_SIG1 = 72, + LEDC_HS_SIG2 = 73, + LEDC_HS_SIG3 = 74, + LEDC_HS_SIG4 = 75, + LEDC_HS_SIG5 = 76, + LEDC_HS_SIG6 = 77, + LEDC_HS_SIG7 = 78, + LEDC_LS_SIG0 = 79, + LEDC_LS_SIG1 = 80, + LEDC_LS_SIG2 = 81, + LEDC_LS_SIG3 = 82, + LEDC_LS_SIG4 = 83, + LEDC_LS_SIG5 = 84, + LEDC_LS_SIG6 = 85, + LEDC_LS_SIG7 = 86, + RMT_SIG_0 = 87, + RMT_SIG_1 = 88, + RMT_SIG_2 = 89, + RMT_SIG_3 = 90, + RMT_SIG_4 = 91, + RMT_SIG_5 = 92, + RMT_SIG_6 = 93, + RMT_SIG_7 = 94, + I2CEXT1_SCL = 95, + I2CEXT1_SDA = 96, + HOST_CCMD_OD_PULLUP_EN_N = 97, + HOST_RST_N_1 = 98, + HOST_RST_N_2 = 99, + GPIO_SD0 = 100, + GPIO_SD1 = 101, + GPIO_SD2 = 102, + GPIO_SD3 = 103, + GPIO_SD4 = 104, + GPIO_SD5 = 105, + GPIO_SD6 = 106, + GPIO_SD7 = 107, + PWM1_0A = 108, + PWM1_0B = 109, + PWM1_1A = 110, + PWM1_1B = 111, + PWM1_2A = 112, + PWM1_2B = 113, + TWAI_TX = 123, + TWAI_BUS_OFF_ON = 124, + TWAI_CLKOUT = 125, + I2S0O_DATA_0 = 140, + I2S0O_DATA_1 = 141, + I2S0O_DATA_2 = 142, + I2S0O_DATA_3 = 143, + I2S0O_DATA_4 = 144, + I2S0O_DATA_5 = 145, + I2S0O_DATA_6 = 146, + I2S0O_DATA_7 = 147, + I2S0O_DATA_8 = 148, + I2S0O_DATA_9 = 149, + I2S0O_DATA_10 = 150, + I2S0O_DATA_11 = 151, + I2S0O_DATA_12 = 152, + I2S0O_DATA_13 = 153, + I2S0O_DATA_14 = 154, + I2S0O_DATA_15 = 155, + I2S0O_DATA_16 = 156, + I2S0O_DATA_17 = 157, + I2S0O_DATA_18 = 158, + I2S0O_DATA_19 = 159, + I2S0O_DATA_20 = 160, + I2S0O_DATA_21 = 161, + I2S0O_DATA_22 = 162, + I2S0O_DATA_23 = 163, + I2S1I_BCK = 164, + I2S1I_WS = 165, + I2S1O_DATA_0 = 166, + I2S1O_DATA_1 = 167, + I2S1O_DATA_2 = 168, + I2S1O_DATA_3 = 169, + I2S1O_DATA_4 = 170, + I2S1O_DATA_5 = 171, + I2S1O_DATA_6 = 172, + I2S1O_DATA_7 = 173, + I2S1O_DATA_8 = 174, + I2S1O_DATA_9 = 175, + I2S1O_DATA_10 = 176, + I2S1O_DATA_11 = 177, + I2S1O_DATA_12 = 178, + I2S1O_DATA_13 = 179, + I2S1O_DATA_14 = 180, + I2S1O_DATA_15 = 181, + I2S1O_DATA_16 = 182, + I2S1O_DATA_17 = 183, + I2S1O_DATA_18 = 184, + I2S1O_DATA_19 = 185, + I2S1O_DATA_20 = 186, + I2S1O_DATA_21 = 187, + I2S1O_DATA_22 = 188, + I2S1O_DATA_23 = 189, + U2TXD = 198, + U2RTS = 199, + EMAC_MDC = 200, + EMAC_MDO = 201, + EMAC_CRS = 202, + EMAC_COL = 203, + BT_AUDIO0RQ = 204, + BT_AUDIO1RQ = 205, + BT_AUDIO2RQ = 206, + BLE_AUDIO0RQ = 207, + BLE_AUDIO1RQ = 208, + BLE_AUDIO2RQ = 209, + PCMFSYNC = 210, + PCMCLK = 211, + PCMDOUT = 212, + BLE_AUDIO_SYNC0_P = 213, + BLE_AUDIO_SYNC1_P = 214, + BLE_AUDIO_SYNC2_P = 215, + ANT_SEL0 = 216, + ANT_SEL1 = 217, + ANT_SEL2 = 218, + ANT_SEL3 = 219, + ANT_SEL4 = 220, + ANT_SEL5 = 221, + ANT_SEL6 = 222, + ANT_SEL7 = 223, + SIGNAL_224 = 224, + SIGNAL_225 = 225, + SIGNAL_226 = 226, + SIGNAL_227 = 227, + SIGNAL_228 = 228, + GPIO = 256, + CLK_OUT1, + CLK_OUT2, + CLK_OUT3, + SD_CLK, + SD_CMD, + SD_DATA0, + SD_DATA1, + SD_DATA2, + SD_DATA3, + HS1_CLK, + HS1_CMD, + HS1_DATA0, + HS1_DATA1, + HS1_DATA2, + HS1_DATA3, + HS1_DATA4, + HS1_DATA5, + HS1_DATA6, + HS1_DATA7, + HS1_STROBE, + HS2_CLK, + HS2_CMD, + HS2_DATA0, + HS2_DATA1, + HS2_DATA2, + HS2_DATA3, + EMAC_TX_CLK, + EMAC_TX_ER, + EMAC_TXD3, + EMAC_RX_ER, + EMAC_TXD2, + EMAC_CLK_OUT, + EMAC_CLK_180, + EMAC_TXD0, + EMAC_TX_EN, + EMAC_TXD1, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { + let iomux = crate::peripherals::IO_MUX::regs(); + match gpio_num { + 0 => iomux.gpio0(), + 1 => iomux.gpio1(), + 2 => iomux.gpio2(), + 3 => iomux.gpio3(), + 4 => iomux.gpio4(), + 5 => iomux.gpio5(), + 6 => iomux.gpio6(), + 7 => iomux.gpio7(), + 8 => iomux.gpio8(), + 9 => iomux.gpio9(), + 10 => iomux.gpio10(), + 11 => iomux.gpio11(), + 12 => iomux.gpio12(), + 13 => iomux.gpio13(), + 14 => iomux.gpio14(), + 15 => iomux.gpio15(), + 16 => iomux.gpio16(), + 17 => iomux.gpio17(), + 18 => iomux.gpio18(), + 19 => iomux.gpio19(), + 20 => iomux.gpio20(), + 21 => iomux.gpio21(), + 22 => iomux.gpio22(), + 23 => iomux.gpio23(), + 25 => iomux.gpio25(), + 26 => iomux.gpio26(), + 27 => iomux.gpio27(), + 32 => iomux.gpio32(), + 33 => iomux.gpio33(), + 34 => iomux.gpio34(), + 35 => iomux.gpio35(), + 36 => iomux.gpio36(), + 37 => iomux.gpio37(), + 38 => iomux.gpio38(), + 39 => iomux.gpio39(), + other => panic!("GPIO {} does not exist", other), + } + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32c2.rs b/esp-metadata-generated/src/_generated_esp32c2.rs new file mode 100644 index 00000000000..d9ef54b7b79 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -0,0 +1,3338 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32c2" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-C2" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32c2" + }; + ("arch") => { + "riscv" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp8684_technical_reference_manual_en.pdf" + }; + ("assist_debug.has_sp_monitor") => { + true + }; + ("assist_debug.has_region_monitor") => { + false + }; + ("bt.controller") => { + "npl" + }; + ("dedicated_gpio.needs_initialization") => { + false + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + false + }; + ("dma.max_priority") => { + 9 + }; + ("dma.max_priority", str) => { + stringify!(9) + }; + ("dma.gdma_version") => { + 1 + }; + ("dma.gdma_version", str) => { + stringify!(1) + }; + ("ecc.zero_extend_writes") => { + true + }; + ("ecc.separate_jacobian_point_memory") => { + false + }; + ("ecc.has_memory_clock_gate") => { + false + }; + ("ecc.supports_enhanced_security") => { + false + }; + ("ecc.mem_block_size") => { + 32 + }; + ("gpio.has_bank_1") => { + false + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 31 + }; + ("gpio.constant_0_input", str) => { + stringify!(31) + }; + ("gpio.constant_1_input") => { + 30 + }; + ("gpio.constant_1_input", str) => { + stringify!(30) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 100 + }; + ("gpio.input_signal_max", str) => { + stringify!(100) + }; + ("gpio.output_signal_max") => { + 128 + }; + ("gpio.output_signal_max", str) => { + stringify!(128) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + true + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + false + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + false + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 16 + }; + ("i2c_master.fifo_size", str) => { + stringify!(16) + }; + ("interrupts.status_registers") => { + 2 + }; + ("interrupts.status_registers", str) => { + stringify!(2) + }; + ("interrupts.disabled_interrupt") => { + 0 + }; + ("phy.combo_module") => { + true + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + true + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.rc_fast_clk_default") => { + 17500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(17500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + false + }; + ("timergroup.timg_has_divcnt_rst") => { + true + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + false + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 1 + }; + ("wifi.mac_version", str) => { + stringify!(1) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + CPU_GPIO_0)); _for_each_inner_dedicated_gpio!((0, 1, CPU_GPIO_1)); + _for_each_inner_dedicated_gpio!((0, 2, CPU_GPIO_2)); + _for_each_inner_dedicated_gpio!((0, 3, CPU_GPIO_3)); + _for_each_inner_dedicated_gpio!((0, 4, CPU_GPIO_4)); + _for_each_inner_dedicated_gpio!((0, 5, CPU_GPIO_5)); + _for_each_inner_dedicated_gpio!((0, 6, CPU_GPIO_6)); + _for_each_inner_dedicated_gpio!((0, 7, CPU_GPIO_7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, CPU_GPIO_0), (0, 1, + CPU_GPIO_1), (0, 2, CPU_GPIO_2), (0, 3, CPU_GPIO_3), (0, 4, CPU_GPIO_4), (0, 5, + CPU_GPIO_5), (0, 6, CPU_GPIO_6), (0, 7, CPU_GPIO_7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_working_mode { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_working_mode { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_ecc_working_mode!((0, AffinePointMultiplication)); + _for_each_inner_ecc_working_mode!((1, FiniteFieldDivision)); + _for_each_inner_ecc_working_mode!((2, AffinePointVerification)); + _for_each_inner_ecc_working_mode!((3, AffinePointVerificationAndMultiplication)); + _for_each_inner_ecc_working_mode!((4, JacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((6, JacobianPointVerification)); + _for_each_inner_ecc_working_mode!((7, + AffinePointVerificationAndJacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((all(0, AffinePointMultiplication), (1, + FiniteFieldDivision), (2, AffinePointVerification), (3, + AffinePointVerificationAndMultiplication), (4, JacobianPointMultiplication), (6, + JacobianPointVerification), (7, + AffinePointVerificationAndJacobianPointMultiplication))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_curve { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_curve { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_ecc_curve!((0, P192, 192)); + _for_each_inner_ecc_curve!((1, P256, 256)); _for_each_inner_ecc_curve!((all(0, + P192, 192), (1, P256, 256))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_interrupt!(([disabled 0] 0)); + _for_each_inner_interrupt!(([reserved 0] 1)); + _for_each_inner_interrupt!(([direct_bindable 0] 2)); + _for_each_inner_interrupt!(([direct_bindable 1] 3)); + _for_each_inner_interrupt!(([direct_bindable 2] 4)); + _for_each_inner_interrupt!(([direct_bindable 3] 5)); + _for_each_inner_interrupt!(([direct_bindable 4] 6)); + _for_each_inner_interrupt!(([direct_bindable 5] 7)); + _for_each_inner_interrupt!(([direct_bindable 6] 8)); + _for_each_inner_interrupt!(([direct_bindable 7] 9)); + _for_each_inner_interrupt!(([direct_bindable 8] 10)); + _for_each_inner_interrupt!(([direct_bindable 9] 11)); + _for_each_inner_interrupt!(([direct_bindable 10] 12)); + _for_each_inner_interrupt!(([direct_bindable 11] 13)); + _for_each_inner_interrupt!(([direct_bindable 12] 14)); + _for_each_inner_interrupt!(([direct_bindable 13] 15)); + _for_each_inner_interrupt!(([direct_bindable 14] 16)); + _for_each_inner_interrupt!(([vector 0] 17)); _for_each_inner_interrupt!(([vector + 1] 18)); _for_each_inner_interrupt!(([vector 2] 19)); + _for_each_inner_interrupt!(([vector 3] 20)); _for_each_inner_interrupt!(([vector + 4] 21)); _for_each_inner_interrupt!(([vector 5] 22)); + _for_each_inner_interrupt!(([vector 6] 23)); _for_each_inner_interrupt!(([vector + 7] 24)); _for_each_inner_interrupt!(([vector 8] 25)); + _for_each_inner_interrupt!(([vector 9] 26)); _for_each_inner_interrupt!(([vector + 10] 27)); _for_each_inner_interrupt!(([vector 11] 28)); + _for_each_inner_interrupt!(([vector 12] 29)); _for_each_inner_interrupt!(([vector + 13] 30)); _for_each_inner_interrupt!(([vector 14] 31)); + _for_each_inner_interrupt!((all([disabled 0] 0), ([reserved 0] 1), + ([direct_bindable 0] 2), ([direct_bindable 1] 3), ([direct_bindable 2] 4), + ([direct_bindable 3] 5), ([direct_bindable 4] 6), ([direct_bindable 5] 7), + ([direct_bindable 6] 8), ([direct_bindable 7] 9), ([direct_bindable 8] 10), + ([direct_bindable 9] 11), ([direct_bindable 10] 12), ([direct_bindable 11] 13), + ([direct_bindable 12] 14), ([direct_bindable 13] 15), ([direct_bindable 14] 16), + ([vector 0] 17), ([vector 1] 18), ([vector 2] 19), ([vector 3] 20), ([vector 4] + 21), ([vector 5] 22), ([vector 6] 23), ([vector 7] 24), ([vector 8] 25), ([vector + 9] 26), ([vector 10] 27), ([vector 11] 28), ([vector 12] 29), ([vector 13] 30), + ([vector 14] 31))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_classified_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_classified_interrupt { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_classified_interrupt!(([direct_bindable 0] + 2)); _for_each_inner_classified_interrupt!(([direct_bindable 1] 3)); + _for_each_inner_classified_interrupt!(([direct_bindable 2] 4)); + _for_each_inner_classified_interrupt!(([direct_bindable 3] 5)); + _for_each_inner_classified_interrupt!(([direct_bindable 4] 6)); + _for_each_inner_classified_interrupt!(([direct_bindable 5] 7)); + _for_each_inner_classified_interrupt!(([direct_bindable 6] 8)); + _for_each_inner_classified_interrupt!(([direct_bindable 7] 9)); + _for_each_inner_classified_interrupt!(([direct_bindable 8] 10)); + _for_each_inner_classified_interrupt!(([direct_bindable 9] 11)); + _for_each_inner_classified_interrupt!(([direct_bindable 10] 12)); + _for_each_inner_classified_interrupt!(([direct_bindable 11] 13)); + _for_each_inner_classified_interrupt!(([direct_bindable 12] 14)); + _for_each_inner_classified_interrupt!(([direct_bindable 13] 15)); + _for_each_inner_classified_interrupt!(([direct_bindable 14] 16)); + _for_each_inner_classified_interrupt!(([vector 0] 17)); + _for_each_inner_classified_interrupt!(([vector 1] 18)); + _for_each_inner_classified_interrupt!(([vector 2] 19)); + _for_each_inner_classified_interrupt!(([vector 3] 20)); + _for_each_inner_classified_interrupt!(([vector 4] 21)); + _for_each_inner_classified_interrupt!(([vector 5] 22)); + _for_each_inner_classified_interrupt!(([vector 6] 23)); + _for_each_inner_classified_interrupt!(([vector 7] 24)); + _for_each_inner_classified_interrupt!(([vector 8] 25)); + _for_each_inner_classified_interrupt!(([vector 9] 26)); + _for_each_inner_classified_interrupt!(([vector 10] 27)); + _for_each_inner_classified_interrupt!(([vector 11] 28)); + _for_each_inner_classified_interrupt!(([vector 12] 29)); + _for_each_inner_classified_interrupt!(([vector 13] 30)); + _for_each_inner_classified_interrupt!(([vector 14] 31)); + _for_each_inner_classified_interrupt!(([reserved 0] 1)); + _for_each_inner_classified_interrupt!((direct_bindable([direct_bindable 0] 2), + ([direct_bindable 1] 3), ([direct_bindable 2] 4), ([direct_bindable 3] 5), + ([direct_bindable 4] 6), ([direct_bindable 5] 7), ([direct_bindable 6] 8), + ([direct_bindable 7] 9), ([direct_bindable 8] 10), ([direct_bindable 9] 11), + ([direct_bindable 10] 12), ([direct_bindable 11] 13), ([direct_bindable 12] 14), + ([direct_bindable 13] 15), ([direct_bindable 14] 16))); + _for_each_inner_classified_interrupt!((vector([vector 0] 17), ([vector 1] 18), + ([vector 2] 19), ([vector 3] 20), ([vector 4] 21), ([vector 5] 22), ([vector 6] + 23), ([vector 7] 24), ([vector 8] 25), ([vector 9] 26), ([vector 10] 27), + ([vector 11] 28), ([vector 12] 29), ([vector 13] 30), ([vector 14] 31))); + _for_each_inner_classified_interrupt!((reserved([reserved 0] 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt_priority { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt_priority { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_interrupt_priority!((0, 1, Priority1)); + _for_each_inner_interrupt_priority!((1, 2, Priority2)); + _for_each_inner_interrupt_priority!((2, 3, Priority3)); + _for_each_inner_interrupt_priority!((3, 4, Priority4)); + _for_each_inner_interrupt_priority!((4, 5, Priority5)); + _for_each_inner_interrupt_priority!((5, 6, Priority6)); + _for_each_inner_interrupt_priority!((6, 7, Priority7)); + _for_each_inner_interrupt_priority!((7, 8, Priority8)); + _for_each_inner_interrupt_priority!((8, 9, Priority9)); + _for_each_inner_interrupt_priority!((9, 10, Priority10)); + _for_each_inner_interrupt_priority!((10, 11, Priority11)); + _for_each_inner_interrupt_priority!((11, 12, Priority12)); + _for_each_inner_interrupt_priority!((12, 13, Priority13)); + _for_each_inner_interrupt_priority!((13, 14, Priority14)); + _for_each_inner_interrupt_priority!((14, 15, Priority15)); + _for_each_inner_interrupt_priority!((all(0, 1, Priority1), (1, 2, Priority2), (2, + 3, Priority3), (3, 4, Priority4), (4, 5, Priority5), (5, 6, Priority6), (6, 7, + Priority7), (7, 8, Priority8), (8, 9, Priority9), (9, 10, Priority10), (10, 11, + Priority11), (11, 12, Priority12), (12, 13, Priority13), (13, 14, Priority14), + (14, 15, Priority15))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe { + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + } + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // OSC_SLOW_CLK +/// +/// fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_DIV_CLK +/// +/// fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV_IN +/// +/// fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SystemPreDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV +/// +/// fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_impl(_clocks: &mut ClockTree, _new_config: SystemPreDivConfig) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV +/// +/// fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ApbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CRYPTO_CLK +/// +/// fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_crypto_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CryptoClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // MSPI_CLK +/// +/// fn enable_mspi_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mspi_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: MspiClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // PLL_40M +/// +/// fn enable_pll_40m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_60M +/// +/// fn enable_pll_60m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_80M +/// +/// fn enable_pll_80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // CPU_DIV2 +/// +/// fn enable_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK_DIV_N +/// +/// fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _new_config: RcFastClkDivNConfig) { +/// todo!() +/// } +/// +/// // XTAL_DIV_CLK +/// +/// fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RTC_SLOW_CLK +/// +/// fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // RTC_FAST_CLK +/// +/// fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LOW_POWER_CLK +/// +/// fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_low_power_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LowPowerClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART_MEM_CLK +/// +/// fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_WDT_CLOCK +/// +/// fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_MEM_CLOCK +/// +/// fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_MEM_CLOCK +/// +/// fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 26 MHz + _26, + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_26 => 26000000, + XtalClkConfig::_40 => 40000000, + } + } + } + /// The list of clock signals that the `SYSTEM_PRE_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SystemPreDivInConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `SYSTEM_PRE_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = SYSTEM_PRE_DIV_IN / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SystemPreDivConfig { + divisor: u32, + } + impl SystemPreDivConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 1023). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 1023u32, + "`SYSTEM_PRE_DIV` divisor value must be between 0 and 1023 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `CPU_PLL_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = PLL_CLK / divisor`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivConfig { + /// Selects `divisor = 4`. + _4 = 4, + /// Selects `divisor = 6`. + _6 = 6, + } + impl CpuPllDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 4 => CpuPllDivConfig::_4, + 6 => CpuPllDivConfig::_6, + _ => ::core::panic!("Invalid CPU_PLL_DIV divisor value"), + } + } + } + impl CpuPllDivConfig { + fn divisor(self) -> u32 { + match self { + CpuPllDivConfig::_4 => 4, + CpuPllDivConfig::_6 => 6, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `APB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `PLL_40M`. + Pll40m, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CRYPTO_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CryptoClkConfig { + /// Selects `PLL_80M`. + Pll80m, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `MSPI_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MspiClkConfig { + /// Selects `CPU_DIV2`. + CpuDiv2, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `SYSTEM_PRE_DIV`. + Xtal, + /// Selects `SYSTEM_PRE_DIV`. + RcFast, + /// Selects `CPU_PLL_DIV`. + Pll, + } + /// Configures the `RC_FAST_CLK_DIV_N` clock divider. + /// + /// The output is calculated as `OUTPUT = RC_FAST_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RcFastClkDivNConfig { + divisor: u32, + } + impl RcFastClkDivNConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 3). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 3u32, + "`RC_FAST_CLK_DIV_N` divisor value must be between 0 and 3 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `RTC_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcSlowClkConfig { + /// Selects `OSC_SLOW_CLK`. + OscSlow, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `RC_FAST_DIV_CLK`. + RcFast, + } + /// The list of clock signals that the `RTC_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcFastClkConfig { + /// Selects `XTAL_DIV_CLK`. + Xtal, + /// Selects `RC_FAST_CLK_DIV_N`. + Rc, + } + /// The list of clock signals that the `LOW_POWER_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LowPowerClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `OSC_SLOW_CLK`. + OscSlow, + /// Selects `RTC_SLOW_CLK`. + RtcSlow, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `PLL_40M`. + Pll40m, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + /// Selects `RC_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_DIV_CLK`. + RcFastDivClk, + /// Selects `OSC_SLOW_CLK`. + Osc32kClk, + } + /// The list of clock signals that the `TIMG0_WDT_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0WdtClockConfig { + #[default] + /// Selects `PLL_40M`. + Pll40m, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + /// Selects `PLL_40M`. + PllF40m, + /// Selects `RC_FAST_CLK`. + RcFast, + #[default] + /// Selects `XTAL_CLK`. + Xtal, + } + /// The list of clock signals that the `UART0_MEM_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0MemClockConfig { + #[default] + /// Selects `UART_MEM_CLK`. + Mem, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + system_pre_div_in: Option, + system_pre_div: Option, + cpu_pll_div: Option, + apb_clk: Option, + crypto_clk: Option, + mspi_clk: Option, + cpu_clk: Option, + rc_fast_clk_div_n: Option, + rtc_slow_clk: Option, + rtc_fast_clk: Option, + low_power_clk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg0_wdt_clock: Option, + uart0_function_clock: Option, + uart0_mem_clock: Option, + uart1_function_clock: Option, + uart1_mem_clock: Option, + rc_fast_clk_refcount: u32, + osc_slow_clk_refcount: u32, + rc_slow_clk_refcount: u32, + rc_fast_div_clk_refcount: u32, + apb_clk_refcount: u32, + crypto_clk_refcount: u32, + mspi_clk_refcount: u32, + pll_40m_refcount: u32, + pll_60m_refcount: u32, + rtc_fast_clk_refcount: u32, + low_power_clk_refcount: u32, + uart_mem_clk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg0_wdt_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart0_mem_clock_refcount: u32, + uart1_function_clock_refcount: u32, + uart1_mem_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the SYSTEM_PRE_DIV_IN clock tree node + pub fn system_pre_div_in(&self) -> Option { + self.system_pre_div_in + } + /// Returns the current configuration of the SYSTEM_PRE_DIV clock tree node + pub fn system_pre_div(&self) -> Option { + self.system_pre_div + } + /// Returns the current configuration of the CPU_PLL_DIV clock tree node + pub fn cpu_pll_div(&self) -> Option { + self.cpu_pll_div + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the CRYPTO_CLK clock tree node + pub fn crypto_clk(&self) -> Option { + self.crypto_clk + } + /// Returns the current configuration of the MSPI_CLK clock tree node + pub fn mspi_clk(&self) -> Option { + self.mspi_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the RC_FAST_CLK_DIV_N clock tree node + pub fn rc_fast_clk_div_n(&self) -> Option { + self.rc_fast_clk_div_n + } + /// Returns the current configuration of the RTC_SLOW_CLK clock tree node + pub fn rtc_slow_clk(&self) -> Option { + self.rtc_slow_clk + } + /// Returns the current configuration of the RTC_FAST_CLK clock tree node + pub fn rtc_fast_clk(&self) -> Option { + self.rtc_fast_clk + } + /// Returns the current configuration of the LOW_POWER_CLK clock tree node + pub fn low_power_clk(&self) -> Option { + self.low_power_clk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG0_WDT_CLOCK clock tree node + pub fn timg0_wdt_clock(&self) -> Option { + self.timg0_wdt_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART0_MEM_CLOCK clock tree node + pub fn uart0_mem_clock(&self) -> Option { + self.uart0_mem_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + /// Returns the current configuration of the UART1_MEM_CLOCK clock tree node + pub fn uart1_mem_clock(&self) -> Option { + self.uart1_mem_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + system_pre_div_in: None, + system_pre_div: None, + cpu_pll_div: None, + apb_clk: None, + crypto_clk: None, + mspi_clk: None, + cpu_clk: None, + rc_fast_clk_div_n: None, + rtc_slow_clk: None, + rtc_fast_clk: None, + low_power_clk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg0_wdt_clock: None, + uart0_function_clock: None, + uart0_mem_clock: None, + uart1_function_clock: None, + uart1_mem_clock: None, + rc_fast_clk_refcount: 0, + osc_slow_clk_refcount: 0, + rc_slow_clk_refcount: 0, + rc_fast_div_clk_refcount: 0, + apb_clk_refcount: 0, + crypto_clk_refcount: 0, + mspi_clk_refcount: 0, + pll_40m_refcount: 0, + pll_60m_refcount: 0, + rtc_fast_clk_refcount: 0, + low_power_clk_refcount: 0, + uart_mem_clk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg0_wdt_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart0_mem_clock_refcount: 0, + uart1_function_clock_refcount: 0, + uart1_mem_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + 480000000 + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 17500000 + } + pub fn request_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting OSC_SLOW_CLK"); + if increment_reference_count(&mut clocks.osc_slow_clk_refcount) { + trace!("Enabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, true); + } + } + pub fn release_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing OSC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.osc_slow_clk_refcount) { + trace!("Disabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, false); + } + } + pub fn osc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 136000 + } + pub fn request_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_DIV_CLK"); + if increment_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Enabling RC_FAST_DIV_CLK"); + request_rc_fast_clk(clocks); + enable_rc_fast_div_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_DIV_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Disabling RC_FAST_DIV_CLK"); + enable_rc_fast_div_clk_impl(clocks, false); + release_rc_fast_clk(clocks); + } + } + pub fn rc_fast_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / 256) + } + pub fn configure_system_pre_div_in( + clocks: &mut ClockTree, + new_selector: SystemPreDivInConfig, + ) { + let old_selector = clocks.system_pre_div_in.replace(new_selector); + match new_selector { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_system_pre_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn system_pre_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div_in + } + pub fn request_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV_IN"); + trace!("Enabling SYSTEM_PRE_DIV_IN"); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_system_pre_div_in_impl(clocks, true); + } + pub fn release_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV_IN"); + trace!("Disabling SYSTEM_PRE_DIV_IN"); + enable_system_pre_div_in_impl(clocks, false); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + pub fn system_pre_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => xtal_clk_frequency(clocks), + SystemPreDivInConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_system_pre_div(clocks: &mut ClockTree, config: SystemPreDivConfig) { + clocks.system_pre_div = Some(config); + configure_system_pre_div_impl(clocks, config); + } + pub fn system_pre_div_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div + } + pub fn request_system_pre_div(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV"); + trace!("Enabling SYSTEM_PRE_DIV"); + request_system_pre_div_in(clocks); + enable_system_pre_div_impl(clocks, true); + } + pub fn release_system_pre_div(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV"); + trace!("Disabling SYSTEM_PRE_DIV"); + enable_system_pre_div_impl(clocks, false); + release_system_pre_div_in(clocks); + } + pub fn system_pre_div_frequency(clocks: &mut ClockTree) -> u32 { + (system_pre_div_in_frequency(clocks) / (unwrap!(clocks.system_pre_div).divisor() + 1)) + } + pub fn configure_cpu_pll_div(clocks: &mut ClockTree, config: CpuPllDivConfig) { + clocks.cpu_pll_div = Some(config); + configure_cpu_pll_div_impl(clocks, config); + } + pub fn cpu_pll_div_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div + } + pub fn request_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV"); + trace!("Enabling CPU_PLL_DIV"); + request_pll_clk(clocks); + enable_cpu_pll_div_impl(clocks, true); + } + pub fn release_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV"); + trace!("Disabling CPU_PLL_DIV"); + enable_cpu_pll_div_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn cpu_pll_div_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / unwrap!(clocks.cpu_pll_div).divisor()) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, new_selector: ApbClkConfig) { + let old_selector = clocks.apb_clk.replace(new_selector); + if clocks.apb_clk_refcount > 0 { + match new_selector { + ApbClkConfig::Pll40m => request_pll_40m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_apb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ApbClkConfig::Pll40m => release_pll_40m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_apb_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll40m => request_pll_40m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll40m => release_pll_40m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll40m => pll_40m_frequency(clocks), + ApbClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_crypto_clk(clocks: &mut ClockTree, new_selector: CryptoClkConfig) { + let old_selector = clocks.crypto_clk.replace(new_selector); + if clocks.crypto_clk_refcount > 0 { + match new_selector { + CryptoClkConfig::Pll80m => request_pll_80m(clocks), + CryptoClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_crypto_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CryptoClkConfig::Pll80m => release_pll_80m(clocks), + CryptoClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_crypto_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn crypto_clk_config(clocks: &mut ClockTree) -> Option { + clocks.crypto_clk + } + pub fn request_crypto_clk(clocks: &mut ClockTree) { + trace!("Requesting CRYPTO_CLK"); + if increment_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Enabling CRYPTO_CLK"); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll80m => request_pll_80m(clocks), + CryptoClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_crypto_clk_impl(clocks, true); + } + } + pub fn release_crypto_clk(clocks: &mut ClockTree) { + trace!("Releasing CRYPTO_CLK"); + if decrement_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Disabling CRYPTO_CLK"); + enable_crypto_clk_impl(clocks, false); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll80m => release_pll_80m(clocks), + CryptoClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn crypto_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll80m => pll_80m_frequency(clocks), + CryptoClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_mspi_clk(clocks: &mut ClockTree, new_selector: MspiClkConfig) { + let old_selector = clocks.mspi_clk.replace(new_selector); + if clocks.mspi_clk_refcount > 0 { + match new_selector { + MspiClkConfig::CpuDiv2 => request_cpu_div2(clocks), + MspiClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_mspi_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + MspiClkConfig::CpuDiv2 => release_cpu_div2(clocks), + MspiClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_mspi_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn mspi_clk_config(clocks: &mut ClockTree) -> Option { + clocks.mspi_clk + } + pub fn request_mspi_clk(clocks: &mut ClockTree) { + trace!("Requesting MSPI_CLK"); + if increment_reference_count(&mut clocks.mspi_clk_refcount) { + trace!("Enabling MSPI_CLK"); + match unwrap!(clocks.mspi_clk) { + MspiClkConfig::CpuDiv2 => request_cpu_div2(clocks), + MspiClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_mspi_clk_impl(clocks, true); + } + } + pub fn release_mspi_clk(clocks: &mut ClockTree) { + trace!("Releasing MSPI_CLK"); + if decrement_reference_count(&mut clocks.mspi_clk_refcount) { + trace!("Disabling MSPI_CLK"); + enable_mspi_clk_impl(clocks, false); + match unwrap!(clocks.mspi_clk) { + MspiClkConfig::CpuDiv2 => release_cpu_div2(clocks), + MspiClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn mspi_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.mspi_clk) { + MspiClkConfig::CpuDiv2 => cpu_div2_frequency(clocks), + MspiClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Xtal => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_clk(clocks, CryptoClkConfig::Cpu); + configure_mspi_clk(clocks, MspiClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::Xtal); + } + CpuClkConfig::RcFast => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_clk(clocks, CryptoClkConfig::Cpu); + configure_mspi_clk(clocks, MspiClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::RcFast); + } + CpuClkConfig::Pll => { + configure_apb_clk(clocks, ApbClkConfig::Pll40m); + configure_crypto_clk(clocks, CryptoClkConfig::Pll80m); + configure_mspi_clk(clocks, MspiClkConfig::CpuDiv2); + } + } + match new_selector { + CpuClkConfig::Xtal => request_system_pre_div(clocks), + CpuClkConfig::RcFast => request_system_pre_div(clocks), + CpuClkConfig::Pll => request_cpu_pll_div(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Xtal => release_system_pre_div(clocks), + CpuClkConfig::RcFast => release_system_pre_div(clocks), + CpuClkConfig::Pll => release_cpu_pll_div(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Xtal => system_pre_div_frequency(clocks), + CpuClkConfig::RcFast => system_pre_div_frequency(clocks), + CpuClkConfig::Pll => cpu_pll_div_frequency(clocks), + } + } + pub fn request_pll_40m(clocks: &mut ClockTree) { + trace!("Requesting PLL_40M"); + if increment_reference_count(&mut clocks.pll_40m_refcount) { + trace!("Enabling PLL_40M"); + request_cpu_clk(clocks); + enable_pll_40m_impl(clocks, true); + } + } + pub fn release_pll_40m(clocks: &mut ClockTree) { + trace!("Releasing PLL_40M"); + if decrement_reference_count(&mut clocks.pll_40m_refcount) { + trace!("Disabling PLL_40M"); + enable_pll_40m_impl(clocks, false); + release_cpu_clk(clocks); + } + } + pub fn pll_40m_frequency(clocks: &mut ClockTree) -> u32 { + 40000000 + } + pub fn request_pll_60m(clocks: &mut ClockTree) { + trace!("Requesting PLL_60M"); + if increment_reference_count(&mut clocks.pll_60m_refcount) { + trace!("Enabling PLL_60M"); + request_cpu_clk(clocks); + enable_pll_60m_impl(clocks, true); + } + } + pub fn release_pll_60m(clocks: &mut ClockTree) { + trace!("Releasing PLL_60M"); + if decrement_reference_count(&mut clocks.pll_60m_refcount) { + trace!("Disabling PLL_60M"); + enable_pll_60m_impl(clocks, false); + release_cpu_clk(clocks); + } + } + pub fn pll_60m_frequency(clocks: &mut ClockTree) -> u32 { + 60000000 + } + pub fn request_pll_80m(clocks: &mut ClockTree) { + trace!("Requesting PLL_80M"); + trace!("Enabling PLL_80M"); + request_cpu_clk(clocks); + enable_pll_80m_impl(clocks, true); + } + pub fn release_pll_80m(clocks: &mut ClockTree) { + trace!("Releasing PLL_80M"); + trace!("Disabling PLL_80M"); + enable_pll_80m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn pll_80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn request_cpu_div2(clocks: &mut ClockTree) { + trace!("Requesting CPU_DIV2"); + trace!("Enabling CPU_DIV2"); + request_cpu_clk(clocks); + enable_cpu_div2_impl(clocks, true); + } + pub fn release_cpu_div2(clocks: &mut ClockTree) { + trace!("Releasing CPU_DIV2"); + trace!("Disabling CPU_DIV2"); + enable_cpu_div2_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn cpu_div2_frequency(clocks: &mut ClockTree) -> u32 { + (cpu_clk_frequency(clocks) / 2) + } + pub fn configure_rc_fast_clk_div_n(clocks: &mut ClockTree, config: RcFastClkDivNConfig) { + clocks.rc_fast_clk_div_n = Some(config); + configure_rc_fast_clk_div_n_impl(clocks, config); + } + pub fn rc_fast_clk_div_n_config(clocks: &mut ClockTree) -> Option { + clocks.rc_fast_clk_div_n + } + pub fn request_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK_DIV_N"); + trace!("Enabling RC_FAST_CLK_DIV_N"); + request_rc_fast_clk(clocks); + enable_rc_fast_clk_div_n_impl(clocks, true); + } + pub fn release_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK_DIV_N"); + trace!("Disabling RC_FAST_CLK_DIV_N"); + enable_rc_fast_clk_div_n_impl(clocks, false); + release_rc_fast_clk(clocks); + } + pub fn rc_fast_clk_div_n_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / (unwrap!(clocks.rc_fast_clk_div_n).divisor() + 1)) + } + pub fn request_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_DIV_CLK"); + trace!("Enabling XTAL_DIV_CLK"); + request_xtal_clk(clocks); + enable_xtal_div_clk_impl(clocks, true); + } + pub fn release_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_DIV_CLK"); + trace!("Disabling XTAL_DIV_CLK"); + enable_xtal_div_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_rtc_slow_clk(clocks: &mut ClockTree, new_selector: RtcSlowClkConfig) { + let old_selector = clocks.rtc_slow_clk.replace(new_selector); + match new_selector { + RtcSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } + pub fn rtc_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_slow_clk + } + pub fn request_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_SLOW_CLK"); + trace!("Enabling RTC_SLOW_CLK"); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + enable_rtc_slow_clk_impl(clocks, true); + } + pub fn release_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_SLOW_CLK"); + trace!("Disabling RTC_SLOW_CLK"); + enable_rtc_slow_clk_impl(clocks, false); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + pub fn rtc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::OscSlow => osc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcFast => rc_fast_div_clk_frequency(clocks), + } + } + pub fn configure_rtc_fast_clk(clocks: &mut ClockTree, new_selector: RtcFastClkConfig) { + let old_selector = clocks.rtc_fast_clk.replace(new_selector); + if clocks.rtc_fast_clk_refcount > 0 { + match new_selector { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } else { + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_fast_clk + } + pub fn request_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_FAST_CLK"); + if increment_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Enabling RTC_FAST_CLK"); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + enable_rtc_fast_clk_impl(clocks, true); + } + } + pub fn release_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Disabling RTC_FAST_CLK"); + enable_rtc_fast_clk_impl(clocks, false); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } + pub fn rtc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => xtal_div_clk_frequency(clocks), + RtcFastClkConfig::Rc => rc_fast_clk_div_n_frequency(clocks), + } + } + pub fn configure_low_power_clk(clocks: &mut ClockTree, new_selector: LowPowerClkConfig) { + let old_selector = clocks.low_power_clk.replace(new_selector); + if clocks.low_power_clk_refcount > 0 { + match new_selector { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::OscSlow => request_osc_slow_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + configure_low_power_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::OscSlow => release_osc_slow_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } else { + configure_low_power_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn low_power_clk_config(clocks: &mut ClockTree) -> Option { + clocks.low_power_clk + } + pub fn request_low_power_clk(clocks: &mut ClockTree) { + trace!("Requesting LOW_POWER_CLK"); + if increment_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Enabling LOW_POWER_CLK"); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::OscSlow => request_osc_slow_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + enable_low_power_clk_impl(clocks, true); + } + } + pub fn release_low_power_clk(clocks: &mut ClockTree) { + trace!("Releasing LOW_POWER_CLK"); + if decrement_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Disabling LOW_POWER_CLK"); + enable_low_power_clk_impl(clocks, false); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::OscSlow => release_osc_slow_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } + pub fn low_power_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => xtal_clk_frequency(clocks), + LowPowerClkConfig::RcFast => rc_fast_clk_frequency(clocks), + LowPowerClkConfig::OscSlow => osc_slow_clk_frequency(clocks), + LowPowerClkConfig::RtcSlow => rtc_slow_clk_frequency(clocks), + } + } + pub fn request_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Requesting UART_MEM_CLK"); + if increment_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Enabling UART_MEM_CLK"); + request_xtal_clk(clocks); + enable_uart_mem_clk_impl(clocks, true); + } + } + pub fn release_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Releasing UART_MEM_CLK"); + if decrement_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Disabling UART_MEM_CLK"); + enable_uart_mem_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn uart_mem_clk_frequency(clocks: &mut ClockTree) -> u32 { + xtal_clk_frequency(clocks) + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::Pll40m => request_pll_40m(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::Pll40m => release_pll_40m(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::Pll40m => request_pll_40m(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::Pll40m => release_pll_40m(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::Pll40m => pll_40m_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Osc32kClk => request_osc_slow_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Osc32kClk => release_osc_slow_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Osc32kClk => request_osc_slow_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Osc32kClk => release_osc_slow_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Osc32kClk => osc_slow_clk_frequency(clocks), + } + } + pub fn configure_timg0_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg0_wdt_clock.replace(new_selector); + if clocks.timg0_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::Pll40m => request_pll_40m(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::Pll40m => release_pll_40m(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg0_wdt_clock + } + pub fn request_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Enabling TIMG0_WDT_CLOCK"); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::Pll40m => request_pll_40m(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + enable_timg0_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Disabling TIMG0_WDT_CLOCK"); + enable_timg0_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::Pll40m => release_pll_40m(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn timg0_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::Pll40m => pll_40m_frequency(clocks), + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF40m => request_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF40m => release_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF40m => request_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF40m => release_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF40m => pll_40m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart0_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart0_mem_clock.replace(new_selector); + if clocks.uart0_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart0_mem_clock + } + pub fn request_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Enabling UART0_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart0_mem_clock_impl(clocks, true); + } + } + pub fn release_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Disabling UART0_MEM_CLOCK"); + enable_uart0_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart0_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF40m => request_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF40m => release_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF40m => request_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF40m => release_pll_40m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF40m => pll_40m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart1_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart1_mem_clock.replace(new_selector); + if clocks.uart1_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart1_mem_clock + } + pub fn request_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Enabling UART1_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart1_mem_clock_impl(clocks, true); + } + } + pub fn release_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Disabling UART1_MEM_CLOCK"); + enable_uart1_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart1_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `SYSTEM_PRE_DIV` configuration. + pub system_pre_div: Option, + /// `CPU_PLL_DIV` configuration. + pub cpu_pll_div: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `RC_FAST_CLK_DIV_N` configuration. + pub rc_fast_clk_div_n: Option, + /// `RTC_SLOW_CLK` configuration. + pub rtc_slow_clk: Option, + /// `RTC_FAST_CLK` configuration. + pub rtc_fast_clk: Option, + /// `LOW_POWER_CLK` configuration. + pub low_power_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.system_pre_div { + configure_system_pre_div(clocks, config); + } + if let Some(config) = self.cpu_pll_div { + configure_cpu_pll_div(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.rc_fast_clk_div_n { + configure_rc_fast_clk_div_n(clocks, config); + } + if let Some(config) = self.rtc_slow_clk { + configure_rtc_slow_clk(clocks, config); + } + if let Some(config) = self.rtc_fast_clk { + configure_rtc_fast_clk(clocks, config); + } + if let Some(config) = self.low_power_clk { + configure_low_power_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// DMA peripheral clock signal + Dma, + /// ECC peripheral clock signal + Ecc, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// LEDC peripheral clock signal + Ledc, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TSENS peripheral clock signal + Tsens, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UART_MEM peripheral clock signal + UartMem, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = + &[Self::Systimer, Self::Timg0, Self::Uart0, Self::UartMem]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::ApbSarAdc, + Self::Dma, + Self::Ecc, + Self::I2cExt0, + Self::Ledc, + Self::Sha, + Self::Spi2, + Self::Systimer, + Self::Timg0, + Self::Tsens, + Self::Uart0, + Self::Uart1, + Self::UartMem, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.dma_clk_en().bit(enable)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_ecc_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext0_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup_clk_en().bit(enable)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.tsens_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart1_clk_en().bit(enable)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.dma_rst().bit(reset)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_ecc_rst().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext0_rst().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.ledc_rst().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_sha_rst().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi2_rst().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.systimer_rst().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup_rst().bit(reset)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.tsens_rst().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_rst().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart1_rst().bit(reset)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x3FCA0000..0x3FCE0000 + }; + (size as str, "DRAM") => { + "262144" + }; + ("DRAM2_UNINIT") => { + 0x3FCCE800..0x3FCDEB70 + }; + (size as str, "DRAM2_UNINIT") => { + "66416" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true)); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO3 peripheral singleton"] + GPIO3 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO12 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO14 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO15 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO18 peripheral singleton"] + GPIO18 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO19 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO19 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO20 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "APB_CTRL peripheral singleton"] + APB_CTRL <= APB_CTRL() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BB peripheral singleton"] BB <= BB() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA peripheral singleton"] DMA + <= DMA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "ECC peripheral singleton"] ECC <= ECC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EFUSE peripheral singleton"] + EFUSE <= EFUSE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LPWR peripheral singleton"] + LPWR <= RTC_CNTL() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_CLKRST peripheral singleton"] MODEM_CLKRST <= MODEM_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SENSITIVE peripheral singleton"] SENSITIVE <= SENSITIVE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SHA peripheral singleton"] SHA + <= SHA(SHA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI0 peripheral singleton"] SPI0 <= SPI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI1 peripheral singleton"] + SPI1 <= SPI1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTEM peripheral singleton"] + SYSTEM <= SYSTEM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG0 peripheral singleton"] + TIMG0 <= TIMG0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "XTS_AES peripheral singleton"] XTS_AES <= XTS_AES() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH0 peripheral singleton"] + DMA_CH0 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "BT peripheral singleton"] BT <= + virtual(LP_TIMER : { bind_lp_timer_interrupt, enable_lp_timer_interrupt, + disable_lp_timer_interrupt }, BT_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FLASH peripheral singleton"] + FLASH <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WIFI peripheral singleton"] + WIFI <= virtual(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt }))); _for_each_inner_peripheral!((GPIO0)); + _for_each_inner_peripheral!((GPIO1)); _for_each_inner_peripheral!((GPIO2)); + _for_each_inner_peripheral!((GPIO3)); _for_each_inner_peripheral!((GPIO4)); + _for_each_inner_peripheral!((GPIO5)); _for_each_inner_peripheral!((GPIO6)); + _for_each_inner_peripheral!((GPIO7)); _for_each_inner_peripheral!((GPIO8)); + _for_each_inner_peripheral!((GPIO9)); _for_each_inner_peripheral!((GPIO10)); + _for_each_inner_peripheral!((GPIO11)); _for_each_inner_peripheral!((GPIO12)); + _for_each_inner_peripheral!((GPIO13)); _for_each_inner_peripheral!((GPIO14)); + _for_each_inner_peripheral!((GPIO15)); _for_each_inner_peripheral!((GPIO16)); + _for_each_inner_peripheral!((GPIO17)); _for_each_inner_peripheral!((GPIO18)); + _for_each_inner_peripheral!((GPIO19)); _for_each_inner_peripheral!((GPIO20)); + _for_each_inner_peripheral!((APB_CTRL(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((BB(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((ECC(unstable))); + _for_each_inner_peripheral!((EXTMEM(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((MODEM_CLKRST(unstable))); + _for_each_inner_peripheral!((SENSITIVE(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((XTS_AES(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((WIFI)); _for_each_inner_peripheral!((SPI2, Spi2, + 0)); _for_each_inner_peripheral!((SHA, Sha, 7)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton"] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO20 <= virtual()), (@ peri_type #[doc = + "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable)), (@ peri_type + #[doc = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() + (unstable)), (@ peri_type #[doc = "BB peripheral singleton"] BB <= BB() + (unstable)), (@ peri_type #[doc = "ASSIST_DEBUG peripheral singleton"] + ASSIST_DEBUG <= ASSIST_DEBUG() (unstable)), (@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable)), (@ peri_type #[doc = + "ECC peripheral singleton"] ECC <= ECC() (unstable)), (@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable)), (@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable)), (@ peri_type #[doc + = "GPIO peripheral singleton"] GPIO <= GPIO() (unstable)), (@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable)), (@ + peri_type #[doc = "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= + INTERRUPT_CORE0() (unstable)), (@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type #[doc + = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable)), (@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable)), (@ peri_type #[doc = + "LPWR peripheral singleton"] LPWR <= RTC_CNTL() (unstable)), (@ peri_type #[doc = + "MODEM_CLKRST peripheral singleton"] MODEM_CLKRST <= MODEM_CLKRST() (unstable)), + (@ peri_type #[doc = "SENSITIVE peripheral singleton"] SENSITIVE <= SENSITIVE() + (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= SHA(SHA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 <= SPI0() + (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= SPI1() + (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 + : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() (unstable)), + (@ peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() + (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() + (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 <= + UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "XTS_AES peripheral singleton"] + XTS_AES <= XTS_AES() (unstable)), (@ peri_type #[doc = + "DMA_CH0 peripheral singleton"] DMA_CH0 <= virtual() (unstable)), (@ peri_type + #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable)), (@ peri_type + #[doc = "BT peripheral singleton"] BT <= virtual(LP_TIMER : { + bind_lp_timer_interrupt, enable_lp_timer_interrupt, disable_lp_timer_interrupt }, + BT_MAC : { bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) + (unstable)), (@ peri_type #[doc = "FLASH peripheral singleton"] FLASH <= + virtual() (unstable)), (@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= + virtual() (unstable)), (@ peri_type #[doc = "WIFI peripheral singleton"] WIFI <= + virtual(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt })))); _for_each_inner_peripheral!((singletons(GPIO0), + (GPIO1), (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), + (GPIO10), (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), + (GPIO18), (GPIO19), (GPIO20), (APB_CTRL(unstable)), (APB_SARADC(unstable)), + (BB(unstable)), (ASSIST_DEBUG(unstable)), (DMA(unstable)), (ECC(unstable)), + (EXTMEM(unstable)), (GPIO(unstable)), (I2C_ANA_MST(unstable)), (I2C0), + (INTERRUPT_CORE0(unstable)), (IO_MUX(unstable)), (LEDC(unstable)), + (RNG(unstable)), (LPWR(unstable)), (MODEM_CLKRST(unstable)), + (SENSITIVE(unstable)), (SHA(unstable)), (SPI0(unstable)), (SPI1(unstable)), + (SPI2), (SYSTEM(unstable)), (SYSTIMER(unstable)), (TIMG0(unstable)), (UART0), + (UART1), (XTS_AES(unstable)), (DMA_CH0(unstable)), (ADC1(unstable)), + (BT(unstable)), (FLASH(unstable)), (GPIO_DEDICATED(unstable)), + (SW_INTERRUPT(unstable)), (WIFI))); + _for_each_inner_peripheral!((dma_eligible(SPI2, Spi2, 0), (SHA, Sha, 7))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3() () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4(_0 => MTMS _2 => FSPIHD) (_2 => FSPIHD) ([Input] + [Output]))); _for_each_inner_gpio!((5, GPIO5(_0 => MTDI _2 => FSPIWP) (_2 => + FSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((6, GPIO6(_0 => MTCK _2 => + FSPICLK) (_2 => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((7, GPIO7(_2 + => FSPID) (_0 => MTDO _2 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() () ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9() () ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10(_2 => FSPICS0) (_2 => FSPICS0) ([Input] + [Output]))); _for_each_inner_gpio!((11, GPIO11(_0 => SPIHD) (_0 => SPIHD) + ([Input] [Output]))); _for_each_inner_gpio!((12, GPIO12(_0 => SPIHD) (_0 => + SPIHD) ([Input] [Output]))); _for_each_inner_gpio!((13, GPIO13(_0 => SPIWP) (_0 + => SPIWP) ([Input] [Output]))); _for_each_inner_gpio!((14, GPIO14() (_0 => + SPICS0) ([Input] [Output]))); _for_each_inner_gpio!((15, GPIO15() (_0 => SPICLK) + ([Input] [Output]))); _for_each_inner_gpio!((16, GPIO16(_0 => SPID) (_0 => SPID) + ([Input] [Output]))); _for_each_inner_gpio!((17, GPIO17(_0 => SPIQ) (_0 => SPIQ) + ([Input] [Output]))); _for_each_inner_gpio!((18, GPIO18() () ([Input] + [Output]))); _for_each_inner_gpio!((19, GPIO19(_0 => U0RXD) () ([Input] + [Output]))); _for_each_inner_gpio!((20, GPIO20() (_0 => U0TXD) ([Input] + [Output]))); _for_each_inner_gpio!((all(0, GPIO0() () ([Input] [Output])), (1, + GPIO1() () ([Input] [Output])), (2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] + [Output])), (3, GPIO3() () ([Input] [Output])), (4, GPIO4(_0 => MTMS _2 => + FSPIHD) (_2 => FSPIHD) ([Input] [Output])), (5, GPIO5(_0 => MTDI _2 => FSPIWP) + (_2 => FSPIWP) ([Input] [Output])), (6, GPIO6(_0 => MTCK _2 => FSPICLK) (_2 => + FSPICLK) ([Input] [Output])), (7, GPIO7(_2 => FSPID) (_0 => MTDO _2 => FSPID) + ([Input] [Output])), (8, GPIO8() () ([Input] [Output])), (9, GPIO9() () ([Input] + [Output])), (10, GPIO10(_2 => FSPICS0) (_2 => FSPICS0) ([Input] [Output])), (11, + GPIO11(_0 => SPIHD) (_0 => SPIHD) ([Input] [Output])), (12, GPIO12(_0 => SPIHD) + (_0 => SPIHD) ([Input] [Output])), (13, GPIO13(_0 => SPIWP) (_0 => SPIWP) + ([Input] [Output])), (14, GPIO14() (_0 => SPICS0) ([Input] [Output])), (15, + GPIO15() (_0 => SPICLK) ([Input] [Output])), (16, GPIO16(_0 => SPID) (_0 => SPID) + ([Input] [Output])), (17, GPIO17(_0 => SPIQ) (_0 => SPIQ) ([Input] [Output])), + (18, GPIO18() () ([Input] [Output])), (19, GPIO19(_0 => U0RXD) () ([Input] + [Output])), (20, GPIO20() (_0 => U0TXD) ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((ADC1_CH0, GPIO0)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO4)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO0)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO4)); + _for_each_inner_analog_function!((all(ADC1_CH0, GPIO0), (ADC1_CH1, GPIO1), + (ADC1_CH2, GPIO2), (ADC1_CH3, GPIO3), (ADC1_CH4, GPIO4))); + _for_each_inner_analog_function!((all_expanded((ADC1_CH0, ADCn_CHm, 1, 0), + GPIO0), ((ADC1_CH1, ADCn_CHm, 1, 1), GPIO1), ((ADC1_CH2, ADCn_CHm, 1, 2), GPIO2), + ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO3), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO4))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((RTC_GPIO0, GPIO0)); + _for_each_inner_lp_function!((RTC_GPIO1, GPIO1)); + _for_each_inner_lp_function!((RTC_GPIO2, GPIO2)); + _for_each_inner_lp_function!((RTC_GPIO3, GPIO3)); + _for_each_inner_lp_function!((RTC_GPIO4, GPIO4)); + _for_each_inner_lp_function!((RTC_GPIO5, GPIO5)); + _for_each_inner_lp_function!(((RTC_GPIO0, RTC_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((RTC_GPIO1, RTC_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((RTC_GPIO2, RTC_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((RTC_GPIO3, RTC_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((RTC_GPIO4, RTC_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((RTC_GPIO5, RTC_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!((all(RTC_GPIO0, GPIO0), (RTC_GPIO1, GPIO1), + (RTC_GPIO2, GPIO2), (RTC_GPIO3, GPIO3), (RTC_GPIO4, GPIO4), (RTC_GPIO5, GPIO5))); + _for_each_inner_lp_function!((all_expanded((RTC_GPIO0, RTC_GPIOn, 0), GPIO0), + ((RTC_GPIO1, RTC_GPIOn, 1), GPIO1), ((RTC_GPIO2, RTC_GPIOn, 2), GPIO2), + ((RTC_GPIO3, RTC_GPIOn, 3), GPIO3), ((RTC_GPIO4, RTC_GPIOn, 4), GPIO4), + ((RTC_GPIO5, RTC_GPIOn, 5), GPIO5))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + U0RXD = 6, + U0CTS = 7, + U0DSR = 8, + U1RXD = 9, + U1CTS = 10, + U1DSR = 11, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + EXT_ADC_START = 45, + RMT_SIG_0 = 51, + RMT_SIG_1 = 52, + I2CEXT0_SCL = 53, + I2CEXT0_SDA = 54, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + SIG_FUNC_97 = 97, + SIG_FUNC_98 = 98, + SIG_FUNC_99 = 99, + SIG_FUNC_100 = 100, + MTCK, + MTMS, + MTDI, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPICLK = 4, + SPICS0 = 5, + U0TXD = 6, + U0RTS = 7, + U0DTR = 8, + U1TXD = 9, + U1RTS = 10, + U1DTR = 11, + SPIQ_MONITOR = 15, + SPID_MONITOR = 16, + SPIHD_MONITOR = 17, + SPIWP_MONITOR = 18, + SPICS1 = 19, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + LEDC_LS_SIG0 = 45, + LEDC_LS_SIG1 = 46, + LEDC_LS_SIG2 = 47, + LEDC_LS_SIG3 = 48, + LEDC_LS_SIG4 = 49, + LEDC_LS_SIG5 = 50, + RMT_SIG_0 = 51, + RMT_SIG_1 = 52, + I2CEXT0_SCL = 53, + I2CEXT0_SDA = 54, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + FSPICS1 = 69, + FSPICS3 = 70, + FSPICS2 = 71, + FSPICS4 = 72, + FSPICS5 = 73, + ANT_SEL0 = 89, + ANT_SEL1 = 90, + ANT_SEL2 = 91, + ANT_SEL3 = 92, + ANT_SEL4 = 93, + ANT_SEL5 = 94, + ANT_SEL6 = 95, + ANT_SEL7 = 96, + SIG_FUNC_97 = 97, + SIG_FUNC_98 = 98, + SIG_FUNC_99 = 99, + SIG_FUNC_100 = 100, + CLK_OUT1 = 123, + CLK_OUT2 = 124, + CLK_OUT3 = 125, + GPIO = 128, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32c3.rs b/esp-metadata-generated/src/_generated_esp32c3.rs new file mode 100644 index 00000000000..a1abae30e44 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -0,0 +1,4044 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32c3" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-C3" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32c3" + }; + ("arch") => { + "riscv" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + false + }; + ("assist_debug.has_sp_monitor") => { + true + }; + ("assist_debug.has_region_monitor") => { + true + }; + ("bt.controller") => { + "btdm" + }; + ("dedicated_gpio.needs_initialization") => { + false + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + false + }; + ("dma.max_priority") => { + 9 + }; + ("dma.max_priority", str) => { + stringify!(9) + }; + ("dma.gdma_version") => { + 1 + }; + ("dma.gdma_version", str) => { + stringify!(1) + }; + ("gpio.has_bank_1") => { + false + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 31 + }; + ("gpio.constant_0_input", str) => { + stringify!(31) + }; + ("gpio.constant_1_input") => { + 30 + }; + ("gpio.constant_1_input", str) => { + stringify!(30) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 100 + }; + ("gpio.input_signal_max", str) => { + stringify!(100) + }; + ("gpio.output_signal_max") => { + 128 + }; + ("gpio.output_signal_max", str) => { + stringify!(128) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + true + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + false + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + false + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 2 + }; + ("interrupts.status_registers", str) => { + stringify!(2) + }; + ("interrupts.disabled_interrupt") => { + 0 + }; + ("phy.combo_module") => { + true + }; + ("phy.backed_up_digital_register_count") => { + 21 + }; + ("phy.backed_up_digital_register_count", str) => { + stringify!(21) + }; + ("rmt.ram_start") => { + 1610703872 + }; + ("rmt.ram_start", str) => { + stringify!(1610703872) + }; + ("rmt.channel_ram_size") => { + 48 + }; + ("rmt.channel_ram_size", str) => { + stringify!(48) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + false + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + true + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + false + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 384 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(384) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + true + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.rc_fast_clk_default") => { + 17500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(17500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + false + }; + ("timergroup.timg_has_divcnt_rst") => { + true + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + false + }; + ("uhci.combined_uart_selector_field") => { + false + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 1 + }; + ("wifi.mac_version", str) => { + stringify!(1) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((256)); _for_each_inner_aes_key_length!((128, 0, + 4)); _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + CPU_GPIO_0)); _for_each_inner_dedicated_gpio!((0, 1, CPU_GPIO_1)); + _for_each_inner_dedicated_gpio!((0, 2, CPU_GPIO_2)); + _for_each_inner_dedicated_gpio!((0, 3, CPU_GPIO_3)); + _for_each_inner_dedicated_gpio!((0, 4, CPU_GPIO_4)); + _for_each_inner_dedicated_gpio!((0, 5, CPU_GPIO_5)); + _for_each_inner_dedicated_gpio!((0, 6, CPU_GPIO_6)); + _for_each_inner_dedicated_gpio!((0, 7, CPU_GPIO_7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, CPU_GPIO_0), (0, 1, + CPU_GPIO_1), (0, 2, CPU_GPIO_2), (0, 3, CPU_GPIO_3), (0, 4, CPU_GPIO_4), (0, 5, + CPU_GPIO_5), (0, 6, CPU_GPIO_6), (0, 7, CPU_GPIO_7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_interrupt!(([disabled 0] 0)); + _for_each_inner_interrupt!(([reserved 0] 1)); + _for_each_inner_interrupt!(([direct_bindable 0] 2)); + _for_each_inner_interrupt!(([direct_bindable 1] 3)); + _for_each_inner_interrupt!(([direct_bindable 2] 4)); + _for_each_inner_interrupt!(([direct_bindable 3] 5)); + _for_each_inner_interrupt!(([direct_bindable 4] 6)); + _for_each_inner_interrupt!(([direct_bindable 5] 7)); + _for_each_inner_interrupt!(([direct_bindable 6] 8)); + _for_each_inner_interrupt!(([direct_bindable 7] 9)); + _for_each_inner_interrupt!(([direct_bindable 8] 10)); + _for_each_inner_interrupt!(([direct_bindable 9] 11)); + _for_each_inner_interrupt!(([direct_bindable 10] 12)); + _for_each_inner_interrupt!(([direct_bindable 11] 13)); + _for_each_inner_interrupt!(([direct_bindable 12] 14)); + _for_each_inner_interrupt!(([direct_bindable 13] 15)); + _for_each_inner_interrupt!(([direct_bindable 14] 16)); + _for_each_inner_interrupt!(([vector 0] 17)); _for_each_inner_interrupt!(([vector + 1] 18)); _for_each_inner_interrupt!(([vector 2] 19)); + _for_each_inner_interrupt!(([vector 3] 20)); _for_each_inner_interrupt!(([vector + 4] 21)); _for_each_inner_interrupt!(([vector 5] 22)); + _for_each_inner_interrupt!(([vector 6] 23)); _for_each_inner_interrupt!(([vector + 7] 24)); _for_each_inner_interrupt!(([vector 8] 25)); + _for_each_inner_interrupt!(([vector 9] 26)); _for_each_inner_interrupt!(([vector + 10] 27)); _for_each_inner_interrupt!(([vector 11] 28)); + _for_each_inner_interrupt!(([vector 12] 29)); _for_each_inner_interrupt!(([vector + 13] 30)); _for_each_inner_interrupt!(([vector 14] 31)); + _for_each_inner_interrupt!((all([disabled 0] 0), ([reserved 0] 1), + ([direct_bindable 0] 2), ([direct_bindable 1] 3), ([direct_bindable 2] 4), + ([direct_bindable 3] 5), ([direct_bindable 4] 6), ([direct_bindable 5] 7), + ([direct_bindable 6] 8), ([direct_bindable 7] 9), ([direct_bindable 8] 10), + ([direct_bindable 9] 11), ([direct_bindable 10] 12), ([direct_bindable 11] 13), + ([direct_bindable 12] 14), ([direct_bindable 13] 15), ([direct_bindable 14] 16), + ([vector 0] 17), ([vector 1] 18), ([vector 2] 19), ([vector 3] 20), ([vector 4] + 21), ([vector 5] 22), ([vector 6] 23), ([vector 7] 24), ([vector 8] 25), ([vector + 9] 26), ([vector 10] 27), ([vector 11] 28), ([vector 12] 29), ([vector 13] 30), + ([vector 14] 31))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_classified_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_classified_interrupt { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_classified_interrupt!(([direct_bindable 0] + 2)); _for_each_inner_classified_interrupt!(([direct_bindable 1] 3)); + _for_each_inner_classified_interrupt!(([direct_bindable 2] 4)); + _for_each_inner_classified_interrupt!(([direct_bindable 3] 5)); + _for_each_inner_classified_interrupt!(([direct_bindable 4] 6)); + _for_each_inner_classified_interrupt!(([direct_bindable 5] 7)); + _for_each_inner_classified_interrupt!(([direct_bindable 6] 8)); + _for_each_inner_classified_interrupt!(([direct_bindable 7] 9)); + _for_each_inner_classified_interrupt!(([direct_bindable 8] 10)); + _for_each_inner_classified_interrupt!(([direct_bindable 9] 11)); + _for_each_inner_classified_interrupt!(([direct_bindable 10] 12)); + _for_each_inner_classified_interrupt!(([direct_bindable 11] 13)); + _for_each_inner_classified_interrupt!(([direct_bindable 12] 14)); + _for_each_inner_classified_interrupt!(([direct_bindable 13] 15)); + _for_each_inner_classified_interrupt!(([direct_bindable 14] 16)); + _for_each_inner_classified_interrupt!(([vector 0] 17)); + _for_each_inner_classified_interrupt!(([vector 1] 18)); + _for_each_inner_classified_interrupt!(([vector 2] 19)); + _for_each_inner_classified_interrupt!(([vector 3] 20)); + _for_each_inner_classified_interrupt!(([vector 4] 21)); + _for_each_inner_classified_interrupt!(([vector 5] 22)); + _for_each_inner_classified_interrupt!(([vector 6] 23)); + _for_each_inner_classified_interrupt!(([vector 7] 24)); + _for_each_inner_classified_interrupt!(([vector 8] 25)); + _for_each_inner_classified_interrupt!(([vector 9] 26)); + _for_each_inner_classified_interrupt!(([vector 10] 27)); + _for_each_inner_classified_interrupt!(([vector 11] 28)); + _for_each_inner_classified_interrupt!(([vector 12] 29)); + _for_each_inner_classified_interrupt!(([vector 13] 30)); + _for_each_inner_classified_interrupt!(([vector 14] 31)); + _for_each_inner_classified_interrupt!(([reserved 0] 1)); + _for_each_inner_classified_interrupt!((direct_bindable([direct_bindable 0] 2), + ([direct_bindable 1] 3), ([direct_bindable 2] 4), ([direct_bindable 3] 5), + ([direct_bindable 4] 6), ([direct_bindable 5] 7), ([direct_bindable 6] 8), + ([direct_bindable 7] 9), ([direct_bindable 8] 10), ([direct_bindable 9] 11), + ([direct_bindable 10] 12), ([direct_bindable 11] 13), ([direct_bindable 12] 14), + ([direct_bindable 13] 15), ([direct_bindable 14] 16))); + _for_each_inner_classified_interrupt!((vector([vector 0] 17), ([vector 1] 18), + ([vector 2] 19), ([vector 3] 20), ([vector 4] 21), ([vector 5] 22), ([vector 6] + 23), ([vector 7] 24), ([vector 8] 25), ([vector 9] 26), ([vector 10] 27), + ([vector 11] 28), ([vector 12] 29), ([vector 13] 30), ([vector 14] 31))); + _for_each_inner_classified_interrupt!((reserved([reserved 0] 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt_priority { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt_priority { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_interrupt_priority!((0, 1, Priority1)); + _for_each_inner_interrupt_priority!((1, 2, Priority2)); + _for_each_inner_interrupt_priority!((2, 3, Priority3)); + _for_each_inner_interrupt_priority!((3, 4, Priority4)); + _for_each_inner_interrupt_priority!((4, 5, Priority5)); + _for_each_inner_interrupt_priority!((5, 6, Priority6)); + _for_each_inner_interrupt_priority!((6, 7, Priority7)); + _for_each_inner_interrupt_priority!((7, 8, Priority8)); + _for_each_inner_interrupt_priority!((8, 9, Priority9)); + _for_each_inner_interrupt_priority!((9, 10, Priority10)); + _for_each_inner_interrupt_priority!((10, 11, Priority11)); + _for_each_inner_interrupt_priority!((11, 12, Priority12)); + _for_each_inner_interrupt_priority!((12, 13, Priority13)); + _for_each_inner_interrupt_priority!((13, 14, Priority14)); + _for_each_inner_interrupt_priority!((14, 15, Priority15)); + _for_each_inner_interrupt_priority!((all(0, 1, Priority1), (1, 2, Priority2), (2, + 3, Priority3), (3, 4, Priority4), (4, 5, Priority5), (5, 6, Priority6), (6, 7, + Priority7), (7, 8, Priority8), (8, 9, Priority9), (9, 10, Priority10), (10, 11, + Priority11), (11, 12, Priority12), (12, 13, Priority13), (13, 14, Priority14), + (14, 15, Priority15))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe { + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + } + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 0)); _for_each_inner_rmt_channel!((3, 1)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1))); + _for_each_inner_rmt_channel!((rx(2, 0), (3, 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((Apb, 1)); + _for_each_inner_rmt_clock_source!((RcFast, 2)); + _for_each_inner_rmt_clock_source!((Xtal, 3)); + _for_each_inner_rmt_clock_source!((Apb)); + _for_each_inner_rmt_clock_source!((all(Apb, 1), (RcFast, 2), (Xtal, 3))); + _for_each_inner_rmt_clock_source!((default(Apb))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_DIV_CLK +/// +/// fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV_IN +/// +/// fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SystemPreDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV +/// +/// fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_impl(_clocks: &mut ClockTree, _new_config: SystemPreDivConfig) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV_OUT +/// +/// fn enable_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _config: CpuPllDivOutConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ApbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CRYPTO_CLK +/// +/// fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_crypto_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CryptoClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // PLL_80M +/// +/// fn enable_pll_80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_160M +/// +/// fn enable_pll_160m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK_DIV_N +/// +/// fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _new_config: RcFastClkDivNConfig) { +/// todo!() +/// } +/// +/// // XTAL_DIV_CLK +/// +/// fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RTC_SLOW_CLK +/// +/// fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // RTC_FAST_CLK +/// +/// fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LOW_POWER_CLK +/// +/// fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_low_power_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LowPowerClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART_MEM_CLK +/// +/// fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RMT_SCLK +/// +/// fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rmt_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RmtSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_WDT_CLOCK +/// +/// fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_WDT_CLOCK +/// +/// fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_MEM_CLOCK +/// +/// fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_MEM_CLOCK +/// +/// fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_40 => 40000000, + } + } + } + /// Selects the output frequency of `PLL_CLK`. Depends on `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PllClkConfig { + /// 320 MHz + _320, + /// 480 MHz + _480, + } + impl PllClkConfig { + pub fn value(&self) -> u32 { + match self { + PllClkConfig::_320 => 320000000, + PllClkConfig::_480 => 480000000, + } + } + } + /// The list of clock signals that the `SYSTEM_PRE_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SystemPreDivInConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `SYSTEM_PRE_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = SYSTEM_PRE_DIV_IN / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SystemPreDivConfig { + divisor: u32, + } + impl SystemPreDivConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 1023). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 1023u32, + "`SYSTEM_PRE_DIV` divisor value must be between 0 and 1023 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Selects the output frequency of `CPU_PLL_DIV_OUT`. Depends on `PLL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivOutConfig { + /// 80 MHz + _80, + /// 160 MHz + _160, + } + impl CpuPllDivOutConfig { + pub fn value(&self) -> u32 { + match self { + CpuPllDivOutConfig::_80 => 80000000, + CpuPllDivOutConfig::_160 => 160000000, + } + } + } + /// The list of clock signals that the `APB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `PLL_80M`. + Pll80m, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CRYPTO_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CryptoClkConfig { + /// Selects `PLL_160M`. + Pll160m, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `SYSTEM_PRE_DIV`. + Xtal, + /// Selects `SYSTEM_PRE_DIV`. + RcFast, + /// Selects `CPU_PLL_DIV_OUT`. + Pll, + } + /// Configures the `RC_FAST_CLK_DIV_N` clock divider. + /// + /// The output is calculated as `OUTPUT = RC_FAST_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RcFastClkDivNConfig { + divisor: u32, + } + impl RcFastClkDivNConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 3). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 3u32, + "`RC_FAST_CLK_DIV_N` divisor value must be between 0 and 3 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `RTC_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `RC_FAST_DIV_CLK`. + RcFast, + } + /// The list of clock signals that the `RTC_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcFastClkConfig { + /// Selects `XTAL_DIV_CLK`. + Xtal, + /// Selects `RC_FAST_CLK_DIV_N`. + Rc, + } + /// The list of clock signals that the `LOW_POWER_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LowPowerClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RTC_SLOW_CLK`. + RtcSlow, + } + /// The list of clock signals that the `RMT_SCLK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RmtSclkConfig { + #[default] + /// Selects `APB_CLK`. + ApbClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `APB_CLK`. + ApbClk, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + /// Selects `RC_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_DIV_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `TIMG0_WDT_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0WdtClockConfig { + #[default] + /// Selects `APB_CLK`. + ApbClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + /// Selects `APB_CLK`. + Apb, + /// Selects `RC_FAST_CLK`. + RcFast, + #[default] + /// Selects `XTAL_CLK`. + Xtal, + } + /// The list of clock signals that the `UART0_MEM_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0MemClockConfig { + #[default] + /// Selects `UART_MEM_CLK`. + Mem, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + pll_clk: Option, + system_pre_div_in: Option, + system_pre_div: Option, + cpu_pll_div_out: Option, + apb_clk: Option, + crypto_clk: Option, + cpu_clk: Option, + rc_fast_clk_div_n: Option, + rtc_slow_clk: Option, + rtc_fast_clk: Option, + low_power_clk: Option, + rmt_sclk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg0_wdt_clock: Option, + timg1_function_clock: Option, + timg1_calibration_clock: Option, + timg1_wdt_clock: Option, + uart0_function_clock: Option, + uart0_mem_clock: Option, + uart1_function_clock: Option, + uart1_mem_clock: Option, + rc_fast_clk_refcount: u32, + xtal32k_clk_refcount: u32, + rc_slow_clk_refcount: u32, + rc_fast_div_clk_refcount: u32, + apb_clk_refcount: u32, + crypto_clk_refcount: u32, + rtc_fast_clk_refcount: u32, + low_power_clk_refcount: u32, + uart_mem_clk_refcount: u32, + rmt_sclk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg0_wdt_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + timg1_wdt_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart0_mem_clock_refcount: u32, + uart1_function_clock_refcount: u32, + uart1_mem_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the PLL_CLK clock tree node + pub fn pll_clk(&self) -> Option { + self.pll_clk + } + /// Returns the current configuration of the SYSTEM_PRE_DIV_IN clock tree node + pub fn system_pre_div_in(&self) -> Option { + self.system_pre_div_in + } + /// Returns the current configuration of the SYSTEM_PRE_DIV clock tree node + pub fn system_pre_div(&self) -> Option { + self.system_pre_div + } + /// Returns the current configuration of the CPU_PLL_DIV_OUT clock tree node + pub fn cpu_pll_div_out(&self) -> Option { + self.cpu_pll_div_out + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the CRYPTO_CLK clock tree node + pub fn crypto_clk(&self) -> Option { + self.crypto_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the RC_FAST_CLK_DIV_N clock tree node + pub fn rc_fast_clk_div_n(&self) -> Option { + self.rc_fast_clk_div_n + } + /// Returns the current configuration of the RTC_SLOW_CLK clock tree node + pub fn rtc_slow_clk(&self) -> Option { + self.rtc_slow_clk + } + /// Returns the current configuration of the RTC_FAST_CLK clock tree node + pub fn rtc_fast_clk(&self) -> Option { + self.rtc_fast_clk + } + /// Returns the current configuration of the LOW_POWER_CLK clock tree node + pub fn low_power_clk(&self) -> Option { + self.low_power_clk + } + /// Returns the current configuration of the RMT_SCLK clock tree node + pub fn rmt_sclk(&self) -> Option { + self.rmt_sclk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG0_WDT_CLOCK clock tree node + pub fn timg0_wdt_clock(&self) -> Option { + self.timg0_wdt_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the TIMG1_WDT_CLOCK clock tree node + pub fn timg1_wdt_clock(&self) -> Option { + self.timg1_wdt_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART0_MEM_CLOCK clock tree node + pub fn uart0_mem_clock(&self) -> Option { + self.uart0_mem_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + /// Returns the current configuration of the UART1_MEM_CLOCK clock tree node + pub fn uart1_mem_clock(&self) -> Option { + self.uart1_mem_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + pll_clk: None, + system_pre_div_in: None, + system_pre_div: None, + cpu_pll_div_out: None, + apb_clk: None, + crypto_clk: None, + cpu_clk: None, + rc_fast_clk_div_n: None, + rtc_slow_clk: None, + rtc_fast_clk: None, + low_power_clk: None, + rmt_sclk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg0_wdt_clock: None, + timg1_function_clock: None, + timg1_calibration_clock: None, + timg1_wdt_clock: None, + uart0_function_clock: None, + uart0_mem_clock: None, + uart1_function_clock: None, + uart1_mem_clock: None, + rc_fast_clk_refcount: 0, + xtal32k_clk_refcount: 0, + rc_slow_clk_refcount: 0, + rc_fast_div_clk_refcount: 0, + apb_clk_refcount: 0, + crypto_clk_refcount: 0, + rtc_fast_clk_refcount: 0, + low_power_clk_refcount: 0, + uart_mem_clk_refcount: 0, + rmt_sclk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg0_wdt_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + timg1_wdt_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart0_mem_clock_refcount: 0, + uart1_function_clock_refcount: 0, + uart1_mem_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn configure_pll_clk(clocks: &mut ClockTree, config: PllClkConfig) { + clocks.pll_clk = Some(config); + configure_pll_clk_impl(clocks, config); + } + pub fn pll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.pll_clk + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.pll_clk).value() + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 17500000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 136000 + } + pub fn request_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_DIV_CLK"); + if increment_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Enabling RC_FAST_DIV_CLK"); + request_rc_fast_clk(clocks); + enable_rc_fast_div_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_DIV_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Disabling RC_FAST_DIV_CLK"); + enable_rc_fast_div_clk_impl(clocks, false); + release_rc_fast_clk(clocks); + } + } + pub fn rc_fast_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / 256) + } + pub fn configure_system_pre_div_in( + clocks: &mut ClockTree, + new_selector: SystemPreDivInConfig, + ) { + let old_selector = clocks.system_pre_div_in.replace(new_selector); + match new_selector { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_system_pre_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn system_pre_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div_in + } + pub fn request_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV_IN"); + trace!("Enabling SYSTEM_PRE_DIV_IN"); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_system_pre_div_in_impl(clocks, true); + } + pub fn release_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV_IN"); + trace!("Disabling SYSTEM_PRE_DIV_IN"); + enable_system_pre_div_in_impl(clocks, false); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + pub fn system_pre_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => xtal_clk_frequency(clocks), + SystemPreDivInConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_system_pre_div(clocks: &mut ClockTree, config: SystemPreDivConfig) { + clocks.system_pre_div = Some(config); + configure_system_pre_div_impl(clocks, config); + } + pub fn system_pre_div_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div + } + pub fn request_system_pre_div(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV"); + trace!("Enabling SYSTEM_PRE_DIV"); + request_system_pre_div_in(clocks); + enable_system_pre_div_impl(clocks, true); + } + pub fn release_system_pre_div(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV"); + trace!("Disabling SYSTEM_PRE_DIV"); + enable_system_pre_div_impl(clocks, false); + release_system_pre_div_in(clocks); + } + pub fn system_pre_div_frequency(clocks: &mut ClockTree) -> u32 { + (system_pre_div_in_frequency(clocks) / (unwrap!(clocks.system_pre_div).divisor() + 1)) + } + pub fn configure_cpu_pll_div_out(clocks: &mut ClockTree, config: CpuPllDivOutConfig) { + clocks.cpu_pll_div_out = Some(config); + configure_cpu_pll_div_out_impl(clocks, config); + } + pub fn cpu_pll_div_out_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div_out + } + pub fn request_cpu_pll_div_out(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV_OUT"); + trace!("Enabling CPU_PLL_DIV_OUT"); + request_pll_clk(clocks); + enable_cpu_pll_div_out_impl(clocks, true); + } + pub fn release_cpu_pll_div_out(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV_OUT"); + trace!("Disabling CPU_PLL_DIV_OUT"); + enable_cpu_pll_div_out_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn cpu_pll_div_out_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.cpu_pll_div_out).value() + } + pub fn configure_apb_clk(clocks: &mut ClockTree, new_selector: ApbClkConfig) { + let old_selector = clocks.apb_clk.replace(new_selector); + if clocks.apb_clk_refcount > 0 { + match new_selector { + ApbClkConfig::Pll80m => request_pll_80m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_apb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ApbClkConfig::Pll80m => release_pll_80m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_apb_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => request_pll_80m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => release_pll_80m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll80m => pll_80m_frequency(clocks), + ApbClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_crypto_clk(clocks: &mut ClockTree, new_selector: CryptoClkConfig) { + let old_selector = clocks.crypto_clk.replace(new_selector); + if clocks.crypto_clk_refcount > 0 { + match new_selector { + CryptoClkConfig::Pll160m => request_pll_160m(clocks), + CryptoClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_crypto_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CryptoClkConfig::Pll160m => release_pll_160m(clocks), + CryptoClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_crypto_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn crypto_clk_config(clocks: &mut ClockTree) -> Option { + clocks.crypto_clk + } + pub fn request_crypto_clk(clocks: &mut ClockTree) { + trace!("Requesting CRYPTO_CLK"); + if increment_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Enabling CRYPTO_CLK"); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll160m => request_pll_160m(clocks), + CryptoClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_crypto_clk_impl(clocks, true); + } + } + pub fn release_crypto_clk(clocks: &mut ClockTree) { + trace!("Releasing CRYPTO_CLK"); + if decrement_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Disabling CRYPTO_CLK"); + enable_crypto_clk_impl(clocks, false); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll160m => release_pll_160m(clocks), + CryptoClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn crypto_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Pll160m => pll_160m_frequency(clocks), + CryptoClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Xtal => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_clk(clocks, CryptoClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::Xtal); + } + CpuClkConfig::RcFast => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_clk(clocks, CryptoClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::RcFast); + } + CpuClkConfig::Pll => { + configure_apb_clk(clocks, ApbClkConfig::Pll80m); + configure_crypto_clk(clocks, CryptoClkConfig::Pll160m); + } + } + match new_selector { + CpuClkConfig::Xtal => request_system_pre_div(clocks), + CpuClkConfig::RcFast => request_system_pre_div(clocks), + CpuClkConfig::Pll => request_cpu_pll_div_out(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Xtal => release_system_pre_div(clocks), + CpuClkConfig::RcFast => release_system_pre_div(clocks), + CpuClkConfig::Pll => release_cpu_pll_div_out(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Xtal => system_pre_div_frequency(clocks), + CpuClkConfig::RcFast => system_pre_div_frequency(clocks), + CpuClkConfig::Pll => cpu_pll_div_out_frequency(clocks), + } + } + pub fn request_pll_80m(clocks: &mut ClockTree) { + trace!("Requesting PLL_80M"); + trace!("Enabling PLL_80M"); + request_cpu_clk(clocks); + enable_pll_80m_impl(clocks, true); + } + pub fn release_pll_80m(clocks: &mut ClockTree) { + trace!("Releasing PLL_80M"); + trace!("Disabling PLL_80M"); + enable_pll_80m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn pll_80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn request_pll_160m(clocks: &mut ClockTree) { + trace!("Requesting PLL_160M"); + trace!("Enabling PLL_160M"); + request_cpu_clk(clocks); + enable_pll_160m_impl(clocks, true); + } + pub fn release_pll_160m(clocks: &mut ClockTree) { + trace!("Releasing PLL_160M"); + trace!("Disabling PLL_160M"); + enable_pll_160m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn pll_160m_frequency(clocks: &mut ClockTree) -> u32 { + 160000000 + } + pub fn configure_rc_fast_clk_div_n(clocks: &mut ClockTree, config: RcFastClkDivNConfig) { + clocks.rc_fast_clk_div_n = Some(config); + configure_rc_fast_clk_div_n_impl(clocks, config); + } + pub fn rc_fast_clk_div_n_config(clocks: &mut ClockTree) -> Option { + clocks.rc_fast_clk_div_n + } + pub fn request_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK_DIV_N"); + trace!("Enabling RC_FAST_CLK_DIV_N"); + request_rc_fast_clk(clocks); + enable_rc_fast_clk_div_n_impl(clocks, true); + } + pub fn release_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK_DIV_N"); + trace!("Disabling RC_FAST_CLK_DIV_N"); + enable_rc_fast_clk_div_n_impl(clocks, false); + release_rc_fast_clk(clocks); + } + pub fn rc_fast_clk_div_n_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / (unwrap!(clocks.rc_fast_clk_div_n).divisor() + 1)) + } + pub fn request_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_DIV_CLK"); + trace!("Enabling XTAL_DIV_CLK"); + request_xtal_clk(clocks); + enable_xtal_div_clk_impl(clocks, true); + } + pub fn release_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_DIV_CLK"); + trace!("Disabling XTAL_DIV_CLK"); + enable_xtal_div_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_rtc_slow_clk(clocks: &mut ClockTree, new_selector: RtcSlowClkConfig) { + let old_selector = clocks.rtc_slow_clk.replace(new_selector); + match new_selector { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } + pub fn rtc_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_slow_clk + } + pub fn request_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_SLOW_CLK"); + trace!("Enabling RTC_SLOW_CLK"); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + enable_rtc_slow_clk_impl(clocks, true); + } + pub fn release_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_SLOW_CLK"); + trace!("Disabling RTC_SLOW_CLK"); + enable_rtc_slow_clk_impl(clocks, false); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + pub fn rtc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + RtcSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcFast => rc_fast_div_clk_frequency(clocks), + } + } + pub fn configure_rtc_fast_clk(clocks: &mut ClockTree, new_selector: RtcFastClkConfig) { + let old_selector = clocks.rtc_fast_clk.replace(new_selector); + if clocks.rtc_fast_clk_refcount > 0 { + match new_selector { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } else { + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_fast_clk + } + pub fn request_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_FAST_CLK"); + if increment_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Enabling RTC_FAST_CLK"); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + enable_rtc_fast_clk_impl(clocks, true); + } + } + pub fn release_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Disabling RTC_FAST_CLK"); + enable_rtc_fast_clk_impl(clocks, false); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } + pub fn rtc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => xtal_div_clk_frequency(clocks), + RtcFastClkConfig::Rc => rc_fast_clk_div_n_frequency(clocks), + } + } + pub fn configure_low_power_clk(clocks: &mut ClockTree, new_selector: LowPowerClkConfig) { + let old_selector = clocks.low_power_clk.replace(new_selector); + if clocks.low_power_clk_refcount > 0 { + match new_selector { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + configure_low_power_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } else { + configure_low_power_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn low_power_clk_config(clocks: &mut ClockTree) -> Option { + clocks.low_power_clk + } + pub fn request_low_power_clk(clocks: &mut ClockTree) { + trace!("Requesting LOW_POWER_CLK"); + if increment_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Enabling LOW_POWER_CLK"); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + enable_low_power_clk_impl(clocks, true); + } + } + pub fn release_low_power_clk(clocks: &mut ClockTree) { + trace!("Releasing LOW_POWER_CLK"); + if decrement_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Disabling LOW_POWER_CLK"); + enable_low_power_clk_impl(clocks, false); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } + pub fn low_power_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => xtal_clk_frequency(clocks), + LowPowerClkConfig::RcFast => rc_fast_clk_frequency(clocks), + LowPowerClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + LowPowerClkConfig::RtcSlow => rtc_slow_clk_frequency(clocks), + } + } + pub fn request_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Requesting UART_MEM_CLK"); + if increment_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Enabling UART_MEM_CLK"); + request_xtal_clk(clocks); + enable_uart_mem_clk_impl(clocks, true); + } + } + pub fn release_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Releasing UART_MEM_CLK"); + if decrement_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Disabling UART_MEM_CLK"); + enable_uart_mem_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn uart_mem_clk_frequency(clocks: &mut ClockTree) -> u32 { + xtal_clk_frequency(clocks) + } + pub fn configure_rmt_sclk(clocks: &mut ClockTree, new_selector: RmtSclkConfig) { + let old_selector = clocks.rmt_sclk.replace(new_selector); + if clocks.rmt_sclk_refcount > 0 { + match new_selector { + RmtSclkConfig::ApbClk => request_apb_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RmtSclkConfig::ApbClk => release_apb_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn rmt_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.rmt_sclk + } + pub fn request_rmt_sclk(clocks: &mut ClockTree) { + trace!("Requesting RMT_SCLK"); + if increment_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Enabling RMT_SCLK"); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => request_apb_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + enable_rmt_sclk_impl(clocks, true); + } + } + pub fn release_rmt_sclk(clocks: &mut ClockTree) { + trace!("Releasing RMT_SCLK"); + if decrement_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Disabling RMT_SCLK"); + enable_rmt_sclk_impl(clocks, false); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => release_apb_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn rmt_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => apb_clk_frequency(clocks), + RmtSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + RmtSclkConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg0_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg0_wdt_clock.replace(new_selector); + if clocks.timg0_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::ApbClk => request_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::ApbClk => release_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg0_wdt_clock + } + pub fn request_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Enabling TIMG0_WDT_CLOCK"); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::ApbClk => request_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + enable_timg0_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Disabling TIMG0_WDT_CLOCK"); + enable_timg0_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::ApbClk => release_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn timg0_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::ApbClk => apb_clk_frequency(clocks), + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg1_wdt_clock.replace(new_selector); + if clocks.timg1_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::ApbClk => request_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::ApbClk => release_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg1_wdt_clock + } + pub fn request_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Enabling TIMG1_WDT_CLOCK"); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::ApbClk => request_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + } + enable_timg1_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Disabling TIMG1_WDT_CLOCK"); + enable_timg1_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::ApbClk => release_apb_clk(clocks), + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn timg1_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::ApbClk => apb_clk_frequency(clocks), + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart0_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart0_mem_clock.replace(new_selector); + if clocks.uart0_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart0_mem_clock + } + pub fn request_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Enabling UART0_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart0_mem_clock_impl(clocks, true); + } + } + pub fn release_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Disabling UART0_MEM_CLOCK"); + enable_uart0_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart0_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart1_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart1_mem_clock.replace(new_selector); + if clocks.uart1_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart1_mem_clock + } + pub fn request_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Enabling UART1_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart1_mem_clock_impl(clocks, true); + } + } + pub fn release_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Disabling UART1_MEM_CLOCK"); + enable_uart1_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart1_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `PLL_CLK` configuration. + pub pll_clk: Option, + /// `SYSTEM_PRE_DIV` configuration. + pub system_pre_div: Option, + /// `CPU_PLL_DIV_OUT` configuration. + pub cpu_pll_div_out: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `RC_FAST_CLK_DIV_N` configuration. + pub rc_fast_clk_div_n: Option, + /// `RTC_SLOW_CLK` configuration. + pub rtc_slow_clk: Option, + /// `RTC_FAST_CLK` configuration. + pub rtc_fast_clk: Option, + /// `LOW_POWER_CLK` configuration. + pub low_power_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.pll_clk { + configure_pll_clk(clocks, config); + } + if let Some(config) = self.system_pre_div { + configure_system_pre_div(clocks, config); + } + if let Some(config) = self.cpu_pll_div_out { + configure_cpu_pll_div_out(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.rc_fast_clk_div_n { + configure_rc_fast_clk_div_n(clocks, config); + } + if let Some(config) = self.rtc_slow_clk { + configure_rtc_slow_clk(clocks, config); + } + if let Some(config) = self.rtc_fast_clk { + configure_rtc_fast_clk(clocks, config); + } + if let Some(config) = self.low_power_clk { + configure_low_power_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// DMA peripheral clock signal + Dma, + /// DS peripheral clock signal + Ds, + /// HMAC peripheral clock signal + Hmac, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2S0 peripheral clock signal + I2s0, + /// LEDC peripheral clock signal + Ledc, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TSENS peripheral clock signal + Tsens, + /// TWAI0 peripheral clock signal + Twai0, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UART_MEM peripheral clock signal + UartMem, + /// UHCI0 peripheral clock signal + Uhci0, + /// USB_DEVICE peripheral clock signal + UsbDevice, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = &[ + Self::Systimer, + Self::Timg0, + Self::Uart0, + Self::UartMem, + Self::UsbDevice, + ]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::ApbSarAdc, + Self::Dma, + Self::Ds, + Self::Hmac, + Self::I2cExt0, + Self::I2s0, + Self::Ledc, + Self::Rmt, + Self::Rsa, + Self::Sha, + Self::Spi2, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Tsens, + Self::Twai0, + Self::Uart0, + Self::Uart1, + Self::UartMem, + Self::Uhci0, + Self::UsbDevice, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_aes_clk_en().bit(enable)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.dma_clk_en().bit(enable)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_ds_clk_en().bit(enable)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_hmac_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext0_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2s0_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_rsa_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup1_clk_en().bit(enable)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.tsens_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.twai_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart1_clk_en().bit(enable)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uhci0_clk_en().bit(enable)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.usb_device_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_aes_rst().bit(reset)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.dma_rst().bit(reset)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_ds_rst().bit(reset)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_hmac_rst().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext0_rst().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2s0_rst().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.ledc_rst().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.rmt_rst().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_rsa_rst().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_sha_rst().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi2_rst().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.systimer_rst().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup_rst().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup1_rst().bit(reset)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.tsens_rst().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.twai_rst().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_rst().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart1_rst().bit(reset)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uhci0_rst().bit(reset)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.usb_device_rst().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x3FC80000..0x3FCE0000 + }; + (size as str, "DRAM") => { + "393216" + }; + ("DRAM2_UNINIT") => { + 0x3FCCE400..0x3FCDE710 + }; + (size as str, "DRAM2_UNINIT") => { + "66320" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true)); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO3 peripheral singleton"] + GPIO3 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO12 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO14 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO15 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO18 peripheral singleton"] + GPIO18 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO20 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO21 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO21 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "BB peripheral singleton"] BB <= + BB() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DS peripheral singleton"] DS <= + DS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EXTMEM peripheral singleton"] + EXTMEM <= EXTMEM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FE peripheral singleton"] FE <= FE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FE2 peripheral singleton"] FE2 + <= FE2() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO peripheral singleton"] GPIO <= GPIO() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO_SD peripheral singleton"] + GPIO_SD <= GPIO_SD() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "HMAC peripheral singleton"] HMAC <= HMAC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "NRX peripheral singleton"] NRX <= NRX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LPWR peripheral singleton"] LPWR <= RTC_CNTL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SENSITIVE peripheral singleton"] SENSITIVE <= SENSITIVE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SHA peripheral singleton"] SHA + <= SHA(SHA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI0 peripheral singleton"] SPI0 <= SPI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI1 peripheral singleton"] + SPI1 <= SPI1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTEM peripheral singleton"] + SYSTEM <= SYSTEM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG0 peripheral singleton"] + TIMG0 <= TIMG0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TWAI0 peripheral singleton"] + TWAI0 <= TWAI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "XTS_AES peripheral singleton"] XTS_AES <= XTS_AES() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH0 peripheral singleton"] + DMA_CH0 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH1 peripheral singleton"] DMA_CH1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH2 peripheral singleton"] + DMA_CH2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC2 peripheral singleton"] + ADC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BT peripheral singleton"] BT <= virtual(BT_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, RWBLE : { bind_rwble_interrupt, + enable_rwble_interrupt, disable_rwble_interrupt }, RWBT : { bind_rwbt_interrupt, + enable_rwbt_interrupt, disable_rwbt_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FLASH peripheral singleton"] + FLASH <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TSENS peripheral singleton"] + TSENS <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "WIFI peripheral singleton"] WIFI <= virtual(WIFI_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, + enable_pwr_interrupt, disable_pwr_interrupt }))); + _for_each_inner_peripheral!((GPIO0)); _for_each_inner_peripheral!((GPIO1)); + _for_each_inner_peripheral!((GPIO2)); _for_each_inner_peripheral!((GPIO3)); + _for_each_inner_peripheral!((GPIO4)); _for_each_inner_peripheral!((GPIO5)); + _for_each_inner_peripheral!((GPIO6)); _for_each_inner_peripheral!((GPIO7)); + _for_each_inner_peripheral!((GPIO8)); _for_each_inner_peripheral!((GPIO9)); + _for_each_inner_peripheral!((GPIO10)); _for_each_inner_peripheral!((GPIO11)); + _for_each_inner_peripheral!((GPIO12)); _for_each_inner_peripheral!((GPIO13)); + _for_each_inner_peripheral!((GPIO14)); _for_each_inner_peripheral!((GPIO15)); + _for_each_inner_peripheral!((GPIO16)); _for_each_inner_peripheral!((GPIO17)); + _for_each_inner_peripheral!((GPIO18)); _for_each_inner_peripheral!((GPIO19)); + _for_each_inner_peripheral!((GPIO20)); _for_each_inner_peripheral!((GPIO21)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_CTRL(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((BB(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((EXTMEM(unstable))); + _for_each_inner_peripheral!((FE(unstable))); + _for_each_inner_peripheral!((FE2(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((NRX(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((SENSITIVE(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB_DEVICE(unstable))); + _for_each_inner_peripheral!((XTS_AES(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((DMA_CH1(unstable))); + _for_each_inner_peripheral!((DMA_CH2(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((ADC2(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((TSENS(unstable))); + _for_each_inner_peripheral!((WIFI)); _for_each_inner_peripheral!((SPI2, Spi2, + 0)); _for_each_inner_peripheral!((UHCI0, Uhci0, 2)); + _for_each_inner_peripheral!((I2S0, I2s0, 3)); _for_each_inner_peripheral!((AES, + Aes, 6)); _for_each_inner_peripheral!((SHA, Sha, 7)); + _for_each_inner_peripheral!((APB_SARADC, ApbSaradc, 8)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton"] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO20 <= virtual()), (@ peri_type #[doc = + "GPIO21 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO21 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable)), (@ + peri_type #[doc = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() + (unstable)), (@ peri_type #[doc = "ASSIST_DEBUG peripheral singleton"] + ASSIST_DEBUG <= ASSIST_DEBUG() (unstable)), (@ peri_type #[doc = + "BB peripheral singleton"] BB <= BB() (unstable)), (@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable)), (@ peri_type #[doc = + "DS peripheral singleton"] DS <= DS() (unstable)), (@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable)), (@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable)), (@ peri_type #[doc + = "FE peripheral singleton"] FE <= FE() (unstable)), (@ peri_type #[doc = + "FE2 peripheral singleton"] FE2 <= FE2() (unstable)), (@ peri_type #[doc = + "GPIO peripheral singleton"] GPIO <= GPIO() (unstable)), (@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable)), (@ peri_type + #[doc = "HMAC peripheral singleton"] HMAC <= HMAC() (unstable)), (@ peri_type + #[doc = "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() + (unstable)), (@ peri_type #[doc = "I2C0 peripheral singleton"] I2C0 <= + I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "I2S0 peripheral singleton"] + I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable)), (@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable)), (@ peri_type #[doc = "IO_MUX peripheral singleton"] IO_MUX <= + IO_MUX() (unstable)), (@ peri_type #[doc = "LEDC peripheral singleton"] LEDC <= + LEDC() (unstable)), (@ peri_type #[doc = "NRX peripheral singleton"] NRX <= NRX() + (unstable)), (@ peri_type #[doc = "RMT peripheral singleton"] RMT <= RMT() + (unstable)), (@ peri_type #[doc = "RNG peripheral singleton"] RNG <= RNG() + (unstable)), (@ peri_type #[doc = "RSA peripheral singleton"] RSA <= RSA(RSA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "LPWR peripheral singleton"] LPWR <= RTC_CNTL() + (unstable)), (@ peri_type #[doc = "SENSITIVE peripheral singleton"] SENSITIVE <= + SENSITIVE() (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= + SHA(SHA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 <= SPI0() + (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= SPI1() + (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 + : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() (unstable)), + (@ peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() + (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() + (unstable)), (@ peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() + (unstable)), (@ peri_type #[doc = "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() + (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 <= + UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable)), (@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "XTS_AES peripheral singleton"] XTS_AES <= + XTS_AES() (unstable)), (@ peri_type #[doc = "DMA_CH0 peripheral singleton"] + DMA_CH0 <= virtual() (unstable)), (@ peri_type #[doc = + "DMA_CH1 peripheral singleton"] DMA_CH1 <= virtual() (unstable)), (@ peri_type + #[doc = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable)), (@ + peri_type #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable)), (@ + peri_type #[doc = "ADC2 peripheral singleton"] ADC2 <= virtual() (unstable)), (@ + peri_type #[doc = "BT peripheral singleton"] BT <= virtual(BT_BB : { + bind_bb_interrupt, enable_bb_interrupt, disable_bb_interrupt }, RWBLE : { + bind_rwble_interrupt, enable_rwble_interrupt, disable_rwble_interrupt }, RWBT : { + bind_rwbt_interrupt, enable_rwbt_interrupt, disable_rwbt_interrupt }) + (unstable)), (@ peri_type #[doc = "FLASH peripheral singleton"] FLASH <= + virtual() (unstable)), (@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= + virtual() (unstable)), (@ peri_type #[doc = "TSENS peripheral singleton"] TSENS + <= virtual() (unstable)), (@ peri_type #[doc = "WIFI peripheral singleton"] WIFI + <= virtual(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt })))); _for_each_inner_peripheral!((singletons(GPIO0), + (GPIO1), (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), + (GPIO10), (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), + (GPIO18), (GPIO19), (GPIO20), (GPIO21), (AES(unstable)), (APB_CTRL(unstable)), + (APB_SARADC(unstable)), (ASSIST_DEBUG(unstable)), (BB(unstable)), + (DMA(unstable)), (DS(unstable)), (EXTMEM(unstable)), (FE(unstable)), + (FE2(unstable)), (GPIO(unstable)), (GPIO_SD(unstable)), (HMAC(unstable)), + (I2C_ANA_MST(unstable)), (I2C0), (I2S0(unstable)), (INTERRUPT_CORE0(unstable)), + (IO_MUX(unstable)), (LEDC(unstable)), (NRX(unstable)), (RMT(unstable)), + (RNG(unstable)), (RSA(unstable)), (LPWR(unstable)), (SENSITIVE(unstable)), + (SHA(unstable)), (SPI0(unstable)), (SPI1(unstable)), (SPI2), (SYSTEM(unstable)), + (SYSTIMER(unstable)), (TIMG0(unstable)), (TIMG1(unstable)), (TWAI0(unstable)), + (UART0), (UART1), (UHCI0(unstable)), (USB_DEVICE(unstable)), (XTS_AES(unstable)), + (DMA_CH0(unstable)), (DMA_CH1(unstable)), (DMA_CH2(unstable)), (ADC1(unstable)), + (ADC2(unstable)), (BT(unstable)), (FLASH(unstable)), (GPIO_DEDICATED(unstable)), + (SW_INTERRUPT(unstable)), (TSENS(unstable)), (WIFI))); + _for_each_inner_peripheral!((dma_eligible(SPI2, Spi2, 0), (UHCI0, Uhci0, 2), + (I2S0, I2s0, 3), (AES, Aes, 6), (SHA, Sha, 7), (APB_SARADC, ApbSaradc, 8))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3() () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4(_0 => MTMS _2 => FSPIHD) (_2 => FSPIHD) ([Input] + [Output]))); _for_each_inner_gpio!((5, GPIO5(_0 => MTDI _2 => FSPIWP) (_2 => + FSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((6, GPIO6(_0 => MTCK _2 => + FSPICLK) (_2 => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((7, GPIO7(_2 + => FSPID) (_0 => MTDO _2 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() () ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9() () ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10(_2 => FSPICS0) (_2 => FSPICS0) ([Input] + [Output]))); _for_each_inner_gpio!((11, GPIO11() () ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12(_0 => SPIHD) (_0 => SPIHD) ([Input] + [Output]))); _for_each_inner_gpio!((13, GPIO13(_0 => SPIWP) (_0 => SPIWP) + ([Input] [Output]))); _for_each_inner_gpio!((14, GPIO14() (_0 => SPICS0) ([Input] + [Output]))); _for_each_inner_gpio!((15, GPIO15() (_0 => SPICLK) ([Input] + [Output]))); _for_each_inner_gpio!((16, GPIO16(_0 => SPID) (_0 => SPID) ([Input] + [Output]))); _for_each_inner_gpio!((17, GPIO17(_0 => SPIQ) (_0 => SPIQ) ([Input] + [Output]))); _for_each_inner_gpio!((18, GPIO18() () ([Input] [Output]))); + _for_each_inner_gpio!((19, GPIO19() () ([Input] [Output]))); + _for_each_inner_gpio!((20, GPIO20(_0 => U0RXD) () ([Input] [Output]))); + _for_each_inner_gpio!((21, GPIO21() (_0 => U0TXD) ([Input] [Output]))); + _for_each_inner_gpio!((all(0, GPIO0() () ([Input] [Output])), (1, GPIO1() () + ([Input] [Output])), (2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] [Output])), + (3, GPIO3() () ([Input] [Output])), (4, GPIO4(_0 => MTMS _2 => FSPIHD) (_2 => + FSPIHD) ([Input] [Output])), (5, GPIO5(_0 => MTDI _2 => FSPIWP) (_2 => FSPIWP) + ([Input] [Output])), (6, GPIO6(_0 => MTCK _2 => FSPICLK) (_2 => FSPICLK) ([Input] + [Output])), (7, GPIO7(_2 => FSPID) (_0 => MTDO _2 => FSPID) ([Input] [Output])), + (8, GPIO8() () ([Input] [Output])), (9, GPIO9() () ([Input] [Output])), (10, + GPIO10(_2 => FSPICS0) (_2 => FSPICS0) ([Input] [Output])), (11, GPIO11() () + ([Input] [Output])), (12, GPIO12(_0 => SPIHD) (_0 => SPIHD) ([Input] [Output])), + (13, GPIO13(_0 => SPIWP) (_0 => SPIWP) ([Input] [Output])), (14, GPIO14() (_0 => + SPICS0) ([Input] [Output])), (15, GPIO15() (_0 => SPICLK) ([Input] [Output])), + (16, GPIO16(_0 => SPID) (_0 => SPID) ([Input] [Output])), (17, GPIO17(_0 => SPIQ) + (_0 => SPIQ) ([Input] [Output])), (18, GPIO18() () ([Input] [Output])), (19, + GPIO19() () ([Input] [Output])), (20, GPIO20(_0 => U0RXD) () ([Input] [Output])), + (21, GPIO21() (_0 => U0TXD) ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((ADC1_CH0, GPIO0)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO4)); + _for_each_inner_analog_function!((ADC2_CH0, GPIO5)); + _for_each_inner_analog_function!((USB_DM, GPIO18)); + _for_each_inner_analog_function!((USB_DP, GPIO19)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO0)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO4)); + _for_each_inner_analog_function!(((ADC2_CH0, ADCn_CHm, 2, 0), GPIO5)); + _for_each_inner_analog_function!((all(ADC1_CH0, GPIO0), (ADC1_CH1, GPIO1), + (ADC1_CH2, GPIO2), (ADC1_CH3, GPIO3), (ADC1_CH4, GPIO4), (ADC2_CH0, GPIO5), + (USB_DM, GPIO18), (USB_DP, GPIO19))); + _for_each_inner_analog_function!((all_expanded((ADC1_CH0, ADCn_CHm, 1, 0), + GPIO0), ((ADC1_CH1, ADCn_CHm, 1, 1), GPIO1), ((ADC1_CH2, ADCn_CHm, 1, 2), GPIO2), + ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO3), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO4), + ((ADC2_CH0, ADCn_CHm, 2, 0), GPIO5))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((RTC_GPIO0, GPIO0)); + _for_each_inner_lp_function!((RTC_GPIO1, GPIO1)); + _for_each_inner_lp_function!((RTC_GPIO2, GPIO2)); + _for_each_inner_lp_function!((RTC_GPIO3, GPIO3)); + _for_each_inner_lp_function!((RTC_GPIO4, GPIO4)); + _for_each_inner_lp_function!((RTC_GPIO5, GPIO5)); + _for_each_inner_lp_function!(((RTC_GPIO0, RTC_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((RTC_GPIO1, RTC_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((RTC_GPIO2, RTC_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((RTC_GPIO3, RTC_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((RTC_GPIO4, RTC_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((RTC_GPIO5, RTC_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!((all(RTC_GPIO0, GPIO0), (RTC_GPIO1, GPIO1), + (RTC_GPIO2, GPIO2), (RTC_GPIO3, GPIO3), (RTC_GPIO4, GPIO4), (RTC_GPIO5, GPIO5))); + _for_each_inner_lp_function!((all_expanded((RTC_GPIO0, RTC_GPIOn, 0), GPIO0), + ((RTC_GPIO1, RTC_GPIOn, 1), GPIO1), ((RTC_GPIO2, RTC_GPIOn, 2), GPIO2), + ((RTC_GPIO3, RTC_GPIOn, 3), GPIO3), ((RTC_GPIO4, RTC_GPIOn, 4), GPIO4), + ((RTC_GPIO5, RTC_GPIOn, 5), GPIO5))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + U0RXD = 6, + U0CTS = 7, + U0DSR = 8, + U1RXD = 9, + U1CTS = 10, + U1DSR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SI_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + GPIO_BT_PRIORITY = 18, + GPIO_BT_ACTIVE = 19, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + EXT_ADC_START = 45, + RMT_SIG_0 = 51, + RMT_SIG_1 = 52, + I2CEXT0_SCL = 53, + I2CEXT0_SDA = 54, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + TWAI_RX = 74, + SIG_FUNC_97 = 97, + SIG_FUNC_98 = 98, + SIG_FUNC_99 = 99, + SIG_FUNC_100 = 100, + MTCK, + MTMS, + MTDI, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPICLK = 4, + SPICS0 = 5, + U0TXD = 6, + U0RTS = 7, + U0DTR = 8, + U1TXD = 9, + U1RTS = 10, + U1DTR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SO_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + GPIO_WLAN_PRIO = 18, + GPIO_WLAN_ACTIVE = 19, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + USB_JTAG_TCK = 36, + USB_JTAG_TMS = 37, + USB_JTAG_TDI = 38, + USB_JTAG_TDO = 39, + LEDC_LS_SIG0 = 45, + LEDC_LS_SIG1 = 46, + LEDC_LS_SIG2 = 47, + LEDC_LS_SIG3 = 48, + LEDC_LS_SIG4 = 49, + LEDC_LS_SIG5 = 50, + RMT_SIG_0 = 51, + RMT_SIG_1 = 52, + I2CEXT0_SCL = 53, + I2CEXT0_SDA = 54, + GPIO_SD0 = 55, + GPIO_SD1 = 56, + GPIO_SD2 = 57, + GPIO_SD3 = 58, + I2SO_SD1 = 59, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + FSPICS1 = 69, + FSPICS3 = 70, + FSPICS2 = 71, + FSPICS4 = 72, + FSPICS5 = 73, + TWAI_TX = 74, + TWAI_BUS_OFF_ON = 75, + TWAI_CLKOUT = 76, + ANT_SEL0 = 89, + ANT_SEL1 = 90, + ANT_SEL2 = 91, + ANT_SEL3 = 92, + ANT_SEL4 = 93, + ANT_SEL5 = 94, + ANT_SEL6 = 95, + ANT_SEL7 = 96, + SIG_FUNC_97 = 97, + SIG_FUNC_98 = 98, + SIG_FUNC_99 = 99, + SIG_FUNC_100 = 100, + CLK_OUT1 = 123, + CLK_OUT2 = 124, + CLK_OUT3 = 125, + SPICS1 = 126, + USB_JTAG_TRST = 127, + GPIO = 128, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs new file mode 100644 index 00000000000..e1e6efb43aa --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -0,0 +1,4222 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32c5" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-C5" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32c5" + }; + ("arch") => { + "riscv" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-c5_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + false + }; + ("assist_debug.has_sp_monitor") => { + true + }; + ("assist_debug.has_region_monitor") => { + true + }; + ("bt.controller") => { + "npl" + }; + ("dedicated_gpio.needs_initialization") => { + false + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + true + }; + ("dma.max_priority") => { + 5 + }; + ("dma.max_priority", str) => { + stringify!(5) + }; + ("dma.gdma_version") => { + 2 + }; + ("dma.gdma_version", str) => { + stringify!(2) + }; + ("ecc.zero_extend_writes") => { + false + }; + ("ecc.separate_jacobian_point_memory") => { + true + }; + ("ecc.has_memory_clock_gate") => { + true + }; + ("ecc.supports_enhanced_security") => { + true + }; + ("ecc.mem_block_size") => { + 48 + }; + ("gpio.has_bank_1") => { + false + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 96 + }; + ("gpio.constant_0_input", str) => { + stringify!(96) + }; + ("gpio.constant_1_input") => { + 64 + }; + ("gpio.constant_1_input", str) => { + stringify!(64) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 116 + }; + ("gpio.input_signal_max", str) => { + stringify!(116) + }; + ("gpio.output_signal_max") => { + 256 + }; + ("gpio.output_signal_max", str) => { + stringify!(256) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + false + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + true + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + true + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 3 + }; + ("interrupts.status_registers", str) => { + stringify!(3) + }; + ("interrupts.disabled_interrupt") => { + 0 + }; + ("lp_i2c_master.fifo_size") => { + 16 + }; + ("lp_i2c_master.fifo_size", str) => { + stringify!(16) + }; + ("lp_uart.ram_size") => { + 32 + }; + ("lp_uart.ram_size", str) => { + stringify!(32) + }; + ("parl_io.version") => { + 2 + }; + ("parl_io.version", str) => { + stringify!(2) + }; + ("phy.combo_module") => { + true + }; + ("rmt.ram_start") => { + 1610638336 + }; + ("rmt.ram_start", str) => { + stringify!(1610638336) + }; + ("rmt.channel_ram_size") => { + 48 + }; + ("rmt.channel_ram_size", str) => { + stringify!(48) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + true + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + true + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + false + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + false + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 384 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(384) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + false + }; + ("sleep.deep_sleep") => { + false + }; + ("soc.cpu_has_branch_predictor") => { + true + }; + ("soc.cpu_has_csr_pc") => { + false + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.cpu_csr_prv_mode") => { + 2064 + }; + ("soc.cpu_csr_prv_mode", str) => { + stringify!(2064) + }; + ("soc.rc_fast_clk_default") => { + 17500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(17500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + true + }; + ("spi_slave.supports_dma") => { + false + }; + ("timergroup.timg_has_timer1") => { + false + }; + ("timergroup.timg_has_divcnt_rst") => { + true + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + true + }; + ("uhci.combined_uart_selector_field") => { + true + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 3 + }; + ("wifi.mac_version", str) => { + stringify!(3) + }; + ("wifi.has_5g") => { + true + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((256)); _for_each_inner_aes_key_length!((128, 0, + 4)); _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + CPU_GPIO_0)); _for_each_inner_dedicated_gpio!((0, 1, CPU_GPIO_1)); + _for_each_inner_dedicated_gpio!((0, 2, CPU_GPIO_2)); + _for_each_inner_dedicated_gpio!((0, 3, CPU_GPIO_3)); + _for_each_inner_dedicated_gpio!((0, 4, CPU_GPIO_4)); + _for_each_inner_dedicated_gpio!((0, 5, CPU_GPIO_5)); + _for_each_inner_dedicated_gpio!((0, 6, CPU_GPIO_6)); + _for_each_inner_dedicated_gpio!((0, 7, CPU_GPIO_7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, CPU_GPIO_0), (0, 1, + CPU_GPIO_1), (0, 2, CPU_GPIO_2), (0, 3, CPU_GPIO_3), (0, 4, CPU_GPIO_4), (0, 5, + CPU_GPIO_5), (0, 6, CPU_GPIO_6), (0, 7, CPU_GPIO_7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_working_mode { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_working_mode { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_ecc_working_mode!((0, AffinePointMultiplication)); + _for_each_inner_ecc_working_mode!((2, AffinePointVerification)); + _for_each_inner_ecc_working_mode!((3, AffinePointVerificationAndMultiplication)); + _for_each_inner_ecc_working_mode!((4, JacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((5, AffinePointAddition)); + _for_each_inner_ecc_working_mode!((6, JacobianPointVerification)); + _for_each_inner_ecc_working_mode!((7, + AffinePointVerificationAndJacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((8, ModularAddition)); + _for_each_inner_ecc_working_mode!((9, ModularSubtraction)); + _for_each_inner_ecc_working_mode!((10, ModularMultiplication)); + _for_each_inner_ecc_working_mode!((11, ModularDivision)); + _for_each_inner_ecc_working_mode!((all(0, AffinePointMultiplication), (2, + AffinePointVerification), (3, AffinePointVerificationAndMultiplication), (4, + JacobianPointMultiplication), (5, AffinePointAddition), (6, + JacobianPointVerification), (7, + AffinePointVerificationAndJacobianPointMultiplication), (8, ModularAddition), (9, + ModularSubtraction), (10, ModularMultiplication), (11, ModularDivision))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_curve { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_curve { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_ecc_curve!((0, P192, 192)); + _for_each_inner_ecc_curve!((1, P256, 256)); _for_each_inner_ecc_curve!((2, P384, + 384)); _for_each_inner_ecc_curve!((all(0, P192, 192), (1, P256, 256), (2, P384, + 384))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_interrupt!(([disabled 0] 0)); + _for_each_inner_interrupt!(([reserved 0] 1)); + _for_each_inner_interrupt!(([reserved 1] 2)); + _for_each_inner_interrupt!(([reserved 2] 3)); + _for_each_inner_interrupt!(([reserved 3] 4)); + _for_each_inner_interrupt!(([reserved 4] 5)); + _for_each_inner_interrupt!(([reserved 5] 6)); + _for_each_inner_interrupt!(([reserved 6] 7)); + _for_each_inner_interrupt!(([reserved 7] 8)); + _for_each_inner_interrupt!(([reserved 8] 9)); + _for_each_inner_interrupt!(([reserved 9] 10)); + _for_each_inner_interrupt!(([reserved 10] 11)); + _for_each_inner_interrupt!(([reserved 11] 12)); + _for_each_inner_interrupt!(([reserved 12] 13)); + _for_each_inner_interrupt!(([reserved 13] 14)); + _for_each_inner_interrupt!(([reserved 14] 15)); + _for_each_inner_interrupt!(([vector 0] 16)); _for_each_inner_interrupt!(([vector + 1] 17)); _for_each_inner_interrupt!(([vector 2] 18)); + _for_each_inner_interrupt!(([vector 3] 19)); _for_each_inner_interrupt!(([vector + 4] 20)); _for_each_inner_interrupt!(([vector 5] 21)); + _for_each_inner_interrupt!(([vector 6] 22)); _for_each_inner_interrupt!(([vector + 7] 23)); _for_each_inner_interrupt!(([direct_bindable 0] 24)); + _for_each_inner_interrupt!(([direct_bindable 1] 25)); + _for_each_inner_interrupt!(([direct_bindable 2] 26)); + _for_each_inner_interrupt!(([direct_bindable 3] 27)); + _for_each_inner_interrupt!(([direct_bindable 4] 28)); + _for_each_inner_interrupt!(([direct_bindable 5] 29)); + _for_each_inner_interrupt!(([direct_bindable 6] 30)); + _for_each_inner_interrupt!(([direct_bindable 7] 31)); + _for_each_inner_interrupt!(([direct_bindable 8] 32)); + _for_each_inner_interrupt!(([direct_bindable 9] 33)); + _for_each_inner_interrupt!(([direct_bindable 10] 34)); + _for_each_inner_interrupt!(([direct_bindable 11] 35)); + _for_each_inner_interrupt!(([direct_bindable 12] 36)); + _for_each_inner_interrupt!(([direct_bindable 13] 37)); + _for_each_inner_interrupt!(([direct_bindable 14] 38)); + _for_each_inner_interrupt!(([direct_bindable 15] 39)); + _for_each_inner_interrupt!(([direct_bindable 16] 40)); + _for_each_inner_interrupt!(([direct_bindable 17] 41)); + _for_each_inner_interrupt!(([direct_bindable 18] 42)); + _for_each_inner_interrupt!(([direct_bindable 19] 43)); + _for_each_inner_interrupt!(([direct_bindable 20] 44)); + _for_each_inner_interrupt!(([direct_bindable 21] 45)); + _for_each_inner_interrupt!(([direct_bindable 22] 46)); + _for_each_inner_interrupt!(([direct_bindable 23] 47)); + _for_each_inner_interrupt!((all([disabled 0] 0), ([reserved 0] 1), ([reserved 1] + 2), ([reserved 2] 3), ([reserved 3] 4), ([reserved 4] 5), ([reserved 5] 6), + ([reserved 6] 7), ([reserved 7] 8), ([reserved 8] 9), ([reserved 9] 10), + ([reserved 10] 11), ([reserved 11] 12), ([reserved 12] 13), ([reserved 13] 14), + ([reserved 14] 15), ([vector 0] 16), ([vector 1] 17), ([vector 2] 18), ([vector + 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] 22), ([vector 7] 23), + ([direct_bindable 0] 24), ([direct_bindable 1] 25), ([direct_bindable 2] 26), + ([direct_bindable 3] 27), ([direct_bindable 4] 28), ([direct_bindable 5] 29), + ([direct_bindable 6] 30), ([direct_bindable 7] 31), ([direct_bindable 8] 32), + ([direct_bindable 9] 33), ([direct_bindable 10] 34), ([direct_bindable 11] 35), + ([direct_bindable 12] 36), ([direct_bindable 13] 37), ([direct_bindable 14] 38), + ([direct_bindable 15] 39), ([direct_bindable 16] 40), ([direct_bindable 17] 41), + ([direct_bindable 18] 42), ([direct_bindable 19] 43), ([direct_bindable 20] 44), + ([direct_bindable 21] 45), ([direct_bindable 22] 46), ([direct_bindable 23] + 47))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_classified_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_classified_interrupt { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_classified_interrupt!(([direct_bindable 0] + 24)); _for_each_inner_classified_interrupt!(([direct_bindable 1] 25)); + _for_each_inner_classified_interrupt!(([direct_bindable 2] 26)); + _for_each_inner_classified_interrupt!(([direct_bindable 3] 27)); + _for_each_inner_classified_interrupt!(([direct_bindable 4] 28)); + _for_each_inner_classified_interrupt!(([direct_bindable 5] 29)); + _for_each_inner_classified_interrupt!(([direct_bindable 6] 30)); + _for_each_inner_classified_interrupt!(([direct_bindable 7] 31)); + _for_each_inner_classified_interrupt!(([direct_bindable 8] 32)); + _for_each_inner_classified_interrupt!(([direct_bindable 9] 33)); + _for_each_inner_classified_interrupt!(([direct_bindable 10] 34)); + _for_each_inner_classified_interrupt!(([direct_bindable 11] 35)); + _for_each_inner_classified_interrupt!(([direct_bindable 12] 36)); + _for_each_inner_classified_interrupt!(([direct_bindable 13] 37)); + _for_each_inner_classified_interrupt!(([direct_bindable 14] 38)); + _for_each_inner_classified_interrupt!(([direct_bindable 15] 39)); + _for_each_inner_classified_interrupt!(([direct_bindable 16] 40)); + _for_each_inner_classified_interrupt!(([direct_bindable 17] 41)); + _for_each_inner_classified_interrupt!(([direct_bindable 18] 42)); + _for_each_inner_classified_interrupt!(([direct_bindable 19] 43)); + _for_each_inner_classified_interrupt!(([direct_bindable 20] 44)); + _for_each_inner_classified_interrupt!(([direct_bindable 21] 45)); + _for_each_inner_classified_interrupt!(([direct_bindable 22] 46)); + _for_each_inner_classified_interrupt!(([direct_bindable 23] 47)); + _for_each_inner_classified_interrupt!(([vector 0] 16)); + _for_each_inner_classified_interrupt!(([vector 1] 17)); + _for_each_inner_classified_interrupt!(([vector 2] 18)); + _for_each_inner_classified_interrupt!(([vector 3] 19)); + _for_each_inner_classified_interrupt!(([vector 4] 20)); + _for_each_inner_classified_interrupt!(([vector 5] 21)); + _for_each_inner_classified_interrupt!(([vector 6] 22)); + _for_each_inner_classified_interrupt!(([vector 7] 23)); + _for_each_inner_classified_interrupt!(([reserved 0] 1)); + _for_each_inner_classified_interrupt!(([reserved 1] 2)); + _for_each_inner_classified_interrupt!(([reserved 2] 3)); + _for_each_inner_classified_interrupt!(([reserved 3] 4)); + _for_each_inner_classified_interrupt!(([reserved 4] 5)); + _for_each_inner_classified_interrupt!(([reserved 5] 6)); + _for_each_inner_classified_interrupt!(([reserved 6] 7)); + _for_each_inner_classified_interrupt!(([reserved 7] 8)); + _for_each_inner_classified_interrupt!(([reserved 8] 9)); + _for_each_inner_classified_interrupt!(([reserved 9] 10)); + _for_each_inner_classified_interrupt!(([reserved 10] 11)); + _for_each_inner_classified_interrupt!(([reserved 11] 12)); + _for_each_inner_classified_interrupt!(([reserved 12] 13)); + _for_each_inner_classified_interrupt!(([reserved 13] 14)); + _for_each_inner_classified_interrupt!(([reserved 14] 15)); + _for_each_inner_classified_interrupt!((direct_bindable([direct_bindable 0] 24), + ([direct_bindable 1] 25), ([direct_bindable 2] 26), ([direct_bindable 3] 27), + ([direct_bindable 4] 28), ([direct_bindable 5] 29), ([direct_bindable 6] 30), + ([direct_bindable 7] 31), ([direct_bindable 8] 32), ([direct_bindable 9] 33), + ([direct_bindable 10] 34), ([direct_bindable 11] 35), ([direct_bindable 12] 36), + ([direct_bindable 13] 37), ([direct_bindable 14] 38), ([direct_bindable 15] 39), + ([direct_bindable 16] 40), ([direct_bindable 17] 41), ([direct_bindable 18] 42), + ([direct_bindable 19] 43), ([direct_bindable 20] 44), ([direct_bindable 21] 45), + ([direct_bindable 22] 46), ([direct_bindable 23] 47))); + _for_each_inner_classified_interrupt!((vector([vector 0] 16), ([vector 1] 17), + ([vector 2] 18), ([vector 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] + 22), ([vector 7] 23))); _for_each_inner_classified_interrupt!((reserved([reserved + 0] 1), ([reserved 1] 2), ([reserved 2] 3), ([reserved 3] 4), ([reserved 4] 5), + ([reserved 5] 6), ([reserved 6] 7), ([reserved 7] 8), ([reserved 8] 9), + ([reserved 9] 10), ([reserved 10] 11), ([reserved 11] 12), ([reserved 12] 13), + ([reserved 13] 14), ([reserved 14] 15))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt_priority { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt_priority { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_interrupt_priority!((0, 1, Priority1)); + _for_each_inner_interrupt_priority!((1, 2, Priority2)); + _for_each_inner_interrupt_priority!((2, 3, Priority3)); + _for_each_inner_interrupt_priority!((3, 4, Priority4)); + _for_each_inner_interrupt_priority!((4, 5, Priority5)); + _for_each_inner_interrupt_priority!((5, 6, Priority6)); + _for_each_inner_interrupt_priority!((6, 7, Priority7)); + _for_each_inner_interrupt_priority!((7, 8, Priority8)); + _for_each_inner_interrupt_priority!((all(0, 1, Priority1), (1, 2, Priority2), (2, + 3, Priority3), (3, 4, Priority4), (4, 5, Priority5), (5, 6, Priority6), (6, 7, + Priority7), (7, 8, Priority8))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe { + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + } + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 0)); _for_each_inner_rmt_channel!((3, 1)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1))); + _for_each_inner_rmt_channel!((rx(2, 0), (3, 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((Xtal, 0)); + _for_each_inner_rmt_clock_source!((RcFast, 1)); + _for_each_inner_rmt_clock_source!((Pll80MHz, 2)); + _for_each_inner_rmt_clock_source!((Pll80MHz)); + _for_each_inner_rmt_clock_source!((all(Xtal, 0), (RcFast, 1), (Pll80MHz, 2))); + _for_each_inner_rmt_clock_source!((default(Pll80MHz))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // OSC_SLOW_CLK +/// +/// fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F12M +/// +/// fn enable_pll_f12m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F20M +/// +/// fn enable_pll_f20m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F40M +/// +/// fn enable_pll_f40m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F48M +/// +/// fn enable_pll_f48m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F60M +/// +/// fn enable_pll_f60m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F80M +/// +/// fn enable_pll_f80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F120M +/// +/// fn enable_pll_f120m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F160M +/// +/// fn enable_pll_f160m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F240M +/// +/// fn enable_pll_f240m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // HP_ROOT_CLK +/// +/// fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_hp_root_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: HpRootClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn enable_cpu_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_clk_impl(_clocks: &mut ClockTree, _new_config: CpuClkConfig) { +/// todo!() +/// } +/// +/// // AHB_CLK +/// +/// fn enable_ahb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ahb_clk_impl(_clocks: &mut ClockTree, _new_config: AhbClkConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl(_clocks: &mut ClockTree, _new_config: ApbClkConfig) { +/// todo!() +/// } +/// +/// // XTAL_D2_CLK +/// +/// fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // LP_FAST_CLK +/// +/// fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LP_SLOW_CLK +/// +/// fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CRYPTO_CLK +/// +/// fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_crypto_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CryptoClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG_CALIBRATION_CLOCK +/// +/// fn enable_timg_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: TimgCalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_RX_CLOCK +/// +/// fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_rx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioRxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_TX_CLOCK +/// +/// fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_tx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioTxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // RMT_SCLK +/// +/// fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rmt_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RmtSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_WDT_CLOCK +/// +/// fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_WDT_CLOCK +/// +/// fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 40 MHz + _40, + /// 48 MHz + _48, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_40 => 40000000, + XtalClkConfig::_48 => 48000000, + } + } + } + /// The list of clock signals that the `HP_ROOT_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum HpRootClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `PLL_F160M`. + PllF160m, + /// Selects `PLL_F240M`. + PllF240m, + } + /// Configures the `CPU_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct CpuClkConfig { + divisor: u32, + } + impl CpuClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`CPU_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `AHB_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct AhbClkConfig { + divisor: u32, + } + impl AhbClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`AHB_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `APB_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = AHB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct ApbClkConfig { + divisor: u32, + } + impl ApbClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`APB_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `LP_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpFastClkConfig { + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `XTAL_D2_CLK`. + XtalD2, + /// Selects `XTAL_CLK`. + Xtal, + } + /// The list of clock signals that the `LP_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpSlowClkConfig { + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `OSC_SLOW_CLK`. + OscSlow, + } + /// The list of clock signals that the `CRYPTO_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CryptoClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + Fosc, + /// Selects `PLL_CLK`. + PllF480m, + } + /// The list of clock signals that the `TIMG_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum TimgCalibrationClockConfig { + /// Selects `OSC_SLOW_CLK`. + OscSlowClk, + /// Selects `RC_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `PARLIO_RX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioRxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F240M`. + PllF240m, + } + /// The list of clock signals that the `PARLIO_TX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioTxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F240M`. + PllF240m, + } + /// The list of clock signals that the `RMT_SCLK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RmtSclkConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F80M`. + PllF80m, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `PLL_F80M`. + PllF80m, + } + /// The list of clock signals that the `TIMG0_WDT_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0WdtClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + hp_root_clk: Option, + cpu_clk: Option, + ahb_clk: Option, + apb_clk: Option, + lp_fast_clk: Option, + lp_slow_clk: Option, + crypto_clk: Option, + timg_calibration_clock: Option, + parlio_rx_clock: Option, + parlio_tx_clock: Option, + rmt_sclk: Option, + timg0_function_clock: Option, + timg0_wdt_clock: Option, + timg1_function_clock: Option, + timg1_wdt_clock: Option, + uart0_function_clock: Option, + uart1_function_clock: Option, + pll_clk_refcount: u32, + rc_fast_clk_refcount: u32, + xtal32k_clk_refcount: u32, + osc_slow_clk_refcount: u32, + rc_slow_clk_refcount: u32, + pll_f12m_refcount: u32, + pll_f20m_refcount: u32, + pll_f40m_refcount: u32, + pll_f48m_refcount: u32, + pll_f60m_refcount: u32, + pll_f80m_refcount: u32, + pll_f120m_refcount: u32, + pll_f240m_refcount: u32, + hp_root_clk_refcount: u32, + cpu_clk_refcount: u32, + apb_clk_refcount: u32, + lp_fast_clk_refcount: u32, + lp_slow_clk_refcount: u32, + crypto_clk_refcount: u32, + timg_calibration_clock_refcount: u32, + parlio_rx_clock_refcount: u32, + parlio_tx_clock_refcount: u32, + rmt_sclk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_wdt_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_wdt_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart1_function_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the HP_ROOT_CLK clock tree node + pub fn hp_root_clk(&self) -> Option { + self.hp_root_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the AHB_CLK clock tree node + pub fn ahb_clk(&self) -> Option { + self.ahb_clk + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the LP_FAST_CLK clock tree node + pub fn lp_fast_clk(&self) -> Option { + self.lp_fast_clk + } + /// Returns the current configuration of the LP_SLOW_CLK clock tree node + pub fn lp_slow_clk(&self) -> Option { + self.lp_slow_clk + } + /// Returns the current configuration of the CRYPTO_CLK clock tree node + pub fn crypto_clk(&self) -> Option { + self.crypto_clk + } + /// Returns the current configuration of the TIMG_CALIBRATION_CLOCK clock tree node + pub fn timg_calibration_clock(&self) -> Option { + self.timg_calibration_clock + } + /// Returns the current configuration of the PARLIO_RX_CLOCK clock tree node + pub fn parlio_rx_clock(&self) -> Option { + self.parlio_rx_clock + } + /// Returns the current configuration of the PARLIO_TX_CLOCK clock tree node + pub fn parlio_tx_clock(&self) -> Option { + self.parlio_tx_clock + } + /// Returns the current configuration of the RMT_SCLK clock tree node + pub fn rmt_sclk(&self) -> Option { + self.rmt_sclk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_WDT_CLOCK clock tree node + pub fn timg0_wdt_clock(&self) -> Option { + self.timg0_wdt_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_WDT_CLOCK clock tree node + pub fn timg1_wdt_clock(&self) -> Option { + self.timg1_wdt_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + hp_root_clk: None, + cpu_clk: None, + ahb_clk: None, + apb_clk: None, + lp_fast_clk: None, + lp_slow_clk: None, + crypto_clk: None, + timg_calibration_clock: None, + parlio_rx_clock: None, + parlio_tx_clock: None, + rmt_sclk: None, + timg0_function_clock: None, + timg0_wdt_clock: None, + timg1_function_clock: None, + timg1_wdt_clock: None, + uart0_function_clock: None, + uart1_function_clock: None, + pll_clk_refcount: 0, + rc_fast_clk_refcount: 0, + xtal32k_clk_refcount: 0, + osc_slow_clk_refcount: 0, + rc_slow_clk_refcount: 0, + pll_f12m_refcount: 0, + pll_f20m_refcount: 0, + pll_f40m_refcount: 0, + pll_f48m_refcount: 0, + pll_f60m_refcount: 0, + pll_f80m_refcount: 0, + pll_f120m_refcount: 0, + pll_f240m_refcount: 0, + hp_root_clk_refcount: 0, + cpu_clk_refcount: 0, + apb_clk_refcount: 0, + lp_fast_clk_refcount: 0, + lp_slow_clk_refcount: 0, + crypto_clk_refcount: 0, + timg_calibration_clock_refcount: 0, + parlio_rx_clock_refcount: 0, + parlio_tx_clock_refcount: 0, + rmt_sclk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_wdt_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_wdt_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart1_function_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + if increment_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + if decrement_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + 480000000 + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 20000000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting OSC_SLOW_CLK"); + if increment_reference_count(&mut clocks.osc_slow_clk_refcount) { + trace!("Enabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, true); + } + } + pub fn release_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing OSC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.osc_slow_clk_refcount) { + trace!("Disabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, false); + } + } + pub fn osc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 130000 + } + pub fn request_pll_f12m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F12M"); + if increment_reference_count(&mut clocks.pll_f12m_refcount) { + trace!("Enabling PLL_F12M"); + request_pll_clk(clocks); + enable_pll_f12m_impl(clocks, true); + } + } + pub fn release_pll_f12m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F12M"); + if decrement_reference_count(&mut clocks.pll_f12m_refcount) { + trace!("Disabling PLL_F12M"); + enable_pll_f12m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f12m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 40) + } + pub fn request_pll_f20m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F20M"); + if increment_reference_count(&mut clocks.pll_f20m_refcount) { + trace!("Enabling PLL_F20M"); + request_pll_clk(clocks); + enable_pll_f20m_impl(clocks, true); + } + } + pub fn release_pll_f20m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F20M"); + if decrement_reference_count(&mut clocks.pll_f20m_refcount) { + trace!("Disabling PLL_F20M"); + enable_pll_f20m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f20m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 24) + } + pub fn request_pll_f40m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F40M"); + if increment_reference_count(&mut clocks.pll_f40m_refcount) { + trace!("Enabling PLL_F40M"); + request_pll_clk(clocks); + enable_pll_f40m_impl(clocks, true); + } + } + pub fn release_pll_f40m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F40M"); + if decrement_reference_count(&mut clocks.pll_f40m_refcount) { + trace!("Disabling PLL_F40M"); + enable_pll_f40m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f40m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 12) + } + pub fn request_pll_f48m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F48M"); + if increment_reference_count(&mut clocks.pll_f48m_refcount) { + trace!("Enabling PLL_F48M"); + request_pll_clk(clocks); + enable_pll_f48m_impl(clocks, true); + } + } + pub fn release_pll_f48m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F48M"); + if decrement_reference_count(&mut clocks.pll_f48m_refcount) { + trace!("Disabling PLL_F48M"); + enable_pll_f48m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f48m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 10) + } + pub fn request_pll_f60m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F60M"); + if increment_reference_count(&mut clocks.pll_f60m_refcount) { + trace!("Enabling PLL_F60M"); + request_pll_clk(clocks); + enable_pll_f60m_impl(clocks, true); + } + } + pub fn release_pll_f60m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F60M"); + if decrement_reference_count(&mut clocks.pll_f60m_refcount) { + trace!("Disabling PLL_F60M"); + enable_pll_f60m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f60m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 8) + } + pub fn request_pll_f80m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F80M"); + if increment_reference_count(&mut clocks.pll_f80m_refcount) { + trace!("Enabling PLL_F80M"); + request_pll_clk(clocks); + enable_pll_f80m_impl(clocks, true); + } + } + pub fn release_pll_f80m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F80M"); + if decrement_reference_count(&mut clocks.pll_f80m_refcount) { + trace!("Disabling PLL_F80M"); + enable_pll_f80m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f80m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 6) + } + pub fn request_pll_f120m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F120M"); + if increment_reference_count(&mut clocks.pll_f120m_refcount) { + trace!("Enabling PLL_F120M"); + request_pll_clk(clocks); + enable_pll_f120m_impl(clocks, true); + } + } + pub fn release_pll_f120m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F120M"); + if decrement_reference_count(&mut clocks.pll_f120m_refcount) { + trace!("Disabling PLL_F120M"); + enable_pll_f120m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f120m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 4) + } + pub fn request_pll_f160m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F160M"); + trace!("Enabling PLL_F160M"); + request_pll_clk(clocks); + enable_pll_f160m_impl(clocks, true); + } + pub fn release_pll_f160m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F160M"); + trace!("Disabling PLL_F160M"); + enable_pll_f160m_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn pll_f160m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 3) + } + pub fn request_pll_f240m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F240M"); + if increment_reference_count(&mut clocks.pll_f240m_refcount) { + trace!("Enabling PLL_F240M"); + request_pll_clk(clocks); + enable_pll_f240m_impl(clocks, true); + } + } + pub fn release_pll_f240m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F240M"); + if decrement_reference_count(&mut clocks.pll_f240m_refcount) { + trace!("Disabling PLL_F240M"); + enable_pll_f240m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f240m_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 2) + } + pub fn configure_hp_root_clk(clocks: &mut ClockTree, new_selector: HpRootClkConfig) { + let old_selector = clocks.hp_root_clk.replace(new_selector); + if clocks.hp_root_clk_refcount > 0 { + match new_selector { + HpRootClkConfig::Xtal => request_xtal_clk(clocks), + HpRootClkConfig::RcFast => request_rc_fast_clk(clocks), + HpRootClkConfig::PllF160m => request_pll_f160m(clocks), + HpRootClkConfig::PllF240m => request_pll_f240m(clocks), + } + configure_hp_root_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + HpRootClkConfig::Xtal => release_xtal_clk(clocks), + HpRootClkConfig::RcFast => release_rc_fast_clk(clocks), + HpRootClkConfig::PllF160m => release_pll_f160m(clocks), + HpRootClkConfig::PllF240m => release_pll_f240m(clocks), + } + } + } else { + configure_hp_root_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn hp_root_clk_config(clocks: &mut ClockTree) -> Option { + clocks.hp_root_clk + } + pub fn request_hp_root_clk(clocks: &mut ClockTree) { + trace!("Requesting HP_ROOT_CLK"); + if increment_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Enabling HP_ROOT_CLK"); + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Xtal => request_xtal_clk(clocks), + HpRootClkConfig::RcFast => request_rc_fast_clk(clocks), + HpRootClkConfig::PllF160m => request_pll_f160m(clocks), + HpRootClkConfig::PllF240m => request_pll_f240m(clocks), + } + enable_hp_root_clk_impl(clocks, true); + } + } + pub fn release_hp_root_clk(clocks: &mut ClockTree) { + trace!("Releasing HP_ROOT_CLK"); + if decrement_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Disabling HP_ROOT_CLK"); + enable_hp_root_clk_impl(clocks, false); + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Xtal => release_xtal_clk(clocks), + HpRootClkConfig::RcFast => release_rc_fast_clk(clocks), + HpRootClkConfig::PllF160m => release_pll_f160m(clocks), + HpRootClkConfig::PllF240m => release_pll_f240m(clocks), + } + } + } + pub fn hp_root_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Xtal => xtal_clk_frequency(clocks), + HpRootClkConfig::RcFast => rc_fast_clk_frequency(clocks), + HpRootClkConfig::PllF160m => pll_f160m_frequency(clocks), + HpRootClkConfig::PllF240m => pll_f240m_frequency(clocks), + } + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, config: CpuClkConfig) { + clocks.cpu_clk = Some(config); + configure_cpu_clk_impl(clocks, config); + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + pub fn request_cpu_clk(clocks: &mut ClockTree) { + trace!("Requesting CPU_CLK"); + if increment_reference_count(&mut clocks.cpu_clk_refcount) { + trace!("Enabling CPU_CLK"); + request_hp_root_clk(clocks); + enable_cpu_clk_impl(clocks, true); + } + } + pub fn release_cpu_clk(clocks: &mut ClockTree) { + trace!("Releasing CPU_CLK"); + if decrement_reference_count(&mut clocks.cpu_clk_refcount) { + trace!("Disabling CPU_CLK"); + enable_cpu_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + } + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.cpu_clk).divisor() + 1)) + } + pub fn configure_ahb_clk(clocks: &mut ClockTree, config: AhbClkConfig) { + clocks.ahb_clk = Some(config); + configure_ahb_clk_impl(clocks, config); + } + pub fn ahb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.ahb_clk + } + pub fn request_ahb_clk(clocks: &mut ClockTree) { + trace!("Requesting AHB_CLK"); + trace!("Enabling AHB_CLK"); + request_hp_root_clk(clocks); + enable_ahb_clk_impl(clocks, true); + } + pub fn release_ahb_clk(clocks: &mut ClockTree) { + trace!("Releasing AHB_CLK"); + trace!("Disabling AHB_CLK"); + enable_ahb_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn ahb_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.ahb_clk).divisor() + 1)) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, config: ApbClkConfig) { + clocks.apb_clk = Some(config); + configure_apb_clk_impl(clocks, config); + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + request_ahb_clk(clocks); + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + release_ahb_clk(clocks); + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + (ahb_clk_frequency(clocks) / (unwrap!(clocks.apb_clk).divisor() + 1)) + } + pub fn request_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_D2_CLK"); + trace!("Enabling XTAL_D2_CLK"); + request_xtal_clk(clocks); + enable_xtal_d2_clk_impl(clocks, true); + } + pub fn release_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_D2_CLK"); + trace!("Disabling XTAL_D2_CLK"); + enable_xtal_d2_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_d2_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_lp_fast_clk(clocks: &mut ClockTree, new_selector: LpFastClkConfig) { + let old_selector = clocks.lp_fast_clk.replace(new_selector); + if clocks.lp_fast_clk_refcount > 0 { + match new_selector { + LpFastClkConfig::RcFast => request_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2 => request_xtal_d2_clk(clocks), + LpFastClkConfig::Xtal => request_xtal_clk(clocks), + } + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpFastClkConfig::RcFast => release_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2 => release_xtal_d2_clk(clocks), + LpFastClkConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_fast_clk + } + pub fn request_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_FAST_CLK"); + if increment_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Enabling LP_FAST_CLK"); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFast => request_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2 => request_xtal_d2_clk(clocks), + LpFastClkConfig::Xtal => request_xtal_clk(clocks), + } + enable_lp_fast_clk_impl(clocks, true); + } + } + pub fn release_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_FAST_CLK"); + if decrement_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Disabling LP_FAST_CLK"); + enable_lp_fast_clk_impl(clocks, false); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFast => release_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2 => release_xtal_d2_clk(clocks), + LpFastClkConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn lp_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFast => rc_fast_clk_frequency(clocks), + LpFastClkConfig::XtalD2 => xtal_d2_clk_frequency(clocks), + LpFastClkConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_lp_slow_clk(clocks: &mut ClockTree, new_selector: LpSlowClkConfig) { + let old_selector = clocks.lp_slow_clk.replace(new_selector); + if clocks.lp_slow_clk_refcount > 0 { + match new_selector { + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } else { + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_slow_clk + } + pub fn request_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_SLOW_CLK"); + if increment_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Enabling LP_SLOW_CLK"); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + enable_lp_slow_clk_impl(clocks, true); + } + } + pub fn release_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_SLOW_CLK"); + if decrement_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Disabling LP_SLOW_CLK"); + enable_lp_slow_clk_impl(clocks, false); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } + pub fn lp_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + LpSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + LpSlowClkConfig::OscSlow => osc_slow_clk_frequency(clocks), + } + } + pub fn configure_crypto_clk(clocks: &mut ClockTree, new_selector: CryptoClkConfig) { + let old_selector = clocks.crypto_clk.replace(new_selector); + if clocks.crypto_clk_refcount > 0 { + match new_selector { + CryptoClkConfig::Xtal => request_xtal_clk(clocks), + CryptoClkConfig::Fosc => request_rc_fast_clk(clocks), + CryptoClkConfig::PllF480m => request_pll_clk(clocks), + } + configure_crypto_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CryptoClkConfig::Xtal => release_xtal_clk(clocks), + CryptoClkConfig::Fosc => release_rc_fast_clk(clocks), + CryptoClkConfig::PllF480m => release_pll_clk(clocks), + } + } + } else { + configure_crypto_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn crypto_clk_config(clocks: &mut ClockTree) -> Option { + clocks.crypto_clk + } + pub fn request_crypto_clk(clocks: &mut ClockTree) { + trace!("Requesting CRYPTO_CLK"); + if increment_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Enabling CRYPTO_CLK"); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Xtal => request_xtal_clk(clocks), + CryptoClkConfig::Fosc => request_rc_fast_clk(clocks), + CryptoClkConfig::PllF480m => request_pll_clk(clocks), + } + enable_crypto_clk_impl(clocks, true); + } + } + pub fn release_crypto_clk(clocks: &mut ClockTree) { + trace!("Releasing CRYPTO_CLK"); + if decrement_reference_count(&mut clocks.crypto_clk_refcount) { + trace!("Disabling CRYPTO_CLK"); + enable_crypto_clk_impl(clocks, false); + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Xtal => release_xtal_clk(clocks), + CryptoClkConfig::Fosc => release_rc_fast_clk(clocks), + CryptoClkConfig::PllF480m => release_pll_clk(clocks), + } + } + } + pub fn crypto_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.crypto_clk) { + CryptoClkConfig::Xtal => xtal_clk_frequency(clocks), + CryptoClkConfig::Fosc => rc_fast_clk_frequency(clocks), + CryptoClkConfig::PllF480m => pll_clk_frequency(clocks), + } + } + pub fn configure_timg_calibration_clock( + clocks: &mut ClockTree, + new_selector: TimgCalibrationClockConfig, + ) { + let old_selector = clocks.timg_calibration_clock.replace(new_selector); + if clocks.timg_calibration_clock_refcount > 0 { + match new_selector { + TimgCalibrationClockConfig::OscSlowClk => request_osc_slow_clk(clocks), + TimgCalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + TimgCalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + TimgCalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + TimgCalibrationClockConfig::OscSlowClk => release_osc_slow_clk(clocks), + TimgCalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + TimgCalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + TimgCalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg_calibration_clock + } + pub fn request_timg_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg_calibration_clock_refcount) { + trace!("Enabling TIMG_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg_calibration_clock) { + TimgCalibrationClockConfig::OscSlowClk => request_osc_slow_clk(clocks), + TimgCalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + TimgCalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + TimgCalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg_calibration_clock_refcount) { + trace!("Disabling TIMG_CALIBRATION_CLOCK"); + enable_timg_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg_calibration_clock) { + TimgCalibrationClockConfig::OscSlowClk => release_osc_slow_clk(clocks), + TimgCalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + TimgCalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + TimgCalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg_calibration_clock) { + TimgCalibrationClockConfig::OscSlowClk => osc_slow_clk_frequency(clocks), + TimgCalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + TimgCalibrationClockConfig::RcFastDivClk => rc_fast_clk_frequency(clocks), + TimgCalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_parlio_rx_clock( + clocks: &mut ClockTree, + new_selector: ParlioRxClockConfig, + ) { + let old_selector = clocks.parlio_rx_clock.replace(new_selector); + if clocks.parlio_rx_clock_refcount > 0 { + match new_selector { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => request_pll_f240m(clocks), + } + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } else { + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_rx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_rx_clock + } + pub fn request_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_RX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Enabling PARLIO_RX_CLOCK"); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => request_pll_f240m(clocks), + } + enable_parlio_rx_clock_impl(clocks, true); + } + } + pub fn release_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_RX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Disabling PARLIO_RX_CLOCK"); + enable_parlio_rx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } + pub fn parlio_rx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioRxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioRxClockConfig::PllF240m => pll_f240m_frequency(clocks), + } + } + pub fn configure_parlio_tx_clock( + clocks: &mut ClockTree, + new_selector: ParlioTxClockConfig, + ) { + let old_selector = clocks.parlio_tx_clock.replace(new_selector); + if clocks.parlio_tx_clock_refcount > 0 { + match new_selector { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => request_pll_f240m(clocks), + } + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } else { + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_tx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_tx_clock + } + pub fn request_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_TX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Enabling PARLIO_TX_CLOCK"); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => request_pll_f240m(clocks), + } + enable_parlio_tx_clock_impl(clocks, true); + } + } + pub fn release_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_TX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Disabling PARLIO_TX_CLOCK"); + enable_parlio_tx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } + pub fn parlio_tx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioTxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioTxClockConfig::PllF240m => pll_f240m_frequency(clocks), + } + } + pub fn configure_rmt_sclk(clocks: &mut ClockTree, new_selector: RmtSclkConfig) { + let old_selector = clocks.rmt_sclk.replace(new_selector); + if clocks.rmt_sclk_refcount > 0 { + match new_selector { + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::PllF80m => request_pll_f80m(clocks), + } + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::PllF80m => release_pll_f80m(clocks), + } + } + } else { + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn rmt_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.rmt_sclk + } + pub fn request_rmt_sclk(clocks: &mut ClockTree) { + trace!("Requesting RMT_SCLK"); + if increment_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Enabling RMT_SCLK"); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::PllF80m => request_pll_f80m(clocks), + } + enable_rmt_sclk_impl(clocks, true); + } + } + pub fn release_rmt_sclk(clocks: &mut ClockTree) { + trace!("Releasing RMT_SCLK"); + if decrement_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Disabling RMT_SCLK"); + enable_rmt_sclk_impl(clocks, false); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::PllF80m => release_pll_f80m(clocks), + } + } + } + pub fn rmt_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => xtal_clk_frequency(clocks), + RmtSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + RmtSclkConfig::PllF80m => pll_f80m_frequency(clocks), + } + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + } + } + pub fn configure_timg0_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg0_wdt_clock.replace(new_selector); + if clocks.timg0_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } else { + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg0_wdt_clock + } + pub fn request_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Enabling TIMG0_WDT_CLOCK"); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + enable_timg0_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Disabling TIMG0_WDT_CLOCK"); + enable_timg0_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } + pub fn timg0_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::PllF80m => pll_f80m_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + } + } + pub fn configure_timg1_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg1_wdt_clock.replace(new_selector); + if clocks.timg1_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } else { + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg1_wdt_clock + } + pub fn request_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Enabling TIMG1_WDT_CLOCK"); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + enable_timg1_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Disabling TIMG1_WDT_CLOCK"); + enable_timg1_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } + pub fn timg1_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::PllF80m => pll_f80m_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + Uart0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + Uart0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `HP_ROOT_CLK` configuration. + pub hp_root_clk: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `AHB_CLK` configuration. + pub ahb_clk: Option, + /// `APB_CLK` configuration. + pub apb_clk: Option, + /// `LP_FAST_CLK` configuration. + pub lp_fast_clk: Option, + /// `LP_SLOW_CLK` configuration. + pub lp_slow_clk: Option, + /// `CRYPTO_CLK` configuration. + pub crypto_clk: Option, + /// `TIMG_CALIBRATION_CLOCK` configuration. + pub timg_calibration_clock: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.hp_root_clk { + configure_hp_root_clk(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.ahb_clk { + configure_ahb_clk(clocks, config); + } + if let Some(config) = self.apb_clk { + configure_apb_clk(clocks, config); + } + if let Some(config) = self.lp_fast_clk { + configure_lp_fast_clk(clocks, config); + } + if let Some(config) = self.lp_slow_clk { + configure_lp_slow_clk(clocks, config); + } + if let Some(config) = self.crypto_clk { + configure_crypto_clk(clocks, config); + } + if let Some(config) = self.timg_calibration_clock { + configure_timg_calibration_clock(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// DMA peripheral clock signal + Dma, + /// ECC peripheral clock signal + Ecc, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// PARL_IO peripheral clock signal + ParlIo, + /// PCNT peripheral clock signal + Pcnt, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UHCI0 peripheral clock signal + Uhci0, + /// USB_DEVICE peripheral clock signal + UsbDevice, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = &[ + Self::Systimer, + Self::Timg0, + Self::Uart0, + Self::Uart1, + Self::UsbDevice, + ]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::Dma, + Self::Ecc, + Self::I2cExt0, + Self::ParlIo, + Self::Pcnt, + Self::Rmt, + Self::Rsa, + Self::Sha, + Self::Spi2, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Uart0, + Self::Uart1, + Self::Uhci0, + Self::UsbDevice, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_clk_en().bit(enable)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_clk_en().bit(enable)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_clk_en().bit(enable)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_rst_en().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_rst_en().bit(reset)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_rst_en().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_rst_en().bit(reset)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_rst_en().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_rst_en().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_rst_en().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_rst_en().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_rst_en().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_rst_en().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_rst_en().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_rst_en().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_rst_en().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_rst_en().bit(reset)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_rst_en().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x40800000..0x40860000 + }; + (size as str, "DRAM") => { + "393216" + }; + ("DRAM2_UNINIT") => { + 0x0..0x4085E5A0 + }; + (size as str, "DRAM2_UNINIT") => { + "1082516896" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true)); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO3 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO6 peripheral singleton"] + GPIO6 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO8 peripheral singleton"] + GPIO8 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton"] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO12 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO13 peripheral singleton"] + GPIO13 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO23 peripheral singleton"] + GPIO23 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO24 peripheral singleton"] GPIO24 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO25 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO26 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO28 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "CLINT peripheral singleton"] + CLINT <= CLINT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DS peripheral singleton"] DS <= + DS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "ECC peripheral singleton"] ECC <= ECC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ECDSA peripheral singleton"] + ECDSA <= ECDSA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ETM peripheral singleton"] ETM + <= SOC_ETM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO peripheral singleton"] GPIO <= GPIO() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO_SD peripheral singleton"] + GPIO_SD <= GPIO_EXT() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "HMAC peripheral singleton"] HMAC <= HMAC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HP_APM peripheral singleton"] + HP_APM <= HP_APM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "HP_SYS peripheral singleton"] HP_SYS <= HP_SYS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HUK peripheral singleton"] HUK + <= HUK() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "IO_MUX peripheral singleton"] + IO_MUX <= IO_MUX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "KEYMNG peripheral singleton"] KEYMNG <= KEYMNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_ANA peripheral singleton"] + LP_ANA <= LP_ANA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_APM0 peripheral singleton"] + LP_APM0 <= LP_APM0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_IO_MUX peripheral singleton"] LP_IO_MUX <= LP_IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_PERI peripheral singleton"] + LP_PERI <= LPPERI() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_TEE peripheral singleton"] LP_TEE <= LP_TEE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_TIMER peripheral singleton"] + LP_TIMER <= LP_TIMER() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "LP_UART peripheral singleton"] LP_UART <= LP_UART() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_WDT peripheral singleton"] + LP_WDT <= LP_WDT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LPWR peripheral singleton"] LPWR <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MCPWM0 peripheral singleton"] + MCPWM0 <= MCPWM0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= MODEM_LPCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PARL_IO peripheral singleton"] + PARL_IO <= PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, + disable_rx_interrupt }, PARL_IO_TX : { bind_tx_interrupt, enable_tx_interrupt, + disable_tx_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "PAU peripheral singleton"] PAU <= PAU() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PCNT peripheral singleton"] + PCNT <= PCNT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PCR peripheral singleton"] PCR <= PCR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PMU peripheral singleton"] PMU + <= PMU() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PVT_MONITOR peripheral singleton"] PVT_MONITOR <= PVT() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SHA peripheral singleton"] SHA <= SHA(SHA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SLC peripheral singleton"] SLC + <= SLC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTEM peripheral singleton"] + SYSTEM <= PCR() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TEE peripheral singleton"] TEE + <= TEE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG1 peripheral singleton"] + TIMG1 <= TIMG1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_CH0 peripheral singleton"] DMA_CH0 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH1 peripheral singleton"] + DMA_CH1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "BT peripheral singleton"] BT <= + virtual(LP_TIMER : { bind_lp_timer_interrupt, enable_lp_timer_interrupt, + disable_lp_timer_interrupt }, BT_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FLASH peripheral singleton"] + FLASH <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_CORE peripheral singleton"] + LP_CORE <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WIFI peripheral singleton"] + WIFI <= virtual(WIFI_BB : { bind_bb_interrupt, enable_bb_interrupt, + disable_bb_interrupt }, WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MEM2MEM0 peripheral singleton"] MEM2MEM0 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM1 peripheral singleton"] + MEM2MEM1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM2 peripheral singleton"] MEM2MEM2 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM3 peripheral singleton"] + MEM2MEM3 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM4 peripheral singleton"] MEM2MEM4 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM5 peripheral singleton"] + MEM2MEM5 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM6 peripheral singleton"] MEM2MEM6 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM7 peripheral singleton"] + MEM2MEM7 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM8 peripheral singleton"] MEM2MEM8 <= virtual() (unstable))); + _for_each_inner_peripheral!((GPIO0)); _for_each_inner_peripheral!((GPIO1)); + _for_each_inner_peripheral!((GPIO2)); _for_each_inner_peripheral!((GPIO3)); + _for_each_inner_peripheral!((GPIO4)); _for_each_inner_peripheral!((GPIO5)); + _for_each_inner_peripheral!((GPIO6)); _for_each_inner_peripheral!((GPIO7)); + _for_each_inner_peripheral!((GPIO8)); _for_each_inner_peripheral!((GPIO9)); + _for_each_inner_peripheral!((GPIO10)); _for_each_inner_peripheral!((GPIO11)); + _for_each_inner_peripheral!((GPIO12)); _for_each_inner_peripheral!((GPIO13)); + _for_each_inner_peripheral!((GPIO14)); _for_each_inner_peripheral!((GPIO23)); + _for_each_inner_peripheral!((GPIO24)); _for_each_inner_peripheral!((GPIO25)); + _for_each_inner_peripheral!((GPIO26)); _for_each_inner_peripheral!((GPIO27)); + _for_each_inner_peripheral!((GPIO28)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((CLINT(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((ECC(unstable))); + _for_each_inner_peripheral!((ECDSA(unstable))); + _for_each_inner_peripheral!((ETM(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((HP_APM(unstable))); + _for_each_inner_peripheral!((HP_SYS(unstable))); + _for_each_inner_peripheral!((HUK(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((IEEE802154(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((INTPRI(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((KEYMNG(unstable))); + _for_each_inner_peripheral!((LP_ANA(unstable))); + _for_each_inner_peripheral!((LP_AON(unstable))); + _for_each_inner_peripheral!((LP_APM0(unstable))); + _for_each_inner_peripheral!((LP_CLKRST(unstable))); + _for_each_inner_peripheral!((LP_I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((LP_IO_MUX(unstable))); + _for_each_inner_peripheral!((LP_PERI(unstable))); + _for_each_inner_peripheral!((LP_TEE(unstable))); + _for_each_inner_peripheral!((LP_TIMER(unstable))); + _for_each_inner_peripheral!((LP_UART(unstable))); + _for_each_inner_peripheral!((LP_WDT(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((MCPWM0(unstable))); + _for_each_inner_peripheral!((MEM_MONITOR(unstable))); + _for_each_inner_peripheral!((MODEM_LPCON(unstable))); + _for_each_inner_peripheral!((MODEM_SYSCON(unstable))); + _for_each_inner_peripheral!((PARL_IO(unstable))); + _for_each_inner_peripheral!((PAU(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((PCR(unstable))); + _for_each_inner_peripheral!((PMU(unstable))); + _for_each_inner_peripheral!((PVT_MONITOR(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SLC(unstable))); + _for_each_inner_peripheral!((SPI2)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TEE(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB_DEVICE(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((DMA_CH1(unstable))); + _for_each_inner_peripheral!((DMA_CH2(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((LP_CORE(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((WIFI)); + _for_each_inner_peripheral!((MEM2MEM0(unstable))); + _for_each_inner_peripheral!((MEM2MEM1(unstable))); + _for_each_inner_peripheral!((MEM2MEM2(unstable))); + _for_each_inner_peripheral!((MEM2MEM3(unstable))); + _for_each_inner_peripheral!((MEM2MEM4(unstable))); + _for_each_inner_peripheral!((MEM2MEM5(unstable))); + _for_each_inner_peripheral!((MEM2MEM6(unstable))); + _for_each_inner_peripheral!((MEM2MEM7(unstable))); + _for_each_inner_peripheral!((MEM2MEM8(unstable))); + _for_each_inner_peripheral!((MEM2MEM0, Mem2mem0, 0)); + _for_each_inner_peripheral!((SPI2, Spi2, 1)); _for_each_inner_peripheral!((UHCI0, + Uhci0, 2)); _for_each_inner_peripheral!((I2S0, I2s0, 3)); + _for_each_inner_peripheral!((MEM2MEM1, Mem2mem1, 4)); + _for_each_inner_peripheral!((MEM2MEM2, Mem2mem2, 5)); + _for_each_inner_peripheral!((AES, Aes, 6)); _for_each_inner_peripheral!((SHA, + Sha, 7)); _for_each_inner_peripheral!((APB_SARADC, ApbSaradc, 8)); + _for_each_inner_peripheral!((PARL_IO, ParlIo, 9)); + _for_each_inner_peripheral!((MEM2MEM3, Mem2mem3, 10)); + _for_each_inner_peripheral!((MEM2MEM4, Mem2mem4, 11)); + _for_each_inner_peripheral!((MEM2MEM5, Mem2mem5, 12)); + _for_each_inner_peripheral!((MEM2MEM6, Mem2mem6, 13)); + _for_each_inner_peripheral!((MEM2MEM7, Mem2mem7, 14)); + _for_each_inner_peripheral!((MEM2MEM8, Mem2mem8, 15)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton"] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton"] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton"] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO23 peripheral singleton"] GPIO23 <= virtual()), (@ peri_type #[doc = + "GPIO24 peripheral singleton"] GPIO24 <= virtual()), (@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO25 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO27 <= virtual()), (@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO28 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() + (unstable)), (@ peri_type #[doc = "APB_SARADC peripheral singleton"] APB_SARADC + <= APB_SARADC() (unstable)), (@ peri_type #[doc = "CLINT peripheral singleton"] + CLINT <= CLINT() (unstable)), (@ peri_type #[doc = "DMA peripheral singleton"] + DMA <= DMA() (unstable)), (@ peri_type #[doc = "DS peripheral singleton"] DS <= + DS() (unstable)), (@ peri_type #[doc = "ECC peripheral singleton"] ECC <= ECC() + (unstable)), (@ peri_type #[doc = "ECDSA peripheral singleton"] ECDSA <= ECDSA() + (unstable)), (@ peri_type #[doc = "EFUSE peripheral singleton"] EFUSE <= EFUSE() + (unstable)), (@ peri_type #[doc = "ETM peripheral singleton"] ETM <= SOC_ETM() + (unstable)), (@ peri_type #[doc = "GPIO peripheral singleton"] GPIO <= GPIO() + (unstable)), (@ peri_type #[doc = "GPIO_SD peripheral singleton"] GPIO_SD <= + GPIO_EXT() (unstable)), (@ peri_type #[doc = "HMAC peripheral singleton"] HMAC <= + HMAC() (unstable)), (@ peri_type #[doc = "HP_APM peripheral singleton"] HP_APM <= + HP_APM() (unstable)), (@ peri_type #[doc = "HP_SYS peripheral singleton"] HP_SYS + <= HP_SYS() (unstable)), (@ peri_type #[doc = "HUK peripheral singleton"] HUK <= + HUK() (unstable)), (@ peri_type #[doc = "I2C_ANA_MST peripheral singleton"] + I2C_ANA_MST <= I2C_ANA_MST() (unstable)), (@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable)), + (@ peri_type #[doc = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= + INTERRUPT_CORE0() (unstable)), (@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable)), (@ peri_type #[doc + = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type + #[doc = "KEYMNG peripheral singleton"] KEYMNG <= KEYMNG() (unstable)), (@ + peri_type #[doc = "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable)), + (@ peri_type #[doc = "LP_AON peripheral singleton"] LP_AON <= LP_AON() + (unstable)), (@ peri_type #[doc = "LP_APM0 peripheral singleton"] LP_APM0 <= + LP_APM0() (unstable)), (@ peri_type #[doc = "LP_CLKRST peripheral singleton"] + LP_CLKRST <= LP_CLKRST() (unstable)), (@ peri_type #[doc = + "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() + (unstable)), (@ peri_type #[doc = "LP_IO_MUX peripheral singleton"] LP_IO_MUX <= + LP_IO_MUX() (unstable)), (@ peri_type #[doc = "LP_PERI peripheral singleton"] + LP_PERI <= LPPERI() (unstable)), (@ peri_type #[doc = + "LP_TEE peripheral singleton"] LP_TEE <= LP_TEE() (unstable)), (@ peri_type #[doc + = "LP_TIMER peripheral singleton"] LP_TIMER <= LP_TIMER() (unstable)), (@ + peri_type #[doc = "LP_UART peripheral singleton"] LP_UART <= LP_UART() + (unstable)), (@ peri_type #[doc = "LP_WDT peripheral singleton"] LP_WDT <= + LP_WDT() (unstable)), (@ peri_type #[doc = "LPWR peripheral singleton"] LPWR <= + LP_CLKRST() (unstable)), (@ peri_type #[doc = "MCPWM0 peripheral singleton"] + MCPWM0 <= MCPWM0() (unstable)), (@ peri_type #[doc = + "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable)), (@ + peri_type #[doc = "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= + MODEM_LPCON() (unstable)), (@ peri_type #[doc = + "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable)), + (@ peri_type #[doc = "PARL_IO peripheral singleton"] PARL_IO <= + PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, + disable_rx_interrupt }, PARL_IO_TX : { bind_tx_interrupt, enable_tx_interrupt, + disable_tx_interrupt }) (unstable)), (@ peri_type #[doc = + "PAU peripheral singleton"] PAU <= PAU() (unstable)), (@ peri_type #[doc = + "PCNT peripheral singleton"] PCNT <= PCNT() (unstable)), (@ peri_type #[doc = + "PCR peripheral singleton"] PCR <= PCR() (unstable)), (@ peri_type #[doc = + "PMU peripheral singleton"] PMU <= PMU() (unstable)), (@ peri_type #[doc = + "PVT_MONITOR peripheral singleton"] PVT_MONITOR <= PVT() (unstable)), (@ + peri_type #[doc = "RMT peripheral singleton"] RMT <= RMT() (unstable)), (@ + peri_type #[doc = "RNG peripheral singleton"] RNG <= RNG() (unstable)), (@ + peri_type #[doc = "RSA peripheral singleton"] RSA <= RSA(RSA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= SHA(SHA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SLC peripheral singleton"] SLC <= SLC() + (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 + : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= PCR() (unstable)), (@ + peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() + (unstable)), (@ peri_type #[doc = "TEE peripheral singleton"] TEE <= TEE() + (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() + (unstable)), (@ peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() + (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 <= + UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable)), (@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "DMA_CH0 peripheral singleton"] DMA_CH0 <= + virtual() (unstable)), (@ peri_type #[doc = "DMA_CH1 peripheral singleton"] + DMA_CH1 <= virtual() (unstable)), (@ peri_type #[doc = + "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable)), (@ peri_type + #[doc = "BT peripheral singleton"] BT <= virtual(LP_TIMER : { + bind_lp_timer_interrupt, enable_lp_timer_interrupt, disable_lp_timer_interrupt }, + BT_MAC : { bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) + (unstable)), (@ peri_type #[doc = "FLASH peripheral singleton"] FLASH <= + virtual() (unstable)), (@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "LP_CORE peripheral singleton"] LP_CORE <= virtual() + (unstable)), (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] + SW_INTERRUPT <= virtual() (unstable)), (@ peri_type #[doc = + "WIFI peripheral singleton"] WIFI <= virtual(WIFI_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, WIFI_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, + enable_pwr_interrupt, disable_pwr_interrupt })), (@ peri_type #[doc = + "MEM2MEM0 peripheral singleton"] MEM2MEM0 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM1 peripheral singleton"] MEM2MEM1 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM2 peripheral singleton"] MEM2MEM2 <= virtual() + (unstable)), (@ peri_type #[doc = "MEM2MEM3 peripheral singleton"] MEM2MEM3 <= + virtual() (unstable)), (@ peri_type #[doc = "MEM2MEM4 peripheral singleton"] + MEM2MEM4 <= virtual() (unstable)), (@ peri_type #[doc = + "MEM2MEM5 peripheral singleton"] MEM2MEM5 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM6 peripheral singleton"] MEM2MEM6 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM7 peripheral singleton"] MEM2MEM7 <= virtual() + (unstable)), (@ peri_type #[doc = "MEM2MEM8 peripheral singleton"] MEM2MEM8 <= + virtual() (unstable)))); _for_each_inner_peripheral!((singletons(GPIO0), (GPIO1), + (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), (GPIO10), + (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO23), (GPIO24), (GPIO25), (GPIO26), + (GPIO27), (GPIO28), (AES(unstable)), (ASSIST_DEBUG(unstable)), + (APB_SARADC(unstable)), (CLINT(unstable)), (DMA(unstable)), (DS(unstable)), + (ECC(unstable)), (ECDSA(unstable)), (ETM(unstable)), (GPIO(unstable)), + (GPIO_SD(unstable)), (HMAC(unstable)), (HP_APM(unstable)), (HP_SYS(unstable)), + (HUK(unstable)), (I2C_ANA_MST(unstable)), (I2C0), (I2S0(unstable)), + (IEEE802154(unstable)), (INTERRUPT_CORE0(unstable)), (INTPRI(unstable)), + (IO_MUX(unstable)), (KEYMNG(unstable)), (LP_ANA(unstable)), (LP_AON(unstable)), + (LP_APM0(unstable)), (LP_CLKRST(unstable)), (LP_I2C_ANA_MST(unstable)), + (LP_IO_MUX(unstable)), (LP_PERI(unstable)), (LP_TEE(unstable)), + (LP_TIMER(unstable)), (LP_UART(unstable)), (LP_WDT(unstable)), (LPWR(unstable)), + (MCPWM0(unstable)), (MEM_MONITOR(unstable)), (MODEM_LPCON(unstable)), + (MODEM_SYSCON(unstable)), (PARL_IO(unstable)), (PAU(unstable)), (PCNT(unstable)), + (PCR(unstable)), (PMU(unstable)), (PVT_MONITOR(unstable)), (RMT(unstable)), + (RNG(unstable)), (RSA(unstable)), (SHA(unstable)), (SLC(unstable)), (SPI2), + (SYSTEM(unstable)), (SYSTIMER(unstable)), (TEE(unstable)), (TIMG0(unstable)), + (TIMG1(unstable)), (UART0), (UART1), (UHCI0(unstable)), (USB_DEVICE(unstable)), + (DMA_CH0(unstable)), (DMA_CH1(unstable)), (DMA_CH2(unstable)), (BT(unstable)), + (FLASH(unstable)), (GPIO_DEDICATED(unstable)), (LP_CORE(unstable)), + (SW_INTERRUPT(unstable)), (WIFI), (MEM2MEM0(unstable)), (MEM2MEM1(unstable)), + (MEM2MEM2(unstable)), (MEM2MEM3(unstable)), (MEM2MEM4(unstable)), + (MEM2MEM5(unstable)), (MEM2MEM6(unstable)), (MEM2MEM7(unstable)), + (MEM2MEM8(unstable)))); _for_each_inner_peripheral!((dma_eligible(MEM2MEM0, + Mem2mem0, 0), (SPI2, Spi2, 1), (UHCI0, Uhci0, 2), (I2S0, I2s0, 3), (MEM2MEM1, + Mem2mem1, 4), (MEM2MEM2, Mem2mem2, 5), (AES, Aes, 6), (SHA, Sha, 7), (APB_SARADC, + ApbSaradc, 8), (PARL_IO, ParlIo, 9), (MEM2MEM3, Mem2mem3, 10), (MEM2MEM4, + Mem2mem4, 11), (MEM2MEM5, Mem2mem5, 12), (MEM2MEM6, Mem2mem6, 13), (MEM2MEM7, + Mem2mem7, 14), (MEM2MEM8, Mem2mem8, 15))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2(_0 => MTMS _2 => FSPIQ) (_2 => FSPIQ) ([Input] + [Output]))); _for_each_inner_gpio!((3, GPIO3(_0 => MTDI) () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4(_0 => MTCK _2 => FSPIHD) (_2 => FSPIHD) ([Input] + [Output]))); _for_each_inner_gpio!((5, GPIO5(_0 => MTDO _2 => FSPIWP) (_2 => + FSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((6, GPIO6(_2 => FSPICLK) (_2 + => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((7, GPIO7(_0 => + SDIO_DATA1 _2 => FSPID) (_2 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8(_0 => SDIO_DATA0) () ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9(_0 => SDIO_CLK) () ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10(_0 => SDIO_CMD _2 => FSPICS0) (_2 => FSPICS0) + ([Input] [Output]))); _for_each_inner_gpio!((11, GPIO11() (_0 => U0TXD) ([Input] + [Output]))); _for_each_inner_gpio!((12, GPIO12(_0 => U0RXD) () ([Input] + [Output]))); _for_each_inner_gpio!((13, GPIO13(_0 => SDIO_DATA3) () ([Input] + [Output]))); _for_each_inner_gpio!((14, GPIO14(_0 => SDIO_DATA2) () ([Input] + [Output]))); _for_each_inner_gpio!((23, GPIO23() () ([Input] [Output]))); + _for_each_inner_gpio!((24, GPIO24() () ([Input] [Output]))); + _for_each_inner_gpio!((25, GPIO25() () ([Input] [Output]))); + _for_each_inner_gpio!((26, GPIO26() () ([Input] [Output]))); + _for_each_inner_gpio!((27, GPIO27() () ([Input] [Output]))); + _for_each_inner_gpio!((28, GPIO28() () ([Input] [Output]))); + _for_each_inner_gpio!((all(0, GPIO0() () ([Input] [Output])), (1, GPIO1() () + ([Input] [Output])), (2, GPIO2(_0 => MTMS _2 => FSPIQ) (_2 => FSPIQ) ([Input] + [Output])), (3, GPIO3(_0 => MTDI) () ([Input] [Output])), (4, GPIO4(_0 => MTCK _2 + => FSPIHD) (_2 => FSPIHD) ([Input] [Output])), (5, GPIO5(_0 => MTDO _2 => FSPIWP) + (_2 => FSPIWP) ([Input] [Output])), (6, GPIO6(_2 => FSPICLK) (_2 => FSPICLK) + ([Input] [Output])), (7, GPIO7(_0 => SDIO_DATA1 _2 => FSPID) (_2 => FSPID) + ([Input] [Output])), (8, GPIO8(_0 => SDIO_DATA0) () ([Input] [Output])), (9, + GPIO9(_0 => SDIO_CLK) () ([Input] [Output])), (10, GPIO10(_0 => SDIO_CMD _2 => + FSPICS0) (_2 => FSPICS0) ([Input] [Output])), (11, GPIO11() (_0 => U0TXD) + ([Input] [Output])), (12, GPIO12(_0 => U0RXD) () ([Input] [Output])), (13, + GPIO13(_0 => SDIO_DATA3) () ([Input] [Output])), (14, GPIO14(_0 => SDIO_DATA2) () + ([Input] [Output])), (23, GPIO23() () ([Input] [Output])), (24, GPIO24() () + ([Input] [Output])), (25, GPIO25() () ([Input] [Output])), (26, GPIO26() () + ([Input] [Output])), (27, GPIO27() () ([Input] [Output])), (28, GPIO28() () + ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((XTAL_32K_P, GPIO0)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH0, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO4)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO5)); + _for_each_inner_analog_function!((ADC1_CH5, GPIO6)); + _for_each_inner_analog_function!((ZCD0, GPIO8)); + _for_each_inner_analog_function!((ZCD1, GPIO9)); + _for_each_inner_analog_function!((USB_DM, GPIO13)); + _for_each_inner_analog_function!((USB_DP, GPIO14)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5)); + _for_each_inner_analog_function!(((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6)); + _for_each_inner_analog_function!(((ZCD0, ZCDn, 0), GPIO8)); + _for_each_inner_analog_function!(((ZCD1, ZCDn, 1), GPIO9)); + _for_each_inner_analog_function!((all(XTAL_32K_P, GPIO0), (XTAL_32K_N, GPIO1), + (ADC1_CH0, GPIO1), (ADC1_CH1, GPIO2), (ADC1_CH2, GPIO3), (ADC1_CH3, GPIO4), + (ADC1_CH4, GPIO5), (ADC1_CH5, GPIO6), (ZCD0, GPIO8), (ZCD1, GPIO9), (USB_DM, + GPIO13), (USB_DP, GPIO14))); + _for_each_inner_analog_function!((all_expanded((ADC1_CH0, ADCn_CHm, 1, 0), + GPIO1), ((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2), ((ADC1_CH2, ADCn_CHm, 1, 2), GPIO3), + ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5), + ((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6), ((ZCD0, ZCDn, 0), GPIO8), ((ZCD1, ZCDn, 1), + GPIO9))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((LP_UART_DTRN, GPIO0)); + _for_each_inner_lp_function!((LP_GPIO0, GPIO0)); + _for_each_inner_lp_function!((LP_UART_DSRN, GPIO1)); + _for_each_inner_lp_function!((LP_GPIO1, GPIO1)); + _for_each_inner_lp_function!((LP_UART_RTSN, GPIO2)); + _for_each_inner_lp_function!((LP_GPIO2, GPIO2)); + _for_each_inner_lp_function!((LP_I2C_SDA, GPIO2)); + _for_each_inner_lp_function!((LP_UART_CTSN, GPIO3)); + _for_each_inner_lp_function!((LP_GPIO3, GPIO3)); + _for_each_inner_lp_function!((LP_I2C_SCL, GPIO3)); + _for_each_inner_lp_function!((LP_UART_RXD_PAD, GPIO4)); + _for_each_inner_lp_function!((LP_GPIO4, GPIO4)); + _for_each_inner_lp_function!((LP_UART_TXD_PAD, GPIO5)); + _for_each_inner_lp_function!((LP_GPIO5, GPIO5)); + _for_each_inner_lp_function!((LP_GPIO6, GPIO6)); + _for_each_inner_lp_function!(((LP_GPIO0, LP_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((LP_GPIO1, LP_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((LP_GPIO2, LP_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((LP_GPIO3, LP_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((LP_GPIO4, LP_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((LP_GPIO5, LP_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!(((LP_GPIO6, LP_GPIOn, 6), GPIO6)); + _for_each_inner_lp_function!((all(LP_UART_DTRN, GPIO0), (LP_GPIO0, GPIO0), + (LP_UART_DSRN, GPIO1), (LP_GPIO1, GPIO1), (LP_UART_RTSN, GPIO2), (LP_GPIO2, + GPIO2), (LP_I2C_SDA, GPIO2), (LP_UART_CTSN, GPIO3), (LP_GPIO3, GPIO3), + (LP_I2C_SCL, GPIO3), (LP_UART_RXD_PAD, GPIO4), (LP_GPIO4, GPIO4), + (LP_UART_TXD_PAD, GPIO5), (LP_GPIO5, GPIO5), (LP_GPIO6, GPIO6))); + _for_each_inner_lp_function!((all_expanded((LP_GPIO0, LP_GPIOn, 0), GPIO0), + ((LP_GPIO1, LP_GPIOn, 1), GPIO1), ((LP_GPIO2, LP_GPIOn, 2), GPIO2), ((LP_GPIO3, + LP_GPIOn, 3), GPIO3), ((LP_GPIO4, LP_GPIOn, 4), GPIO4), ((LP_GPIO5, LP_GPIOn, 5), + GPIO5), ((LP_GPIO6, LP_GPIOn, 6), GPIO6))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + U0RXD = 6, + U0CTS = 7, + U0DSR = 8, + U1RXD = 9, + U1CTS = 10, + U1DSR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SI_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + CPU_GPIO_0 = 27, + CPU_GPIO_1 = 28, + CPU_GPIO_2 = 29, + CPU_GPIO_3 = 30, + CPU_GPIO_4 = 31, + CPU_GPIO_5 = 32, + CPU_GPIO_6 = 33, + CPU_GPIO_7 = 34, + USB_JTAG_TDO_BRIDGE = 35, + I2CEXT0_SCL = 46, + I2CEXT0_SDA = 47, + PARL_RX_DATA0 = 48, + PARL_RX_DATA1 = 49, + PARL_RX_DATA2 = 50, + PARL_RX_DATA3 = 51, + PARL_RX_DATA4 = 52, + PARL_RX_DATA5 = 53, + PARL_RX_DATA6 = 54, + PARL_RX_DATA7 = 55, + FSPICLK = 56, + FSPIQ = 57, + FSPID = 58, + FSPIHD = 59, + FSPIWP = 60, + FSPICS0 = 61, + PARL_RX_CLK = 62, + PARL_TX_CLK = 63, + RMT_SIG_0 = 64, + RMT_SIG_1 = 65, + TWAI0_RX = 66, + TWAI1_RX = 70, + PCNT0_RST = 76, + PCNT1_RST = 77, + PCNT2_RST = 78, + PCNT3_RST = 79, + PWM0_SYNC0 = 80, + PWM0_SYNC1 = 81, + PWM0_SYNC2 = 82, + PWM0_F0 = 83, + PWM0_F1 = 84, + PWM0_F2 = 85, + PWM0_CAP0 = 86, + PWM0_CAP1 = 87, + PWM0_CAP2 = 88, + SIG_IN_FUNC97 = 97, + SIG_IN_FUNC98 = 98, + SIG_IN_FUNC99 = 99, + SIG_IN_FUNC100 = 100, + PCNT0_SIG_CH0 = 101, + PCNT0_SIG_CH1 = 102, + PCNT0_CTRL_CH0 = 103, + PCNT0_CTRL_CH1 = 104, + PCNT1_SIG_CH0 = 105, + PCNT1_SIG_CH1 = 106, + PCNT1_CTRL_CH0 = 107, + PCNT1_CTRL_CH1 = 108, + PCNT2_SIG_CH0 = 109, + PCNT2_SIG_CH1 = 110, + PCNT2_CTRL_CH0 = 111, + PCNT2_CTRL_CH1 = 112, + PCNT3_SIG_CH0 = 113, + PCNT3_SIG_CH1 = 114, + PCNT3_CTRL_CH0 = 115, + PCNT3_CTRL_CH1 = 116, + SDIO_CLK, + SDIO_CMD, + SDIO_DATA0, + SDIO_DATA1, + SDIO_DATA2, + SDIO_DATA3, + MTDI, + MTDO, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + LEDC_LS_SIG0 = 0, + LEDC_LS_SIG1 = 1, + LEDC_LS_SIG2 = 2, + LEDC_LS_SIG3 = 3, + LEDC_LS_SIG4 = 4, + LEDC_LS_SIG5 = 5, + U0TXD = 6, + U0RTS = 7, + U0DTR = 8, + U1TXD = 9, + U1RTS = 10, + U1DTR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SO_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + I2SO_SD1 = 18, + CPU_GPIO_0 = 27, + CPU_GPIO_1 = 28, + CPU_GPIO_2 = 29, + CPU_GPIO_3 = 30, + CPU_GPIO_4 = 31, + CPU_GPIO_5 = 32, + CPU_GPIO_6 = 33, + CPU_GPIO_7 = 34, + I2CEXT0_SCL = 46, + I2CEXT0_SDA = 47, + PARL_TX_DATA0 = 48, + PARL_TX_DATA1 = 49, + PARL_TX_DATA2 = 50, + PARL_TX_DATA3 = 51, + PARL_TX_DATA4 = 52, + PARL_TX_DATA5 = 53, + PARL_TX_DATA6 = 54, + PARL_TX_DATA7 = 55, + FSPICLK = 56, + FSPIQ = 57, + FSPID = 58, + FSPIHD = 59, + FSPIWP = 60, + FSPICS0 = 61, + PARL_RX_CLK = 62, + PARL_TX_CLK = 63, + RMT_SIG_0 = 64, + RMT_SIG_1 = 65, + TWAI0_TX = 66, + TWAI0_BUS_OFF_ON = 67, + TWAI0_CLKOUT = 68, + TWAI0_STANDBY = 69, + TWAI1_TX = 70, + TWAI1_BUS_OFF_ON = 71, + TWAI1_CLKOUT = 72, + TWAI1_STANDBY = 73, + GPIO_SD0 = 76, + GPIO_SD1 = 77, + GPIO_SD2 = 78, + GPIO_SD3 = 79, + PWM0_0A = 80, + PWM0_0B = 81, + PWM0_1A = 82, + PWM0_1B = 83, + PWM0_2A = 84, + PWM0_2B = 85, + PARL_TX_CS = 86, + SIG_IN_FUNC97 = 97, + SIG_IN_FUNC98 = 98, + SIG_IN_FUNC99 = 99, + SIG_IN_FUNC100 = 100, + FSPICS1 = 101, + FSPICS2 = 102, + FSPICS3 = 103, + FSPICS4 = 104, + FSPICS5 = 105, + GPIO = 256, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs new file mode 100644 index 00000000000..e1a5f5fd4fe --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -0,0 +1,5165 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32c6" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-C6" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32c6" + }; + ("arch") => { + "riscv" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + false + }; + ("assist_debug.has_sp_monitor") => { + true + }; + ("assist_debug.has_region_monitor") => { + true + }; + ("bt.controller") => { + "npl" + }; + ("dedicated_gpio.needs_initialization") => { + false + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + true + }; + ("dma.max_priority") => { + 9 + }; + ("dma.max_priority", str) => { + stringify!(9) + }; + ("dma.gdma_version") => { + 1 + }; + ("dma.gdma_version", str) => { + stringify!(1) + }; + ("ecc.zero_extend_writes") => { + true + }; + ("ecc.separate_jacobian_point_memory") => { + false + }; + ("ecc.has_memory_clock_gate") => { + true + }; + ("ecc.supports_enhanced_security") => { + false + }; + ("ecc.mem_block_size") => { + 32 + }; + ("gpio.has_bank_1") => { + false + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 60 + }; + ("gpio.constant_0_input", str) => { + stringify!(60) + }; + ("gpio.constant_1_input") => { + 56 + }; + ("gpio.constant_1_input", str) => { + stringify!(56) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 124 + }; + ("gpio.input_signal_max", str) => { + stringify!(124) + }; + ("gpio.output_signal_max") => { + 128 + }; + ("gpio.output_signal_max", str) => { + stringify!(128) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + true + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + true + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + true + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 3 + }; + ("interrupts.status_registers", str) => { + stringify!(3) + }; + ("interrupts.disabled_interrupt") => { + 31 + }; + ("lp_i2c_master.fifo_size") => { + 16 + }; + ("lp_i2c_master.fifo_size", str) => { + stringify!(16) + }; + ("lp_uart.ram_size") => { + 32 + }; + ("lp_uart.ram_size", str) => { + stringify!(32) + }; + ("parl_io.version") => { + 1 + }; + ("parl_io.version", str) => { + stringify!(1) + }; + ("phy.combo_module") => { + true + }; + ("rmt.ram_start") => { + 1610638336 + }; + ("rmt.ram_start", str) => { + stringify!(1610638336) + }; + ("rmt.channel_ram_size") => { + 48 + }; + ("rmt.channel_ram_size", str) => { + stringify!(48) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + true + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + true + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + false + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 384 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(384) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + true + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.cpu_csr_prv_mode") => { + 3088 + }; + ("soc.cpu_csr_prv_mode", str) => { + stringify!(3088) + }; + ("soc.rc_fast_clk_default") => { + 17500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(17500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + false + }; + ("timergroup.timg_has_divcnt_rst") => { + true + }; + ("timergroup.rc_fast_calibration_divider_min_rev") => { + 1 + }; + ("timergroup.rc_fast_calibration_divider") => { + 32 + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + true + }; + ("uhci.combined_uart_selector_field") => { + false + }; + ("wifi.has_wifi6") => { + true + }; + ("wifi.mac_version") => { + 2 + }; + ("wifi.mac_version", str) => { + stringify!(2) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((256)); _for_each_inner_aes_key_length!((128, 0, + 4)); _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + CPU_GPIO_0)); _for_each_inner_dedicated_gpio!((0, 1, CPU_GPIO_1)); + _for_each_inner_dedicated_gpio!((0, 2, CPU_GPIO_2)); + _for_each_inner_dedicated_gpio!((0, 3, CPU_GPIO_3)); + _for_each_inner_dedicated_gpio!((0, 4, CPU_GPIO_4)); + _for_each_inner_dedicated_gpio!((0, 5, CPU_GPIO_5)); + _for_each_inner_dedicated_gpio!((0, 6, CPU_GPIO_6)); + _for_each_inner_dedicated_gpio!((0, 7, CPU_GPIO_7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, CPU_GPIO_0), (0, 1, + CPU_GPIO_1), (0, 2, CPU_GPIO_2), (0, 3, CPU_GPIO_3), (0, 4, CPU_GPIO_4), (0, 5, + CPU_GPIO_5), (0, 6, CPU_GPIO_6), (0, 7, CPU_GPIO_7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_working_mode { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_working_mode { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_ecc_working_mode!((0, AffinePointMultiplication)); + _for_each_inner_ecc_working_mode!((2, AffinePointVerification)); + _for_each_inner_ecc_working_mode!((3, AffinePointVerificationAndMultiplication)); + _for_each_inner_ecc_working_mode!((4, JacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((6, JacobianPointVerification)); + _for_each_inner_ecc_working_mode!((7, + AffinePointVerificationAndJacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((all(0, AffinePointMultiplication), (2, + AffinePointVerification), (3, AffinePointVerificationAndMultiplication), (4, + JacobianPointMultiplication), (6, JacobianPointVerification), (7, + AffinePointVerificationAndJacobianPointMultiplication))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_curve { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_curve { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_ecc_curve!((0, P192, 192)); + _for_each_inner_ecc_curve!((1, P256, 256)); _for_each_inner_ecc_curve!((all(0, + P192, 192), (1, P256, 256))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_interrupt!(([reserved 0] 0)); + _for_each_inner_interrupt!(([direct_bindable 0] 1)); + _for_each_inner_interrupt!(([direct_bindable 1] 2)); + _for_each_inner_interrupt!(([reserved 1] 3)); + _for_each_inner_interrupt!(([reserved 2] 4)); + _for_each_inner_interrupt!(([direct_bindable 2] 5)); + _for_each_inner_interrupt!(([direct_bindable 3] 6)); + _for_each_inner_interrupt!(([reserved 3] 7)); + _for_each_inner_interrupt!(([direct_bindable 4] 8)); + _for_each_inner_interrupt!(([direct_bindable 5] 9)); + _for_each_inner_interrupt!(([direct_bindable 6] 10)); + _for_each_inner_interrupt!(([direct_bindable 7] 11)); + _for_each_inner_interrupt!(([direct_bindable 8] 12)); + _for_each_inner_interrupt!(([direct_bindable 9] 13)); + _for_each_inner_interrupt!(([direct_bindable 10] 14)); + _for_each_inner_interrupt!(([direct_bindable 11] 15)); + _for_each_inner_interrupt!(([vector 0] 16)); _for_each_inner_interrupt!(([vector + 1] 17)); _for_each_inner_interrupt!(([vector 2] 18)); + _for_each_inner_interrupt!(([vector 3] 19)); _for_each_inner_interrupt!(([vector + 4] 20)); _for_each_inner_interrupt!(([vector 5] 21)); + _for_each_inner_interrupt!(([vector 6] 22)); _for_each_inner_interrupt!(([vector + 7] 23)); _for_each_inner_interrupt!(([vector 8] 24)); + _for_each_inner_interrupt!(([vector 9] 25)); _for_each_inner_interrupt!(([vector + 10] 26)); _for_each_inner_interrupt!(([vector 11] 27)); + _for_each_inner_interrupt!(([vector 12] 28)); _for_each_inner_interrupt!(([vector + 13] 29)); _for_each_inner_interrupt!(([vector 14] 30)); + _for_each_inner_interrupt!(([disabled 0] 31)); + _for_each_inner_interrupt!((all([reserved 0] 0), ([direct_bindable 0] 1), + ([direct_bindable 1] 2), ([reserved 1] 3), ([reserved 2] 4), ([direct_bindable 2] + 5), ([direct_bindable 3] 6), ([reserved 3] 7), ([direct_bindable 4] 8), + ([direct_bindable 5] 9), ([direct_bindable 6] 10), ([direct_bindable 7] 11), + ([direct_bindable 8] 12), ([direct_bindable 9] 13), ([direct_bindable 10] 14), + ([direct_bindable 11] 15), ([vector 0] 16), ([vector 1] 17), ([vector 2] 18), + ([vector 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] 22), ([vector 7] + 23), ([vector 8] 24), ([vector 9] 25), ([vector 10] 26), ([vector 11] 27), + ([vector 12] 28), ([vector 13] 29), ([vector 14] 30), ([disabled 0] 31))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_classified_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_classified_interrupt { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_classified_interrupt!(([direct_bindable 0] + 1)); _for_each_inner_classified_interrupt!(([direct_bindable 1] 2)); + _for_each_inner_classified_interrupt!(([direct_bindable 2] 5)); + _for_each_inner_classified_interrupt!(([direct_bindable 3] 6)); + _for_each_inner_classified_interrupt!(([direct_bindable 4] 8)); + _for_each_inner_classified_interrupt!(([direct_bindable 5] 9)); + _for_each_inner_classified_interrupt!(([direct_bindable 6] 10)); + _for_each_inner_classified_interrupt!(([direct_bindable 7] 11)); + _for_each_inner_classified_interrupt!(([direct_bindable 8] 12)); + _for_each_inner_classified_interrupt!(([direct_bindable 9] 13)); + _for_each_inner_classified_interrupt!(([direct_bindable 10] 14)); + _for_each_inner_classified_interrupt!(([direct_bindable 11] 15)); + _for_each_inner_classified_interrupt!(([vector 0] 16)); + _for_each_inner_classified_interrupt!(([vector 1] 17)); + _for_each_inner_classified_interrupt!(([vector 2] 18)); + _for_each_inner_classified_interrupt!(([vector 3] 19)); + _for_each_inner_classified_interrupt!(([vector 4] 20)); + _for_each_inner_classified_interrupt!(([vector 5] 21)); + _for_each_inner_classified_interrupt!(([vector 6] 22)); + _for_each_inner_classified_interrupt!(([vector 7] 23)); + _for_each_inner_classified_interrupt!(([vector 8] 24)); + _for_each_inner_classified_interrupt!(([vector 9] 25)); + _for_each_inner_classified_interrupt!(([vector 10] 26)); + _for_each_inner_classified_interrupt!(([vector 11] 27)); + _for_each_inner_classified_interrupt!(([vector 12] 28)); + _for_each_inner_classified_interrupt!(([vector 13] 29)); + _for_each_inner_classified_interrupt!(([vector 14] 30)); + _for_each_inner_classified_interrupt!(([reserved 0] 0)); + _for_each_inner_classified_interrupt!(([reserved 1] 3)); + _for_each_inner_classified_interrupt!(([reserved 2] 4)); + _for_each_inner_classified_interrupt!(([reserved 3] 7)); + _for_each_inner_classified_interrupt!((direct_bindable([direct_bindable 0] 1), + ([direct_bindable 1] 2), ([direct_bindable 2] 5), ([direct_bindable 3] 6), + ([direct_bindable 4] 8), ([direct_bindable 5] 9), ([direct_bindable 6] 10), + ([direct_bindable 7] 11), ([direct_bindable 8] 12), ([direct_bindable 9] 13), + ([direct_bindable 10] 14), ([direct_bindable 11] 15))); + _for_each_inner_classified_interrupt!((vector([vector 0] 16), ([vector 1] 17), + ([vector 2] 18), ([vector 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] + 22), ([vector 7] 23), ([vector 8] 24), ([vector 9] 25), ([vector 10] 26), + ([vector 11] 27), ([vector 12] 28), ([vector 13] 29), ([vector 14] 30))); + _for_each_inner_classified_interrupt!((reserved([reserved 0] 0), ([reserved 1] + 3), ([reserved 2] 4), ([reserved 3] 7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt_priority { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt_priority { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_interrupt_priority!((0, 1, Priority1)); + _for_each_inner_interrupt_priority!((1, 2, Priority2)); + _for_each_inner_interrupt_priority!((2, 3, Priority3)); + _for_each_inner_interrupt_priority!((3, 4, Priority4)); + _for_each_inner_interrupt_priority!((4, 5, Priority5)); + _for_each_inner_interrupt_priority!((5, 6, Priority6)); + _for_each_inner_interrupt_priority!((6, 7, Priority7)); + _for_each_inner_interrupt_priority!((7, 8, Priority8)); + _for_each_inner_interrupt_priority!((8, 9, Priority9)); + _for_each_inner_interrupt_priority!((9, 10, Priority10)); + _for_each_inner_interrupt_priority!((10, 11, Priority11)); + _for_each_inner_interrupt_priority!((11, 12, Priority12)); + _for_each_inner_interrupt_priority!((12, 13, Priority13)); + _for_each_inner_interrupt_priority!((13, 14, Priority14)); + _for_each_inner_interrupt_priority!((14, 15, Priority15)); + _for_each_inner_interrupt_priority!((all(0, 1, Priority1), (1, 2, Priority2), (2, + 3, Priority3), (3, 4, Priority4), (4, 5, Priority5), (5, 6, Priority6), (6, 7, + Priority7), (7, 8, Priority8), (8, 9, Priority9), (9, 10, Priority10), (10, 11, + Priority11), (11, 12, Priority12), (12, 13, Priority13), (13, 14, Priority14), + (14, 15, Priority15))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe { + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + } + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 0)); _for_each_inner_rmt_channel!((3, 1)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1))); + _for_each_inner_rmt_channel!((rx(2, 0), (3, 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((Pll80MHz, 1)); + _for_each_inner_rmt_clock_source!((RcFast, 2)); + _for_each_inner_rmt_clock_source!((Xtal, 3)); + _for_each_inner_rmt_clock_source!((Pll80MHz)); + _for_each_inner_rmt_clock_source!((all(Pll80MHz, 1), (RcFast, 2), (Xtal, 3))); + _for_each_inner_rmt_clock_source!((default(Pll80MHz))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // OSC_SLOW_CLK +/// +/// fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // HP_ROOT_CLK +/// +/// fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_hp_root_clk_impl(_clocks: &mut ClockTree, _new_config: HpRootClkConfig) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // AHB_CLK +/// +/// fn configure_ahb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: AhbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // MSPI_FAST_CLK +/// +/// fn enable_mspi_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mspi_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: MspiFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // SOC_ROOT_CLK +/// +/// fn enable_soc_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_soc_root_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SocRootClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_HS_DIV +/// +/// fn enable_cpu_hs_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_hs_div_impl(_clocks: &mut ClockTree, _new_config: CpuHsDivConfig) { +/// todo!() +/// } +/// +/// // CPU_LS_DIV +/// +/// fn enable_cpu_ls_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_ls_div_impl(_clocks: &mut ClockTree, _new_config: CpuLsDivConfig) { +/// todo!() +/// } +/// +/// // AHB_HS_DIV +/// +/// fn enable_ahb_hs_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ahb_hs_div_impl(_clocks: &mut ClockTree, _new_config: AhbHsDivConfig) { +/// todo!() +/// } +/// +/// // AHB_LS_DIV +/// +/// fn enable_ahb_ls_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ahb_ls_div_impl(_clocks: &mut ClockTree, _new_config: AhbLsDivConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl(_clocks: &mut ClockTree, _new_config: ApbClkConfig) { +/// todo!() +/// } +/// +/// // MSPI_FAST_HS_CLK +/// +/// fn enable_mspi_fast_hs_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mspi_fast_hs_clk_impl(_clocks: &mut ClockTree, _new_config: MspiFastHsClkConfig) { +/// todo!() +/// } +/// +/// // MSPI_FAST_LS_CLK +/// +/// fn enable_mspi_fast_ls_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mspi_fast_ls_clk_impl(_clocks: &mut ClockTree, _new_config: MspiFastLsClkConfig) { +/// todo!() +/// } +/// +/// // PLL_F48M +/// +/// fn enable_pll_f48m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F80M +/// +/// fn enable_pll_f80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F160M +/// +/// fn enable_pll_f160m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F240M +/// +/// fn enable_pll_f240m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // LEDC_SCLK +/// +/// fn enable_ledc_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ledc_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LedcSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // XTAL_D2_CLK +/// +/// fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // LP_FAST_CLK +/// +/// fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LP_SLOW_CLK +/// +/// fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // MCPWM0_FUNCTION_CLOCK +/// +/// fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_RX_CLOCK +/// +/// fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_rx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioRxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_TX_CLOCK +/// +/// fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_tx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioTxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // RMT_SCLK +/// +/// fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rmt_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RmtSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_WDT_CLOCK +/// +/// fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_WDT_CLOCK +/// +/// fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_40 => 40000000, + } + } + } + /// Configures the `HP_ROOT_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = SOC_ROOT_CLK / divisor`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum HpRootClkConfig { + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 3`. + _3 = 3, + } + impl HpRootClkConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 1 => HpRootClkConfig::_1, + 3 => HpRootClkConfig::_3, + _ => ::core::panic!("Invalid HP_ROOT_CLK divisor value"), + } + } + } + impl HpRootClkConfig { + fn divisor(self) -> u32 { + match self { + HpRootClkConfig::_1 => 1, + HpRootClkConfig::_3 => 3, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `CPU_HS_DIV`. + Hs, + /// Selects `CPU_LS_DIV`. + Ls, + } + /// The list of clock signals that the `AHB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AhbClkConfig { + /// Selects `AHB_HS_DIV`. + Hs, + /// Selects `AHB_LS_DIV`. + Ls, + } + /// The list of clock signals that the `MSPI_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MspiFastClkConfig { + /// Selects `MSPI_FAST_HS_CLK`. + Hs, + /// Selects `MSPI_FAST_LS_CLK`. + Ls, + } + /// The list of clock signals that the `SOC_ROOT_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SocRootClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `PLL_CLK`. + Pll, + } + /// Configures the `CPU_HS_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuHsDivConfig { + /// Selects `divisor = 0`. + _0 = 0, + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 3`. + _3 = 3, + } + impl CpuHsDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 0 => CpuHsDivConfig::_0, + 1 => CpuHsDivConfig::_1, + 3 => CpuHsDivConfig::_3, + _ => ::core::panic!("Invalid CPU_HS_DIV divisor value"), + } + } + } + impl CpuHsDivConfig { + fn divisor(self) -> u32 { + match self { + CpuHsDivConfig::_0 => 0, + CpuHsDivConfig::_1 => 1, + CpuHsDivConfig::_3 => 3, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `CPU_LS_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuLsDivConfig { + /// Selects `divisor = 0`. + _0 = 0, + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 3`. + _3 = 3, + /// Selects `divisor = 7`. + _7 = 7, + /// Selects `divisor = 15`. + _15 = 15, + /// Selects `divisor = 31`. + _31 = 31, + } + impl CpuLsDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 0 => CpuLsDivConfig::_0, + 1 => CpuLsDivConfig::_1, + 3 => CpuLsDivConfig::_3, + 7 => CpuLsDivConfig::_7, + 15 => CpuLsDivConfig::_15, + 31 => CpuLsDivConfig::_31, + _ => ::core::panic!("Invalid CPU_LS_DIV divisor value"), + } + } + } + impl CpuLsDivConfig { + fn divisor(self) -> u32 { + match self { + CpuLsDivConfig::_0 => 0, + CpuLsDivConfig::_1 => 1, + CpuLsDivConfig::_3 => 3, + CpuLsDivConfig::_7 => 7, + CpuLsDivConfig::_15 => 15, + CpuLsDivConfig::_31 => 31, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `AHB_HS_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AhbHsDivConfig { + /// Selects `divisor = 3`. + _3 = 3, + /// Selects `divisor = 7`. + _7 = 7, + /// Selects `divisor = 15`. + _15 = 15, + } + impl AhbHsDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 3 => AhbHsDivConfig::_3, + 7 => AhbHsDivConfig::_7, + 15 => AhbHsDivConfig::_15, + _ => ::core::panic!("Invalid AHB_HS_DIV divisor value"), + } + } + } + impl AhbHsDivConfig { + fn divisor(self) -> u32 { + match self { + AhbHsDivConfig::_3 => 3, + AhbHsDivConfig::_7 => 7, + AhbHsDivConfig::_15 => 15, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `AHB_LS_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AhbLsDivConfig { + /// Selects `divisor = 0`. + _0 = 0, + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 3`. + _3 = 3, + /// Selects `divisor = 7`. + _7 = 7, + /// Selects `divisor = 15`. + _15 = 15, + /// Selects `divisor = 31`. + _31 = 31, + } + impl AhbLsDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 0 => AhbLsDivConfig::_0, + 1 => AhbLsDivConfig::_1, + 3 => AhbLsDivConfig::_3, + 7 => AhbLsDivConfig::_7, + 15 => AhbLsDivConfig::_15, + 31 => AhbLsDivConfig::_31, + _ => ::core::panic!("Invalid AHB_LS_DIV divisor value"), + } + } + } + impl AhbLsDivConfig { + fn divisor(self) -> u32 { + match self { + AhbLsDivConfig::_0 => 0, + AhbLsDivConfig::_1 => 1, + AhbLsDivConfig::_3 => 3, + AhbLsDivConfig::_7 => 7, + AhbLsDivConfig::_15 => 15, + AhbLsDivConfig::_31 => 31, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `APB_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = AHB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `divisor = 0`. + _0 = 0, + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 3`. + _3 = 3, + } + impl ApbClkConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 0 => ApbClkConfig::_0, + 1 => ApbClkConfig::_1, + 3 => ApbClkConfig::_3, + _ => ::core::panic!("Invalid APB_CLK divisor value"), + } + } + } + impl ApbClkConfig { + fn divisor(self) -> u32 { + match self { + ApbClkConfig::_0 => 0, + ApbClkConfig::_1 => 1, + ApbClkConfig::_3 => 3, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `MSPI_FAST_HS_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MspiFastHsClkConfig { + /// Selects `divisor = 3`. + _3 = 3, + /// Selects `divisor = 4`. + _4 = 4, + /// Selects `divisor = 5`. + _5 = 5, + } + impl MspiFastHsClkConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 3 => MspiFastHsClkConfig::_3, + 4 => MspiFastHsClkConfig::_4, + 5 => MspiFastHsClkConfig::_5, + _ => ::core::panic!("Invalid MSPI_FAST_HS_CLK divisor value"), + } + } + } + impl MspiFastHsClkConfig { + fn divisor(self) -> u32 { + match self { + MspiFastHsClkConfig::_3 => 3, + MspiFastHsClkConfig::_4 => 4, + MspiFastHsClkConfig::_5 => 5, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `MSPI_FAST_LS_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MspiFastLsClkConfig { + /// Selects `divisor = 0`. + _0 = 0, + /// Selects `divisor = 1`. + _1 = 1, + /// Selects `divisor = 2`. + _2 = 2, + } + impl MspiFastLsClkConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 0 => MspiFastLsClkConfig::_0, + 1 => MspiFastLsClkConfig::_1, + 2 => MspiFastLsClkConfig::_2, + _ => ::core::panic!("Invalid MSPI_FAST_LS_CLK divisor value"), + } + } + } + impl MspiFastLsClkConfig { + fn divisor(self) -> u32 { + match self { + MspiFastLsClkConfig::_0 => 0, + MspiFastLsClkConfig::_1 => 1, + MspiFastLsClkConfig::_2 => 2, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `LEDC_SCLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LedcSclkConfig { + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `LP_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpFastClkConfig { + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_D2_CLK`. + XtalD2Clk, + } + /// The list of clock signals that the `LP_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `OSC_SLOW_CLK`. + OscSlow, + } + /// The list of clock signals that the `MCPWM0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Mcpwm0FunctionClockConfig { + #[default] + /// Selects `PLL_F160M`. + PllF160m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `PARLIO_RX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioRxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F240M`. + PllF240m, + } + /// The list of clock signals that the `PARLIO_TX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioTxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F240M`. + PllF240m, + } + /// The list of clock signals that the `RMT_SCLK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RmtSclkConfig { + #[default] + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `PLL_F80M`. + PllF80m, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + /// Selects `LP_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `TIMG0_WDT_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0WdtClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFast, + #[default] + /// Selects `XTAL_CLK`. + Xtal, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + hp_root_clk: Option, + cpu_clk: Option, + ahb_clk: Option, + mspi_fast_clk: Option, + soc_root_clk: Option, + cpu_hs_div: Option, + cpu_ls_div: Option, + ahb_hs_div: Option, + ahb_ls_div: Option, + apb_clk: Option, + mspi_fast_hs_clk: Option, + mspi_fast_ls_clk: Option, + ledc_sclk: Option, + lp_fast_clk: Option, + lp_slow_clk: Option, + mcpwm0_function_clock: Option, + parlio_rx_clock: Option, + parlio_tx_clock: Option, + rmt_sclk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg0_wdt_clock: Option, + timg1_function_clock: Option, + timg1_calibration_clock: Option, + timg1_wdt_clock: Option, + uart0_function_clock: Option, + uart1_function_clock: Option, + pll_clk_refcount: u32, + rc_fast_clk_refcount: u32, + xtal32k_clk_refcount: u32, + hp_root_clk_refcount: u32, + mspi_fast_clk_refcount: u32, + apb_clk_refcount: u32, + pll_f48m_refcount: u32, + pll_f80m_refcount: u32, + pll_f240m_refcount: u32, + ledc_sclk_refcount: u32, + lp_fast_clk_refcount: u32, + lp_slow_clk_refcount: u32, + mcpwm0_function_clock_refcount: u32, + parlio_rx_clock_refcount: u32, + parlio_tx_clock_refcount: u32, + rmt_sclk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg0_wdt_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + timg1_wdt_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart1_function_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the HP_ROOT_CLK clock tree node + pub fn hp_root_clk(&self) -> Option { + self.hp_root_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the AHB_CLK clock tree node + pub fn ahb_clk(&self) -> Option { + self.ahb_clk + } + /// Returns the current configuration of the MSPI_FAST_CLK clock tree node + pub fn mspi_fast_clk(&self) -> Option { + self.mspi_fast_clk + } + /// Returns the current configuration of the SOC_ROOT_CLK clock tree node + pub fn soc_root_clk(&self) -> Option { + self.soc_root_clk + } + /// Returns the current configuration of the CPU_HS_DIV clock tree node + pub fn cpu_hs_div(&self) -> Option { + self.cpu_hs_div + } + /// Returns the current configuration of the CPU_LS_DIV clock tree node + pub fn cpu_ls_div(&self) -> Option { + self.cpu_ls_div + } + /// Returns the current configuration of the AHB_HS_DIV clock tree node + pub fn ahb_hs_div(&self) -> Option { + self.ahb_hs_div + } + /// Returns the current configuration of the AHB_LS_DIV clock tree node + pub fn ahb_ls_div(&self) -> Option { + self.ahb_ls_div + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the MSPI_FAST_HS_CLK clock tree node + pub fn mspi_fast_hs_clk(&self) -> Option { + self.mspi_fast_hs_clk + } + /// Returns the current configuration of the MSPI_FAST_LS_CLK clock tree node + pub fn mspi_fast_ls_clk(&self) -> Option { + self.mspi_fast_ls_clk + } + /// Returns the current configuration of the LEDC_SCLK clock tree node + pub fn ledc_sclk(&self) -> Option { + self.ledc_sclk + } + /// Returns the current configuration of the LP_FAST_CLK clock tree node + pub fn lp_fast_clk(&self) -> Option { + self.lp_fast_clk + } + /// Returns the current configuration of the LP_SLOW_CLK clock tree node + pub fn lp_slow_clk(&self) -> Option { + self.lp_slow_clk + } + /// Returns the current configuration of the MCPWM0_FUNCTION_CLOCK clock tree node + pub fn mcpwm0_function_clock(&self) -> Option { + self.mcpwm0_function_clock + } + /// Returns the current configuration of the PARLIO_RX_CLOCK clock tree node + pub fn parlio_rx_clock(&self) -> Option { + self.parlio_rx_clock + } + /// Returns the current configuration of the PARLIO_TX_CLOCK clock tree node + pub fn parlio_tx_clock(&self) -> Option { + self.parlio_tx_clock + } + /// Returns the current configuration of the RMT_SCLK clock tree node + pub fn rmt_sclk(&self) -> Option { + self.rmt_sclk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG0_WDT_CLOCK clock tree node + pub fn timg0_wdt_clock(&self) -> Option { + self.timg0_wdt_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the TIMG1_WDT_CLOCK clock tree node + pub fn timg1_wdt_clock(&self) -> Option { + self.timg1_wdt_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + hp_root_clk: None, + cpu_clk: None, + ahb_clk: None, + mspi_fast_clk: None, + soc_root_clk: None, + cpu_hs_div: None, + cpu_ls_div: None, + ahb_hs_div: None, + ahb_ls_div: None, + apb_clk: None, + mspi_fast_hs_clk: None, + mspi_fast_ls_clk: None, + ledc_sclk: None, + lp_fast_clk: None, + lp_slow_clk: None, + mcpwm0_function_clock: None, + parlio_rx_clock: None, + parlio_tx_clock: None, + rmt_sclk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg0_wdt_clock: None, + timg1_function_clock: None, + timg1_calibration_clock: None, + timg1_wdt_clock: None, + uart0_function_clock: None, + uart1_function_clock: None, + pll_clk_refcount: 0, + rc_fast_clk_refcount: 0, + xtal32k_clk_refcount: 0, + hp_root_clk_refcount: 0, + mspi_fast_clk_refcount: 0, + apb_clk_refcount: 0, + pll_f48m_refcount: 0, + pll_f80m_refcount: 0, + pll_f240m_refcount: 0, + ledc_sclk_refcount: 0, + lp_fast_clk_refcount: 0, + lp_slow_clk_refcount: 0, + mcpwm0_function_clock_refcount: 0, + parlio_rx_clock_refcount: 0, + parlio_tx_clock_refcount: 0, + rmt_sclk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg0_wdt_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + timg1_wdt_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart1_function_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + if increment_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + if decrement_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + 480000000 + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 17500000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting OSC_SLOW_CLK"); + trace!("Enabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, true); + } + pub fn release_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing OSC_SLOW_CLK"); + trace!("Disabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, false); + } + pub fn osc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 136000 + } + pub fn configure_hp_root_clk(clocks: &mut ClockTree, config: HpRootClkConfig) { + clocks.hp_root_clk = Some(config); + configure_hp_root_clk_impl(clocks, config); + } + pub fn hp_root_clk_config(clocks: &mut ClockTree) -> Option { + clocks.hp_root_clk + } + pub fn request_hp_root_clk(clocks: &mut ClockTree) { + trace!("Requesting HP_ROOT_CLK"); + if increment_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Enabling HP_ROOT_CLK"); + request_soc_root_clk(clocks); + enable_hp_root_clk_impl(clocks, true); + } + } + pub fn release_hp_root_clk(clocks: &mut ClockTree) { + trace!("Releasing HP_ROOT_CLK"); + if decrement_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Disabling HP_ROOT_CLK"); + enable_hp_root_clk_impl(clocks, false); + release_soc_root_clk(clocks); + } + } + pub fn hp_root_clk_frequency(clocks: &mut ClockTree) -> u32 { + (soc_root_clk_frequency(clocks) / unwrap!(clocks.hp_root_clk).divisor()) + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Hs => request_cpu_hs_div(clocks), + CpuClkConfig::Ls => request_cpu_ls_div(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Hs => release_cpu_hs_div(clocks), + CpuClkConfig::Ls => release_cpu_ls_div(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Hs => cpu_hs_div_frequency(clocks), + CpuClkConfig::Ls => cpu_ls_div_frequency(clocks), + } + } + pub fn configure_ahb_clk(clocks: &mut ClockTree, new_selector: AhbClkConfig) { + let old_selector = clocks.ahb_clk.replace(new_selector); + match new_selector { + AhbClkConfig::Hs => request_ahb_hs_div(clocks), + AhbClkConfig::Ls => request_ahb_ls_div(clocks), + } + configure_ahb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + AhbClkConfig::Hs => release_ahb_hs_div(clocks), + AhbClkConfig::Ls => release_ahb_ls_div(clocks), + } + } + } + pub fn ahb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.ahb_clk + } + fn request_ahb_clk(_clocks: &mut ClockTree) {} + fn release_ahb_clk(_clocks: &mut ClockTree) {} + pub fn ahb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.ahb_clk) { + AhbClkConfig::Hs => ahb_hs_div_frequency(clocks), + AhbClkConfig::Ls => ahb_ls_div_frequency(clocks), + } + } + pub fn configure_mspi_fast_clk(clocks: &mut ClockTree, new_selector: MspiFastClkConfig) { + let old_selector = clocks.mspi_fast_clk.replace(new_selector); + if clocks.mspi_fast_clk_refcount > 0 { + match new_selector { + MspiFastClkConfig::Hs => request_mspi_fast_hs_clk(clocks), + MspiFastClkConfig::Ls => request_mspi_fast_ls_clk(clocks), + } + configure_mspi_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + MspiFastClkConfig::Hs => release_mspi_fast_hs_clk(clocks), + MspiFastClkConfig::Ls => release_mspi_fast_ls_clk(clocks), + } + } + } else { + configure_mspi_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn mspi_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.mspi_fast_clk + } + pub fn request_mspi_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting MSPI_FAST_CLK"); + if increment_reference_count(&mut clocks.mspi_fast_clk_refcount) { + trace!("Enabling MSPI_FAST_CLK"); + match unwrap!(clocks.mspi_fast_clk) { + MspiFastClkConfig::Hs => request_mspi_fast_hs_clk(clocks), + MspiFastClkConfig::Ls => request_mspi_fast_ls_clk(clocks), + } + enable_mspi_fast_clk_impl(clocks, true); + } + } + pub fn release_mspi_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing MSPI_FAST_CLK"); + if decrement_reference_count(&mut clocks.mspi_fast_clk_refcount) { + trace!("Disabling MSPI_FAST_CLK"); + enable_mspi_fast_clk_impl(clocks, false); + match unwrap!(clocks.mspi_fast_clk) { + MspiFastClkConfig::Hs => release_mspi_fast_hs_clk(clocks), + MspiFastClkConfig::Ls => release_mspi_fast_ls_clk(clocks), + } + } + } + pub fn mspi_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.mspi_fast_clk) { + MspiFastClkConfig::Hs => mspi_fast_hs_clk_frequency(clocks), + MspiFastClkConfig::Ls => mspi_fast_ls_clk_frequency(clocks), + } + } + pub fn configure_soc_root_clk(clocks: &mut ClockTree, new_selector: SocRootClkConfig) { + let old_selector = clocks.soc_root_clk.replace(new_selector); + match new_selector { + SocRootClkConfig::Xtal => { + let config_value = HpRootClkConfig::new(1); + configure_hp_root_clk(clocks, config_value); + configure_cpu_clk(clocks, CpuClkConfig::Ls); + configure_ahb_clk(clocks, AhbClkConfig::Ls); + configure_mspi_fast_clk(clocks, MspiFastClkConfig::Ls); + } + SocRootClkConfig::RcFast => { + let config_value = HpRootClkConfig::new(1); + configure_hp_root_clk(clocks, config_value); + configure_cpu_clk(clocks, CpuClkConfig::Ls); + configure_ahb_clk(clocks, AhbClkConfig::Ls); + configure_mspi_fast_clk(clocks, MspiFastClkConfig::Ls); + } + SocRootClkConfig::Pll => { + let config_value = HpRootClkConfig::new(3); + configure_hp_root_clk(clocks, config_value); + configure_cpu_clk(clocks, CpuClkConfig::Hs); + configure_ahb_clk(clocks, AhbClkConfig::Hs); + configure_mspi_fast_clk(clocks, MspiFastClkConfig::Hs); + } + } + match new_selector { + SocRootClkConfig::Xtal => request_xtal_clk(clocks), + SocRootClkConfig::RcFast => request_rc_fast_clk(clocks), + SocRootClkConfig::Pll => request_pll_clk(clocks), + } + configure_soc_root_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SocRootClkConfig::Xtal => release_xtal_clk(clocks), + SocRootClkConfig::RcFast => release_rc_fast_clk(clocks), + SocRootClkConfig::Pll => release_pll_clk(clocks), + } + } + } + pub fn soc_root_clk_config(clocks: &mut ClockTree) -> Option { + clocks.soc_root_clk + } + pub fn request_soc_root_clk(clocks: &mut ClockTree) { + trace!("Requesting SOC_ROOT_CLK"); + trace!("Enabling SOC_ROOT_CLK"); + match unwrap!(clocks.soc_root_clk) { + SocRootClkConfig::Xtal => request_xtal_clk(clocks), + SocRootClkConfig::RcFast => request_rc_fast_clk(clocks), + SocRootClkConfig::Pll => request_pll_clk(clocks), + } + enable_soc_root_clk_impl(clocks, true); + } + pub fn release_soc_root_clk(clocks: &mut ClockTree) { + trace!("Releasing SOC_ROOT_CLK"); + trace!("Disabling SOC_ROOT_CLK"); + enable_soc_root_clk_impl(clocks, false); + match unwrap!(clocks.soc_root_clk) { + SocRootClkConfig::Xtal => release_xtal_clk(clocks), + SocRootClkConfig::RcFast => release_rc_fast_clk(clocks), + SocRootClkConfig::Pll => release_pll_clk(clocks), + } + } + pub fn soc_root_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.soc_root_clk) { + SocRootClkConfig::Xtal => xtal_clk_frequency(clocks), + SocRootClkConfig::RcFast => rc_fast_clk_frequency(clocks), + SocRootClkConfig::Pll => pll_clk_frequency(clocks), + } + } + pub fn configure_cpu_hs_div(clocks: &mut ClockTree, config: CpuHsDivConfig) { + clocks.cpu_hs_div = Some(config); + configure_cpu_hs_div_impl(clocks, config); + } + pub fn cpu_hs_div_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_hs_div + } + pub fn request_cpu_hs_div(clocks: &mut ClockTree) { + trace!("Requesting CPU_HS_DIV"); + trace!("Enabling CPU_HS_DIV"); + request_hp_root_clk(clocks); + enable_cpu_hs_div_impl(clocks, true); + } + pub fn release_cpu_hs_div(clocks: &mut ClockTree) { + trace!("Releasing CPU_HS_DIV"); + trace!("Disabling CPU_HS_DIV"); + enable_cpu_hs_div_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn cpu_hs_div_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.cpu_hs_div).divisor() + 1)) + } + pub fn configure_cpu_ls_div(clocks: &mut ClockTree, config: CpuLsDivConfig) { + clocks.cpu_ls_div = Some(config); + configure_cpu_ls_div_impl(clocks, config); + } + pub fn cpu_ls_div_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_ls_div + } + pub fn request_cpu_ls_div(clocks: &mut ClockTree) { + trace!("Requesting CPU_LS_DIV"); + trace!("Enabling CPU_LS_DIV"); + request_hp_root_clk(clocks); + enable_cpu_ls_div_impl(clocks, true); + } + pub fn release_cpu_ls_div(clocks: &mut ClockTree) { + trace!("Releasing CPU_LS_DIV"); + trace!("Disabling CPU_LS_DIV"); + enable_cpu_ls_div_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn cpu_ls_div_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.cpu_ls_div).divisor() + 1)) + } + pub fn configure_ahb_hs_div(clocks: &mut ClockTree, config: AhbHsDivConfig) { + clocks.ahb_hs_div = Some(config); + configure_ahb_hs_div_impl(clocks, config); + } + pub fn ahb_hs_div_config(clocks: &mut ClockTree) -> Option { + clocks.ahb_hs_div + } + pub fn request_ahb_hs_div(clocks: &mut ClockTree) { + trace!("Requesting AHB_HS_DIV"); + trace!("Enabling AHB_HS_DIV"); + request_hp_root_clk(clocks); + enable_ahb_hs_div_impl(clocks, true); + } + pub fn release_ahb_hs_div(clocks: &mut ClockTree) { + trace!("Releasing AHB_HS_DIV"); + trace!("Disabling AHB_HS_DIV"); + enable_ahb_hs_div_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn ahb_hs_div_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.ahb_hs_div).divisor() + 1)) + } + pub fn configure_ahb_ls_div(clocks: &mut ClockTree, config: AhbLsDivConfig) { + clocks.ahb_ls_div = Some(config); + configure_ahb_ls_div_impl(clocks, config); + } + pub fn ahb_ls_div_config(clocks: &mut ClockTree) -> Option { + clocks.ahb_ls_div + } + pub fn request_ahb_ls_div(clocks: &mut ClockTree) { + trace!("Requesting AHB_LS_DIV"); + trace!("Enabling AHB_LS_DIV"); + request_hp_root_clk(clocks); + enable_ahb_ls_div_impl(clocks, true); + } + pub fn release_ahb_ls_div(clocks: &mut ClockTree) { + trace!("Releasing AHB_LS_DIV"); + trace!("Disabling AHB_LS_DIV"); + enable_ahb_ls_div_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn ahb_ls_div_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.ahb_ls_div).divisor() + 1)) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, config: ApbClkConfig) { + clocks.apb_clk = Some(config); + configure_apb_clk_impl(clocks, config); + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + request_ahb_clk(clocks); + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + release_ahb_clk(clocks); + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + (ahb_clk_frequency(clocks) / (unwrap!(clocks.apb_clk).divisor() + 1)) + } + pub fn configure_mspi_fast_hs_clk(clocks: &mut ClockTree, config: MspiFastHsClkConfig) { + clocks.mspi_fast_hs_clk = Some(config); + configure_mspi_fast_hs_clk_impl(clocks, config); + } + pub fn mspi_fast_hs_clk_config(clocks: &mut ClockTree) -> Option { + clocks.mspi_fast_hs_clk + } + pub fn request_mspi_fast_hs_clk(clocks: &mut ClockTree) { + trace!("Requesting MSPI_FAST_HS_CLK"); + trace!("Enabling MSPI_FAST_HS_CLK"); + request_hp_root_clk(clocks); + enable_mspi_fast_hs_clk_impl(clocks, true); + } + pub fn release_mspi_fast_hs_clk(clocks: &mut ClockTree) { + trace!("Releasing MSPI_FAST_HS_CLK"); + trace!("Disabling MSPI_FAST_HS_CLK"); + enable_mspi_fast_hs_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn mspi_fast_hs_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.mspi_fast_hs_clk).divisor() + 1)) + } + pub fn configure_mspi_fast_ls_clk(clocks: &mut ClockTree, config: MspiFastLsClkConfig) { + clocks.mspi_fast_ls_clk = Some(config); + configure_mspi_fast_ls_clk_impl(clocks, config); + } + pub fn mspi_fast_ls_clk_config(clocks: &mut ClockTree) -> Option { + clocks.mspi_fast_ls_clk + } + pub fn request_mspi_fast_ls_clk(clocks: &mut ClockTree) { + trace!("Requesting MSPI_FAST_LS_CLK"); + trace!("Enabling MSPI_FAST_LS_CLK"); + request_hp_root_clk(clocks); + enable_mspi_fast_ls_clk_impl(clocks, true); + } + pub fn release_mspi_fast_ls_clk(clocks: &mut ClockTree) { + trace!("Releasing MSPI_FAST_LS_CLK"); + trace!("Disabling MSPI_FAST_LS_CLK"); + enable_mspi_fast_ls_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn mspi_fast_ls_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.mspi_fast_ls_clk).divisor() + 1)) + } + pub fn request_pll_f48m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F48M"); + if increment_reference_count(&mut clocks.pll_f48m_refcount) { + trace!("Enabling PLL_F48M"); + request_pll_clk(clocks); + enable_pll_f48m_impl(clocks, true); + } + } + pub fn release_pll_f48m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F48M"); + if decrement_reference_count(&mut clocks.pll_f48m_refcount) { + trace!("Disabling PLL_F48M"); + enable_pll_f48m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f48m_frequency(clocks: &mut ClockTree) -> u32 { + 48000000 + } + pub fn request_pll_f80m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F80M"); + if increment_reference_count(&mut clocks.pll_f80m_refcount) { + trace!("Enabling PLL_F80M"); + request_pll_clk(clocks); + enable_pll_f80m_impl(clocks, true); + } + } + pub fn release_pll_f80m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F80M"); + if decrement_reference_count(&mut clocks.pll_f80m_refcount) { + trace!("Disabling PLL_F80M"); + enable_pll_f80m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn request_pll_f160m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F160M"); + trace!("Enabling PLL_F160M"); + request_pll_clk(clocks); + enable_pll_f160m_impl(clocks, true); + } + pub fn release_pll_f160m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F160M"); + trace!("Disabling PLL_F160M"); + enable_pll_f160m_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn pll_f160m_frequency(clocks: &mut ClockTree) -> u32 { + 160000000 + } + pub fn request_pll_f240m(clocks: &mut ClockTree) { + trace!("Requesting PLL_F240M"); + if increment_reference_count(&mut clocks.pll_f240m_refcount) { + trace!("Enabling PLL_F240M"); + request_pll_clk(clocks); + enable_pll_f240m_impl(clocks, true); + } + } + pub fn release_pll_f240m(clocks: &mut ClockTree) { + trace!("Releasing PLL_F240M"); + if decrement_reference_count(&mut clocks.pll_f240m_refcount) { + trace!("Disabling PLL_F240M"); + enable_pll_f240m_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_f240m_frequency(clocks: &mut ClockTree) -> u32 { + 240000000 + } + pub fn configure_ledc_sclk(clocks: &mut ClockTree, new_selector: LedcSclkConfig) { + let old_selector = clocks.ledc_sclk.replace(new_selector); + if clocks.ledc_sclk_refcount > 0 { + match new_selector { + LedcSclkConfig::PllF80m => request_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => request_xtal_clk(clocks), + } + configure_ledc_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LedcSclkConfig::PllF80m => release_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_ledc_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn ledc_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.ledc_sclk + } + pub fn request_ledc_sclk(clocks: &mut ClockTree) { + trace!("Requesting LEDC_SCLK"); + if increment_reference_count(&mut clocks.ledc_sclk_refcount) { + trace!("Enabling LEDC_SCLK"); + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => request_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => request_xtal_clk(clocks), + } + enable_ledc_sclk_impl(clocks, true); + } + } + pub fn release_ledc_sclk(clocks: &mut ClockTree) { + trace!("Releasing LEDC_SCLK"); + if decrement_reference_count(&mut clocks.ledc_sclk_refcount) { + trace!("Disabling LEDC_SCLK"); + enable_ledc_sclk_impl(clocks, false); + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => release_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn ledc_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => pll_f80m_frequency(clocks), + LedcSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + LedcSclkConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn request_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_D2_CLK"); + trace!("Enabling XTAL_D2_CLK"); + request_xtal_clk(clocks); + enable_xtal_d2_clk_impl(clocks, true); + } + pub fn release_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_D2_CLK"); + trace!("Disabling XTAL_D2_CLK"); + enable_xtal_d2_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_d2_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_lp_fast_clk(clocks: &mut ClockTree, new_selector: LpFastClkConfig) { + let old_selector = clocks.lp_fast_clk.replace(new_selector); + if clocks.lp_fast_clk_refcount > 0 { + match new_selector { + LpFastClkConfig::RcFastClk => request_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2Clk => request_xtal_d2_clk(clocks), + } + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpFastClkConfig::RcFastClk => release_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2Clk => release_xtal_d2_clk(clocks), + } + } + } else { + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_fast_clk + } + pub fn request_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_FAST_CLK"); + if increment_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Enabling LP_FAST_CLK"); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => request_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2Clk => request_xtal_d2_clk(clocks), + } + enable_lp_fast_clk_impl(clocks, true); + } + } + pub fn release_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_FAST_CLK"); + if decrement_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Disabling LP_FAST_CLK"); + enable_lp_fast_clk_impl(clocks, false); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => release_rc_fast_clk(clocks), + LpFastClkConfig::XtalD2Clk => release_xtal_d2_clk(clocks), + } + } + } + pub fn lp_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + LpFastClkConfig::XtalD2Clk => xtal_d2_clk_frequency(clocks), + } + } + pub fn configure_lp_slow_clk(clocks: &mut ClockTree, new_selector: LpSlowClkConfig) { + let old_selector = clocks.lp_slow_clk.replace(new_selector); + if clocks.lp_slow_clk_refcount > 0 { + match new_selector { + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } else { + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_slow_clk + } + pub fn request_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_SLOW_CLK"); + if increment_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Enabling LP_SLOW_CLK"); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + enable_lp_slow_clk_impl(clocks, true); + } + } + pub fn release_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_SLOW_CLK"); + if decrement_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Disabling LP_SLOW_CLK"); + enable_lp_slow_clk_impl(clocks, false); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } + pub fn lp_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + LpSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + LpSlowClkConfig::OscSlow => osc_slow_clk_frequency(clocks), + } + } + pub fn configure_mcpwm0_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm0_function_clock.replace(new_selector); + if clocks.mcpwm0_function_clock_refcount > 0 { + match new_selector { + Mcpwm0FunctionClockConfig::PllF160m => request_pll_f160m(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + } + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Mcpwm0FunctionClockConfig::PllF160m => release_pll_f160m(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm0_function_clock + } + pub fn request_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Enabling MCPWM0_FUNCTION_CLOCK"); + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF160m => request_pll_f160m(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + } + enable_mcpwm0_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Disabling MCPWM0_FUNCTION_CLOCK"); + enable_mcpwm0_function_clock_impl(clocks, false); + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF160m => release_pll_f160m(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn mcpwm0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF160m => pll_f160m_frequency(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Mcpwm0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_parlio_rx_clock( + clocks: &mut ClockTree, + new_selector: ParlioRxClockConfig, + ) { + let old_selector = clocks.parlio_rx_clock.replace(new_selector); + if clocks.parlio_rx_clock_refcount > 0 { + match new_selector { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => request_pll_f240m(clocks), + } + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } else { + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_rx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_rx_clock + } + pub fn request_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_RX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Enabling PARLIO_RX_CLOCK"); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => request_pll_f240m(clocks), + } + enable_parlio_rx_clock_impl(clocks, true); + } + } + pub fn release_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_RX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Disabling PARLIO_RX_CLOCK"); + enable_parlio_rx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } + pub fn parlio_rx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioRxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioRxClockConfig::PllF240m => pll_f240m_frequency(clocks), + } + } + pub fn configure_parlio_tx_clock( + clocks: &mut ClockTree, + new_selector: ParlioTxClockConfig, + ) { + let old_selector = clocks.parlio_tx_clock.replace(new_selector); + if clocks.parlio_tx_clock_refcount > 0 { + match new_selector { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => request_pll_f240m(clocks), + } + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } else { + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_tx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_tx_clock + } + pub fn request_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_TX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Enabling PARLIO_TX_CLOCK"); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => request_pll_f240m(clocks), + } + enable_parlio_tx_clock_impl(clocks, true); + } + } + pub fn release_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_TX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Disabling PARLIO_TX_CLOCK"); + enable_parlio_tx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF240m => release_pll_f240m(clocks), + } + } + } + pub fn parlio_tx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioTxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioTxClockConfig::PllF240m => pll_f240m_frequency(clocks), + } + } + pub fn configure_rmt_sclk(clocks: &mut ClockTree, new_selector: RmtSclkConfig) { + let old_selector = clocks.rmt_sclk.replace(new_selector); + if clocks.rmt_sclk_refcount > 0 { + match new_selector { + RmtSclkConfig::PllF80m => request_pll_f80m(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RmtSclkConfig::PllF80m => release_pll_f80m(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn rmt_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.rmt_sclk + } + pub fn request_rmt_sclk(clocks: &mut ClockTree) { + trace!("Requesting RMT_SCLK"); + if increment_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Enabling RMT_SCLK"); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::PllF80m => request_pll_f80m(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + enable_rmt_sclk_impl(clocks, true); + } + } + pub fn release_rmt_sclk(clocks: &mut ClockTree) { + trace!("Releasing RMT_SCLK"); + if decrement_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Disabling RMT_SCLK"); + enable_rmt_sclk_impl(clocks, false); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::PllF80m => release_pll_f80m(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn rmt_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::PllF80m => pll_f80m_frequency(clocks), + RmtSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + RmtSclkConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => lp_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg0_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg0_wdt_clock.replace(new_selector); + if clocks.timg0_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } else { + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg0_wdt_clock + } + pub fn request_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Enabling TIMG0_WDT_CLOCK"); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + enable_timg0_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Disabling TIMG0_WDT_CLOCK"); + enable_timg0_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } + pub fn timg0_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::PllF80m => pll_f80m_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => lp_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg1_wdt_clock.replace(new_selector); + if clocks.timg1_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } else { + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg1_wdt_clock + } + pub fn request_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Enabling TIMG1_WDT_CLOCK"); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => request_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + } + enable_timg1_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Disabling TIMG1_WDT_CLOCK"); + enable_timg1_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::PllF80m => release_pll_f80m(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } + pub fn timg1_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::PllF80m => pll_f80m_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF80m => request_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF80m => release_pll_f80m(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF80m => pll_f80m_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `SOC_ROOT_CLK` configuration. + pub soc_root_clk: Option, + /// `CPU_HS_DIV` configuration. + pub cpu_hs_div: Option, + /// `CPU_LS_DIV` configuration. + pub cpu_ls_div: Option, + /// `AHB_HS_DIV` configuration. + pub ahb_hs_div: Option, + /// `AHB_LS_DIV` configuration. + pub ahb_ls_div: Option, + /// `APB_CLK` configuration. + pub apb_clk: Option, + /// `MSPI_FAST_HS_CLK` configuration. + pub mspi_fast_hs_clk: Option, + /// `MSPI_FAST_LS_CLK` configuration. + pub mspi_fast_ls_clk: Option, + /// `LEDC_SCLK` configuration. + pub ledc_sclk: Option, + /// `LP_FAST_CLK` configuration. + pub lp_fast_clk: Option, + /// `LP_SLOW_CLK` configuration. + pub lp_slow_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.soc_root_clk { + configure_soc_root_clk(clocks, config); + } + if let Some(config) = self.cpu_hs_div { + configure_cpu_hs_div(clocks, config); + } + if let Some(config) = self.cpu_ls_div { + configure_cpu_ls_div(clocks, config); + } + if let Some(config) = self.ahb_hs_div { + configure_ahb_hs_div(clocks, config); + } + if let Some(config) = self.ahb_ls_div { + configure_ahb_ls_div(clocks, config); + } + if let Some(config) = self.apb_clk { + configure_apb_clk(clocks, config); + } + if let Some(config) = self.mspi_fast_hs_clk { + configure_mspi_fast_hs_clk(clocks, config); + } + if let Some(config) = self.mspi_fast_ls_clk { + configure_mspi_fast_ls_clk(clocks, config); + } + if let Some(config) = self.ledc_sclk { + configure_ledc_sclk(clocks, config); + } + if let Some(config) = self.lp_fast_clk { + configure_lp_fast_clk(clocks, config); + } + if let Some(config) = self.lp_slow_clk { + configure_lp_slow_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// DMA peripheral clock signal + Dma, + /// DS peripheral clock signal + Ds, + /// ECC peripheral clock signal + Ecc, + /// ETM peripheral clock signal + Etm, + /// HMAC peripheral clock signal + Hmac, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2S0 peripheral clock signal + I2s0, + /// LEDC peripheral clock signal + Ledc, + /// MCPWM0 peripheral clock signal + Mcpwm0, + /// PARL_IO peripheral clock signal + ParlIo, + /// PCNT peripheral clock signal + Pcnt, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SDIO_SLAVE peripheral clock signal + SdioSlave, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TRACE0 peripheral clock signal + Trace0, + /// TSENS peripheral clock signal + Tsens, + /// TWAI0 peripheral clock signal + Twai0, + /// TWAI1 peripheral clock signal + Twai1, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UHCI0 peripheral clock signal + Uhci0, + /// USB_DEVICE peripheral clock signal + UsbDevice, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = &[ + Self::ApbSarAdc, + Self::Systimer, + Self::Timg0, + Self::Uart0, + Self::UsbDevice, + ]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::ApbSarAdc, + Self::Dma, + Self::Ds, + Self::Ecc, + Self::Etm, + Self::Hmac, + Self::I2cExt0, + Self::I2s0, + Self::Ledc, + Self::Mcpwm0, + Self::ParlIo, + Self::Pcnt, + Self::Rmt, + Self::Rsa, + Self::SdioSlave, + Self::Sha, + Self::Spi2, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Trace0, + Self::Tsens, + Self::Twai0, + Self::Twai1, + Self::Uart0, + Self::Uart1, + Self::Uhci0, + Self::UsbDevice, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_clk_en().bit(enable)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .saradc_conf() + .modify(|_, w| w.saradc_reg_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_clk_en().bit(enable)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .ds_conf() + .modify(|_, w| w.ds_clk_en().bit(enable)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_clk_en().bit(enable)); + } + Peripheral::Etm => { + crate::peripherals::SYSTEM::regs() + .etm_conf() + .modify(|_, w| w.etm_clk_en().bit(enable)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .hmac_conf() + .modify(|_, w| w.hmac_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .i2s_conf() + .modify(|_, w| w.i2s_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .pwm_conf() + .modify(|_, w| w.pwm_clk_en().bit(enable)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_clk_en().bit(enable)); + } + Peripheral::SdioSlave => { + crate::peripherals::SYSTEM::regs() + .sdio_slave_conf() + .modify(|_, w| w.sdio_slave_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(enable)); + } + Peripheral::Trace0 => { + crate::peripherals::SYSTEM::regs() + .trace_conf() + .modify(|_, w| w.trace_clk_en().bit(enable)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .tsens_clk_conf() + .modify(|_, w| w.tsens_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .twai0_conf() + .modify(|_, w| w.twai0_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .twai0_func_clk_conf() + .modify(|_, w| w.twai0_func_clk_en().bit(enable)); + } + Peripheral::Twai1 => { + crate::peripherals::SYSTEM::regs() + .twai1_conf() + .modify(|_, w| w.twai1_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .twai1_func_clk_conf() + .modify(|_, w| w.twai1_func_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_clk_en().bit(enable)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_rst_en().bit(reset)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .saradc_conf() + .modify(|_, w| w.saradc_reg_rst_en().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_rst_en().bit(reset)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .ds_conf() + .modify(|_, w| w.ds_rst_en().bit(reset)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_rst_en().bit(reset)); + } + Peripheral::Etm => { + crate::peripherals::SYSTEM::regs() + .etm_conf() + .modify(|_, w| w.etm_rst_en().bit(reset)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .hmac_conf() + .modify(|_, w| w.hmac_rst_en().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_rst_en().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .i2s_conf() + .modify(|_, w| w.i2s_rst_en().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_rst_en().bit(reset)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .pwm_conf() + .modify(|_, w| w.pwm_rst_en().bit(reset)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_rst_en().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_rst_en().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_rst_en().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_rst_en().bit(reset)); + } + Peripheral::SdioSlave => { + crate::peripherals::SYSTEM::regs() + .sdio_slave_conf() + .modify(|_, w| w.sdio_slave_rst_en().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_rst_en().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_rst_en().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_rst_en().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_rst_en().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_rst_en().bit(reset)); + } + Peripheral::Trace0 => { + crate::peripherals::SYSTEM::regs() + .trace_conf() + .modify(|_, w| w.trace_rst_en().bit(reset)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .tsens_clk_conf() + .modify(|_, w| w.tsens_rst_en().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .twai0_conf() + .modify(|_, w| w.twai0_rst_en().bit(reset)); + } + Peripheral::Twai1 => { + crate::peripherals::SYSTEM::regs() + .twai1_conf() + .modify(|_, w| w.twai1_rst_en().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_rst_en().bit(reset)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_rst_en().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x40800000..0x40880000 + }; + (size as str, "DRAM") => { + "524288" + }; + ("DRAM2_UNINIT") => { + 0x4086E610..0x4087E610 + }; + (size as str, "DRAM2_UNINIT") => { + "65536" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true)); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO3 peripheral singleton"] + GPIO3 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO12 peripheral singleton"] + GPIO12 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO14 peripheral singleton"] + GPIO14 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO15 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO16 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO17 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO18 peripheral singleton"] + GPIO18 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO20 peripheral singleton"] + GPIO20 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO22 peripheral singleton"] + GPIO22 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO23 peripheral singleton"] GPIO23 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO24 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO24 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO25 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ATOMIC peripheral singleton"] + ATOMIC <= ATOMIC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DS peripheral singleton"] DS <= + DS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "ECC peripheral singleton"] ECC <= ECC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EFUSE peripheral singleton"] + EFUSE <= EFUSE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HINF peripheral singleton"] + HINF <= HINF() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "HMAC peripheral singleton"] HMAC <= HMAC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HP_APM peripheral singleton"] + HP_APM <= HP_APM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "HP_SYS peripheral singleton"] HP_SYS <= HP_SYS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "IO_MUX peripheral singleton"] + IO_MUX <= IO_MUX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LEDC peripheral singleton"] LEDC <= LEDC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_ANA peripheral singleton"] + LP_ANA <= LP_ANA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_APM peripheral singleton"] + LP_APM <= LP_APM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_APM0 peripheral singleton"] LP_APM0 <= LP_APM0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_I2C0 peripheral singleton"] + LP_I2C0 <= LP_I2C0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_IO peripheral singleton"] LP_IO <= LP_IO() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_PERI peripheral singleton"] + LP_PERI <= LP_PERI() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_TEE peripheral singleton"] LP_TEE <= LP_TEE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_TIMER peripheral singleton"] + LP_TIMER <= LP_TIMER() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "LP_UART peripheral singleton"] LP_UART <= LP_UART() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_WDT peripheral singleton"] + LP_WDT <= LP_WDT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LPWR peripheral singleton"] LPWR <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MCPWM0 peripheral singleton"] + MCPWM0 <= MCPWM0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= MODEM_LPCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "OTP_DEBUG peripheral singleton"] OTP_DEBUG <= OTP_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PARL_IO peripheral singleton"] + PARL_IO <= PARL_IO(PARL_IO : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "PAU peripheral singleton"] PAU <= PAU() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PCNT peripheral singleton"] + PCNT <= PCNT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PCR peripheral singleton"] PCR <= PCR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PLIC_MX peripheral singleton"] + PLIC_MX <= PLIC_MX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "PMU peripheral singleton"] PMU <= PMU() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SHA peripheral singleton"] SHA <= SHA(SHA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SLCHOST peripheral singleton"] + SLCHOST <= SLCHOST() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "ETM peripheral singleton"] ETM <= SOC_ETM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI0 peripheral singleton"] + SPI0 <= SPI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI1 peripheral singleton"] SPI1 <= SPI1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI2 peripheral singleton"] + SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTEM peripheral singleton"] SYSTEM <= PCR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTIMER peripheral singleton"] + SYSTIMER <= SYSTIMER() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "TEE peripheral singleton"] TEE <= TEE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG0 peripheral singleton"] + TIMG0 <= TIMG0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TRACE0 peripheral singleton"] + TRACE0 <= TRACE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TWAI1 peripheral singleton"] + TWAI1 <= TWAI1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_CH0 peripheral singleton"] DMA_CH0 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH1 peripheral singleton"] + DMA_CH1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC1 peripheral singleton"] + ADC1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BT peripheral singleton"] BT <= virtual(LP_TIMER : { bind_lp_timer_interrupt, + enable_lp_timer_interrupt, disable_lp_timer_interrupt }, BT_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FLASH peripheral singleton"] + FLASH <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_CORE peripheral singleton"] + LP_CORE <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TSENS peripheral singleton"] + TSENS <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "WIFI peripheral singleton"] WIFI <= virtual(WIFI_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, WIFI_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }, MODEM_PERI_TIMEOUT : { + bind_modem_peri_timeout_interrupt, enable_modem_peri_timeout_interrupt, + disable_modem_peri_timeout_interrupt }, WIFI_PWR : { bind_pwr_interrupt, + enable_pwr_interrupt, disable_pwr_interrupt }))); _for_each_inner_peripheral!((@ + peri_type #[doc = "MEM2MEM0 peripheral singleton"] MEM2MEM0 <= virtual() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MEM2MEM1 peripheral singleton"] MEM2MEM1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM2 peripheral singleton"] + MEM2MEM2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM3 peripheral singleton"] MEM2MEM3 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM4 peripheral singleton"] + MEM2MEM4 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM5 peripheral singleton"] MEM2MEM5 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM6 peripheral singleton"] + MEM2MEM6 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM7 peripheral singleton"] MEM2MEM7 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM8 peripheral singleton"] + MEM2MEM8 <= virtual() (unstable))); _for_each_inner_peripheral!((GPIO0)); + _for_each_inner_peripheral!((GPIO1)); _for_each_inner_peripheral!((GPIO2)); + _for_each_inner_peripheral!((GPIO3)); _for_each_inner_peripheral!((GPIO4)); + _for_each_inner_peripheral!((GPIO5)); _for_each_inner_peripheral!((GPIO6)); + _for_each_inner_peripheral!((GPIO7)); _for_each_inner_peripheral!((GPIO8)); + _for_each_inner_peripheral!((GPIO9)); _for_each_inner_peripheral!((GPIO10)); + _for_each_inner_peripheral!((GPIO11)); _for_each_inner_peripheral!((GPIO12)); + _for_each_inner_peripheral!((GPIO13)); _for_each_inner_peripheral!((GPIO14)); + _for_each_inner_peripheral!((GPIO15)); _for_each_inner_peripheral!((GPIO16)); + _for_each_inner_peripheral!((GPIO17)); _for_each_inner_peripheral!((GPIO18)); + _for_each_inner_peripheral!((GPIO19)); _for_each_inner_peripheral!((GPIO20)); + _for_each_inner_peripheral!((GPIO21)); _for_each_inner_peripheral!((GPIO22)); + _for_each_inner_peripheral!((GPIO23)); _for_each_inner_peripheral!((GPIO24)); + _for_each_inner_peripheral!((GPIO25)); _for_each_inner_peripheral!((GPIO26)); + _for_each_inner_peripheral!((GPIO27)); _for_each_inner_peripheral!((GPIO28)); + _for_each_inner_peripheral!((GPIO29)); _for_each_inner_peripheral!((GPIO30)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((ATOMIC(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((ECC(unstable))); + _for_each_inner_peripheral!((EXTMEM(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HINF(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((HP_APM(unstable))); + _for_each_inner_peripheral!((HP_SYS(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((IEEE802154(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((INTPRI(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((LP_ANA(unstable))); + _for_each_inner_peripheral!((LP_AON(unstable))); + _for_each_inner_peripheral!((LP_APM(unstable))); + _for_each_inner_peripheral!((LP_APM0(unstable))); + _for_each_inner_peripheral!((LP_CLKRST(unstable))); + _for_each_inner_peripheral!((LP_I2C0(unstable))); + _for_each_inner_peripheral!((LP_I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((LP_IO(unstable))); + _for_each_inner_peripheral!((LP_PERI(unstable))); + _for_each_inner_peripheral!((LP_TEE(unstable))); + _for_each_inner_peripheral!((LP_TIMER(unstable))); + _for_each_inner_peripheral!((LP_UART(unstable))); + _for_each_inner_peripheral!((LP_WDT(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((MCPWM0(unstable))); + _for_each_inner_peripheral!((MEM_MONITOR(unstable))); + _for_each_inner_peripheral!((MODEM_LPCON(unstable))); + _for_each_inner_peripheral!((MODEM_SYSCON(unstable))); + _for_each_inner_peripheral!((OTP_DEBUG(unstable))); + _for_each_inner_peripheral!((PARL_IO(unstable))); + _for_each_inner_peripheral!((PAU(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((PCR(unstable))); + _for_each_inner_peripheral!((PLIC_MX(unstable))); + _for_each_inner_peripheral!((PMU(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SLCHOST(unstable))); + _for_each_inner_peripheral!((ETM(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TEE(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TRACE0(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((TWAI1(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB_DEVICE(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((DMA_CH1(unstable))); + _for_each_inner_peripheral!((DMA_CH2(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((LP_CORE(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((TSENS(unstable))); + _for_each_inner_peripheral!((WIFI)); + _for_each_inner_peripheral!((MEM2MEM0(unstable))); + _for_each_inner_peripheral!((MEM2MEM1(unstable))); + _for_each_inner_peripheral!((MEM2MEM2(unstable))); + _for_each_inner_peripheral!((MEM2MEM3(unstable))); + _for_each_inner_peripheral!((MEM2MEM4(unstable))); + _for_each_inner_peripheral!((MEM2MEM5(unstable))); + _for_each_inner_peripheral!((MEM2MEM6(unstable))); + _for_each_inner_peripheral!((MEM2MEM7(unstable))); + _for_each_inner_peripheral!((MEM2MEM8(unstable))); + _for_each_inner_peripheral!((SPI2, Spi2, 0)); + _for_each_inner_peripheral!((MEM2MEM0, Mem2mem0, 1)); + _for_each_inner_peripheral!((UHCI0, Uhci0, 2)); + _for_each_inner_peripheral!((I2S0, I2s0, 3)); + _for_each_inner_peripheral!((MEM2MEM1, Mem2mem1, 4)); + _for_each_inner_peripheral!((MEM2MEM2, Mem2mem2, 5)); + _for_each_inner_peripheral!((AES, Aes, 6)); _for_each_inner_peripheral!((SHA, + Sha, 7)); _for_each_inner_peripheral!((APB_SARADC, ApbSaradc, 8)); + _for_each_inner_peripheral!((PARL_IO, ParlIo, 9)); + _for_each_inner_peripheral!((MEM2MEM3, Mem2mem3, 10)); + _for_each_inner_peripheral!((MEM2MEM4, Mem2mem4, 11)); + _for_each_inner_peripheral!((MEM2MEM5, Mem2mem5, 12)); + _for_each_inner_peripheral!((MEM2MEM6, Mem2mem6, 13)); + _for_each_inner_peripheral!((MEM2MEM7, Mem2mem7, 14)); + _for_each_inner_peripheral!((MEM2MEM8, Mem2mem8, 15)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton"] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton"] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton"] GPIO20 <= virtual()), (@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual()), (@ peri_type #[doc = + "GPIO22 peripheral singleton"] GPIO22 <= virtual()), (@ peri_type #[doc = + "GPIO23 peripheral singleton"] GPIO23 <= virtual()), (@ peri_type #[doc = + "GPIO24 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO24 <= virtual()), (@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO25 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual()), (@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual()), (@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual()), (@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable)), (@ + peri_type #[doc = "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= + ASSIST_DEBUG() (unstable)), (@ peri_type #[doc = "ATOMIC peripheral singleton"] + ATOMIC <= ATOMIC() (unstable)), (@ peri_type #[doc = "DMA peripheral singleton"] + DMA <= DMA() (unstable)), (@ peri_type #[doc = "DS peripheral singleton"] DS <= + DS() (unstable)), (@ peri_type #[doc = "ECC peripheral singleton"] ECC <= ECC() + (unstable)), (@ peri_type #[doc = "EFUSE peripheral singleton"] EFUSE <= EFUSE() + (unstable)), (@ peri_type #[doc = "EXTMEM peripheral singleton"] EXTMEM <= + EXTMEM() (unstable)), (@ peri_type #[doc = "GPIO peripheral singleton"] GPIO <= + GPIO() (unstable)), (@ peri_type #[doc = "GPIO_SD peripheral singleton"] GPIO_SD + <= GPIO_SD() (unstable)), (@ peri_type #[doc = "HINF peripheral singleton"] HINF + <= HINF() (unstable)), (@ peri_type #[doc = "HMAC peripheral singleton"] HMAC <= + HMAC() (unstable)), (@ peri_type #[doc = "HP_APM peripheral singleton"] HP_APM <= + HP_APM() (unstable)), (@ peri_type #[doc = "HP_SYS peripheral singleton"] HP_SYS + <= HP_SYS() (unstable)), (@ peri_type #[doc = "I2C_ANA_MST peripheral singleton"] + I2C_ANA_MST <= I2C_ANA_MST() (unstable)), (@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable)), + (@ peri_type #[doc = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= + INTERRUPT_CORE0() (unstable)), (@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable)), (@ peri_type #[doc + = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type + #[doc = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable)), (@ peri_type + #[doc = "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable)), (@ + peri_type #[doc = "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable)), + (@ peri_type #[doc = "LP_APM peripheral singleton"] LP_APM <= LP_APM() + (unstable)), (@ peri_type #[doc = "LP_APM0 peripheral singleton"] LP_APM0 <= + LP_APM0() (unstable)), (@ peri_type #[doc = "LP_CLKRST peripheral singleton"] + LP_CLKRST <= LP_CLKRST() (unstable)), (@ peri_type #[doc = + "LP_I2C0 peripheral singleton"] LP_I2C0 <= LP_I2C0() (unstable)), (@ peri_type + #[doc = "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() + (unstable)), (@ peri_type #[doc = "LP_IO peripheral singleton"] LP_IO <= LP_IO() + (unstable)), (@ peri_type #[doc = "LP_PERI peripheral singleton"] LP_PERI <= + LP_PERI() (unstable)), (@ peri_type #[doc = "LP_TEE peripheral singleton"] LP_TEE + <= LP_TEE() (unstable)), (@ peri_type #[doc = "LP_TIMER peripheral singleton"] + LP_TIMER <= LP_TIMER() (unstable)), (@ peri_type #[doc = + "LP_UART peripheral singleton"] LP_UART <= LP_UART() (unstable)), (@ peri_type + #[doc = "LP_WDT peripheral singleton"] LP_WDT <= LP_WDT() (unstable)), (@ + peri_type #[doc = "LPWR peripheral singleton"] LPWR <= LP_CLKRST() (unstable)), + (@ peri_type #[doc = "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() + (unstable)), (@ peri_type #[doc = "MEM_MONITOR peripheral singleton"] MEM_MONITOR + <= MEM_MONITOR() (unstable)), (@ peri_type #[doc = + "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= MODEM_LPCON() (unstable)), (@ + peri_type #[doc = "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= + MODEM_SYSCON() (unstable)), (@ peri_type #[doc = + "OTP_DEBUG peripheral singleton"] OTP_DEBUG <= OTP_DEBUG() (unstable)), (@ + peri_type #[doc = "PARL_IO peripheral singleton"] PARL_IO <= PARL_IO(PARL_IO : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "PAU peripheral singleton"] PAU <= PAU() + (unstable)), (@ peri_type #[doc = "PCNT peripheral singleton"] PCNT <= PCNT() + (unstable)), (@ peri_type #[doc = "PCR peripheral singleton"] PCR <= PCR() + (unstable)), (@ peri_type #[doc = "PLIC_MX peripheral singleton"] PLIC_MX <= + PLIC_MX() (unstable)), (@ peri_type #[doc = "PMU peripheral singleton"] PMU <= + PMU() (unstable)), (@ peri_type #[doc = "RMT peripheral singleton"] RMT <= RMT() + (unstable)), (@ peri_type #[doc = "RNG peripheral singleton"] RNG <= RNG() + (unstable)), (@ peri_type #[doc = "RSA peripheral singleton"] RSA <= RSA(RSA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= SHA(SHA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SLCHOST peripheral singleton"] SLCHOST <= + SLCHOST() (unstable)), (@ peri_type #[doc = "ETM peripheral singleton"] ETM <= + SOC_ETM() (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 <= + SPI0() (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= + SPI1() (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= + SPI2(SPI2 : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + })), (@ peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= PCR() + (unstable)), (@ peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= + SYSTIMER() (unstable)), (@ peri_type #[doc = "TEE peripheral singleton"] TEE <= + TEE() (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= + TIMG0() (unstable)), (@ peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 <= + TIMG1() (unstable)), (@ peri_type #[doc = "TRACE0 peripheral singleton"] TRACE0 + <= TRACE() (unstable)), (@ peri_type #[doc = "TWAI0 peripheral singleton"] TWAI0 + <= TWAI0() (unstable)), (@ peri_type #[doc = "TWAI1 peripheral singleton"] TWAI1 + <= TWAI1() (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 + <= UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable)), (@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "DMA_CH0 peripheral singleton"] DMA_CH0 <= + virtual() (unstable)), (@ peri_type #[doc = "DMA_CH1 peripheral singleton"] + DMA_CH1 <= virtual() (unstable)), (@ peri_type #[doc = + "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable)), (@ peri_type + #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable)), (@ peri_type + #[doc = "BT peripheral singleton"] BT <= virtual(LP_TIMER : { + bind_lp_timer_interrupt, enable_lp_timer_interrupt, disable_lp_timer_interrupt }, + BT_MAC : { bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) + (unstable)), (@ peri_type #[doc = "FLASH peripheral singleton"] FLASH <= + virtual() (unstable)), (@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "LP_CORE peripheral singleton"] LP_CORE <= virtual() + (unstable)), (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] + SW_INTERRUPT <= virtual() (unstable)), (@ peri_type #[doc = + "TSENS peripheral singleton"] TSENS <= virtual() (unstable)), (@ peri_type #[doc + = "WIFI peripheral singleton"] WIFI <= virtual(WIFI_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, WIFI_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }, MODEM_PERI_TIMEOUT : { + bind_modem_peri_timeout_interrupt, enable_modem_peri_timeout_interrupt, + disable_modem_peri_timeout_interrupt }, WIFI_PWR : { bind_pwr_interrupt, + enable_pwr_interrupt, disable_pwr_interrupt })), (@ peri_type #[doc = + "MEM2MEM0 peripheral singleton"] MEM2MEM0 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM1 peripheral singleton"] MEM2MEM1 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM2 peripheral singleton"] MEM2MEM2 <= virtual() + (unstable)), (@ peri_type #[doc = "MEM2MEM3 peripheral singleton"] MEM2MEM3 <= + virtual() (unstable)), (@ peri_type #[doc = "MEM2MEM4 peripheral singleton"] + MEM2MEM4 <= virtual() (unstable)), (@ peri_type #[doc = + "MEM2MEM5 peripheral singleton"] MEM2MEM5 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM6 peripheral singleton"] MEM2MEM6 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM7 peripheral singleton"] MEM2MEM7 <= virtual() + (unstable)), (@ peri_type #[doc = "MEM2MEM8 peripheral singleton"] MEM2MEM8 <= + virtual() (unstable)))); _for_each_inner_peripheral!((singletons(GPIO0), (GPIO1), + (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), (GPIO10), + (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), (GPIO18), + (GPIO19), (GPIO20), (GPIO21), (GPIO22), (GPIO23), (GPIO24), (GPIO25), (GPIO26), + (GPIO27), (GPIO28), (GPIO29), (GPIO30), (AES(unstable)), (APB_SARADC(unstable)), + (ASSIST_DEBUG(unstable)), (ATOMIC(unstable)), (DMA(unstable)), (DS(unstable)), + (ECC(unstable)), (EXTMEM(unstable)), (GPIO(unstable)), (GPIO_SD(unstable)), + (HINF(unstable)), (HMAC(unstable)), (HP_APM(unstable)), (HP_SYS(unstable)), + (I2C_ANA_MST(unstable)), (I2C0), (I2S0(unstable)), (IEEE802154(unstable)), + (INTERRUPT_CORE0(unstable)), (INTPRI(unstable)), (IO_MUX(unstable)), + (LEDC(unstable)), (LP_ANA(unstable)), (LP_AON(unstable)), (LP_APM(unstable)), + (LP_APM0(unstable)), (LP_CLKRST(unstable)), (LP_I2C0(unstable)), + (LP_I2C_ANA_MST(unstable)), (LP_IO(unstable)), (LP_PERI(unstable)), + (LP_TEE(unstable)), (LP_TIMER(unstable)), (LP_UART(unstable)), + (LP_WDT(unstable)), (LPWR(unstable)), (MCPWM0(unstable)), + (MEM_MONITOR(unstable)), (MODEM_LPCON(unstable)), (MODEM_SYSCON(unstable)), + (OTP_DEBUG(unstable)), (PARL_IO(unstable)), (PAU(unstable)), (PCNT(unstable)), + (PCR(unstable)), (PLIC_MX(unstable)), (PMU(unstable)), (RMT(unstable)), + (RNG(unstable)), (RSA(unstable)), (SHA(unstable)), (SLCHOST(unstable)), + (ETM(unstable)), (SPI0(unstable)), (SPI1(unstable)), (SPI2), (SYSTEM(unstable)), + (SYSTIMER(unstable)), (TEE(unstable)), (TIMG0(unstable)), (TIMG1(unstable)), + (TRACE0(unstable)), (TWAI0(unstable)), (TWAI1(unstable)), (UART0), (UART1), + (UHCI0(unstable)), (USB_DEVICE(unstable)), (DMA_CH0(unstable)), + (DMA_CH1(unstable)), (DMA_CH2(unstable)), (ADC1(unstable)), (BT(unstable)), + (FLASH(unstable)), (GPIO_DEDICATED(unstable)), (LP_CORE(unstable)), + (SW_INTERRUPT(unstable)), (TSENS(unstable)), (WIFI), (MEM2MEM0(unstable)), + (MEM2MEM1(unstable)), (MEM2MEM2(unstable)), (MEM2MEM3(unstable)), + (MEM2MEM4(unstable)), (MEM2MEM5(unstable)), (MEM2MEM6(unstable)), + (MEM2MEM7(unstable)), (MEM2MEM8(unstable)))); + _for_each_inner_peripheral!((dma_eligible(SPI2, Spi2, 0), (MEM2MEM0, Mem2mem0, + 1), (UHCI0, Uhci0, 2), (I2S0, I2s0, 3), (MEM2MEM1, Mem2mem1, 4), (MEM2MEM2, + Mem2mem2, 5), (AES, Aes, 6), (SHA, Sha, 7), (APB_SARADC, ApbSaradc, 8), (PARL_IO, + ParlIo, 9), (MEM2MEM3, Mem2mem3, 10), (MEM2MEM4, Mem2mem4, 11), (MEM2MEM5, + Mem2mem5, 12), (MEM2MEM6, Mem2mem6, 13), (MEM2MEM7, Mem2mem7, 14), (MEM2MEM8, + Mem2mem8, 15))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3() () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4(_0 => MTMS _2 => FSPIHD) (_2 => FSPIHD) ([Input] + [Output]))); _for_each_inner_gpio!((5, GPIO5(_0 => MTDI _2 => FSPIWP) (_2 => + FSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((6, GPIO6(_0 => MTCK _2 => + FSPICLK) (_2 => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((7, GPIO7(_2 + => FSPID) (_0 => MTDO _2 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() () ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9() () ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10() () ([Input] [Output]))); + _for_each_inner_gpio!((11, GPIO11() () ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12() () ([Input] [Output]))); + _for_each_inner_gpio!((13, GPIO13() () ([Input] [Output]))); + _for_each_inner_gpio!((14, GPIO14() () ([Input] [Output]))); + _for_each_inner_gpio!((15, GPIO15() () ([Input] [Output]))); + _for_each_inner_gpio!((16, GPIO16(_2 => FSPICS0) (_0 => U0TXD _2 => FSPICS0) + ([Input] [Output]))); _for_each_inner_gpio!((17, GPIO17(_0 => U0RXD) (_2 => + FSPICS1) ([Input] [Output]))); _for_each_inner_gpio!((18, GPIO18(_0 => SDIO_CMD) + (_0 => SDIO_CMD _2 => FSPICS2) ([Input] [Output]))); _for_each_inner_gpio!((19, + GPIO19() (_0 => SDIO_CLK _2 => FSPICS3) ([Input] [Output]))); + _for_each_inner_gpio!((20, GPIO20(_0 => SDIO_DATA0) (_0 => SDIO_DATA0 _2 => + FSPICS4) ([Input] [Output]))); _for_each_inner_gpio!((21, GPIO21(_0 => + SDIO_DATA1) (_0 => SDIO_DATA1 _2 => FSPICS5) ([Input] [Output]))); + _for_each_inner_gpio!((22, GPIO22(_0 => SDIO_DATA2) (_0 => SDIO_DATA2) ([Input] + [Output]))); _for_each_inner_gpio!((23, GPIO23(_0 => SDIO_DATA3) (_0 => + SDIO_DATA3) ([Input] [Output]))); _for_each_inner_gpio!((24, GPIO24() (_0 => + SPICS0) ([Input] [Output]))); _for_each_inner_gpio!((25, GPIO25(_0 => SPIQ) (_0 + => SPIQ) ([Input] [Output]))); _for_each_inner_gpio!((26, GPIO26(_0 => SPIWP) (_0 + => SPIWP) ([Input] [Output]))); _for_each_inner_gpio!((27, GPIO27() () ([Input] + [Output]))); _for_each_inner_gpio!((28, GPIO28(_0 => SPIHD) (_0 => SPIHD) + ([Input] [Output]))); _for_each_inner_gpio!((29, GPIO29() (_0 => SPICLK) ([Input] + [Output]))); _for_each_inner_gpio!((30, GPIO30(_0 => SPID) (_0 => SPID) ([Input] + [Output]))); _for_each_inner_gpio!((all(0, GPIO0() () ([Input] [Output])), (1, + GPIO1() () ([Input] [Output])), (2, GPIO2(_2 => FSPIQ) (_2 => FSPIQ) ([Input] + [Output])), (3, GPIO3() () ([Input] [Output])), (4, GPIO4(_0 => MTMS _2 => + FSPIHD) (_2 => FSPIHD) ([Input] [Output])), (5, GPIO5(_0 => MTDI _2 => FSPIWP) + (_2 => FSPIWP) ([Input] [Output])), (6, GPIO6(_0 => MTCK _2 => FSPICLK) (_2 => + FSPICLK) ([Input] [Output])), (7, GPIO7(_2 => FSPID) (_0 => MTDO _2 => FSPID) + ([Input] [Output])), (8, GPIO8() () ([Input] [Output])), (9, GPIO9() () ([Input] + [Output])), (10, GPIO10() () ([Input] [Output])), (11, GPIO11() () ([Input] + [Output])), (12, GPIO12() () ([Input] [Output])), (13, GPIO13() () ([Input] + [Output])), (14, GPIO14() () ([Input] [Output])), (15, GPIO15() () ([Input] + [Output])), (16, GPIO16(_2 => FSPICS0) (_0 => U0TXD _2 => FSPICS0) ([Input] + [Output])), (17, GPIO17(_0 => U0RXD) (_2 => FSPICS1) ([Input] [Output])), (18, + GPIO18(_0 => SDIO_CMD) (_0 => SDIO_CMD _2 => FSPICS2) ([Input] [Output])), (19, + GPIO19() (_0 => SDIO_CLK _2 => FSPICS3) ([Input] [Output])), (20, GPIO20(_0 => + SDIO_DATA0) (_0 => SDIO_DATA0 _2 => FSPICS4) ([Input] [Output])), (21, GPIO21(_0 + => SDIO_DATA1) (_0 => SDIO_DATA1 _2 => FSPICS5) ([Input] [Output])), (22, + GPIO22(_0 => SDIO_DATA2) (_0 => SDIO_DATA2) ([Input] [Output])), (23, GPIO23(_0 + => SDIO_DATA3) (_0 => SDIO_DATA3) ([Input] [Output])), (24, GPIO24() (_0 => + SPICS0) ([Input] [Output])), (25, GPIO25(_0 => SPIQ) (_0 => SPIQ) ([Input] + [Output])), (26, GPIO26(_0 => SPIWP) (_0 => SPIWP) ([Input] [Output])), (27, + GPIO27() () ([Input] [Output])), (28, GPIO28(_0 => SPIHD) (_0 => SPIHD) ([Input] + [Output])), (29, GPIO29() (_0 => SPICLK) ([Input] [Output])), (30, GPIO30(_0 => + SPID) (_0 => SPID) ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((XTAL_32K_P, GPIO0)); + _for_each_inner_analog_function!((ADC0_CH0, GPIO0)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO1)); + _for_each_inner_analog_function!((ADC0_CH1, GPIO1)); + _for_each_inner_analog_function!((ADC0_CH2, GPIO2)); + _for_each_inner_analog_function!((ADC0_CH3, GPIO3)); + _for_each_inner_analog_function!((ADC0_CH4, GPIO4)); + _for_each_inner_analog_function!((ADC0_CH5, GPIO5)); + _for_each_inner_analog_function!((ADC0_CH6, GPIO6)); + _for_each_inner_analog_function!((USB_DM, GPIO12)); + _for_each_inner_analog_function!((USB_DP, GPIO13)); + _for_each_inner_analog_function!(((ADC0_CH0, ADCn_CHm, 0, 0), GPIO0)); + _for_each_inner_analog_function!(((ADC0_CH1, ADCn_CHm, 0, 1), GPIO1)); + _for_each_inner_analog_function!(((ADC0_CH2, ADCn_CHm, 0, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC0_CH3, ADCn_CHm, 0, 3), GPIO3)); + _for_each_inner_analog_function!(((ADC0_CH4, ADCn_CHm, 0, 4), GPIO4)); + _for_each_inner_analog_function!(((ADC0_CH5, ADCn_CHm, 0, 5), GPIO5)); + _for_each_inner_analog_function!(((ADC0_CH6, ADCn_CHm, 0, 6), GPIO6)); + _for_each_inner_analog_function!((all(XTAL_32K_P, GPIO0), (ADC0_CH0, GPIO0), + (XTAL_32K_N, GPIO1), (ADC0_CH1, GPIO1), (ADC0_CH2, GPIO2), (ADC0_CH3, GPIO3), + (ADC0_CH4, GPIO4), (ADC0_CH5, GPIO5), (ADC0_CH6, GPIO6), (USB_DM, GPIO12), + (USB_DP, GPIO13))); _for_each_inner_analog_function!((all_expanded((ADC0_CH0, + ADCn_CHm, 0, 0), GPIO0), ((ADC0_CH1, ADCn_CHm, 0, 1), GPIO1), ((ADC0_CH2, + ADCn_CHm, 0, 2), GPIO2), ((ADC0_CH3, ADCn_CHm, 0, 3), GPIO3), ((ADC0_CH4, + ADCn_CHm, 0, 4), GPIO4), ((ADC0_CH5, ADCn_CHm, 0, 5), GPIO5), ((ADC0_CH6, + ADCn_CHm, 0, 6), GPIO6))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((LP_GPIO0, GPIO0)); + _for_each_inner_lp_function!((LP_UART_DTRN, GPIO0)); + _for_each_inner_lp_function!((LP_GPIO1, GPIO1)); + _for_each_inner_lp_function!((LP_UART_DSRN, GPIO1)); + _for_each_inner_lp_function!((LP_GPIO2, GPIO2)); + _for_each_inner_lp_function!((LP_UART_RTSN, GPIO2)); + _for_each_inner_lp_function!((LP_GPIO3, GPIO3)); + _for_each_inner_lp_function!((LP_UART_CTSN, GPIO3)); + _for_each_inner_lp_function!((LP_GPIO4, GPIO4)); + _for_each_inner_lp_function!((LP_UART_RXD, GPIO4)); + _for_each_inner_lp_function!((LP_GPIO5, GPIO5)); + _for_each_inner_lp_function!((LP_UART_TXD, GPIO5)); + _for_each_inner_lp_function!((LP_GPIO6, GPIO6)); + _for_each_inner_lp_function!((LP_I2C_SDA, GPIO6)); + _for_each_inner_lp_function!((LP_GPIO7, GPIO7)); + _for_each_inner_lp_function!((LP_I2C_SCL, GPIO7)); + _for_each_inner_lp_function!(((LP_GPIO0, LP_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((LP_GPIO1, LP_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((LP_GPIO2, LP_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((LP_GPIO3, LP_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((LP_GPIO4, LP_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((LP_GPIO5, LP_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!(((LP_GPIO6, LP_GPIOn, 6), GPIO6)); + _for_each_inner_lp_function!(((LP_GPIO7, LP_GPIOn, 7), GPIO7)); + _for_each_inner_lp_function!((all(LP_GPIO0, GPIO0), (LP_UART_DTRN, GPIO0), + (LP_GPIO1, GPIO1), (LP_UART_DSRN, GPIO1), (LP_GPIO2, GPIO2), (LP_UART_RTSN, + GPIO2), (LP_GPIO3, GPIO3), (LP_UART_CTSN, GPIO3), (LP_GPIO4, GPIO4), + (LP_UART_RXD, GPIO4), (LP_GPIO5, GPIO5), (LP_UART_TXD, GPIO5), (LP_GPIO6, GPIO6), + (LP_I2C_SDA, GPIO6), (LP_GPIO7, GPIO7), (LP_I2C_SCL, GPIO7))); + _for_each_inner_lp_function!((all_expanded((LP_GPIO0, LP_GPIOn, 0), GPIO0), + ((LP_GPIO1, LP_GPIOn, 1), GPIO1), ((LP_GPIO2, LP_GPIOn, 2), GPIO2), ((LP_GPIO3, + LP_GPIOn, 3), GPIO3), ((LP_GPIO4, LP_GPIOn, 4), GPIO4), ((LP_GPIO5, LP_GPIOn, 5), + GPIO5), ((LP_GPIO6, LP_GPIOn, 6), GPIO6), ((LP_GPIO7, LP_GPIOn, 7), GPIO7))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + EXT_ADC_START = 0, + U0RXD = 6, + U0CTS = 7, + U0DSR = 8, + U1RXD = 9, + U1CTS = 10, + U1DSR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SI_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + USB_JTAG_TDO_BRIDGE = 19, + CPU_TESTBUS0 = 20, + CPU_TESTBUS1 = 21, + CPU_TESTBUS2 = 22, + CPU_TESTBUS3 = 23, + CPU_TESTBUS4 = 24, + CPU_TESTBUS5 = 25, + CPU_TESTBUS6 = 26, + CPU_TESTBUS7 = 27, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + USB_JTAG_TMS = 37, + USB_EXTPHY_OEN = 40, + USB_EXTPHY_VM = 41, + USB_EXTPHY_VPO = 42, + I2CEXT0_SCL = 45, + I2CEXT0_SDA = 46, + PARL_RX_DATA0 = 47, + PARL_RX_DATA1 = 48, + PARL_RX_DATA2 = 49, + PARL_RX_DATA3 = 50, + PARL_RX_DATA4 = 51, + PARL_RX_DATA5 = 52, + PARL_RX_DATA6 = 53, + PARL_RX_DATA7 = 54, + PARL_RX_DATA8 = 55, + PARL_RX_DATA9 = 56, + PARL_RX_DATA10 = 57, + PARL_RX_DATA11 = 58, + PARL_RX_DATA12 = 59, + PARL_RX_DATA13 = 60, + PARL_RX_DATA14 = 61, + PARL_RX_DATA15 = 62, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + PARL_RX_CLK = 69, + PARL_TX_CLK = 70, + RMT_SIG_0 = 71, + RMT_SIG_1 = 72, + TWAI0_RX = 73, + TWAI1_RX = 77, + PWM0_SYNC0 = 87, + PWM0_SYNC1 = 88, + PWM0_SYNC2 = 89, + PWM0_F0 = 90, + PWM0_F1 = 91, + PWM0_F2 = 92, + PWM0_CAP0 = 93, + PWM0_CAP1 = 94, + PWM0_CAP2 = 95, + SIG_IN_FUNC97 = 97, + SIG_IN_FUNC98 = 98, + SIG_IN_FUNC99 = 99, + SIG_IN_FUNC100 = 100, + PCNT0_SIG_CH0 = 101, + PCNT0_SIG_CH1 = 102, + PCNT0_CTRL_CH0 = 103, + PCNT0_CTRL_CH1 = 104, + PCNT1_SIG_CH0 = 105, + PCNT1_SIG_CH1 = 106, + PCNT1_CTRL_CH0 = 107, + PCNT1_CTRL_CH1 = 108, + PCNT2_SIG_CH0 = 109, + PCNT2_SIG_CH1 = 110, + PCNT2_CTRL_CH0 = 111, + PCNT2_CTRL_CH1 = 112, + PCNT3_SIG_CH0 = 113, + PCNT3_SIG_CH1 = 114, + PCNT3_CTRL_CH0 = 115, + PCNT3_CTRL_CH1 = 116, + SPIQ = 121, + SPID = 122, + SPIHD = 123, + SPIWP = 124, + SDIO_CMD, + SDIO_DATA0, + SDIO_DATA1, + SDIO_DATA2, + SDIO_DATA3, + MTDI, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + LEDC_LS_SIG0 = 0, + LEDC_LS_SIG1 = 1, + LEDC_LS_SIG2 = 2, + LEDC_LS_SIG3 = 3, + LEDC_LS_SIG4 = 4, + LEDC_LS_SIG5 = 5, + U0TXD = 6, + U0RTS = 7, + U0DTR = 8, + U1TXD = 9, + U1RTS = 10, + U1DTR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SO_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + I2SO_SD1 = 18, + USB_JTAG_TDO_BRIDGE = 19, + CPU_TESTBUS0 = 20, + CPU_TESTBUS1 = 21, + CPU_TESTBUS2 = 22, + CPU_TESTBUS3 = 23, + CPU_TESTBUS4 = 24, + CPU_TESTBUS5 = 25, + CPU_TESTBUS6 = 26, + CPU_TESTBUS7 = 27, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + USB_JTAG_TCK = 36, + USB_JTAG_TMS = 37, + USB_JTAG_TDI = 38, + USB_JTAG_TDO = 39, + I2CEXT0_SCL = 45, + I2CEXT0_SDA = 46, + PARL_TX_DATA0 = 47, + PARL_TX_DATA1 = 48, + PARL_TX_DATA2 = 49, + PARL_TX_DATA3 = 50, + PARL_TX_DATA4 = 51, + PARL_TX_DATA5 = 52, + PARL_TX_DATA6 = 53, + PARL_TX_DATA7 = 54, + PARL_TX_DATA8 = 55, + PARL_TX_DATA9 = 56, + PARL_TX_DATA10 = 57, + PARL_TX_DATA11 = 58, + PARL_TX_DATA12 = 59, + PARL_TX_DATA13 = 60, + PARL_TX_DATA14 = 61, + PARL_TX_DATA15 = 62, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + SDIO_TOHOST_INT = 69, + PARL_TX_CLK = 70, + RMT_SIG_0 = 71, + RMT_SIG_1 = 72, + TWAI0_TX = 73, + TWAI0_BUS_OFF_ON = 74, + TWAI0_CLKOUT = 75, + TWAI0_STANDBY = 76, + TWAI1_TX = 77, + TWAI1_BUS_OFF_ON = 78, + TWAI1_CLKOUT = 79, + TWAI1_STANDBY = 80, + GPIO_SD0 = 83, + GPIO_SD1 = 84, + GPIO_SD2 = 85, + GPIO_SD3 = 86, + PWM0_0A = 87, + PWM0_0B = 88, + PWM0_1A = 89, + PWM0_1B = 90, + PWM0_2A = 91, + PWM0_2B = 92, + SIG_IN_FUNC97 = 97, + SIG_IN_FUNC98 = 98, + SIG_IN_FUNC99 = 99, + SIG_IN_FUNC100 = 100, + FSPICS1 = 101, + FSPICS2 = 102, + FSPICS3 = 103, + FSPICS4 = 104, + FSPICS5 = 105, + SPICLK = 114, + SPICS0 = 115, + SPICS1 = 116, + GPIO_TASK_MATRIX_OUT0 = 117, + GPIO_TASK_MATRIX_OUT1 = 118, + GPIO_TASK_MATRIX_OUT2 = 119, + GPIO_TASK_MATRIX_OUT3 = 120, + SPIQ = 121, + SPID = 122, + SPIHD = 123, + SPIWP = 124, + CLK_OUT_OUT1 = 125, + CLK_OUT_OUT2 = 126, + CLK_OUT_OUT3 = 127, + GPIO = 128, + SDIO_CLK, + SDIO_CMD, + SDIO_DATA0, + SDIO_DATA1, + SDIO_DATA2, + SDIO_DATA3, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs new file mode 100644 index 00000000000..4779154bd44 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -0,0 +1,4165 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32h2" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-H2" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32h2" + }; + ("arch") => { + "riscv" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-h2_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + false + }; + ("assist_debug.has_sp_monitor") => { + true + }; + ("assist_debug.has_region_monitor") => { + true + }; + ("bt.controller") => { + "npl" + }; + ("dedicated_gpio.needs_initialization") => { + false + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + true + }; + ("dma.max_priority") => { + 5 + }; + ("dma.max_priority", str) => { + stringify!(5) + }; + ("dma.gdma_version") => { + 1 + }; + ("dma.gdma_version", str) => { + stringify!(1) + }; + ("ecc.zero_extend_writes") => { + true + }; + ("ecc.separate_jacobian_point_memory") => { + true + }; + ("ecc.has_memory_clock_gate") => { + true + }; + ("ecc.supports_enhanced_security") => { + true + }; + ("ecc.mem_block_size") => { + 32 + }; + ("gpio.has_bank_1") => { + false + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 60 + }; + ("gpio.constant_0_input", str) => { + stringify!(60) + }; + ("gpio.constant_1_input") => { + 56 + }; + ("gpio.constant_1_input", str) => { + stringify!(56) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 124 + }; + ("gpio.input_signal_max", str) => { + stringify!(124) + }; + ("gpio.output_signal_max") => { + 128 + }; + ("gpio.output_signal_max", str) => { + stringify!(128) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + true + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + true + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + true + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 2 + }; + ("interrupts.status_registers", str) => { + stringify!(2) + }; + ("interrupts.disabled_interrupt") => { + 31 + }; + ("parl_io.version") => { + 2 + }; + ("parl_io.version", str) => { + stringify!(2) + }; + ("phy.combo_module") => { + false + }; + ("rmt.ram_start") => { + 1610642432 + }; + ("rmt.ram_start", str) => { + stringify!(1610642432) + }; + ("rmt.channel_ram_size") => { + 48 + }; + ("rmt.channel_ram_size", str) => { + stringify!(48) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + true + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + true + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + false + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 384 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(384) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + true + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.cpu_csr_prv_mode") => { + 3088 + }; + ("soc.cpu_csr_prv_mode", str) => { + stringify!(3088) + }; + ("soc.rc_fast_clk_default") => { + 8500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(8500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + false + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + false + }; + ("timergroup.timg_has_divcnt_rst") => { + true + }; + ("timergroup.rc_fast_calibration_divider_min_rev") => { + 2 + }; + ("timergroup.rc_fast_calibration_divider") => { + 32 + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + true + }; + ("uhci.combined_uart_selector_field") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((256)); _for_each_inner_aes_key_length!((128, 0, + 4)); _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + CPU_GPIO_0)); _for_each_inner_dedicated_gpio!((0, 1, CPU_GPIO_1)); + _for_each_inner_dedicated_gpio!((0, 2, CPU_GPIO_2)); + _for_each_inner_dedicated_gpio!((0, 3, CPU_GPIO_3)); + _for_each_inner_dedicated_gpio!((0, 4, CPU_GPIO_4)); + _for_each_inner_dedicated_gpio!((0, 5, CPU_GPIO_5)); + _for_each_inner_dedicated_gpio!((0, 6, CPU_GPIO_6)); + _for_each_inner_dedicated_gpio!((0, 7, CPU_GPIO_7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, CPU_GPIO_0), (0, 1, + CPU_GPIO_1), (0, 2, CPU_GPIO_2), (0, 3, CPU_GPIO_3), (0, 4, CPU_GPIO_4), (0, 5, + CPU_GPIO_5), (0, 6, CPU_GPIO_6), (0, 7, CPU_GPIO_7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_working_mode { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_working_mode { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_ecc_working_mode!((0, AffinePointMultiplication)); + _for_each_inner_ecc_working_mode!((2, AffinePointVerification)); + _for_each_inner_ecc_working_mode!((3, AffinePointVerificationAndMultiplication)); + _for_each_inner_ecc_working_mode!((4, JacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((5, AffinePointAddition)); + _for_each_inner_ecc_working_mode!((6, JacobianPointVerification)); + _for_each_inner_ecc_working_mode!((7, + AffinePointVerificationAndJacobianPointMultiplication)); + _for_each_inner_ecc_working_mode!((8, ModularAddition)); + _for_each_inner_ecc_working_mode!((9, ModularSubtraction)); + _for_each_inner_ecc_working_mode!((10, ModularMultiplication)); + _for_each_inner_ecc_working_mode!((11, ModularDivision)); + _for_each_inner_ecc_working_mode!((all(0, AffinePointMultiplication), (2, + AffinePointVerification), (3, AffinePointVerificationAndMultiplication), (4, + JacobianPointMultiplication), (5, AffinePointAddition), (6, + JacobianPointVerification), (7, + AffinePointVerificationAndJacobianPointMultiplication), (8, ModularAddition), (9, + ModularSubtraction), (10, ModularMultiplication), (11, ModularDivision))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_ecc_curve { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_ecc_curve { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_ecc_curve!((0, P192, 192)); + _for_each_inner_ecc_curve!((1, P256, 256)); _for_each_inner_ecc_curve!((all(0, + P192, 192), (1, P256, 256))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_interrupt!(([reserved 0] 0)); + _for_each_inner_interrupt!(([direct_bindable 0] 1)); + _for_each_inner_interrupt!(([direct_bindable 1] 2)); + _for_each_inner_interrupt!(([reserved 1] 3)); + _for_each_inner_interrupt!(([reserved 2] 4)); + _for_each_inner_interrupt!(([direct_bindable 2] 5)); + _for_each_inner_interrupt!(([direct_bindable 3] 6)); + _for_each_inner_interrupt!(([reserved 3] 7)); + _for_each_inner_interrupt!(([direct_bindable 4] 8)); + _for_each_inner_interrupt!(([direct_bindable 5] 9)); + _for_each_inner_interrupt!(([direct_bindable 6] 10)); + _for_each_inner_interrupt!(([direct_bindable 7] 11)); + _for_each_inner_interrupt!(([direct_bindable 8] 12)); + _for_each_inner_interrupt!(([direct_bindable 9] 13)); + _for_each_inner_interrupt!(([direct_bindable 10] 14)); + _for_each_inner_interrupt!(([direct_bindable 11] 15)); + _for_each_inner_interrupt!(([vector 0] 16)); _for_each_inner_interrupt!(([vector + 1] 17)); _for_each_inner_interrupt!(([vector 2] 18)); + _for_each_inner_interrupt!(([vector 3] 19)); _for_each_inner_interrupt!(([vector + 4] 20)); _for_each_inner_interrupt!(([vector 5] 21)); + _for_each_inner_interrupt!(([vector 6] 22)); _for_each_inner_interrupt!(([vector + 7] 23)); _for_each_inner_interrupt!(([vector 8] 24)); + _for_each_inner_interrupt!(([vector 9] 25)); _for_each_inner_interrupt!(([vector + 10] 26)); _for_each_inner_interrupt!(([vector 11] 27)); + _for_each_inner_interrupt!(([vector 12] 28)); _for_each_inner_interrupt!(([vector + 13] 29)); _for_each_inner_interrupt!(([vector 14] 30)); + _for_each_inner_interrupt!(([disabled 0] 31)); + _for_each_inner_interrupt!((all([reserved 0] 0), ([direct_bindable 0] 1), + ([direct_bindable 1] 2), ([reserved 1] 3), ([reserved 2] 4), ([direct_bindable 2] + 5), ([direct_bindable 3] 6), ([reserved 3] 7), ([direct_bindable 4] 8), + ([direct_bindable 5] 9), ([direct_bindable 6] 10), ([direct_bindable 7] 11), + ([direct_bindable 8] 12), ([direct_bindable 9] 13), ([direct_bindable 10] 14), + ([direct_bindable 11] 15), ([vector 0] 16), ([vector 1] 17), ([vector 2] 18), + ([vector 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] 22), ([vector 7] + 23), ([vector 8] 24), ([vector 9] 25), ([vector 10] 26), ([vector 11] 27), + ([vector 12] 28), ([vector 13] 29), ([vector 14] 30), ([disabled 0] 31))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_classified_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_classified_interrupt { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_classified_interrupt!(([direct_bindable 0] + 1)); _for_each_inner_classified_interrupt!(([direct_bindable 1] 2)); + _for_each_inner_classified_interrupt!(([direct_bindable 2] 5)); + _for_each_inner_classified_interrupt!(([direct_bindable 3] 6)); + _for_each_inner_classified_interrupt!(([direct_bindable 4] 8)); + _for_each_inner_classified_interrupt!(([direct_bindable 5] 9)); + _for_each_inner_classified_interrupt!(([direct_bindable 6] 10)); + _for_each_inner_classified_interrupt!(([direct_bindable 7] 11)); + _for_each_inner_classified_interrupt!(([direct_bindable 8] 12)); + _for_each_inner_classified_interrupt!(([direct_bindable 9] 13)); + _for_each_inner_classified_interrupt!(([direct_bindable 10] 14)); + _for_each_inner_classified_interrupt!(([direct_bindable 11] 15)); + _for_each_inner_classified_interrupt!(([vector 0] 16)); + _for_each_inner_classified_interrupt!(([vector 1] 17)); + _for_each_inner_classified_interrupt!(([vector 2] 18)); + _for_each_inner_classified_interrupt!(([vector 3] 19)); + _for_each_inner_classified_interrupt!(([vector 4] 20)); + _for_each_inner_classified_interrupt!(([vector 5] 21)); + _for_each_inner_classified_interrupt!(([vector 6] 22)); + _for_each_inner_classified_interrupt!(([vector 7] 23)); + _for_each_inner_classified_interrupt!(([vector 8] 24)); + _for_each_inner_classified_interrupt!(([vector 9] 25)); + _for_each_inner_classified_interrupt!(([vector 10] 26)); + _for_each_inner_classified_interrupt!(([vector 11] 27)); + _for_each_inner_classified_interrupt!(([vector 12] 28)); + _for_each_inner_classified_interrupt!(([vector 13] 29)); + _for_each_inner_classified_interrupt!(([vector 14] 30)); + _for_each_inner_classified_interrupt!(([reserved 0] 0)); + _for_each_inner_classified_interrupt!(([reserved 1] 3)); + _for_each_inner_classified_interrupt!(([reserved 2] 4)); + _for_each_inner_classified_interrupt!(([reserved 3] 7)); + _for_each_inner_classified_interrupt!((direct_bindable([direct_bindable 0] 1), + ([direct_bindable 1] 2), ([direct_bindable 2] 5), ([direct_bindable 3] 6), + ([direct_bindable 4] 8), ([direct_bindable 5] 9), ([direct_bindable 6] 10), + ([direct_bindable 7] 11), ([direct_bindable 8] 12), ([direct_bindable 9] 13), + ([direct_bindable 10] 14), ([direct_bindable 11] 15))); + _for_each_inner_classified_interrupt!((vector([vector 0] 16), ([vector 1] 17), + ([vector 2] 18), ([vector 3] 19), ([vector 4] 20), ([vector 5] 21), ([vector 6] + 22), ([vector 7] 23), ([vector 8] 24), ([vector 9] 25), ([vector 10] 26), + ([vector 11] 27), ([vector 12] 28), ([vector 13] 29), ([vector 14] 30))); + _for_each_inner_classified_interrupt!((reserved([reserved 0] 0), ([reserved 1] + 3), ([reserved 2] 4), ([reserved 3] 7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_interrupt_priority { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_interrupt_priority { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_interrupt_priority!((0, 1, Priority1)); + _for_each_inner_interrupt_priority!((1, 2, Priority2)); + _for_each_inner_interrupt_priority!((2, 3, Priority3)); + _for_each_inner_interrupt_priority!((3, 4, Priority4)); + _for_each_inner_interrupt_priority!((4, 5, Priority5)); + _for_each_inner_interrupt_priority!((5, 6, Priority6)); + _for_each_inner_interrupt_priority!((6, 7, Priority7)); + _for_each_inner_interrupt_priority!((7, 8, Priority8)); + _for_each_inner_interrupt_priority!((8, 9, Priority9)); + _for_each_inner_interrupt_priority!((9, 10, Priority10)); + _for_each_inner_interrupt_priority!((10, 11, Priority11)); + _for_each_inner_interrupt_priority!((11, 12, Priority12)); + _for_each_inner_interrupt_priority!((12, 13, Priority13)); + _for_each_inner_interrupt_priority!((13, 14, Priority14)); + _for_each_inner_interrupt_priority!((14, 15, Priority15)); + _for_each_inner_interrupt_priority!((all(0, 1, Priority1), (1, 2, Priority2), (2, + 3, Priority3), (3, 4, Priority4), (4, 5, Priority5), (5, 6, Priority6), (6, 7, + Priority7), (7, 8, Priority8), (8, 9, Priority9), (9, 10, Priority10), (10, 11, + Priority11), (11, 12, Priority12), (12, 13, Priority13), (13, 14, Priority14), + (14, 15, Priority15))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe { + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + ::core::arch::asm!("nop"); + } + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 0)); _for_each_inner_rmt_channel!((3, 1)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1))); + _for_each_inner_rmt_channel!((rx(2, 0), (3, 1))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((Xtal, 0)); + _for_each_inner_rmt_clock_source!((RcFast, 1)); + _for_each_inner_rmt_clock_source!((Xtal)); + _for_each_inner_rmt_clock_source!((all(Xtal, 0), (RcFast, 1))); + _for_each_inner_rmt_clock_source!((default(Xtal))); + _for_each_inner_rmt_clock_source!((is_boolean)); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_F96M_CLK +/// +/// fn enable_pll_f96m_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F64M_CLK +/// +/// fn enable_pll_f64m_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_F48M_CLK +/// +/// fn enable_pll_f48m_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // OSC_SLOW_CLK +/// +/// fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_LP_CLK +/// +/// fn enable_pll_lp_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // HP_ROOT_CLK +/// +/// fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_hp_root_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: HpRootClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn enable_cpu_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_clk_impl(_clocks: &mut ClockTree, _new_config: CpuClkConfig) { +/// todo!() +/// } +/// +/// // AHB_CLK +/// +/// fn enable_ahb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ahb_clk_impl(_clocks: &mut ClockTree, _new_config: AhbClkConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl(_clocks: &mut ClockTree, _new_config: ApbClkConfig) { +/// todo!() +/// } +/// +/// // XTAL_D2_CLK +/// +/// fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // LP_FAST_CLK +/// +/// fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LP_SLOW_CLK +/// +/// fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_lp_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LpSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // MCPWM0_FUNCTION_CLOCK +/// +/// fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_RX_CLOCK +/// +/// fn enable_parlio_rx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_rx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioRxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // PARLIO_TX_CLOCK +/// +/// fn enable_parlio_tx_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_parlio_tx_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ParlioTxClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // RMT_SCLK +/// +/// fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rmt_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RmtSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_WDT_CLOCK +/// +/// fn enable_timg0_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_WDT_CLOCK +/// +/// fn enable_timg1_wdt_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_wdt_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0WdtClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 32 MHz + _32, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_32 => 32000000, + } + } + } + /// The list of clock signals that the `HP_ROOT_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum HpRootClkConfig { + /// Selects `PLL_F96M_CLK`. + Pll96, + /// Selects `PLL_F64M_CLK`. + Pll64, + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `CPU_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct CpuClkConfig { + divisor: u32, + } + impl CpuClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`CPU_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `AHB_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = HP_ROOT_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct AhbClkConfig { + divisor: u32, + } + impl AhbClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`AHB_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `APB_CLK` clock divider. + /// + /// The output is calculated as `OUTPUT = AHB_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct ApbClkConfig { + divisor: u32, + } + impl ApbClkConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`APB_CLK` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `LP_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpFastClkConfig { + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `PLL_LP_CLK`. + PllLpClk, + /// Selects `XTAL_D2_CLK`. + XtalD2Clk, + } + /// The list of clock signals that the `LP_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LpSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `OSC_SLOW_CLK`. + OscSlow, + } + /// The list of clock signals that the `MCPWM0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Mcpwm0FunctionClockConfig { + #[default] + /// Selects `PLL_F96M_CLK`. + PllF96m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `PARLIO_RX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioRxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F96M_CLK`. + PllF96m, + } + /// The list of clock signals that the `PARLIO_TX_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ParlioTxClockConfig { + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + #[default] + /// Selects `PLL_F96M_CLK`. + PllF96m, + } + /// The list of clock signals that the `RMT_SCLK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RmtSclkConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `PLL_F48M_CLK`. + PllF48m, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + /// Selects `LP_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `TIMG0_WDT_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0WdtClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `PLL_F48M_CLK`. + PllF48m, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + /// Selects `PLL_F48M_CLK`. + PllF48m, + /// Selects `RC_FAST_CLK`. + RcFast, + #[default] + /// Selects `XTAL_CLK`. + Xtal, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + hp_root_clk: Option, + cpu_clk: Option, + ahb_clk: Option, + apb_clk: Option, + lp_fast_clk: Option, + lp_slow_clk: Option, + mcpwm0_function_clock: Option, + parlio_rx_clock: Option, + parlio_tx_clock: Option, + rmt_sclk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg0_wdt_clock: Option, + timg1_function_clock: Option, + timg1_calibration_clock: Option, + timg1_wdt_clock: Option, + uart0_function_clock: Option, + uart1_function_clock: Option, + pll_f96m_clk_refcount: u32, + pll_f48m_clk_refcount: u32, + rc_fast_clk_refcount: u32, + xtal32k_clk_refcount: u32, + hp_root_clk_refcount: u32, + cpu_clk_refcount: u32, + apb_clk_refcount: u32, + lp_fast_clk_refcount: u32, + lp_slow_clk_refcount: u32, + mcpwm0_function_clock_refcount: u32, + parlio_rx_clock_refcount: u32, + parlio_tx_clock_refcount: u32, + rmt_sclk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg0_wdt_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + timg1_wdt_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart1_function_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the HP_ROOT_CLK clock tree node + pub fn hp_root_clk(&self) -> Option { + self.hp_root_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the AHB_CLK clock tree node + pub fn ahb_clk(&self) -> Option { + self.ahb_clk + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the LP_FAST_CLK clock tree node + pub fn lp_fast_clk(&self) -> Option { + self.lp_fast_clk + } + /// Returns the current configuration of the LP_SLOW_CLK clock tree node + pub fn lp_slow_clk(&self) -> Option { + self.lp_slow_clk + } + /// Returns the current configuration of the MCPWM0_FUNCTION_CLOCK clock tree node + pub fn mcpwm0_function_clock(&self) -> Option { + self.mcpwm0_function_clock + } + /// Returns the current configuration of the PARLIO_RX_CLOCK clock tree node + pub fn parlio_rx_clock(&self) -> Option { + self.parlio_rx_clock + } + /// Returns the current configuration of the PARLIO_TX_CLOCK clock tree node + pub fn parlio_tx_clock(&self) -> Option { + self.parlio_tx_clock + } + /// Returns the current configuration of the RMT_SCLK clock tree node + pub fn rmt_sclk(&self) -> Option { + self.rmt_sclk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG0_WDT_CLOCK clock tree node + pub fn timg0_wdt_clock(&self) -> Option { + self.timg0_wdt_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the TIMG1_WDT_CLOCK clock tree node + pub fn timg1_wdt_clock(&self) -> Option { + self.timg1_wdt_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + hp_root_clk: None, + cpu_clk: None, + ahb_clk: None, + apb_clk: None, + lp_fast_clk: None, + lp_slow_clk: None, + mcpwm0_function_clock: None, + parlio_rx_clock: None, + parlio_tx_clock: None, + rmt_sclk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg0_wdt_clock: None, + timg1_function_clock: None, + timg1_calibration_clock: None, + timg1_wdt_clock: None, + uart0_function_clock: None, + uart1_function_clock: None, + pll_f96m_clk_refcount: 0, + pll_f48m_clk_refcount: 0, + rc_fast_clk_refcount: 0, + xtal32k_clk_refcount: 0, + hp_root_clk_refcount: 0, + cpu_clk_refcount: 0, + apb_clk_refcount: 0, + lp_fast_clk_refcount: 0, + lp_slow_clk_refcount: 0, + mcpwm0_function_clock_refcount: 0, + parlio_rx_clock_refcount: 0, + parlio_tx_clock_refcount: 0, + rmt_sclk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg0_wdt_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + timg1_wdt_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart1_function_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn request_pll_f96m_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_F96M_CLK"); + if increment_reference_count(&mut clocks.pll_f96m_clk_refcount) { + trace!("Enabling PLL_F96M_CLK"); + request_xtal_clk(clocks); + enable_pll_f96m_clk_impl(clocks, true); + } + } + pub fn release_pll_f96m_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_F96M_CLK"); + if decrement_reference_count(&mut clocks.pll_f96m_clk_refcount) { + trace!("Disabling PLL_F96M_CLK"); + enable_pll_f96m_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_f96m_clk_frequency(clocks: &mut ClockTree) -> u32 { + 96000000 + } + pub fn request_pll_f64m_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_F64M_CLK"); + trace!("Enabling PLL_F64M_CLK"); + request_pll_f96m_clk(clocks); + enable_pll_f64m_clk_impl(clocks, true); + } + pub fn release_pll_f64m_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_F64M_CLK"); + trace!("Disabling PLL_F64M_CLK"); + enable_pll_f64m_clk_impl(clocks, false); + release_pll_f96m_clk(clocks); + } + pub fn pll_f64m_clk_frequency(clocks: &mut ClockTree) -> u32 { + ((pll_f96m_clk_frequency(clocks) * 2) / 3) + } + pub fn request_pll_f48m_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_F48M_CLK"); + if increment_reference_count(&mut clocks.pll_f48m_clk_refcount) { + trace!("Enabling PLL_F48M_CLK"); + request_pll_f96m_clk(clocks); + enable_pll_f48m_clk_impl(clocks, true); + } + } + pub fn release_pll_f48m_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_F48M_CLK"); + if decrement_reference_count(&mut clocks.pll_f48m_clk_refcount) { + trace!("Disabling PLL_F48M_CLK"); + enable_pll_f48m_clk_impl(clocks, false); + release_pll_f96m_clk(clocks); + } + } + pub fn pll_f48m_clk_frequency(clocks: &mut ClockTree) -> u32 { + (pll_f96m_clk_frequency(clocks) / 2) + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 8000000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting OSC_SLOW_CLK"); + trace!("Enabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, true); + } + pub fn release_osc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing OSC_SLOW_CLK"); + trace!("Disabling OSC_SLOW_CLK"); + enable_osc_slow_clk_impl(clocks, false); + } + pub fn osc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 130000 + } + pub fn request_pll_lp_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_LP_CLK"); + trace!("Enabling PLL_LP_CLK"); + request_xtal32k_clk(clocks); + enable_pll_lp_clk_impl(clocks, true); + } + pub fn release_pll_lp_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_LP_CLK"); + trace!("Disabling PLL_LP_CLK"); + enable_pll_lp_clk_impl(clocks, false); + release_xtal32k_clk(clocks); + } + pub fn pll_lp_clk_frequency(clocks: &mut ClockTree) -> u32 { + 8000000 + } + pub fn configure_hp_root_clk(clocks: &mut ClockTree, new_selector: HpRootClkConfig) { + let old_selector = clocks.hp_root_clk.replace(new_selector); + if clocks.hp_root_clk_refcount > 0 { + match new_selector { + HpRootClkConfig::Pll96 => request_pll_f96m_clk(clocks), + HpRootClkConfig::Pll64 => request_pll_f64m_clk(clocks), + HpRootClkConfig::Xtal => request_xtal_clk(clocks), + HpRootClkConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_hp_root_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + HpRootClkConfig::Pll96 => release_pll_f96m_clk(clocks), + HpRootClkConfig::Pll64 => release_pll_f64m_clk(clocks), + HpRootClkConfig::Xtal => release_xtal_clk(clocks), + HpRootClkConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } else { + configure_hp_root_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn hp_root_clk_config(clocks: &mut ClockTree) -> Option { + clocks.hp_root_clk + } + pub fn request_hp_root_clk(clocks: &mut ClockTree) { + trace!("Requesting HP_ROOT_CLK"); + if increment_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Enabling HP_ROOT_CLK"); + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Pll96 => request_pll_f96m_clk(clocks), + HpRootClkConfig::Pll64 => request_pll_f64m_clk(clocks), + HpRootClkConfig::Xtal => request_xtal_clk(clocks), + HpRootClkConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_hp_root_clk_impl(clocks, true); + } + } + pub fn release_hp_root_clk(clocks: &mut ClockTree) { + trace!("Releasing HP_ROOT_CLK"); + if decrement_reference_count(&mut clocks.hp_root_clk_refcount) { + trace!("Disabling HP_ROOT_CLK"); + enable_hp_root_clk_impl(clocks, false); + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Pll96 => release_pll_f96m_clk(clocks), + HpRootClkConfig::Pll64 => release_pll_f64m_clk(clocks), + HpRootClkConfig::Xtal => release_xtal_clk(clocks), + HpRootClkConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn hp_root_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.hp_root_clk) { + HpRootClkConfig::Pll96 => pll_f96m_clk_frequency(clocks), + HpRootClkConfig::Pll64 => pll_f64m_clk_frequency(clocks), + HpRootClkConfig::Xtal => xtal_clk_frequency(clocks), + HpRootClkConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, config: CpuClkConfig) { + clocks.cpu_clk = Some(config); + configure_cpu_clk_impl(clocks, config); + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + pub fn request_cpu_clk(clocks: &mut ClockTree) { + trace!("Requesting CPU_CLK"); + if increment_reference_count(&mut clocks.cpu_clk_refcount) { + trace!("Enabling CPU_CLK"); + request_hp_root_clk(clocks); + enable_cpu_clk_impl(clocks, true); + } + } + pub fn release_cpu_clk(clocks: &mut ClockTree) { + trace!("Releasing CPU_CLK"); + if decrement_reference_count(&mut clocks.cpu_clk_refcount) { + trace!("Disabling CPU_CLK"); + enable_cpu_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + } + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.cpu_clk).divisor() + 1)) + } + pub fn configure_ahb_clk(clocks: &mut ClockTree, config: AhbClkConfig) { + clocks.ahb_clk = Some(config); + configure_ahb_clk_impl(clocks, config); + } + pub fn ahb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.ahb_clk + } + pub fn request_ahb_clk(clocks: &mut ClockTree) { + trace!("Requesting AHB_CLK"); + trace!("Enabling AHB_CLK"); + request_hp_root_clk(clocks); + enable_ahb_clk_impl(clocks, true); + } + pub fn release_ahb_clk(clocks: &mut ClockTree) { + trace!("Releasing AHB_CLK"); + trace!("Disabling AHB_CLK"); + enable_ahb_clk_impl(clocks, false); + release_hp_root_clk(clocks); + } + pub fn ahb_clk_frequency(clocks: &mut ClockTree) -> u32 { + (hp_root_clk_frequency(clocks) / (unwrap!(clocks.ahb_clk).divisor() + 1)) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, config: ApbClkConfig) { + clocks.apb_clk = Some(config); + configure_apb_clk_impl(clocks, config); + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + request_ahb_clk(clocks); + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + release_ahb_clk(clocks); + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + (ahb_clk_frequency(clocks) / (unwrap!(clocks.apb_clk).divisor() + 1)) + } + pub fn request_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_D2_CLK"); + trace!("Enabling XTAL_D2_CLK"); + request_xtal_clk(clocks); + enable_xtal_d2_clk_impl(clocks, true); + } + pub fn release_xtal_d2_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_D2_CLK"); + trace!("Disabling XTAL_D2_CLK"); + enable_xtal_d2_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_d2_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_lp_fast_clk(clocks: &mut ClockTree, new_selector: LpFastClkConfig) { + let old_selector = clocks.lp_fast_clk.replace(new_selector); + if clocks.lp_fast_clk_refcount > 0 { + match new_selector { + LpFastClkConfig::RcFastClk => request_rc_fast_clk(clocks), + LpFastClkConfig::PllLpClk => request_pll_lp_clk(clocks), + LpFastClkConfig::XtalD2Clk => request_xtal_d2_clk(clocks), + } + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpFastClkConfig::RcFastClk => release_rc_fast_clk(clocks), + LpFastClkConfig::PllLpClk => release_pll_lp_clk(clocks), + LpFastClkConfig::XtalD2Clk => release_xtal_d2_clk(clocks), + } + } + } else { + configure_lp_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_fast_clk + } + pub fn request_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_FAST_CLK"); + if increment_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Enabling LP_FAST_CLK"); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => request_rc_fast_clk(clocks), + LpFastClkConfig::PllLpClk => request_pll_lp_clk(clocks), + LpFastClkConfig::XtalD2Clk => request_xtal_d2_clk(clocks), + } + enable_lp_fast_clk_impl(clocks, true); + } + } + pub fn release_lp_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_FAST_CLK"); + if decrement_reference_count(&mut clocks.lp_fast_clk_refcount) { + trace!("Disabling LP_FAST_CLK"); + enable_lp_fast_clk_impl(clocks, false); + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => release_rc_fast_clk(clocks), + LpFastClkConfig::PllLpClk => release_pll_lp_clk(clocks), + LpFastClkConfig::XtalD2Clk => release_xtal_d2_clk(clocks), + } + } + } + pub fn lp_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_fast_clk) { + LpFastClkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + LpFastClkConfig::PllLpClk => pll_lp_clk_frequency(clocks), + LpFastClkConfig::XtalD2Clk => xtal_d2_clk_frequency(clocks), + } + } + pub fn configure_lp_slow_clk(clocks: &mut ClockTree, new_selector: LpSlowClkConfig) { + let old_selector = clocks.lp_slow_clk.replace(new_selector); + if clocks.lp_slow_clk_refcount > 0 { + match new_selector { + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } else { + configure_lp_slow_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn lp_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.lp_slow_clk + } + pub fn request_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting LP_SLOW_CLK"); + if increment_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Enabling LP_SLOW_CLK"); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => request_osc_slow_clk(clocks), + } + enable_lp_slow_clk_impl(clocks, true); + } + } + pub fn release_lp_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing LP_SLOW_CLK"); + if decrement_reference_count(&mut clocks.lp_slow_clk_refcount) { + trace!("Disabling LP_SLOW_CLK"); + enable_lp_slow_clk_impl(clocks, false); + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LpSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + LpSlowClkConfig::OscSlow => release_osc_slow_clk(clocks), + } + } + } + pub fn lp_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.lp_slow_clk) { + LpSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + LpSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + LpSlowClkConfig::OscSlow => osc_slow_clk_frequency(clocks), + } + } + pub fn configure_mcpwm0_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm0_function_clock.replace(new_selector); + if clocks.mcpwm0_function_clock_refcount > 0 { + match new_selector { + Mcpwm0FunctionClockConfig::PllF96m => request_pll_f96m_clk(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + } + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Mcpwm0FunctionClockConfig::PllF96m => release_pll_f96m_clk(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm0_function_clock + } + pub fn request_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Enabling MCPWM0_FUNCTION_CLOCK"); + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF96m => request_pll_f96m_clk(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + } + enable_mcpwm0_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Disabling MCPWM0_FUNCTION_CLOCK"); + enable_mcpwm0_function_clock_impl(clocks, false); + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF96m => release_pll_f96m_clk(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Mcpwm0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn mcpwm0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.mcpwm0_function_clock) { + Mcpwm0FunctionClockConfig::PllF96m => pll_f96m_clk_frequency(clocks), + Mcpwm0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Mcpwm0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_parlio_rx_clock( + clocks: &mut ClockTree, + new_selector: ParlioRxClockConfig, + ) { + let old_selector = clocks.parlio_rx_clock.replace(new_selector); + if clocks.parlio_rx_clock_refcount > 0 { + match new_selector { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF96m => request_pll_f96m_clk(clocks), + } + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF96m => release_pll_f96m_clk(clocks), + } + } + } else { + configure_parlio_rx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_rx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_rx_clock + } + pub fn request_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_RX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Enabling PARLIO_RX_CLOCK"); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF96m => request_pll_f96m_clk(clocks), + } + enable_parlio_rx_clock_impl(clocks, true); + } + } + pub fn release_parlio_rx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_RX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_rx_clock_refcount) { + trace!("Disabling PARLIO_RX_CLOCK"); + enable_parlio_rx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioRxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioRxClockConfig::PllF96m => release_pll_f96m_clk(clocks), + } + } + } + pub fn parlio_rx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_rx_clock) { + ParlioRxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioRxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioRxClockConfig::PllF96m => pll_f96m_clk_frequency(clocks), + } + } + pub fn configure_parlio_tx_clock( + clocks: &mut ClockTree, + new_selector: ParlioTxClockConfig, + ) { + let old_selector = clocks.parlio_tx_clock.replace(new_selector); + if clocks.parlio_tx_clock_refcount > 0 { + match new_selector { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF96m => request_pll_f96m_clk(clocks), + } + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF96m => release_pll_f96m_clk(clocks), + } + } + } else { + configure_parlio_tx_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn parlio_tx_clock_config(clocks: &mut ClockTree) -> Option { + clocks.parlio_tx_clock + } + pub fn request_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Requesting PARLIO_TX_CLOCK"); + if increment_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Enabling PARLIO_TX_CLOCK"); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => request_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => request_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF96m => request_pll_f96m_clk(clocks), + } + enable_parlio_tx_clock_impl(clocks, true); + } + } + pub fn release_parlio_tx_clock(clocks: &mut ClockTree) { + trace!("Releasing PARLIO_TX_CLOCK"); + if decrement_reference_count(&mut clocks.parlio_tx_clock_refcount) { + trace!("Disabling PARLIO_TX_CLOCK"); + enable_parlio_tx_clock_impl(clocks, false); + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => release_xtal_clk(clocks), + ParlioTxClockConfig::RcFastClk => release_rc_fast_clk(clocks), + ParlioTxClockConfig::PllF96m => release_pll_f96m_clk(clocks), + } + } + } + pub fn parlio_tx_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.parlio_tx_clock) { + ParlioTxClockConfig::XtalClk => xtal_clk_frequency(clocks), + ParlioTxClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + ParlioTxClockConfig::PllF96m => pll_f96m_clk_frequency(clocks), + } + } + pub fn configure_rmt_sclk(clocks: &mut ClockTree, new_selector: RmtSclkConfig) { + let old_selector = clocks.rmt_sclk.replace(new_selector); + if clocks.rmt_sclk_refcount > 0 { + match new_selector { + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + } + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } else { + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn rmt_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.rmt_sclk + } + pub fn request_rmt_sclk(clocks: &mut ClockTree) { + trace!("Requesting RMT_SCLK"); + if increment_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Enabling RMT_SCLK"); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + } + enable_rmt_sclk_impl(clocks, true); + } + } + pub fn release_rmt_sclk(clocks: &mut ClockTree) { + trace!("Releasing RMT_SCLK"); + if decrement_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Disabling RMT_SCLK"); + enable_rmt_sclk_impl(clocks, false); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + } + } + } + pub fn rmt_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::XtalClk => xtal_clk_frequency(clocks), + RmtSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => lp_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg0_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg0_wdt_clock.replace(new_selector); + if clocks.timg0_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } else { + configure_timg0_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg0_wdt_clock + } + pub fn request_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Enabling TIMG0_WDT_CLOCK"); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + enable_timg0_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg0_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_wdt_clock_refcount) { + trace!("Disabling TIMG0_WDT_CLOCK"); + enable_timg0_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } + pub fn timg0_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0WdtClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0FunctionClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_lp_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => lp_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_wdt_clock( + clocks: &mut ClockTree, + new_selector: Timg0WdtClockConfig, + ) { + let old_selector = clocks.timg1_wdt_clock.replace(new_selector); + if clocks.timg1_wdt_clock_refcount > 0 { + match new_selector { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } else { + configure_timg1_wdt_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_wdt_clock_config(clocks: &mut ClockTree) -> Option { + clocks.timg1_wdt_clock + } + pub fn request_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_WDT_CLOCK"); + if increment_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Enabling TIMG1_WDT_CLOCK"); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => request_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => request_pll_f48m_clk(clocks), + } + enable_timg1_wdt_clock_impl(clocks, true); + } + } + pub fn release_timg1_wdt_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_WDT_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_wdt_clock_refcount) { + trace!("Disabling TIMG1_WDT_CLOCK"); + enable_timg1_wdt_clock_impl(clocks, false); + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0WdtClockConfig::RcFastClk => release_rc_fast_clk(clocks), + Timg0WdtClockConfig::PllF48m => release_pll_f48m_clk(clocks), + } + } + } + pub fn timg1_wdt_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_wdt_clock) { + Timg0WdtClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0WdtClockConfig::RcFastClk => rc_fast_clk_frequency(clocks), + Timg0WdtClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF48m => request_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF48m => release_pll_f48m_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::PllF48m => pll_f48m_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `HP_ROOT_CLK` configuration. + pub hp_root_clk: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `AHB_CLK` configuration. + pub ahb_clk: Option, + /// `APB_CLK` configuration. + pub apb_clk: Option, + /// `LP_FAST_CLK` configuration. + pub lp_fast_clk: Option, + /// `LP_SLOW_CLK` configuration. + pub lp_slow_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.hp_root_clk { + configure_hp_root_clk(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.ahb_clk { + configure_ahb_clk(clocks, config); + } + if let Some(config) = self.apb_clk { + configure_apb_clk(clocks, config); + } + if let Some(config) = self.lp_fast_clk { + configure_lp_fast_clk(clocks, config); + } + if let Some(config) = self.lp_slow_clk { + configure_lp_slow_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// DMA peripheral clock signal + Dma, + /// DS peripheral clock signal + Ds, + /// ECC peripheral clock signal + Ecc, + /// ECDSA peripheral clock signal + Ecdsa, + /// ETM peripheral clock signal + Etm, + /// HMAC peripheral clock signal + Hmac, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2C_EXT1 peripheral clock signal + I2cExt1, + /// I2S0 peripheral clock signal + I2s0, + /// LEDC peripheral clock signal + Ledc, + /// MCPWM0 peripheral clock signal + Mcpwm0, + /// PARL_IO peripheral clock signal + ParlIo, + /// PCNT peripheral clock signal + Pcnt, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TRACE0 peripheral clock signal + Trace0, + /// TSENS peripheral clock signal + Tsens, + /// TWAI0 peripheral clock signal + Twai0, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UHCI0 peripheral clock signal + Uhci0, + /// USB_DEVICE peripheral clock signal + UsbDevice, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = + &[Self::Systimer, Self::Timg0, Self::Uart0, Self::UsbDevice]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::ApbSarAdc, + Self::Dma, + Self::Ds, + Self::Ecc, + Self::Ecdsa, + Self::Etm, + Self::Hmac, + Self::I2cExt0, + Self::I2cExt1, + Self::I2s0, + Self::Ledc, + Self::Mcpwm0, + Self::ParlIo, + Self::Pcnt, + Self::Rmt, + Self::Rsa, + Self::Sha, + Self::Spi2, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Trace0, + Self::Tsens, + Self::Twai0, + Self::Uart0, + Self::Uart1, + Self::Uhci0, + Self::UsbDevice, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_clk_en().bit(enable)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .saradc_conf() + .modify(|_, w| w.saradc_reg_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_clk_en().bit(enable)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .ds_conf() + .modify(|_, w| w.ds_clk_en().bit(enable)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_clk_en().bit(enable)); + } + Peripheral::Ecdsa => { + crate::peripherals::SYSTEM::regs() + .ecdsa_conf() + .modify(|_, w| w.ecdsa_clk_en().bit(enable)); + } + Peripheral::Etm => { + crate::peripherals::SYSTEM::regs() + .etm_conf() + .modify(|_, w| w.etm_clk_en().bit(enable)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .hmac_conf() + .modify(|_, w| w.hmac_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_clk_en().bit(enable)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .i2c1_conf() + .modify(|_, w| w.i2c1_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .i2s_conf() + .modify(|_, w| w.i2s_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .pwm_conf() + .modify(|_, w| w.pwm_clk_en().bit(enable)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup0_timer_clk_conf() + .modify(|_, w| w.tg0_timer_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .timergroup1_timer_clk_conf() + .modify(|_, w| w.tg1_timer_clk_en().bit(enable)); + } + Peripheral::Trace0 => { + crate::peripherals::SYSTEM::regs() + .trace_conf() + .modify(|_, w| w.trace_clk_en().bit(enable)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .tsens_clk_conf() + .modify(|_, w| w.tsens_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .twai0_conf() + .modify(|_, w| w.twai0_clk_en().bit(enable)); + crate::peripherals::SYSTEM::regs() + .twai0_func_clk_conf() + .modify(|_, w| w.twai0_func_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_clk_en().bit(enable)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .aes_conf() + .modify(|_, w| w.aes_rst_en().bit(reset)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .saradc_conf() + .modify(|_, w| w.saradc_reg_rst_en().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .gdma_conf() + .modify(|_, w| w.gdma_rst_en().bit(reset)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .ds_conf() + .modify(|_, w| w.ds_rst_en().bit(reset)); + } + Peripheral::Ecc => { + crate::peripherals::SYSTEM::regs() + .ecc_conf() + .modify(|_, w| w.ecc_rst_en().bit(reset)); + } + Peripheral::Ecdsa => { + crate::peripherals::SYSTEM::regs() + .ecdsa_conf() + .modify(|_, w| w.ecdsa_rst_en().bit(reset)); + } + Peripheral::Etm => { + crate::peripherals::SYSTEM::regs() + .etm_conf() + .modify(|_, w| w.etm_rst_en().bit(reset)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .hmac_conf() + .modify(|_, w| w.hmac_rst_en().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .i2c0_conf() + .modify(|_, w| w.i2c0_rst_en().bit(reset)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .i2c1_conf() + .modify(|_, w| w.i2c1_rst_en().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .i2s_conf() + .modify(|_, w| w.i2s_rst_en().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_rst_en().bit(reset)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .pwm_conf() + .modify(|_, w| w.pwm_rst_en().bit(reset)); + } + Peripheral::ParlIo => { + crate::peripherals::SYSTEM::regs() + .parl_io_conf() + .modify(|_, w| w.parl_rst_en().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .pcnt_conf() + .modify(|_, w| w.pcnt_rst_en().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .rmt_conf() + .modify(|_, w| w.rmt_rst_en().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .rsa_conf() + .modify(|_, w| w.rsa_rst_en().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .sha_conf() + .modify(|_, w| w.sha_rst_en().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .spi2_conf() + .modify(|_, w| w.spi2_rst_en().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .systimer_conf() + .modify(|_, w| w.systimer_rst_en().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .timergroup0_conf() + .modify(|_, w| w.tg0_rst_en().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .timergroup1_conf() + .modify(|_, w| w.tg1_rst_en().bit(reset)); + } + Peripheral::Trace0 => { + crate::peripherals::SYSTEM::regs() + .trace_conf() + .modify(|_, w| w.trace_rst_en().bit(reset)); + } + Peripheral::Tsens => { + crate::peripherals::SYSTEM::regs() + .tsens_clk_conf() + .modify(|_, w| w.tsens_rst_en().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .twai0_conf() + .modify(|_, w| w.twai0_rst_en().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .uart(0) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .uart(1) + .conf() + .modify(|_, w| w.rst_en().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .uhci_conf() + .modify(|_, w| w.uhci_rst_en().bit(reset)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .usb_device_conf() + .modify(|_, w| w.usb_device_rst_en().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x40800000..0x40850000 + }; + (size as str, "DRAM") => { + "327680" + }; + ("DRAM2_UNINIT") => { + 0x4083EFD0..0x4084FEE0 + }; + (size as str, "DRAM2_UNINIT") => { + "69392" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((1, I2C1, I2cExt1, I2CEXT1_SCL, + I2CEXT1_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA), (1, I2C1, I2cExt1, I2CEXT1_SCL, I2CEXT1_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true)); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO3 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO12 peripheral singleton"] + GPIO12 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO14 peripheral singleton"] + GPIO14 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO22 peripheral singleton"] GPIO22 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO23 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO23 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO24 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO24 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO25 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO26 peripheral singleton"] + GPIO26 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton"] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA peripheral singleton"] DMA + <= DMA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DS peripheral singleton"] DS <= DS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ECC peripheral singleton"] ECC + <= ECC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HMAC peripheral singleton"] + HMAC <= HMAC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "HP_APM peripheral singleton"] HP_APM <= HP_APM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HP_SYS peripheral singleton"] + HP_SYS <= HP_SYS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2S0 peripheral singleton"] + I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "IO_MUX peripheral singleton"] + IO_MUX <= IO_MUX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LEDC peripheral singleton"] LEDC <= LEDC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LPWR peripheral singleton"] + LPWR <= LP_CLKRST() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_AON peripheral singleton"] + LP_AON <= LP_AON() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_APM peripheral singleton"] LP_APM <= LP_APM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_APM0 peripheral singleton"] + LP_APM0 <= LP_APM0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_PERI peripheral singleton"] + LP_PERI <= LP_PERI() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LP_TIMER peripheral singleton"] LP_TIMER <= LP_TIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_WDT peripheral singleton"] + LP_WDT <= LP_WDT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= MODEM_LPCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "OTP_DEBUG peripheral singleton"] OTP_DEBUG <= OTP_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PARL_IO peripheral singleton"] + PARL_IO <= PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, + disable_rx_interrupt }, PARL_IO_TX : { bind_tx_interrupt, enable_tx_interrupt, + disable_tx_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "PAU peripheral singleton"] PAU <= PAU() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PCNT peripheral singleton"] + PCNT <= PCNT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PCR peripheral singleton"] PCR <= PCR() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PLIC_MX peripheral singleton"] + PLIC_MX <= PLIC_MX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "PMU peripheral singleton"] PMU <= PMU() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SHA peripheral singleton"] SHA <= SHA(SHA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ETM peripheral singleton"] ETM + <= SOC_ETM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI0 peripheral singleton"] SPI0 <= SPI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI1 peripheral singleton"] + SPI1 <= SPI1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTEM peripheral singleton"] + SYSTEM <= PCR() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TEE peripheral singleton"] TEE + <= TEE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG1 peripheral singleton"] + TIMG1 <= TIMG1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TRACE0 peripheral singleton"] TRACE0 <= TRACE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TWAI0 peripheral singleton"] + TWAI0 <= TWAI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_CH0 peripheral singleton"] DMA_CH0 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH1 peripheral singleton"] + DMA_CH1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC1 peripheral singleton"] + ADC1 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BT peripheral singleton"] BT <= virtual(LP_BLE_TIMER : { + bind_lp_timer_interrupt, enable_lp_timer_interrupt, disable_lp_timer_interrupt }, + BT_MAC : { bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FLASH peripheral singleton"] FLASH <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM0 peripheral singleton"] + MEM2MEM0 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM1 peripheral singleton"] MEM2MEM1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM2 peripheral singleton"] + MEM2MEM2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM3 peripheral singleton"] MEM2MEM3 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM4 peripheral singleton"] + MEM2MEM4 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM5 peripheral singleton"] MEM2MEM5 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM6 peripheral singleton"] + MEM2MEM6 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "MEM2MEM7 peripheral singleton"] MEM2MEM7 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MEM2MEM8 peripheral singleton"] + MEM2MEM8 <= virtual() (unstable))); _for_each_inner_peripheral!((GPIO0)); + _for_each_inner_peripheral!((GPIO1)); _for_each_inner_peripheral!((GPIO2)); + _for_each_inner_peripheral!((GPIO3)); _for_each_inner_peripheral!((GPIO4)); + _for_each_inner_peripheral!((GPIO5)); _for_each_inner_peripheral!((GPIO6)); + _for_each_inner_peripheral!((GPIO7)); _for_each_inner_peripheral!((GPIO8)); + _for_each_inner_peripheral!((GPIO9)); _for_each_inner_peripheral!((GPIO10)); + _for_each_inner_peripheral!((GPIO11)); _for_each_inner_peripheral!((GPIO12)); + _for_each_inner_peripheral!((GPIO13)); _for_each_inner_peripheral!((GPIO14)); + _for_each_inner_peripheral!((GPIO22)); _for_each_inner_peripheral!((GPIO23)); + _for_each_inner_peripheral!((GPIO24)); _for_each_inner_peripheral!((GPIO25)); + _for_each_inner_peripheral!((GPIO26)); _for_each_inner_peripheral!((GPIO27)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((ECC(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((HP_APM(unstable))); + _for_each_inner_peripheral!((HP_SYS(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); _for_each_inner_peripheral!((I2C1)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((IEEE802154(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((INTPRI(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((LP_ANA(unstable))); + _for_each_inner_peripheral!((LP_AON(unstable))); + _for_each_inner_peripheral!((LP_APM(unstable))); + _for_each_inner_peripheral!((LP_APM0(unstable))); + _for_each_inner_peripheral!((LP_CLKRST(unstable))); + _for_each_inner_peripheral!((LP_PERI(unstable))); + _for_each_inner_peripheral!((LP_TIMER(unstable))); + _for_each_inner_peripheral!((LP_WDT(unstable))); + _for_each_inner_peripheral!((MCPWM0(unstable))); + _for_each_inner_peripheral!((MEM_MONITOR(unstable))); + _for_each_inner_peripheral!((MODEM_LPCON(unstable))); + _for_each_inner_peripheral!((MODEM_SYSCON(unstable))); + _for_each_inner_peripheral!((OTP_DEBUG(unstable))); + _for_each_inner_peripheral!((PARL_IO(unstable))); + _for_each_inner_peripheral!((PAU(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((PCR(unstable))); + _for_each_inner_peripheral!((PLIC_MX(unstable))); + _for_each_inner_peripheral!((PMU(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((ETM(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TEE(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TRACE0(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB_DEVICE(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((DMA_CH1(unstable))); + _for_each_inner_peripheral!((DMA_CH2(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((MEM2MEM0(unstable))); + _for_each_inner_peripheral!((MEM2MEM1(unstable))); + _for_each_inner_peripheral!((MEM2MEM2(unstable))); + _for_each_inner_peripheral!((MEM2MEM3(unstable))); + _for_each_inner_peripheral!((MEM2MEM4(unstable))); + _for_each_inner_peripheral!((MEM2MEM5(unstable))); + _for_each_inner_peripheral!((MEM2MEM6(unstable))); + _for_each_inner_peripheral!((MEM2MEM7(unstable))); + _for_each_inner_peripheral!((MEM2MEM8(unstable))); + _for_each_inner_peripheral!((SPI2, Spi2, 0)); + _for_each_inner_peripheral!((MEM2MEM0, Mem2mem0, 1)); + _for_each_inner_peripheral!((UHCI0, Uhci0, 2)); + _for_each_inner_peripheral!((I2S0, I2s0, 3)); + _for_each_inner_peripheral!((MEM2MEM1, Mem2mem1, 4)); + _for_each_inner_peripheral!((MEM2MEM2, Mem2mem2, 5)); + _for_each_inner_peripheral!((AES, Aes, 6)); _for_each_inner_peripheral!((SHA, + Sha, 7)); _for_each_inner_peripheral!((APB_SARADC, ApbSaradc, 8)); + _for_each_inner_peripheral!((PARL_IO, ParlIo, 9)); + _for_each_inner_peripheral!((MEM2MEM3, Mem2mem3, 10)); + _for_each_inner_peripheral!((MEM2MEM4, Mem2mem4, 11)); + _for_each_inner_peripheral!((MEM2MEM5, Mem2mem5, 12)); + _for_each_inner_peripheral!((MEM2MEM6, Mem2mem6, 13)); + _for_each_inner_peripheral!((MEM2MEM7, Mem2mem7, 14)); + _for_each_inner_peripheral!((MEM2MEM8, Mem2mem8, 15)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton"] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton"] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO22 peripheral singleton"] GPIO22 <= virtual()), (@ peri_type #[doc = + "GPIO23 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO23 <= virtual()), (@ peri_type #[doc = + "GPIO24 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO24 <= virtual()), (@ peri_type #[doc = + "GPIO25 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO25 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton"] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton"] GPIO27 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable)), (@ + peri_type #[doc = "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= + ASSIST_DEBUG() (unstable)), (@ peri_type #[doc = "DMA peripheral singleton"] DMA + <= DMA() (unstable)), (@ peri_type #[doc = "DS peripheral singleton"] DS <= DS() + (unstable)), (@ peri_type #[doc = "ECC peripheral singleton"] ECC <= ECC() + (unstable)), (@ peri_type #[doc = "EFUSE peripheral singleton"] EFUSE <= EFUSE() + (unstable)), (@ peri_type #[doc = "GPIO peripheral singleton"] GPIO <= GPIO() + (unstable)), (@ peri_type #[doc = "GPIO_SD peripheral singleton"] GPIO_SD <= + GPIO_SD() (unstable)), (@ peri_type #[doc = "HMAC peripheral singleton"] HMAC <= + HMAC() (unstable)), (@ peri_type #[doc = "HP_APM peripheral singleton"] HP_APM <= + HP_APM() (unstable)), (@ peri_type #[doc = "HP_SYS peripheral singleton"] HP_SYS + <= HP_SYS() (unstable)), (@ peri_type #[doc = "I2C_ANA_MST peripheral singleton"] + I2C_ANA_MST <= I2C_ANA_MST() (unstable)), (@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "IEEE802154 peripheral singleton"] IEEE802154 <= IEEE802154(ZB_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }) (unstable)), + (@ peri_type #[doc = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= + INTERRUPT_CORE0() (unstable)), (@ peri_type #[doc = + "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable)), (@ peri_type #[doc + = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type + #[doc = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable)), (@ peri_type + #[doc = "LPWR peripheral singleton"] LPWR <= LP_CLKRST() (unstable)), (@ + peri_type #[doc = "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable)), + (@ peri_type #[doc = "LP_AON peripheral singleton"] LP_AON <= LP_AON() + (unstable)), (@ peri_type #[doc = "LP_APM peripheral singleton"] LP_APM <= + LP_APM() (unstable)), (@ peri_type #[doc = "LP_APM0 peripheral singleton"] + LP_APM0 <= LP_APM0() (unstable)), (@ peri_type #[doc = + "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable)), (@ + peri_type #[doc = "LP_PERI peripheral singleton"] LP_PERI <= LP_PERI() + (unstable)), (@ peri_type #[doc = "LP_TIMER peripheral singleton"] LP_TIMER <= + LP_TIMER() (unstable)), (@ peri_type #[doc = "LP_WDT peripheral singleton"] + LP_WDT <= LP_WDT() (unstable)), (@ peri_type #[doc = + "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable)), (@ peri_type #[doc + = "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable)), + (@ peri_type #[doc = "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= + MODEM_LPCON() (unstable)), (@ peri_type #[doc = + "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable)), + (@ peri_type #[doc = "OTP_DEBUG peripheral singleton"] OTP_DEBUG <= OTP_DEBUG() + (unstable)), (@ peri_type #[doc = "PARL_IO peripheral singleton"] PARL_IO <= + PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, + disable_rx_interrupt }, PARL_IO_TX : { bind_tx_interrupt, enable_tx_interrupt, + disable_tx_interrupt }) (unstable)), (@ peri_type #[doc = + "PAU peripheral singleton"] PAU <= PAU() (unstable)), (@ peri_type #[doc = + "PCNT peripheral singleton"] PCNT <= PCNT() (unstable)), (@ peri_type #[doc = + "PCR peripheral singleton"] PCR <= PCR() (unstable)), (@ peri_type #[doc = + "PLIC_MX peripheral singleton"] PLIC_MX <= PLIC_MX() (unstable)), (@ peri_type + #[doc = "PMU peripheral singleton"] PMU <= PMU() (unstable)), (@ peri_type #[doc + = "RMT peripheral singleton"] RMT <= RMT() (unstable)), (@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= virtual() (unstable)), (@ peri_type #[doc = + "RSA peripheral singleton"] RSA <= RSA(RSA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "SHA peripheral singleton"] SHA <= SHA(SHA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "ETM peripheral singleton"] ETM <= SOC_ETM() (unstable)), (@ peri_type #[doc = + "SPI0 peripheral singleton"] SPI0 <= SPI0() (unstable)), (@ peri_type #[doc = + "SPI1 peripheral singleton"] SPI1 <= SPI1() (unstable)), (@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "SYSTEM peripheral singleton"] SYSTEM <= PCR() (unstable)), (@ peri_type #[doc = + "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() (unstable)), (@ peri_type + #[doc = "TEE peripheral singleton"] TEE <= TEE() (unstable)), (@ peri_type #[doc + = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable)), (@ peri_type #[doc + = "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() (unstable)), (@ peri_type #[doc + = "TRACE0 peripheral singleton"] TRACE0 <= TRACE() (unstable)), (@ peri_type + #[doc = "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() (unstable)), (@ peri_type + #[doc = "UART0 peripheral singleton"] UART0 <= UART0(UART0 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "UART1 peripheral singleton"] UART1 <= UART1(UART1 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable)), (@ + peri_type #[doc = "USB_DEVICE peripheral singleton"] USB_DEVICE <= + USB_DEVICE(USB_DEVICE : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable)), (@ peri_type #[doc = + "DMA_CH0 peripheral singleton"] DMA_CH0 <= virtual() (unstable)), (@ peri_type + #[doc = "DMA_CH1 peripheral singleton"] DMA_CH1 <= virtual() (unstable)), (@ + peri_type #[doc = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() + (unstable)), (@ peri_type #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() + (unstable)), (@ peri_type #[doc = "BT peripheral singleton"] BT <= + virtual(LP_BLE_TIMER : { bind_lp_timer_interrupt, enable_lp_timer_interrupt, + disable_lp_timer_interrupt }, BT_MAC : { bind_mac_interrupt, + enable_mac_interrupt, disable_mac_interrupt }) (unstable)), (@ peri_type #[doc = + "FLASH peripheral singleton"] FLASH <= virtual() (unstable)), (@ peri_type #[doc + = "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= + virtual() (unstable)), (@ peri_type #[doc = "MEM2MEM0 peripheral singleton"] + MEM2MEM0 <= virtual() (unstable)), (@ peri_type #[doc = + "MEM2MEM1 peripheral singleton"] MEM2MEM1 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM2 peripheral singleton"] MEM2MEM2 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM3 peripheral singleton"] MEM2MEM3 <= virtual() + (unstable)), (@ peri_type #[doc = "MEM2MEM4 peripheral singleton"] MEM2MEM4 <= + virtual() (unstable)), (@ peri_type #[doc = "MEM2MEM5 peripheral singleton"] + MEM2MEM5 <= virtual() (unstable)), (@ peri_type #[doc = + "MEM2MEM6 peripheral singleton"] MEM2MEM6 <= virtual() (unstable)), (@ peri_type + #[doc = "MEM2MEM7 peripheral singleton"] MEM2MEM7 <= virtual() (unstable)), (@ + peri_type #[doc = "MEM2MEM8 peripheral singleton"] MEM2MEM8 <= virtual() + (unstable)))); _for_each_inner_peripheral!((singletons(GPIO0), (GPIO1), (GPIO2), + (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), (GPIO10), + (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO22), (GPIO23), (GPIO24), (GPIO25), + (GPIO26), (GPIO27), (AES(unstable)), (APB_SARADC(unstable)), + (ASSIST_DEBUG(unstable)), (DMA(unstable)), (DS(unstable)), (ECC(unstable)), + (GPIO(unstable)), (GPIO_SD(unstable)), (HMAC(unstable)), (HP_APM(unstable)), + (HP_SYS(unstable)), (I2C_ANA_MST(unstable)), (I2C0), (I2C1), (I2S0(unstable)), + (IEEE802154(unstable)), (INTERRUPT_CORE0(unstable)), (INTPRI(unstable)), + (IO_MUX(unstable)), (LEDC(unstable)), (LPWR(unstable)), (LP_ANA(unstable)), + (LP_AON(unstable)), (LP_APM(unstable)), (LP_APM0(unstable)), + (LP_CLKRST(unstable)), (LP_PERI(unstable)), (LP_TIMER(unstable)), + (LP_WDT(unstable)), (MCPWM0(unstable)), (MEM_MONITOR(unstable)), + (MODEM_LPCON(unstable)), (MODEM_SYSCON(unstable)), (OTP_DEBUG(unstable)), + (PARL_IO(unstable)), (PAU(unstable)), (PCNT(unstable)), (PCR(unstable)), + (PLIC_MX(unstable)), (PMU(unstable)), (RMT(unstable)), (RNG(unstable)), + (RSA(unstable)), (SHA(unstable)), (ETM(unstable)), (SPI0(unstable)), + (SPI1(unstable)), (SPI2), (SYSTEM(unstable)), (SYSTIMER(unstable)), + (TEE(unstable)), (TIMG0(unstable)), (TIMG1(unstable)), (TRACE0(unstable)), + (TWAI0(unstable)), (UART0), (UART1), (UHCI0(unstable)), (USB_DEVICE(unstable)), + (DMA_CH0(unstable)), (DMA_CH1(unstable)), (DMA_CH2(unstable)), (ADC1(unstable)), + (BT(unstable)), (FLASH(unstable)), (GPIO_DEDICATED(unstable)), + (SW_INTERRUPT(unstable)), (MEM2MEM0(unstable)), (MEM2MEM1(unstable)), + (MEM2MEM2(unstable)), (MEM2MEM3(unstable)), (MEM2MEM4(unstable)), + (MEM2MEM5(unstable)), (MEM2MEM6(unstable)), (MEM2MEM7(unstable)), + (MEM2MEM8(unstable)))); _for_each_inner_peripheral!((dma_eligible(SPI2, Spi2, 0), + (MEM2MEM0, Mem2mem0, 1), (UHCI0, Uhci0, 2), (I2S0, I2s0, 3), (MEM2MEM1, Mem2mem1, + 4), (MEM2MEM2, Mem2mem2, 5), (AES, Aes, 6), (SHA, Sha, 7), (APB_SARADC, + ApbSaradc, 8), (PARL_IO, ParlIo, 9), (MEM2MEM3, Mem2mem3, 10), (MEM2MEM4, + Mem2mem4, 11), (MEM2MEM5, Mem2mem5, 12), (MEM2MEM6, Mem2mem6, 13), (MEM2MEM7, + Mem2mem7, 14), (MEM2MEM8, Mem2mem8, 15))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0(_2 => FSPIQ) (_2 => FSPIQ) ([Input] + [Output]))); _for_each_inner_gpio!((1, GPIO1(_2 => FSPICS0) (_2 => FSPICS0) + ([Input] [Output]))); _for_each_inner_gpio!((2, GPIO2(_0 => MTMS _2 => FSPIWP) + (_2 => FSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((3, GPIO3(_0 => MTDI + _2 => FSPIHD) (_2 => FSPIHD) ([Input] [Output]))); _for_each_inner_gpio!((4, + GPIO4(_0 => MTCK _2 => FSPICLK) (_2 => FSPICLK) ([Input] [Output]))); + _for_each_inner_gpio!((5, GPIO5(_2 => FSPID) (_0 => MTDO _2 => FSPID) ([Input] + [Output]))); _for_each_inner_gpio!((6, GPIO6() () ([Input] [Output]))); + _for_each_inner_gpio!((7, GPIO7() () ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() () ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9() () ([Input] [Output]))); + _for_each_inner_gpio!((10, GPIO10() () ([Input] [Output]))); + _for_each_inner_gpio!((11, GPIO11() () ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12() () ([Input] [Output]))); + _for_each_inner_gpio!((13, GPIO13() () ([Input] [Output]))); + _for_each_inner_gpio!((14, GPIO14() () ([Input] [Output]))); + _for_each_inner_gpio!((22, GPIO22() () ([Input] [Output]))); + _for_each_inner_gpio!((23, GPIO23(_0 => U0RXD) (_2 => FSPICS1) ([Input] + [Output]))); _for_each_inner_gpio!((24, GPIO24() (_0 => U0TXD _2 => FSPICS2) + ([Input] [Output]))); _for_each_inner_gpio!((25, GPIO25() (_2 => FSPICS3) + ([Input] [Output]))); _for_each_inner_gpio!((26, GPIO26() (_2 => FSPICS4) + ([Input] [Output]))); _for_each_inner_gpio!((27, GPIO27() (_2 => FSPICS5) + ([Input] [Output]))); _for_each_inner_gpio!((all(0, GPIO0(_2 => FSPIQ) (_2 => + FSPIQ) ([Input] [Output])), (1, GPIO1(_2 => FSPICS0) (_2 => FSPICS0) ([Input] + [Output])), (2, GPIO2(_0 => MTMS _2 => FSPIWP) (_2 => FSPIWP) ([Input] + [Output])), (3, GPIO3(_0 => MTDI _2 => FSPIHD) (_2 => FSPIHD) ([Input] + [Output])), (4, GPIO4(_0 => MTCK _2 => FSPICLK) (_2 => FSPICLK) ([Input] + [Output])), (5, GPIO5(_2 => FSPID) (_0 => MTDO _2 => FSPID) ([Input] [Output])), + (6, GPIO6() () ([Input] [Output])), (7, GPIO7() () ([Input] [Output])), (8, + GPIO8() () ([Input] [Output])), (9, GPIO9() () ([Input] [Output])), (10, GPIO10() + () ([Input] [Output])), (11, GPIO11() () ([Input] [Output])), (12, GPIO12() () + ([Input] [Output])), (13, GPIO13() () ([Input] [Output])), (14, GPIO14() () + ([Input] [Output])), (22, GPIO22() () ([Input] [Output])), (23, GPIO23(_0 => + U0RXD) (_2 => FSPICS1) ([Input] [Output])), (24, GPIO24() (_0 => U0TXD _2 => + FSPICS2) ([Input] [Output])), (25, GPIO25() (_2 => FSPICS3) ([Input] [Output])), + (26, GPIO26() (_2 => FSPICS4) ([Input] [Output])), (27, GPIO27() (_2 => FSPICS5) + ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((ADC1_CH0, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO4)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO5)); + _for_each_inner_analog_function!((ZCD0, GPIO10)); + _for_each_inner_analog_function!((ZCD1, GPIO11)); + _for_each_inner_analog_function!((XTAL_32K_P, GPIO13)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO14)); + _for_each_inner_analog_function!((USB_DM, GPIO26)); + _for_each_inner_analog_function!((USB_DP, GPIO27)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5)); + _for_each_inner_analog_function!(((ZCD0, ZCDn, 0), GPIO10)); + _for_each_inner_analog_function!(((ZCD1, ZCDn, 1), GPIO11)); + _for_each_inner_analog_function!((all(ADC1_CH0, GPIO1), (ADC1_CH1, GPIO2), + (ADC1_CH2, GPIO3), (ADC1_CH3, GPIO4), (ADC1_CH4, GPIO5), (ZCD0, GPIO10), (ZCD1, + GPIO11), (XTAL_32K_P, GPIO13), (XTAL_32K_N, GPIO14), (USB_DM, GPIO26), (USB_DP, + GPIO27))); _for_each_inner_analog_function!((all_expanded((ADC1_CH0, ADCn_CHm, 1, + 0), GPIO1), ((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2), ((ADC1_CH2, ADCn_CHm, 1, 2), + GPIO3), ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5), + ((ZCD0, ZCDn, 0), GPIO10), ((ZCD1, ZCDn, 1), GPIO11))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((LP_GPIO0, GPIO7)); + _for_each_inner_lp_function!((LP_GPIO1, GPIO8)); + _for_each_inner_lp_function!((LP_GPIO2, GPIO9)); + _for_each_inner_lp_function!((LP_GPIO3, GPIO10)); + _for_each_inner_lp_function!((LP_GPIO4, GPIO11)); + _for_each_inner_lp_function!((LP_GPIO5, GPIO12)); + _for_each_inner_lp_function!((LP_GPIO6, GPIO13)); + _for_each_inner_lp_function!((LP_GPIO7, GPIO14)); + _for_each_inner_lp_function!(((LP_GPIO0, LP_GPIOn, 0), GPIO7)); + _for_each_inner_lp_function!(((LP_GPIO1, LP_GPIOn, 1), GPIO8)); + _for_each_inner_lp_function!(((LP_GPIO2, LP_GPIOn, 2), GPIO9)); + _for_each_inner_lp_function!(((LP_GPIO3, LP_GPIOn, 3), GPIO10)); + _for_each_inner_lp_function!(((LP_GPIO4, LP_GPIOn, 4), GPIO11)); + _for_each_inner_lp_function!(((LP_GPIO5, LP_GPIOn, 5), GPIO12)); + _for_each_inner_lp_function!(((LP_GPIO6, LP_GPIOn, 6), GPIO13)); + _for_each_inner_lp_function!(((LP_GPIO7, LP_GPIOn, 7), GPIO14)); + _for_each_inner_lp_function!((all(LP_GPIO0, GPIO7), (LP_GPIO1, GPIO8), (LP_GPIO2, + GPIO9), (LP_GPIO3, GPIO10), (LP_GPIO4, GPIO11), (LP_GPIO5, GPIO12), (LP_GPIO6, + GPIO13), (LP_GPIO7, GPIO14))); + _for_each_inner_lp_function!((all_expanded((LP_GPIO0, LP_GPIOn, 0), GPIO7), + ((LP_GPIO1, LP_GPIOn, 1), GPIO8), ((LP_GPIO2, LP_GPIOn, 2), GPIO9), ((LP_GPIO3, + LP_GPIOn, 3), GPIO10), ((LP_GPIO4, LP_GPIOn, 4), GPIO11), ((LP_GPIO5, LP_GPIOn, + 5), GPIO12), ((LP_GPIO6, LP_GPIOn, 6), GPIO13), ((LP_GPIO7, LP_GPIOn, 7), + GPIO14))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + EXT_ADC_START = 0, + U0RXD = 6, + U0CTS = 7, + U0DSR = 8, + U1RXD = 9, + U1CTS = 10, + U1DSR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SI_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + USB_JTAG_TDO_BRIDGE = 19, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + I2CEXT0_SCL = 45, + I2CEXT0_SDA = 46, + PARL_RX_DATA0 = 47, + PARL_RX_DATA1 = 48, + PARL_RX_DATA2 = 49, + PARL_RX_DATA3 = 50, + PARL_RX_DATA4 = 51, + PARL_RX_DATA5 = 52, + PARL_RX_DATA6 = 53, + PARL_RX_DATA7 = 54, + I2CEXT1_SCL = 55, + I2CEXT1_SDA = 56, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + PARL_RX_CLK = 69, + PARL_TX_CLK = 70, + RMT_SIG_0 = 71, + RMT_SIG_1 = 72, + TWAI0_RX = 73, + PWM0_SYNC0 = 87, + PWM0_SYNC1 = 88, + PWM0_SYNC2 = 89, + PWM0_F0 = 90, + PWM0_F1 = 91, + PWM0_F2 = 92, + PWM0_CAP0 = 93, + PWM0_CAP1 = 94, + PWM0_CAP2 = 95, + SIG_FUNC_97 = 97, + SIG_FUNC_98 = 98, + SIG_FUNC_99 = 99, + SIG_FUNC_100 = 100, + PCNT0_SIG_CH0 = 101, + PCNT0_SIG_CH1 = 102, + PCNT0_CTRL_CH0 = 103, + PCNT0_CTRL_CH1 = 104, + PCNT1_SIG_CH0 = 105, + PCNT1_SIG_CH1 = 106, + PCNT1_CTRL_CH0 = 107, + PCNT1_CTRL_CH1 = 108, + PCNT2_SIG_CH0 = 109, + PCNT2_SIG_CH1 = 110, + PCNT2_CTRL_CH0 = 111, + PCNT2_CTRL_CH1 = 112, + PCNT3_SIG_CH0 = 113, + PCNT3_SIG_CH1 = 114, + PCNT3_CTRL_CH0 = 115, + PCNT3_CTRL_CH1 = 116, + SPIQ = 121, + SPID = 122, + SPIHD = 123, + SPIWP = 124, + MTDI, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + LEDC_LS_SIG0 = 0, + LEDC_LS_SIG1 = 1, + LEDC_LS_SIG2 = 2, + LEDC_LS_SIG3 = 3, + LEDC_LS_SIG4 = 4, + LEDC_LS_SIG5 = 5, + U0TXD = 6, + U0RTS = 7, + U0DTR = 8, + U1TXD = 9, + U1RTS = 10, + U1DTR = 11, + I2S_MCLK = 12, + I2SO_BCK = 13, + I2SO_WS = 14, + I2SO_SD = 15, + I2SI_BCK = 16, + I2SI_WS = 17, + I2SO_SD1 = 18, + USB_JTAG_TRST = 19, + CPU_GPIO_0 = 28, + CPU_GPIO_1 = 29, + CPU_GPIO_2 = 30, + CPU_GPIO_3 = 31, + CPU_GPIO_4 = 32, + CPU_GPIO_5 = 33, + CPU_GPIO_6 = 34, + CPU_GPIO_7 = 35, + I2CEXT0_SCL = 45, + I2CEXT0_SDA = 46, + PARL_TX_DATA0 = 47, + PARL_TX_DATA1 = 48, + PARL_TX_DATA2 = 49, + PARL_TX_DATA3 = 50, + PARL_TX_DATA4 = 51, + PARL_TX_DATA5 = 52, + PARL_TX_DATA6 = 53, + PARL_TX_DATA7 = 54, + I2CEXT1_SCL = 55, + I2CEXT1_SDA = 56, + FSPICLK = 63, + FSPIQ = 64, + FSPID = 65, + FSPIHD = 66, + FSPIWP = 67, + FSPICS0 = 68, + PARL_RX_CLK = 69, + PARL_TX_CLK = 70, + RMT_SIG_0 = 71, + RMT_SIG_1 = 72, + TWAI0_TX = 73, + TWAI0_BUS_OFF_ON = 74, + TWAI0_CLKOUT = 75, + TWAI0_STANDBY = 76, + CTE_ANT7 = 78, + CTE_ANT8 = 79, + CTE_ANT9 = 80, + GPIO_SD0 = 83, + GPIO_SD1 = 84, + GPIO_SD2 = 85, + GPIO_SD3 = 86, + PWM0_0A = 87, + PWM0_0B = 88, + PWM0_1A = 89, + PWM0_1B = 90, + PWM0_2A = 91, + PWM0_2B = 92, + SIG_IN_FUNC97 = 97, + SIG_IN_FUNC98 = 98, + SIG_IN_FUNC99 = 99, + SIG_IN_FUNC100 = 100, + FSPICS1 = 101, + FSPICS2 = 102, + FSPICS3 = 103, + FSPICS4 = 104, + FSPICS5 = 105, + CTE_ANT10 = 106, + CTE_ANT11 = 107, + CTE_ANT12 = 108, + CTE_ANT13 = 109, + CTE_ANT14 = 110, + CTE_ANT15 = 111, + SPICLK = 114, + SPICS0 = 115, + SPICS1 = 116, + SPIQ = 121, + SPID = 122, + SPIHD = 123, + SPIWP = 124, + CLK_OUT_OUT1 = 125, + CLK_OUT_OUT2 = 126, + CLK_OUT_OUT3 = 127, + GPIO = 128, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32s2.rs b/esp-metadata-generated/src/_generated_esp32s2.rs new file mode 100644 index 00000000000..891bdbefe41 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32s2.rs @@ -0,0 +1,4446 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32s2" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-S2" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32s2" + }; + ("arch") => { + "xtensa" + }; + ("cores") => { + 1 + }; + ("cores", str) => { + stringify!(1) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + true + }; + ("dedicated_gpio.needs_initialization") => { + true + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "pdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + false + }; + ("gpio.has_bank_1") => { + true + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 60 + }; + ("gpio.constant_0_input", str) => { + stringify!(60) + }; + ("gpio.constant_1_input") => { + 56 + }; + ("gpio.constant_1_input", str) => { + stringify!(56) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 242 + }; + ("gpio.input_signal_max", str) => { + stringify!(242) + }; + ("gpio.output_signal_max") => { + 256 + }; + ("gpio.output_signal_max", str) => { + stringify!(256) + }; + ("i2c_master.has_fsm_timeouts") => { + false + }; + ("i2c_master.has_hw_bus_clear") => { + false + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + true + }; + ("i2c_master.can_estimate_nack_reason") => { + false + }; + ("i2c_master.has_conf_update") => { + false + }; + ("i2c_master.has_reliable_fsm_reset") => { + false + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + false + }; + ("i2c_master.bus_timeout_is_exponential") => { + false + }; + ("i2c_master.i2c0_data_register_ahb_address") => { + 1610690588 + }; + ("i2c_master.i2c0_data_register_ahb_address", str) => { + stringify!(1610690588) + }; + ("i2c_master.max_bus_timeout") => { + 16777215 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(16777215) + }; + ("i2c_master.ll_intr_mask") => { + 131071 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(131071) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 3 + }; + ("interrupts.status_registers", str) => { + stringify!(3) + }; + ("phy.combo_module") => { + false + }; + ("rmt.ram_start") => { + 1061250048 + }; + ("rmt.ram_start", str) => { + stringify!(1061250048) + }; + ("rmt.channel_ram_size") => { + 64 + }; + ("rmt.channel_ram_size", str) => { + stringify!(64) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + false + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + false + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + false + }; + ("rmt.has_per_channel_clock") => { + true + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 512 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(512) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + false + }; + ("soc.multi_core_enabled") => { + false + }; + ("soc.rc_fast_clk_default") => { + 8500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(8500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + true + }; + ("spi_master.has_app_interrupts") => { + false + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + true + }; + ("timergroup.timg_has_divcnt_rst") => { + false + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + false + }; + ("uhci.combined_uart_selector_field") => { + false + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 1 + }; + ("wifi.mac_version", str) => { + stringify!(1) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((192)); _for_each_inner_aes_key_length!((256)); + _for_each_inner_aes_key_length!((128, 0, 4)); + _for_each_inner_aes_key_length!((192, 1, 5)); + _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (192), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (192, 1, 5), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + PRO_ALONEGPIO0)); _for_each_inner_dedicated_gpio!((0, 1, PRO_ALONEGPIO1)); + _for_each_inner_dedicated_gpio!((0, 2, PRO_ALONEGPIO2)); + _for_each_inner_dedicated_gpio!((0, 3, PRO_ALONEGPIO3)); + _for_each_inner_dedicated_gpio!((0, 4, PRO_ALONEGPIO4)); + _for_each_inner_dedicated_gpio!((0, 5, PRO_ALONEGPIO5)); + _for_each_inner_dedicated_gpio!((0, 6, PRO_ALONEGPIO6)); + _for_each_inner_dedicated_gpio!((0, 7, PRO_ALONEGPIO7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, PRO_ALONEGPIO0), (0, 1, + PRO_ALONEGPIO1), (0, 2, PRO_ALONEGPIO2), (0, 3, PRO_ALONEGPIO3), (0, 4, + PRO_ALONEGPIO4), (0, 5, PRO_ALONEGPIO5), (0, 6, PRO_ALONEGPIO6), (0, 7, + PRO_ALONEGPIO7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe {} + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 2)); _for_each_inner_rmt_channel!((3, 3)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 2)); _for_each_inner_rmt_channel!((3, 3)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1), (2, 2), (3, 3))); + _for_each_inner_rmt_channel!((rx(0, 0), (1, 1), (2, 2), (3, 3))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((RefTick, 0)); + _for_each_inner_rmt_clock_source!((Apb, 1)); + _for_each_inner_rmt_clock_source!((Apb)); + _for_each_inner_rmt_clock_source!((all(RefTick, 0), (Apb, 1))); + _for_each_inner_rmt_clock_source!((default(Apb))); + _for_each_inner_rmt_clock_source!((is_boolean)); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((3104)); + _for_each_inner_rsa_exponentiation!((3136)); + _for_each_inner_rsa_exponentiation!((3168)); + _for_each_inner_rsa_exponentiation!((3200)); + _for_each_inner_rsa_exponentiation!((3232)); + _for_each_inner_rsa_exponentiation!((3264)); + _for_each_inner_rsa_exponentiation!((3296)); + _for_each_inner_rsa_exponentiation!((3328)); + _for_each_inner_rsa_exponentiation!((3360)); + _for_each_inner_rsa_exponentiation!((3392)); + _for_each_inner_rsa_exponentiation!((3424)); + _for_each_inner_rsa_exponentiation!((3456)); + _for_each_inner_rsa_exponentiation!((3488)); + _for_each_inner_rsa_exponentiation!((3520)); + _for_each_inner_rsa_exponentiation!((3552)); + _for_each_inner_rsa_exponentiation!((3584)); + _for_each_inner_rsa_exponentiation!((3616)); + _for_each_inner_rsa_exponentiation!((3648)); + _for_each_inner_rsa_exponentiation!((3680)); + _for_each_inner_rsa_exponentiation!((3712)); + _for_each_inner_rsa_exponentiation!((3744)); + _for_each_inner_rsa_exponentiation!((3776)); + _for_each_inner_rsa_exponentiation!((3808)); + _for_each_inner_rsa_exponentiation!((3840)); + _for_each_inner_rsa_exponentiation!((3872)); + _for_each_inner_rsa_exponentiation!((3904)); + _for_each_inner_rsa_exponentiation!((3936)); + _for_each_inner_rsa_exponentiation!((3968)); + _for_each_inner_rsa_exponentiation!((4000)); + _for_each_inner_rsa_exponentiation!((4032)); + _for_each_inner_rsa_exponentiation!((4064)); + _for_each_inner_rsa_exponentiation!((4096)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072), (3104), (3136), + (3168), (3200), (3232), (3264), (3296), (3328), (3360), (3392), (3424), (3456), + (3488), (3520), (3552), (3584), (3616), (3648), (3680), (3712), (3744), (3776), + (3808), (3840), (3872), (3904), (3936), (3968), (4000), (4032), (4064), (4096))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((1568)); + _for_each_inner_rsa_multiplication!((1600)); + _for_each_inner_rsa_multiplication!((1632)); + _for_each_inner_rsa_multiplication!((1664)); + _for_each_inner_rsa_multiplication!((1696)); + _for_each_inner_rsa_multiplication!((1728)); + _for_each_inner_rsa_multiplication!((1760)); + _for_each_inner_rsa_multiplication!((1792)); + _for_each_inner_rsa_multiplication!((1824)); + _for_each_inner_rsa_multiplication!((1856)); + _for_each_inner_rsa_multiplication!((1888)); + _for_each_inner_rsa_multiplication!((1920)); + _for_each_inner_rsa_multiplication!((1952)); + _for_each_inner_rsa_multiplication!((1984)); + _for_each_inner_rsa_multiplication!((2016)); + _for_each_inner_rsa_multiplication!((2048)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((Sha384, "SHA-384"(sizes : 128, 48, 16) + (insecure_against :), 3)); _for_each_inner_sha_algorithm!((Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 4)); + _for_each_inner_sha_algorithm!((Sha512_224, "SHA-512/224"(sizes : 128, 28, 16) + (insecure_against :), 5)); _for_each_inner_sha_algorithm!((Sha512_256, + "SHA-512/256"(sizes : 128, 32, 16) (insecure_against :), 6)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2), (Sha384, "SHA-384"(sizes : 128, 48, 16) (insecure_against :), 3), (Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 4), + (Sha512_224, "SHA-512/224"(sizes : 128, 28, 16) (insecure_against :), 5), + (Sha512_256, "SHA-512/256"(sizes : 128, 32, 16) (insecure_against :), 6))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { +/// todo!() +/// } +/// +/// // APLL_CLK +/// +/// fn enable_apll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apll_clk_impl(_clocks: &mut ClockTree, _config: ApllClkConfig) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV_IN +/// +/// fn enable_cpu_pll_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuPllDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV +/// +/// fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_impl(_clocks: &mut ClockTree, _new_config: CpuPllDivConfig) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV_IN +/// +/// fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SystemPreDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV +/// +/// fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_impl(_clocks: &mut ClockTree, _new_config: SystemPreDivConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ApbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // REF_TICK +/// +/// fn enable_ref_tick_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RefTickConfig, +/// ) { +/// todo!() +/// } +/// +/// // REF_TICK_XTAL +/// +/// fn enable_ref_tick_xtal_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_xtal_impl(_clocks: &mut ClockTree, _new_config: RefTickXtalConfig) { +/// todo!() +/// } +/// +/// // REF_TICK_CK8M +/// +/// fn enable_ref_tick_ck8m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ref_tick_ck8m_impl(_clocks: &mut ClockTree, _new_config: RefTickCk8mConfig) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // APB_CLK_CPU_DIV2 +/// +/// fn enable_apb_clk_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // APB_CLK_80M +/// +/// fn enable_apb_clk_80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_DIV_CLK +/// +/// fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL_DIV_CLK +/// +/// fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RTC_SLOW_CLK +/// +/// fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // RTC_FAST_CLK +/// +/// fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART_MEM_CLK +/// +/// fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_MEM_CLOCK +/// +/// fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_MEM_CLOCK +/// +/// fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_40 => 40000000, + } + } + } + /// Selects the output frequency of `PLL_CLK`. Depends on `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PllClkConfig { + /// 320 MHz + _320, + /// 480 MHz + _480, + } + impl PllClkConfig { + pub fn value(&self) -> u32 { + match self { + PllClkConfig::_320 => 320000000, + PllClkConfig::_480 => 480000000, + } + } + } + /// The target frequency of the `APLL_CLK` clock source. Depends on `PLL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct ApllClkConfig(u32); + impl ApllClkConfig { + /// Creates a new clock source configuration. + /// + /// # Panics + /// + /// Panics if the output frequency value is outside the + /// valid range (16 MHz - 128 MHz). + pub const fn new(frequency: u32) -> Self { + ::core::assert!( + frequency >= 16000000u32 && frequency <= 128000000u32, + "`APLL_CLK` output frequency value must be between 16000000 and 128000000 \ + (inclusive)." + ); + Self(frequency) + } + } + impl ApllClkConfig { + pub fn value(&self) -> u32 { + self.0 + } + } + /// The list of clock signals that the `CPU_PLL_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivInConfig { + /// Selects `PLL_CLK`. + Pll, + /// Selects `APLL_CLK`. + Apll, + } + /// Configures the `CPU_PLL_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = CPU_PLL_DIV_IN / divisor`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivConfig { + /// Selects `divisor = 2`. + _2 = 2, + /// Selects `divisor = 3`. + _3 = 3, + /// Selects `divisor = 4`. + _4 = 4, + /// Selects `divisor = 6`. + _6 = 6, + } + impl CpuPllDivConfig { + /// Creates a new divider configuration. + pub const fn new(raw: u32) -> Self { + match raw { + 2 => CpuPllDivConfig::_2, + 3 => CpuPllDivConfig::_3, + 4 => CpuPllDivConfig::_4, + 6 => CpuPllDivConfig::_6, + _ => ::core::panic!("Invalid CPU_PLL_DIV divisor value"), + } + } + } + impl CpuPllDivConfig { + fn divisor(self) -> u32 { + match self { + CpuPllDivConfig::_2 => 2, + CpuPllDivConfig::_3 => 3, + CpuPllDivConfig::_4 => 4, + CpuPllDivConfig::_6 => 6, + } + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `SYSTEM_PRE_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SystemPreDivInConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `SYSTEM_PRE_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = SYSTEM_PRE_DIV_IN / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SystemPreDivConfig { + divisor: u32, + } + impl SystemPreDivConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 1023). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 1023u32, + "`SYSTEM_PRE_DIV` divisor value must be between 0 and 1023 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `APB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `APB_CLK_80M`. + Pll, + /// Selects `APB_CLK_CPU_DIV2`. + Apll, + /// Selects `CPU_CLK`. + Xtal, + /// Selects `CPU_CLK`. + RcFast, + } + /// The list of clock signals that the `REF_TICK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RefTickConfig { + /// Selects `REF_TICK_XTAL`. + Pll, + /// Selects `REF_TICK_XTAL`. + Apll, + /// Selects `REF_TICK_XTAL`. + Xtal, + /// Selects `REF_TICK_CK8M`. + RcFast, + } + /// Configures the `REF_TICK_XTAL` clock divider. + /// + /// The output is calculated as `OUTPUT = XTAL_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickXtalConfig { + divisor: u32, + } + impl RefTickXtalConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_XTAL` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Configures the `REF_TICK_CK8M` clock divider. + /// + /// The output is calculated as `OUTPUT = RC_FAST_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RefTickCk8mConfig { + divisor: u32, + } + impl RefTickCk8mConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 255). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 255u32, + "`REF_TICK_CK8M` divisor value must be between 0 and 255 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `SYSTEM_PRE_DIV`. + Xtal, + /// Selects `SYSTEM_PRE_DIV`. + RcFast, + /// Selects `CPU_PLL_DIV`. + Apll, + /// Selects `CPU_PLL_DIV`. + Pll, + } + /// The list of clock signals that the `RTC_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `RC_FAST_DIV_CLK`. + RcFast, + } + /// The list of clock signals that the `RTC_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcFastClkConfig { + /// Selects `XTAL_DIV_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + Rc, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `APB_CLK`. + ApbClk, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + #[default] + /// Selects `RTC_SLOW_CLK`. + RtcClk, + /// Selects `RC_FAST_DIV_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + #[default] + /// Selects `APB_CLK`. + Apb, + /// Selects `REF_TICK`. + RefTick, + } + /// The list of clock signals that the `UART0_MEM_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0MemClockConfig { + #[default] + /// Selects `UART_MEM_CLK`. + Mem, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + pll_clk: Option, + apll_clk: Option, + cpu_pll_div_in: Option, + cpu_pll_div: Option, + system_pre_div_in: Option, + system_pre_div: Option, + apb_clk: Option, + ref_tick: Option, + ref_tick_xtal: Option, + ref_tick_ck8m: Option, + cpu_clk: Option, + rtc_slow_clk: Option, + rtc_fast_clk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg1_function_clock: Option, + timg1_calibration_clock: Option, + uart0_function_clock: Option, + uart0_mem_clock: Option, + uart1_function_clock: Option, + uart1_mem_clock: Option, + pll_clk_refcount: u32, + rc_fast_clk_refcount: u32, + apb_clk_refcount: u32, + ref_tick_refcount: u32, + xtal32k_clk_refcount: u32, + rc_fast_div_clk_refcount: u32, + rtc_slow_clk_refcount: u32, + rtc_fast_clk_refcount: u32, + uart_mem_clk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart0_mem_clock_refcount: u32, + uart1_function_clock_refcount: u32, + uart1_mem_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the PLL_CLK clock tree node + pub fn pll_clk(&self) -> Option { + self.pll_clk + } + /// Returns the current configuration of the APLL_CLK clock tree node + pub fn apll_clk(&self) -> Option { + self.apll_clk + } + /// Returns the current configuration of the CPU_PLL_DIV_IN clock tree node + pub fn cpu_pll_div_in(&self) -> Option { + self.cpu_pll_div_in + } + /// Returns the current configuration of the CPU_PLL_DIV clock tree node + pub fn cpu_pll_div(&self) -> Option { + self.cpu_pll_div + } + /// Returns the current configuration of the SYSTEM_PRE_DIV_IN clock tree node + pub fn system_pre_div_in(&self) -> Option { + self.system_pre_div_in + } + /// Returns the current configuration of the SYSTEM_PRE_DIV clock tree node + pub fn system_pre_div(&self) -> Option { + self.system_pre_div + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the REF_TICK clock tree node + pub fn ref_tick(&self) -> Option { + self.ref_tick + } + /// Returns the current configuration of the REF_TICK_XTAL clock tree node + pub fn ref_tick_xtal(&self) -> Option { + self.ref_tick_xtal + } + /// Returns the current configuration of the REF_TICK_CK8M clock tree node + pub fn ref_tick_ck8m(&self) -> Option { + self.ref_tick_ck8m + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the RTC_SLOW_CLK clock tree node + pub fn rtc_slow_clk(&self) -> Option { + self.rtc_slow_clk + } + /// Returns the current configuration of the RTC_FAST_CLK clock tree node + pub fn rtc_fast_clk(&self) -> Option { + self.rtc_fast_clk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART0_MEM_CLOCK clock tree node + pub fn uart0_mem_clock(&self) -> Option { + self.uart0_mem_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + /// Returns the current configuration of the UART1_MEM_CLOCK clock tree node + pub fn uart1_mem_clock(&self) -> Option { + self.uart1_mem_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + pll_clk: None, + apll_clk: None, + cpu_pll_div_in: None, + cpu_pll_div: None, + system_pre_div_in: None, + system_pre_div: None, + apb_clk: None, + ref_tick: None, + ref_tick_xtal: None, + ref_tick_ck8m: None, + cpu_clk: None, + rtc_slow_clk: None, + rtc_fast_clk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg1_function_clock: None, + timg1_calibration_clock: None, + uart0_function_clock: None, + uart0_mem_clock: None, + uart1_function_clock: None, + uart1_mem_clock: None, + pll_clk_refcount: 0, + rc_fast_clk_refcount: 0, + apb_clk_refcount: 0, + ref_tick_refcount: 0, + xtal32k_clk_refcount: 0, + rc_fast_div_clk_refcount: 0, + rtc_slow_clk_refcount: 0, + rtc_fast_clk_refcount: 0, + uart_mem_clk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart0_mem_clock_refcount: 0, + uart1_function_clock_refcount: 0, + uart1_mem_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn configure_pll_clk(clocks: &mut ClockTree, config: PllClkConfig) { + clocks.pll_clk = Some(config); + configure_pll_clk_impl(clocks, config); + } + pub fn pll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.pll_clk + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + if increment_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + if decrement_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.pll_clk).value() + } + pub fn configure_apll_clk(clocks: &mut ClockTree, config: ApllClkConfig) { + clocks.apll_clk = Some(config); + configure_apll_clk_impl(clocks, config); + } + pub fn apll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apll_clk + } + pub fn request_apll_clk(clocks: &mut ClockTree) { + trace!("Requesting APLL_CLK"); + trace!("Enabling APLL_CLK"); + request_pll_clk(clocks); + enable_apll_clk_impl(clocks, true); + } + pub fn release_apll_clk(clocks: &mut ClockTree) { + trace!("Releasing APLL_CLK"); + trace!("Disabling APLL_CLK"); + enable_apll_clk_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn apll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.apll_clk).value() + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 8000000 + } + pub fn configure_cpu_pll_div_in(clocks: &mut ClockTree, new_selector: CpuPllDivInConfig) { + let old_selector = clocks.cpu_pll_div_in.replace(new_selector); + match new_selector { + CpuPllDivInConfig::Pll => request_pll_clk(clocks), + CpuPllDivInConfig::Apll => request_apll_clk(clocks), + } + configure_cpu_pll_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuPllDivInConfig::Pll => release_pll_clk(clocks), + CpuPllDivInConfig::Apll => release_apll_clk(clocks), + } + } + } + pub fn cpu_pll_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div_in + } + pub fn request_cpu_pll_div_in(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV_IN"); + trace!("Enabling CPU_PLL_DIV_IN"); + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => request_pll_clk(clocks), + CpuPllDivInConfig::Apll => request_apll_clk(clocks), + } + enable_cpu_pll_div_in_impl(clocks, true); + } + pub fn release_cpu_pll_div_in(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV_IN"); + trace!("Disabling CPU_PLL_DIV_IN"); + enable_cpu_pll_div_in_impl(clocks, false); + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => release_pll_clk(clocks), + CpuPllDivInConfig::Apll => release_apll_clk(clocks), + } + } + pub fn cpu_pll_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_pll_div_in) { + CpuPllDivInConfig::Pll => pll_clk_frequency(clocks), + CpuPllDivInConfig::Apll => apll_clk_frequency(clocks), + } + } + pub fn configure_cpu_pll_div(clocks: &mut ClockTree, config: CpuPllDivConfig) { + clocks.cpu_pll_div = Some(config); + configure_cpu_pll_div_impl(clocks, config); + } + pub fn cpu_pll_div_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div + } + pub fn request_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV"); + trace!("Enabling CPU_PLL_DIV"); + request_cpu_pll_div_in(clocks); + enable_cpu_pll_div_impl(clocks, true); + } + pub fn release_cpu_pll_div(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV"); + trace!("Disabling CPU_PLL_DIV"); + enable_cpu_pll_div_impl(clocks, false); + release_cpu_pll_div_in(clocks); + } + pub fn cpu_pll_div_frequency(clocks: &mut ClockTree) -> u32 { + (cpu_pll_div_in_frequency(clocks) / unwrap!(clocks.cpu_pll_div).divisor()) + } + pub fn configure_system_pre_div_in( + clocks: &mut ClockTree, + new_selector: SystemPreDivInConfig, + ) { + let old_selector = clocks.system_pre_div_in.replace(new_selector); + match new_selector { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_system_pre_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn system_pre_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div_in + } + pub fn request_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV_IN"); + trace!("Enabling SYSTEM_PRE_DIV_IN"); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_system_pre_div_in_impl(clocks, true); + } + pub fn release_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV_IN"); + trace!("Disabling SYSTEM_PRE_DIV_IN"); + enable_system_pre_div_in_impl(clocks, false); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + pub fn system_pre_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => xtal_clk_frequency(clocks), + SystemPreDivInConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_system_pre_div(clocks: &mut ClockTree, config: SystemPreDivConfig) { + clocks.system_pre_div = Some(config); + configure_system_pre_div_impl(clocks, config); + } + pub fn system_pre_div_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div + } + pub fn request_system_pre_div(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV"); + trace!("Enabling SYSTEM_PRE_DIV"); + request_system_pre_div_in(clocks); + enable_system_pre_div_impl(clocks, true); + } + pub fn release_system_pre_div(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV"); + trace!("Disabling SYSTEM_PRE_DIV"); + enable_system_pre_div_impl(clocks, false); + release_system_pre_div_in(clocks); + } + pub fn system_pre_div_frequency(clocks: &mut ClockTree) -> u32 { + (system_pre_div_in_frequency(clocks) / (unwrap!(clocks.system_pre_div).divisor() + 1)) + } + pub fn configure_apb_clk(clocks: &mut ClockTree, new_selector: ApbClkConfig) { + let old_selector = clocks.apb_clk.replace(new_selector); + if clocks.apb_clk_refcount > 0 { + match new_selector { + ApbClkConfig::Pll => request_apb_clk_80m(clocks), + ApbClkConfig::Apll => request_apb_clk_cpu_div2(clocks), + ApbClkConfig::Xtal => request_cpu_clk(clocks), + ApbClkConfig::RcFast => request_cpu_clk(clocks), + } + configure_apb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ApbClkConfig::Pll => release_apb_clk_80m(clocks), + ApbClkConfig::Apll => release_apb_clk_cpu_div2(clocks), + ApbClkConfig::Xtal => release_cpu_clk(clocks), + ApbClkConfig::RcFast => release_cpu_clk(clocks), + } + } + } else { + configure_apb_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => request_apb_clk_80m(clocks), + ApbClkConfig::Apll => request_apb_clk_cpu_div2(clocks), + ApbClkConfig::Xtal => request_cpu_clk(clocks), + ApbClkConfig::RcFast => request_cpu_clk(clocks), + } + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => release_apb_clk_80m(clocks), + ApbClkConfig::Apll => release_apb_clk_cpu_div2(clocks), + ApbClkConfig::Xtal => release_cpu_clk(clocks), + ApbClkConfig::RcFast => release_cpu_clk(clocks), + } + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => apb_clk_80m_frequency(clocks), + ApbClkConfig::Apll => apb_clk_cpu_div2_frequency(clocks), + ApbClkConfig::Xtal => cpu_clk_frequency(clocks), + ApbClkConfig::RcFast => cpu_clk_frequency(clocks), + } + } + pub fn configure_ref_tick(clocks: &mut ClockTree, new_selector: RefTickConfig) { + let old_selector = clocks.ref_tick.replace(new_selector); + if clocks.ref_tick_refcount > 0 { + match new_selector { + RefTickConfig::Pll => request_ref_tick_xtal(clocks), + RefTickConfig::Apll => request_ref_tick_xtal(clocks), + RefTickConfig::Xtal => request_ref_tick_xtal(clocks), + RefTickConfig::RcFast => request_ref_tick_ck8m(clocks), + } + configure_ref_tick_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RefTickConfig::Pll => release_ref_tick_xtal(clocks), + RefTickConfig::Apll => release_ref_tick_xtal(clocks), + RefTickConfig::Xtal => release_ref_tick_xtal(clocks), + RefTickConfig::RcFast => release_ref_tick_ck8m(clocks), + } + } + } else { + configure_ref_tick_impl(clocks, old_selector, new_selector); + } + } + pub fn ref_tick_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick + } + pub fn request_ref_tick(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK"); + if increment_reference_count(&mut clocks.ref_tick_refcount) { + trace!("Enabling REF_TICK"); + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => request_ref_tick_xtal(clocks), + RefTickConfig::Apll => request_ref_tick_xtal(clocks), + RefTickConfig::Xtal => request_ref_tick_xtal(clocks), + RefTickConfig::RcFast => request_ref_tick_ck8m(clocks), + } + enable_ref_tick_impl(clocks, true); + } + } + pub fn release_ref_tick(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK"); + if decrement_reference_count(&mut clocks.ref_tick_refcount) { + trace!("Disabling REF_TICK"); + enable_ref_tick_impl(clocks, false); + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => release_ref_tick_xtal(clocks), + RefTickConfig::Apll => release_ref_tick_xtal(clocks), + RefTickConfig::Xtal => release_ref_tick_xtal(clocks), + RefTickConfig::RcFast => release_ref_tick_ck8m(clocks), + } + } + } + pub fn ref_tick_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.ref_tick) { + RefTickConfig::Pll => ref_tick_xtal_frequency(clocks), + RefTickConfig::Apll => ref_tick_xtal_frequency(clocks), + RefTickConfig::Xtal => ref_tick_xtal_frequency(clocks), + RefTickConfig::RcFast => ref_tick_ck8m_frequency(clocks), + } + } + pub fn configure_ref_tick_xtal(clocks: &mut ClockTree, config: RefTickXtalConfig) { + clocks.ref_tick_xtal = Some(config); + configure_ref_tick_xtal_impl(clocks, config); + } + pub fn ref_tick_xtal_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_xtal + } + pub fn request_ref_tick_xtal(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_XTAL"); + trace!("Enabling REF_TICK_XTAL"); + request_xtal_clk(clocks); + enable_ref_tick_xtal_impl(clocks, true); + } + pub fn release_ref_tick_xtal(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_XTAL"); + trace!("Disabling REF_TICK_XTAL"); + enable_ref_tick_xtal_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn ref_tick_xtal_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_xtal).divisor() + 1)) + } + pub fn configure_ref_tick_ck8m(clocks: &mut ClockTree, config: RefTickCk8mConfig) { + clocks.ref_tick_ck8m = Some(config); + configure_ref_tick_ck8m_impl(clocks, config); + } + pub fn ref_tick_ck8m_config(clocks: &mut ClockTree) -> Option { + clocks.ref_tick_ck8m + } + pub fn request_ref_tick_ck8m(clocks: &mut ClockTree) { + trace!("Requesting REF_TICK_CK8M"); + trace!("Enabling REF_TICK_CK8M"); + request_rc_fast_clk(clocks); + enable_ref_tick_ck8m_impl(clocks, true); + } + pub fn release_ref_tick_ck8m(clocks: &mut ClockTree) { + trace!("Releasing REF_TICK_CK8M"); + trace!("Disabling REF_TICK_CK8M"); + enable_ref_tick_ck8m_impl(clocks, false); + release_rc_fast_clk(clocks); + } + pub fn ref_tick_ck8m_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / (unwrap!(clocks.ref_tick_ck8m).divisor() + 1)) + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Xtal => { + configure_system_pre_div_in(clocks, SystemPreDivInConfig::Xtal); + configure_apb_clk(clocks, ApbClkConfig::Xtal); + configure_ref_tick(clocks, RefTickConfig::Xtal); + let config_value = + RefTickXtalConfig::new(((xtal_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_xtal(clocks, config_value); + } + CpuClkConfig::RcFast => { + configure_system_pre_div_in(clocks, SystemPreDivInConfig::RcFast); + configure_apb_clk(clocks, ApbClkConfig::RcFast); + configure_ref_tick(clocks, RefTickConfig::RcFast); + let config_value = + RefTickCk8mConfig::new(((rc_fast_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_ck8m(clocks, config_value); + } + CpuClkConfig::Apll => { + configure_cpu_pll_div_in(clocks, CpuPllDivInConfig::Apll); + configure_apb_clk(clocks, ApbClkConfig::Apll); + configure_ref_tick(clocks, RefTickConfig::Apll); + let config_value = + RefTickXtalConfig::new(((xtal_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_xtal(clocks, config_value); + } + CpuClkConfig::Pll => { + configure_cpu_pll_div_in(clocks, CpuPllDivInConfig::Pll); + configure_apb_clk(clocks, ApbClkConfig::Pll); + configure_ref_tick(clocks, RefTickConfig::Pll); + let config_value = + RefTickXtalConfig::new(((xtal_clk_frequency(clocks) / 1000000) - 1)); + configure_ref_tick_xtal(clocks, config_value); + } + } + match new_selector { + CpuClkConfig::Xtal => request_system_pre_div(clocks), + CpuClkConfig::RcFast => request_system_pre_div(clocks), + CpuClkConfig::Apll => request_cpu_pll_div(clocks), + CpuClkConfig::Pll => request_cpu_pll_div(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Xtal => release_system_pre_div(clocks), + CpuClkConfig::RcFast => release_system_pre_div(clocks), + CpuClkConfig::Apll => release_cpu_pll_div(clocks), + CpuClkConfig::Pll => release_cpu_pll_div(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Xtal => system_pre_div_frequency(clocks), + CpuClkConfig::RcFast => system_pre_div_frequency(clocks), + CpuClkConfig::Apll => cpu_pll_div_frequency(clocks), + CpuClkConfig::Pll => cpu_pll_div_frequency(clocks), + } + } + pub fn request_apb_clk_cpu_div2(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK_CPU_DIV2"); + trace!("Enabling APB_CLK_CPU_DIV2"); + request_cpu_clk(clocks); + enable_apb_clk_cpu_div2_impl(clocks, true); + } + pub fn release_apb_clk_cpu_div2(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK_CPU_DIV2"); + trace!("Disabling APB_CLK_CPU_DIV2"); + enable_apb_clk_cpu_div2_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn apb_clk_cpu_div2_frequency(clocks: &mut ClockTree) -> u32 { + (cpu_clk_frequency(clocks) / 2) + } + pub fn request_apb_clk_80m(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK_80M"); + trace!("Enabling APB_CLK_80M"); + request_cpu_clk(clocks); + enable_apb_clk_80m_impl(clocks, true); + } + pub fn release_apb_clk_80m(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK_80M"); + trace!("Disabling APB_CLK_80M"); + enable_apb_clk_80m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn apb_clk_80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 90000 + } + pub fn request_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_DIV_CLK"); + if increment_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Enabling RC_FAST_DIV_CLK"); + request_rc_fast_clk(clocks); + enable_rc_fast_div_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_DIV_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Disabling RC_FAST_DIV_CLK"); + enable_rc_fast_div_clk_impl(clocks, false); + release_rc_fast_clk(clocks); + } + } + pub fn rc_fast_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / 256) + } + pub fn request_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_DIV_CLK"); + trace!("Enabling XTAL_DIV_CLK"); + request_xtal_clk(clocks); + enable_xtal_div_clk_impl(clocks, true); + } + pub fn release_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_DIV_CLK"); + trace!("Disabling XTAL_DIV_CLK"); + enable_xtal_div_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 4) + } + pub fn configure_rtc_slow_clk(clocks: &mut ClockTree, new_selector: RtcSlowClkConfig) { + let old_selector = clocks.rtc_slow_clk.replace(new_selector); + if clocks.rtc_slow_clk_refcount > 0 { + match new_selector { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } else { + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_slow_clk + } + pub fn request_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rtc_slow_clk_refcount) { + trace!("Enabling RTC_SLOW_CLK"); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + enable_rtc_slow_clk_impl(clocks, true); + } + } + pub fn release_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rtc_slow_clk_refcount) { + trace!("Disabling RTC_SLOW_CLK"); + enable_rtc_slow_clk_impl(clocks, false); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } + pub fn rtc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + RtcSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcFast => rc_fast_div_clk_frequency(clocks), + } + } + pub fn configure_rtc_fast_clk(clocks: &mut ClockTree, new_selector: RtcFastClkConfig) { + let old_selector = clocks.rtc_fast_clk.replace(new_selector); + if clocks.rtc_fast_clk_refcount > 0 { + match new_selector { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk(clocks), + } + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk(clocks), + } + } + } else { + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_fast_clk + } + pub fn request_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_FAST_CLK"); + if increment_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Enabling RTC_FAST_CLK"); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk(clocks), + } + enable_rtc_fast_clk_impl(clocks, true); + } + } + pub fn release_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Disabling RTC_FAST_CLK"); + enable_rtc_fast_clk_impl(clocks, false); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk(clocks), + } + } + } + pub fn rtc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => xtal_div_clk_frequency(clocks), + RtcFastClkConfig::Rc => rc_fast_clk_frequency(clocks), + } + } + pub fn request_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Requesting UART_MEM_CLK"); + if increment_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Enabling UART_MEM_CLK"); + request_xtal_clk(clocks); + enable_uart_mem_clk_impl(clocks, true); + } + } + pub fn release_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Releasing UART_MEM_CLK"); + if decrement_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Disabling UART_MEM_CLK"); + enable_uart_mem_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn uart_mem_clk_frequency(clocks: &mut ClockTree) -> u32 { + xtal_clk_frequency(clocks) + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RtcClk => request_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RtcClk => release_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => request_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => release_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => rtc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RtcClk => request_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RtcClk => release_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => request_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => release_rtc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RtcClk => rtc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RefTick => ref_tick_frequency(clocks), + } + } + pub fn configure_uart0_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart0_mem_clock.replace(new_selector); + if clocks.uart0_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart0_mem_clock + } + pub fn request_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Enabling UART0_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart0_mem_clock_impl(clocks, true); + } + } + pub fn release_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Disabling UART0_MEM_CLOCK"); + enable_uart0_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart0_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => request_ref_tick(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RefTick => release_ref_tick(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RefTick => ref_tick_frequency(clocks), + } + } + pub fn configure_uart1_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart1_mem_clock.replace(new_selector); + if clocks.uart1_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart1_mem_clock + } + pub fn request_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Enabling UART1_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart1_mem_clock_impl(clocks, true); + } + } + pub fn release_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Disabling UART1_MEM_CLOCK"); + enable_uart1_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart1_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `PLL_CLK` configuration. + pub pll_clk: Option, + /// `APLL_CLK` configuration. + pub apll_clk: Option, + /// `CPU_PLL_DIV` configuration. + pub cpu_pll_div: Option, + /// `SYSTEM_PRE_DIV` configuration. + pub system_pre_div: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `RTC_SLOW_CLK` configuration. + pub rtc_slow_clk: Option, + /// `RTC_FAST_CLK` configuration. + pub rtc_fast_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.pll_clk { + configure_pll_clk(clocks, config); + } + if let Some(config) = self.apll_clk { + configure_apll_clk(clocks, config); + } + if let Some(config) = self.cpu_pll_div { + configure_cpu_pll_div(clocks, config); + } + if let Some(config) = self.system_pre_div { + configure_system_pre_div(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.rtc_slow_clk { + configure_rtc_slow_clk(clocks, config); + } + if let Some(config) = self.rtc_fast_clk { + configure_rtc_fast_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// COPY_DMA peripheral clock signal + CopyDma, + /// CRYPTO_DMA peripheral clock signal + CryptoDma, + /// DEDICATED_GPIO peripheral clock signal + DedicatedGpio, + /// DS peripheral clock signal + Ds, + /// HMAC peripheral clock signal + Hmac, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2C_EXT1 peripheral clock signal + I2cExt1, + /// I2S0 peripheral clock signal + I2s0, + /// LEDC peripheral clock signal + Ledc, + /// MCPWM0 peripheral clock signal + Mcpwm0, + /// MCPWM1 peripheral clock signal + Mcpwm1, + /// PCNT peripheral clock signal + Pcnt, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SPI2_DMA peripheral clock signal + Spi2Dma, + /// SPI3 peripheral clock signal + Spi3, + /// SPI3_DMA peripheral clock signal + Spi3Dma, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TWAI0 peripheral clock signal + Twai0, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UART_MEM peripheral clock signal + UartMem, + /// UHCI0 peripheral clock signal + Uhci0, + /// UHCI1 peripheral clock signal + Uhci1, + /// USB peripheral clock signal + Usb, + /// WDG peripheral clock signal + Wdg, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = + &[Self::Systimer, Self::Timg0, Self::Uart0, Self::UartMem]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::ApbSarAdc, + Self::CopyDma, + Self::CryptoDma, + Self::DedicatedGpio, + Self::Ds, + Self::Hmac, + Self::I2cExt0, + Self::I2cExt1, + Self::I2s0, + Self::Ledc, + Self::Mcpwm0, + Self::Mcpwm1, + Self::Pcnt, + Self::Rmt, + Self::Rsa, + Self::Sha, + Self::Spi2, + Self::Spi2Dma, + Self::Spi3, + Self::Spi3Dma, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Twai0, + Self::Uart0, + Self::Uart1, + Self::UartMem, + Self::Uhci0, + Self::Uhci1, + Self::Usb, + Self::Wdg, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_aes_clk_en().bit(enable)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().bit(enable)); + } + Peripheral::CopyDma => { + crate::peripherals::DMA_COPY::regs() + .conf() + .modify(|_, w| w.clk_en().bit(enable)); + } + Peripheral::CryptoDma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_dma_clk_en().bit(enable)); + } + Peripheral::DedicatedGpio => { + crate::peripherals::SYSTEM::regs() + .cpu_peri_clk_en() + .modify(|_, w| w.dedicated_gpio_clk_en().bit(enable)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_ds_clk_en().bit(enable)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_hmac_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext0_clk_en().bit(enable)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext1_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2s0_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pwm0_clk_en().bit(enable)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pwm1_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_rsa_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Spi2Dma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi2_dma_clk_en().bit(enable)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi3_clk_en().bit(enable)); + } + Peripheral::Spi3Dma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi3_dma_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup1_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.twai_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart1_clk_en().bit(enable)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uhci0_clk_en().bit(enable)); + } + Peripheral::Uhci1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uhci1_clk_en().bit(enable)); + } + Peripheral::Usb => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.usb_clk_en().bit(enable)); + } + Peripheral::Wdg => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.wdg_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_aes_rst().bit(reset)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().bit(reset)); + } + Peripheral::CopyDma => { + crate::peripherals::DMA_COPY::regs().conf().modify(|_, w| { + w.in_rst() + .bit(reset) + .out_rst() + .bit(reset) + .cmdfifo_rst() + .bit(reset) + .fifo_rst() + .bit(reset) + }); + } + Peripheral::CryptoDma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_dma_rst().bit(reset)); + } + Peripheral::DedicatedGpio => { + crate::peripherals::SYSTEM::regs() + .cpu_peri_rst_en() + .modify(|_, w| w.dedicated_gpio_rst().bit(reset)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_ds_rst().bit(reset)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_hmac_rst().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext0_rst().bit(reset)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext1_rst().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2s0_rst().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.ledc_rst().bit(reset)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pwm0_rst().bit(reset)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pwm1_rst().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pcnt_rst().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.rmt_rst().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_rsa_rst().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_sha_rst().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi2_rst().bit(reset)); + } + Peripheral::Spi2Dma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi2_dma_rst().bit(reset)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi3_rst().bit(reset)); + } + Peripheral::Spi3Dma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi3_dma_rst().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.systimer_rst().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup_rst().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup1_rst().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.twai_rst().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_rst().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart1_rst().bit(reset)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uhci0_rst().bit(reset)); + } + Peripheral::Uhci1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uhci1_rst().bit(reset)); + } + Peripheral::Usb => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.usb_rst().bit(reset)); + } + Peripheral::Wdg => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.wdg_rst().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x3FFB0000..0x40000000 + }; + (size as str, "DRAM") => { + "327680" + }; + ("DRAM2_UNINIT") => { + 0x3FFDE000..0x40000000 + }; + (size as str, "DRAM2_UNINIT") => { + "139264" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((1, I2C1, I2cExt1, I2CEXT1_SCL, + I2CEXT1_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA), (1, I2C1, I2cExt1, I2CEXT1_SCL, I2CEXT1_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD, FSPIIO4, + FSPIIO5, FSPIIO6, FSPIIO7], true)); _for_each_inner_spi_master!((SPI3, Spi3, + SPI3_CLK[SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])); + _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, FSPICS2, + FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, + FSPIIO6, FSPIIO7], true), (SPI3, Spi3, SPI3_CLK[SPI3_CS0, SPI3_CS1, SPI3_CS2] + [SPI3_D, SPI3_Q]))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((SPI3, Spi3, SPI3_CLK, SPI3_D, SPI3_Q, SPI3_CS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0), + (SPI3, Spi3, SPI3_CLK, SPI3_D, SPI3_Q, SPI3_CS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO3 peripheral singleton"] + GPIO3 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO4 peripheral singleton"] GPIO4 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO5 peripheral singleton"] + GPIO5 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO6 peripheral singleton"] GPIO6 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO7 peripheral singleton"] + GPIO7 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO8 peripheral singleton"] GPIO8 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO9 peripheral singleton"] + GPIO9 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO11 peripheral singleton"] + GPIO11 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO12 peripheral singleton"] GPIO12 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO13 peripheral singleton"] + GPIO13 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO15 peripheral singleton"] + GPIO15 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO16 peripheral singleton"] GPIO16 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO17 peripheral singleton"] + GPIO17 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO19 peripheral singleton"] + GPIO19 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO20 peripheral singleton"] GPIO20 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO21 peripheral singleton"] + GPIO21 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO31 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO31 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO32 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO32 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO33 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO33 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO34 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO35 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO36 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO37 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO38 peripheral singleton"] + GPIO38 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO39 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO40 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO40 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO41 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO41 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO42 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO42 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO43 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO43 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO44 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO44 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO45 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO45 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO46 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO46 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "DEDICATED_GPIO peripheral singleton"] DEDICATED_GPIO <= DEDICATED_GPIO() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DS peripheral singleton"] DS <= DS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EFUSE peripheral singleton"] + EFUSE <= EFUSE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "FE peripheral singleton"] FE <= + FE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FE2 peripheral singleton"] FE2 <= FE2() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HMAC peripheral singleton"] + HMAC <= HMAC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2S0 peripheral singleton"] + I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= + INTERRUPT_CORE0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "NRX peripheral singleton"] NRX <= NRX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PCNT peripheral singleton"] + PCNT <= PCNT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PMS peripheral singleton"] PMS <= PMS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LPWR peripheral singleton"] LPWR <= RTC_CNTL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RTC_I2C peripheral singleton"] + RTC_I2C <= RTC_I2C() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "RTC_IO peripheral singleton"] RTC_IO <= RTC_IO() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SENS peripheral singleton"] + SENS <= SENS() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SHA peripheral singleton"] SHA <= SHA(SHA : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI0 peripheral singleton"] + SPI0 <= SPI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI1 peripheral singleton"] SPI1 <= SPI1() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI2 peripheral singleton"] + SPI2 <= SPI2(SPI2_DMA : { bind_dma_interrupt, enable_dma_interrupt, + disable_dma_interrupt }, SPI2 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI3 peripheral singleton"] SPI3 <= SPI3(SPI3_DMA : { bind_dma_interrupt, + enable_dma_interrupt, disable_dma_interrupt }, SPI3 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSCON peripheral singleton"] + SYSCON <= SYSCON() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTIMER peripheral singleton"] + SYSTIMER <= SYSTIMER() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG1 peripheral singleton"] + TIMG1 <= TIMG1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART0 peripheral singleton"] + UART0 <= UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART1 peripheral singleton"] UART1 <= UART1(UART1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "USB0 peripheral singleton"] USB0 <= USB0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "USB_WRAP peripheral singleton"] + USB_WRAP <= USB_WRAP() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "XTS_AES peripheral singleton"] XTS_AES <= XTS_AES() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WIFI peripheral singleton"] + WIFI <= WIFI(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_SPI2 peripheral singleton"] DMA_SPI2 <= SPI2() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_SPI3 peripheral singleton"] + DMA_SPI3 <= SPI3() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_I2S0 peripheral singleton"] DMA_I2S0 <= I2S0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "DMA_CRYPTO peripheral singleton"] DMA_CRYPTO <= CRYPTO_DMA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_COPY peripheral singleton"] + DMA_COPY <= COPY_DMA() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC2 peripheral singleton"] + ADC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DAC1 peripheral singleton"] DAC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DAC2 peripheral singleton"] + DAC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "FLASH peripheral singleton"] FLASH <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PSRAM peripheral singleton"] + PSRAM <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ULP_RISCV_CORE peripheral singleton"] ULP_RISCV_CORE <= virtual() (unstable))); + _for_each_inner_peripheral!((GPIO0)); _for_each_inner_peripheral!((GPIO1)); + _for_each_inner_peripheral!((GPIO2)); _for_each_inner_peripheral!((GPIO3)); + _for_each_inner_peripheral!((GPIO4)); _for_each_inner_peripheral!((GPIO5)); + _for_each_inner_peripheral!((GPIO6)); _for_each_inner_peripheral!((GPIO7)); + _for_each_inner_peripheral!((GPIO8)); _for_each_inner_peripheral!((GPIO9)); + _for_each_inner_peripheral!((GPIO10)); _for_each_inner_peripheral!((GPIO11)); + _for_each_inner_peripheral!((GPIO12)); _for_each_inner_peripheral!((GPIO13)); + _for_each_inner_peripheral!((GPIO14)); _for_each_inner_peripheral!((GPIO15)); + _for_each_inner_peripheral!((GPIO16)); _for_each_inner_peripheral!((GPIO17)); + _for_each_inner_peripheral!((GPIO18)); _for_each_inner_peripheral!((GPIO19)); + _for_each_inner_peripheral!((GPIO20)); _for_each_inner_peripheral!((GPIO21)); + _for_each_inner_peripheral!((GPIO26)); _for_each_inner_peripheral!((GPIO27)); + _for_each_inner_peripheral!((GPIO28)); _for_each_inner_peripheral!((GPIO29)); + _for_each_inner_peripheral!((GPIO30)); _for_each_inner_peripheral!((GPIO31)); + _for_each_inner_peripheral!((GPIO32)); _for_each_inner_peripheral!((GPIO33)); + _for_each_inner_peripheral!((GPIO34)); _for_each_inner_peripheral!((GPIO35)); + _for_each_inner_peripheral!((GPIO36)); _for_each_inner_peripheral!((GPIO37)); + _for_each_inner_peripheral!((GPIO38)); _for_each_inner_peripheral!((GPIO39)); + _for_each_inner_peripheral!((GPIO40)); _for_each_inner_peripheral!((GPIO41)); + _for_each_inner_peripheral!((GPIO42)); _for_each_inner_peripheral!((GPIO43)); + _for_each_inner_peripheral!((GPIO44)); _for_each_inner_peripheral!((GPIO45)); + _for_each_inner_peripheral!((GPIO46)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((DEDICATED_GPIO(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((EXTMEM(unstable))); + _for_each_inner_peripheral!((FE(unstable))); + _for_each_inner_peripheral!((FE2(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); _for_each_inner_peripheral!((I2C1)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((NRX(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((PMS(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((RTC_I2C(unstable))); + _for_each_inner_peripheral!((RTC_IO(unstable))); + _for_each_inner_peripheral!((SENS(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); _for_each_inner_peripheral!((SPI3)); + _for_each_inner_peripheral!((SYSCON(unstable))); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB0(unstable))); + _for_each_inner_peripheral!((USB_WRAP(unstable))); + _for_each_inner_peripheral!((XTS_AES(unstable))); + _for_each_inner_peripheral!((WIFI)); + _for_each_inner_peripheral!((DMA_SPI2(unstable))); + _for_each_inner_peripheral!((DMA_SPI3(unstable))); + _for_each_inner_peripheral!((DMA_I2S0(unstable))); + _for_each_inner_peripheral!((DMA_CRYPTO(unstable))); + _for_each_inner_peripheral!((DMA_COPY(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((ADC2(unstable))); + _for_each_inner_peripheral!((DAC1(unstable))); + _for_each_inner_peripheral!((DAC2(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((PSRAM(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((ULP_RISCV_CORE(unstable))); + _for_each_inner_peripheral!((I2S0, I2s0, 0)); _for_each_inner_peripheral!((SPI2, + Spi2, 1)); _for_each_inner_peripheral!((SPI3, Spi3, 2)); + _for_each_inner_peripheral!((UHCI0, Uhci0, 3)); _for_each_inner_peripheral!((AES, + Aes, 4)); _for_each_inner_peripheral!((SHA, Sha, 5)); + _for_each_inner_peripheral!((all(@ peri_type #[doc = + "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton"] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton"] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton"] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton"] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton"] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton"] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton"] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton"] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton"] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton"] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton"] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton"] GPIO20 <= virtual()), (@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual()), (@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual()), (@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual()), (@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual()), (@ peri_type #[doc = + "GPIO31 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO31 <= virtual()), (@ peri_type #[doc = + "GPIO32 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO32 <= virtual()), (@ peri_type #[doc = + "GPIO33 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO33 <= virtual()), (@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO34 <= virtual()), (@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO35 <= virtual()), (@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO36 <= virtual()), (@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO37 <= virtual()), (@ peri_type #[doc = + "GPIO38 peripheral singleton"] GPIO38 <= virtual()), (@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO39 <= virtual()), (@ peri_type #[doc = + "GPIO40 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO40 <= virtual()), (@ peri_type #[doc = + "GPIO41 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO41 <= virtual()), (@ peri_type #[doc = + "GPIO42 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO42 <= virtual()), (@ peri_type #[doc = + "GPIO43 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO43 <= virtual()), (@ peri_type #[doc = + "GPIO44 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO44 <= virtual()), (@ peri_type #[doc = + "GPIO45 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO45 <= virtual()), (@ peri_type #[doc = + "GPIO46 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO46 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable)), (@ + peri_type #[doc = "DEDICATED_GPIO peripheral singleton"] DEDICATED_GPIO <= + DEDICATED_GPIO() (unstable)), (@ peri_type #[doc = "DS peripheral singleton"] DS + <= DS() (unstable)), (@ peri_type #[doc = "EFUSE peripheral singleton"] EFUSE <= + EFUSE() (unstable)), (@ peri_type #[doc = "EXTMEM peripheral singleton"] EXTMEM + <= EXTMEM() (unstable)), (@ peri_type #[doc = "FE peripheral singleton"] FE <= + FE() (unstable)), (@ peri_type #[doc = "FE2 peripheral singleton"] FE2 <= FE2() + (unstable)), (@ peri_type #[doc = "GPIO peripheral singleton"] GPIO <= GPIO() + (unstable)), (@ peri_type #[doc = "GPIO_SD peripheral singleton"] GPIO_SD <= + GPIO_SD() (unstable)), (@ peri_type #[doc = "HMAC peripheral singleton"] HMAC <= + HMAC() (unstable)), (@ peri_type #[doc = "I2C_ANA_MST peripheral singleton"] + I2C_ANA_MST <= I2C_ANA_MST() (unstable)), (@ peri_type #[doc = + "I2C0 peripheral singleton"] I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt })), (@ peri_type #[doc = + "I2S0 peripheral singleton"] I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable)), (@ peri_type #[doc = "IO_MUX peripheral singleton"] IO_MUX <= + IO_MUX() (unstable)), (@ peri_type #[doc = "LEDC peripheral singleton"] LEDC <= + LEDC() (unstable)), (@ peri_type #[doc = "NRX peripheral singleton"] NRX <= NRX() + (unstable)), (@ peri_type #[doc = "PCNT peripheral singleton"] PCNT <= PCNT() + (unstable)), (@ peri_type #[doc = "PMS peripheral singleton"] PMS <= PMS() + (unstable)), (@ peri_type #[doc = "RMT peripheral singleton"] RMT <= RMT() + (unstable)), (@ peri_type #[doc = "RNG peripheral singleton"] RNG <= RNG() + (unstable)), (@ peri_type #[doc = "RSA peripheral singleton"] RSA <= RSA(RSA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "LPWR peripheral singleton"] LPWR <= RTC_CNTL() + (unstable)), (@ peri_type #[doc = "RTC_I2C peripheral singleton"] RTC_I2C <= + RTC_I2C() (unstable)), (@ peri_type #[doc = "RTC_IO peripheral singleton"] RTC_IO + <= RTC_IO() (unstable)), (@ peri_type #[doc = "SENS peripheral singleton"] SENS + <= SENS() (unstable)), (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= + SHA(SHA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 <= SPI0() + (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= SPI1() + (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= + SPI2(SPI2_DMA : { bind_dma_interrupt, enable_dma_interrupt, disable_dma_interrupt + }, SPI2 : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + })), (@ peri_type #[doc = "SPI3 peripheral singleton"] SPI3 <= SPI3(SPI3_DMA : { + bind_dma_interrupt, enable_dma_interrupt, disable_dma_interrupt }, SPI3 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SYSCON peripheral singleton"] SYSCON <= SYSCON() (unstable)), + (@ peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() + (unstable)), (@ peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= + SYSTIMER() (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 + <= TIMG0() (unstable)), (@ peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 + <= TIMG1() (unstable)), (@ peri_type #[doc = "TWAI0 peripheral singleton"] TWAI0 + <= TWAI0() (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 + <= UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable)), (@ peri_type #[doc = "USB0 peripheral singleton"] + USB0 <= USB0() (unstable)), (@ peri_type #[doc = "USB_WRAP peripheral singleton"] + USB_WRAP <= USB_WRAP() (unstable)), (@ peri_type #[doc = + "XTS_AES peripheral singleton"] XTS_AES <= XTS_AES() (unstable)), (@ peri_type + #[doc = "WIFI peripheral singleton"] WIFI <= WIFI(WIFI_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }, WIFI_PWR : { + bind_pwr_interrupt, enable_pwr_interrupt, disable_pwr_interrupt })), (@ peri_type + #[doc = "DMA_SPI2 peripheral singleton"] DMA_SPI2 <= SPI2() (unstable)), (@ + peri_type #[doc = "DMA_SPI3 peripheral singleton"] DMA_SPI3 <= SPI3() + (unstable)), (@ peri_type #[doc = "DMA_I2S0 peripheral singleton"] DMA_I2S0 <= + I2S0() (unstable)), (@ peri_type #[doc = "DMA_CRYPTO peripheral singleton"] + DMA_CRYPTO <= CRYPTO_DMA() (unstable)), (@ peri_type #[doc = + "DMA_COPY peripheral singleton"] DMA_COPY <= COPY_DMA() (unstable)), (@ peri_type + #[doc = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable)), (@ peri_type + #[doc = "ADC2 peripheral singleton"] ADC2 <= virtual() (unstable)), (@ peri_type + #[doc = "DAC1 peripheral singleton"] DAC1 <= virtual() (unstable)), (@ peri_type + #[doc = "DAC2 peripheral singleton"] DAC2 <= virtual() (unstable)), (@ peri_type + #[doc = "FLASH peripheral singleton"] FLASH <= virtual() (unstable)), (@ + peri_type #[doc = "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= + virtual() (unstable)), (@ peri_type #[doc = "PSRAM peripheral singleton"] PSRAM + <= virtual() (unstable)), (@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable)), (@ + peri_type #[doc = "ULP_RISCV_CORE peripheral singleton"] ULP_RISCV_CORE <= + virtual() (unstable)))); _for_each_inner_peripheral!((singletons(GPIO0), (GPIO1), + (GPIO2), (GPIO3), (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), (GPIO10), + (GPIO11), (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), (GPIO18), + (GPIO19), (GPIO20), (GPIO21), (GPIO26), (GPIO27), (GPIO28), (GPIO29), (GPIO30), + (GPIO31), (GPIO32), (GPIO33), (GPIO34), (GPIO35), (GPIO36), (GPIO37), (GPIO38), + (GPIO39), (GPIO40), (GPIO41), (GPIO42), (GPIO43), (GPIO44), (GPIO45), (GPIO46), + (AES(unstable)), (APB_SARADC(unstable)), (DEDICATED_GPIO(unstable)), + (DS(unstable)), (EXTMEM(unstable)), (FE(unstable)), (FE2(unstable)), + (GPIO(unstable)), (GPIO_SD(unstable)), (HMAC(unstable)), (I2C_ANA_MST(unstable)), + (I2C0), (I2C1), (I2S0(unstable)), (INTERRUPT_CORE0(unstable)), + (IO_MUX(unstable)), (LEDC(unstable)), (NRX(unstable)), (PCNT(unstable)), + (PMS(unstable)), (RMT(unstable)), (RNG(unstable)), (RSA(unstable)), + (LPWR(unstable)), (RTC_I2C(unstable)), (RTC_IO(unstable)), (SENS(unstable)), + (SHA(unstable)), (SPI0(unstable)), (SPI1(unstable)), (SPI2), (SPI3), + (SYSCON(unstable)), (SYSTEM(unstable)), (SYSTIMER(unstable)), (TIMG0(unstable)), + (TIMG1(unstable)), (TWAI0(unstable)), (UART0), (UART1), (UHCI0(unstable)), + (USB0(unstable)), (USB_WRAP(unstable)), (XTS_AES(unstable)), (WIFI), + (DMA_SPI2(unstable)), (DMA_SPI3(unstable)), (DMA_I2S0(unstable)), + (DMA_CRYPTO(unstable)), (DMA_COPY(unstable)), (ADC1(unstable)), (ADC2(unstable)), + (DAC1(unstable)), (DAC2(unstable)), (FLASH(unstable)), + (GPIO_DEDICATED(unstable)), (PSRAM(unstable)), (SW_INTERRUPT(unstable)), + (ULP_RISCV_CORE(unstable)))); _for_each_inner_peripheral!((dma_eligible(I2S0, + I2s0, 0), (SPI2, Spi2, 1), (SPI3, Spi3, 2), (UHCI0, Uhci0, 3), (AES, Aes, 4), + (SHA, Sha, 5))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2() () ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3() () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4() () ([Input] [Output]))); + _for_each_inner_gpio!((5, GPIO5() () ([Input] [Output]))); + _for_each_inner_gpio!((6, GPIO6() () ([Input] [Output]))); + _for_each_inner_gpio!((7, GPIO7() () ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() (_3 => SUBSPICS1) ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9(_3 => SUBSPIHD _4 => FSPIHD) (_3 => SUBSPIHD _4 + => FSPIHD) ([Input] [Output]))); _for_each_inner_gpio!((10, GPIO10(_2 => FSPIIO4 + _4 => FSPICS0) (_2 => FSPIIO4 _3 => SUBSPICS0 _4 => FSPICS0) ([Input] + [Output]))); _for_each_inner_gpio!((11, GPIO11(_2 => FSPIIO5 _3 => SUBSPID _4 => + FSPID) (_2 => FSPIIO5 _3 => SUBSPID _4 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12(_2 => FSPIIO6 _4 => FSPICLK) (_2 => FSPIIO6 _3 + => SUBSPICLK _4 => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((13, + GPIO13(_2 => FSPIIO7 _3 => SUBSPIQ _4 => FSPIQ) (_2 => FSPIIO7 _3 => SUBSPIQ _4 + => FSPIQ) ([Input] [Output]))); _for_each_inner_gpio!((14, GPIO14(_3 => SUBSPIWP + _4 => FSPIWP) (_2 => FSPIDQS _3 => SUBSPIWP _4 => FSPIWP) ([Input] [Output]))); + _for_each_inner_gpio!((15, GPIO15() (_2 => U0RTS) ([Input] [Output]))); + _for_each_inner_gpio!((16, GPIO16(_2 => U0CTS) () ([Input] [Output]))); + _for_each_inner_gpio!((17, GPIO17() (_2 => U1TXD) ([Input] [Output]))); + _for_each_inner_gpio!((18, GPIO18(_2 => U1RXD) (_3 => CLK_OUT3) ([Input] + [Output]))); _for_each_inner_gpio!((19, GPIO19() (_2 => U1RTS _3 => CLK_OUT2) + ([Input] [Output]))); _for_each_inner_gpio!((20, GPIO20(_2 => U1CTS) (_3 => + CLK_OUT1) ([Input] [Output]))); _for_each_inner_gpio!((21, GPIO21() () ([Input] + [Output]))); _for_each_inner_gpio!((26, GPIO26() (_0 => SPICS1) ([Input] + [Output]))); _for_each_inner_gpio!((27, GPIO27(_0 => SPIHD) (_0 => SPIHD) + ([Input] [Output]))); _for_each_inner_gpio!((28, GPIO28(_0 => SPIWP) (_0 => + SPIWP) ([Input] [Output]))); _for_each_inner_gpio!((29, GPIO29() (_0 => SPICS0) + ([Input] [Output]))); _for_each_inner_gpio!((30, GPIO30() (_0 => SPICLK) ([Input] + [Output]))); _for_each_inner_gpio!((31, GPIO31(_0 => SPIQ) (_0 => SPIQ) ([Input] + [Output]))); _for_each_inner_gpio!((32, GPIO32(_0 => SPID) (_0 => SPID) ([Input] + [Output]))); _for_each_inner_gpio!((33, GPIO33(_2 => FSPIHD _3 => SUBSPIHD) (_2 + => FSPIHD _3 => SUBSPIHD) ([Input] [Output]))); _for_each_inner_gpio!((34, + GPIO34(_2 => FSPICS0) (_2 => FSPICS0 _3 => SUBSPICS0) ([Input] [Output]))); + _for_each_inner_gpio!((35, GPIO35(_2 => FSPID _3 => SUBSPID) (_2 => FSPID _3 => + SUBSPID) ([Input] [Output]))); _for_each_inner_gpio!((36, GPIO36(_2 => FSPICLK) + (_2 => FSPICLK _3 => SUBSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((37, + GPIO37(_2 => FSPIQ _3 => SUBSPIQ _4 => SPIDQS) (_2 => FSPIQ _3 => SUBSPIQ _4 => + SPIDQS) ([Input] [Output]))); _for_each_inner_gpio!((38, GPIO38(_2 => FSPIWP _3 + => SUBSPIWP) (_2 => FSPIWP _3 => SUBSPIWP) ([Input] [Output]))); + _for_each_inner_gpio!((39, GPIO39(_0 => MTCK) (_2 => CLK_OUT3 _3 => SUBSPICS1) + ([Input] [Output]))); _for_each_inner_gpio!((40, GPIO40() (_0 => MTDO _2 => + CLK_OUT2) ([Input] [Output]))); _for_each_inner_gpio!((41, GPIO41(_0 => MTDI) (_2 + => CLK_OUT1) ([Input] [Output]))); _for_each_inner_gpio!((42, GPIO42(_0 => MTMS) + () ([Input] [Output]))); _for_each_inner_gpio!((43, GPIO43() (_0 => U0TXD _2 => + CLK_OUT1) ([Input] [Output]))); _for_each_inner_gpio!((44, GPIO44(_0 => U0RXD) + (_2 => CLK_OUT2) ([Input] [Output]))); _for_each_inner_gpio!((45, GPIO45() () + ([Input] [Output]))); _for_each_inner_gpio!((46, GPIO46() () ([Input] + [Output]))); _for_each_inner_gpio!((all(0, GPIO0() () ([Input] [Output])), (1, + GPIO1() () ([Input] [Output])), (2, GPIO2() () ([Input] [Output])), (3, GPIO3() + () ([Input] [Output])), (4, GPIO4() () ([Input] [Output])), (5, GPIO5() () + ([Input] [Output])), (6, GPIO6() () ([Input] [Output])), (7, GPIO7() () ([Input] + [Output])), (8, GPIO8() (_3 => SUBSPICS1) ([Input] [Output])), (9, GPIO9(_3 => + SUBSPIHD _4 => FSPIHD) (_3 => SUBSPIHD _4 => FSPIHD) ([Input] [Output])), (10, + GPIO10(_2 => FSPIIO4 _4 => FSPICS0) (_2 => FSPIIO4 _3 => SUBSPICS0 _4 => FSPICS0) + ([Input] [Output])), (11, GPIO11(_2 => FSPIIO5 _3 => SUBSPID _4 => FSPID) (_2 => + FSPIIO5 _3 => SUBSPID _4 => FSPID) ([Input] [Output])), (12, GPIO12(_2 => FSPIIO6 + _4 => FSPICLK) (_2 => FSPIIO6 _3 => SUBSPICLK _4 => FSPICLK) ([Input] [Output])), + (13, GPIO13(_2 => FSPIIO7 _3 => SUBSPIQ _4 => FSPIQ) (_2 => FSPIIO7 _3 => SUBSPIQ + _4 => FSPIQ) ([Input] [Output])), (14, GPIO14(_3 => SUBSPIWP _4 => FSPIWP) (_2 => + FSPIDQS _3 => SUBSPIWP _4 => FSPIWP) ([Input] [Output])), (15, GPIO15() (_2 => + U0RTS) ([Input] [Output])), (16, GPIO16(_2 => U0CTS) () ([Input] [Output])), (17, + GPIO17() (_2 => U1TXD) ([Input] [Output])), (18, GPIO18(_2 => U1RXD) (_3 => + CLK_OUT3) ([Input] [Output])), (19, GPIO19() (_2 => U1RTS _3 => CLK_OUT2) + ([Input] [Output])), (20, GPIO20(_2 => U1CTS) (_3 => CLK_OUT1) ([Input] + [Output])), (21, GPIO21() () ([Input] [Output])), (26, GPIO26() (_0 => SPICS1) + ([Input] [Output])), (27, GPIO27(_0 => SPIHD) (_0 => SPIHD) ([Input] [Output])), + (28, GPIO28(_0 => SPIWP) (_0 => SPIWP) ([Input] [Output])), (29, GPIO29() (_0 => + SPICS0) ([Input] [Output])), (30, GPIO30() (_0 => SPICLK) ([Input] [Output])), + (31, GPIO31(_0 => SPIQ) (_0 => SPIQ) ([Input] [Output])), (32, GPIO32(_0 => SPID) + (_0 => SPID) ([Input] [Output])), (33, GPIO33(_2 => FSPIHD _3 => SUBSPIHD) (_2 => + FSPIHD _3 => SUBSPIHD) ([Input] [Output])), (34, GPIO34(_2 => FSPICS0) (_2 => + FSPICS0 _3 => SUBSPICS0) ([Input] [Output])), (35, GPIO35(_2 => FSPID _3 => + SUBSPID) (_2 => FSPID _3 => SUBSPID) ([Input] [Output])), (36, GPIO36(_2 => + FSPICLK) (_2 => FSPICLK _3 => SUBSPICLK) ([Input] [Output])), (37, GPIO37(_2 => + FSPIQ _3 => SUBSPIQ _4 => SPIDQS) (_2 => FSPIQ _3 => SUBSPIQ _4 => SPIDQS) + ([Input] [Output])), (38, GPIO38(_2 => FSPIWP _3 => SUBSPIWP) (_2 => FSPIWP _3 => + SUBSPIWP) ([Input] [Output])), (39, GPIO39(_0 => MTCK) (_2 => CLK_OUT3 _3 => + SUBSPICS1) ([Input] [Output])), (40, GPIO40() (_0 => MTDO _2 => CLK_OUT2) + ([Input] [Output])), (41, GPIO41(_0 => MTDI) (_2 => CLK_OUT1) ([Input] + [Output])), (42, GPIO42(_0 => MTMS) () ([Input] [Output])), (43, GPIO43() (_0 => + U0TXD _2 => CLK_OUT1) ([Input] [Output])), (44, GPIO44(_0 => U0RXD) (_2 => + CLK_OUT2) ([Input] [Output])), (45, GPIO45() () ([Input] [Output])), (46, + GPIO46() () ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((TOUCH1, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH0, GPIO1)); + _for_each_inner_analog_function!((TOUCH2, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO2)); + _for_each_inner_analog_function!((TOUCH3, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO3)); + _for_each_inner_analog_function!((TOUCH4, GPIO4)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO4)); + _for_each_inner_analog_function!((TOUCH5, GPIO5)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO5)); + _for_each_inner_analog_function!((TOUCH6, GPIO6)); + _for_each_inner_analog_function!((ADC1_CH5, GPIO6)); + _for_each_inner_analog_function!((TOUCH7, GPIO7)); + _for_each_inner_analog_function!((ADC1_CH6, GPIO7)); + _for_each_inner_analog_function!((TOUCH8, GPIO8)); + _for_each_inner_analog_function!((ADC1_CH7, GPIO8)); + _for_each_inner_analog_function!((TOUCH9, GPIO9)); + _for_each_inner_analog_function!((ADC1_CH8, GPIO9)); + _for_each_inner_analog_function!((TOUCH10, GPIO10)); + _for_each_inner_analog_function!((ADC1_CH9, GPIO10)); + _for_each_inner_analog_function!((TOUCH11, GPIO11)); + _for_each_inner_analog_function!((ADC2_CH0, GPIO11)); + _for_each_inner_analog_function!((TOUCH12, GPIO12)); + _for_each_inner_analog_function!((ADC2_CH1, GPIO12)); + _for_each_inner_analog_function!((TOUCH13, GPIO13)); + _for_each_inner_analog_function!((ADC2_CH2, GPIO13)); + _for_each_inner_analog_function!((TOUCH14, GPIO14)); + _for_each_inner_analog_function!((ADC2_CH3, GPIO14)); + _for_each_inner_analog_function!((XTAL_32K_P, GPIO15)); + _for_each_inner_analog_function!((ADC2_CH4, GPIO15)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO16)); + _for_each_inner_analog_function!((ADC2_CH5, GPIO16)); + _for_each_inner_analog_function!((DAC_1, GPIO17)); + _for_each_inner_analog_function!((ADC2_CH6, GPIO17)); + _for_each_inner_analog_function!((DAC_2, GPIO18)); + _for_each_inner_analog_function!((ADC2_CH7, GPIO18)); + _for_each_inner_analog_function!((USB_DM, GPIO19)); + _for_each_inner_analog_function!((ADC2_CH8, GPIO19)); + _for_each_inner_analog_function!((USB_DP, GPIO20)); + _for_each_inner_analog_function!((ADC2_CH9, GPIO20)); + _for_each_inner_analog_function!(((TOUCH1, TOUCHn, 1), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1)); + _for_each_inner_analog_function!(((TOUCH2, TOUCHn, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2)); + _for_each_inner_analog_function!(((TOUCH3, TOUCHn, 3), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO3)); + _for_each_inner_analog_function!(((TOUCH4, TOUCHn, 4), GPIO4)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4)); + _for_each_inner_analog_function!(((TOUCH5, TOUCHn, 5), GPIO5)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5)); + _for_each_inner_analog_function!(((TOUCH6, TOUCHn, 6), GPIO6)); + _for_each_inner_analog_function!(((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6)); + _for_each_inner_analog_function!(((TOUCH7, TOUCHn, 7), GPIO7)); + _for_each_inner_analog_function!(((ADC1_CH6, ADCn_CHm, 1, 6), GPIO7)); + _for_each_inner_analog_function!(((TOUCH8, TOUCHn, 8), GPIO8)); + _for_each_inner_analog_function!(((ADC1_CH7, ADCn_CHm, 1, 7), GPIO8)); + _for_each_inner_analog_function!(((TOUCH9, TOUCHn, 9), GPIO9)); + _for_each_inner_analog_function!(((ADC1_CH8, ADCn_CHm, 1, 8), GPIO9)); + _for_each_inner_analog_function!(((TOUCH10, TOUCHn, 10), GPIO10)); + _for_each_inner_analog_function!(((ADC1_CH9, ADCn_CHm, 1, 9), GPIO10)); + _for_each_inner_analog_function!(((TOUCH11, TOUCHn, 11), GPIO11)); + _for_each_inner_analog_function!(((ADC2_CH0, ADCn_CHm, 2, 0), GPIO11)); + _for_each_inner_analog_function!(((TOUCH12, TOUCHn, 12), GPIO12)); + _for_each_inner_analog_function!(((ADC2_CH1, ADCn_CHm, 2, 1), GPIO12)); + _for_each_inner_analog_function!(((TOUCH13, TOUCHn, 13), GPIO13)); + _for_each_inner_analog_function!(((ADC2_CH2, ADCn_CHm, 2, 2), GPIO13)); + _for_each_inner_analog_function!(((TOUCH14, TOUCHn, 14), GPIO14)); + _for_each_inner_analog_function!(((ADC2_CH3, ADCn_CHm, 2, 3), GPIO14)); + _for_each_inner_analog_function!(((ADC2_CH4, ADCn_CHm, 2, 4), GPIO15)); + _for_each_inner_analog_function!(((ADC2_CH5, ADCn_CHm, 2, 5), GPIO16)); + _for_each_inner_analog_function!(((DAC_1, DAC_n, 1), GPIO17)); + _for_each_inner_analog_function!(((ADC2_CH6, ADCn_CHm, 2, 6), GPIO17)); + _for_each_inner_analog_function!(((DAC_2, DAC_n, 2), GPIO18)); + _for_each_inner_analog_function!(((ADC2_CH7, ADCn_CHm, 2, 7), GPIO18)); + _for_each_inner_analog_function!(((ADC2_CH8, ADCn_CHm, 2, 8), GPIO19)); + _for_each_inner_analog_function!(((ADC2_CH9, ADCn_CHm, 2, 9), GPIO20)); + _for_each_inner_analog_function!((all(TOUCH1, GPIO1), (ADC1_CH0, GPIO1), (TOUCH2, + GPIO2), (ADC1_CH1, GPIO2), (TOUCH3, GPIO3), (ADC1_CH2, GPIO3), (TOUCH4, GPIO4), + (ADC1_CH3, GPIO4), (TOUCH5, GPIO5), (ADC1_CH4, GPIO5), (TOUCH6, GPIO6), + (ADC1_CH5, GPIO6), (TOUCH7, GPIO7), (ADC1_CH6, GPIO7), (TOUCH8, GPIO8), + (ADC1_CH7, GPIO8), (TOUCH9, GPIO9), (ADC1_CH8, GPIO9), (TOUCH10, GPIO10), + (ADC1_CH9, GPIO10), (TOUCH11, GPIO11), (ADC2_CH0, GPIO11), (TOUCH12, GPIO12), + (ADC2_CH1, GPIO12), (TOUCH13, GPIO13), (ADC2_CH2, GPIO13), (TOUCH14, GPIO14), + (ADC2_CH3, GPIO14), (XTAL_32K_P, GPIO15), (ADC2_CH4, GPIO15), (XTAL_32K_N, + GPIO16), (ADC2_CH5, GPIO16), (DAC_1, GPIO17), (ADC2_CH6, GPIO17), (DAC_2, + GPIO18), (ADC2_CH7, GPIO18), (USB_DM, GPIO19), (ADC2_CH8, GPIO19), (USB_DP, + GPIO20), (ADC2_CH9, GPIO20))); + _for_each_inner_analog_function!((all_expanded((TOUCH1, TOUCHn, 1), GPIO1), + ((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1), ((TOUCH2, TOUCHn, 2), GPIO2), ((ADC1_CH1, + ADCn_CHm, 1, 1), GPIO2), ((TOUCH3, TOUCHn, 3), GPIO3), ((ADC1_CH2, ADCn_CHm, 1, + 2), GPIO3), ((TOUCH4, TOUCHn, 4), GPIO4), ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4), + ((TOUCH5, TOUCHn, 5), GPIO5), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5), ((TOUCH6, + TOUCHn, 6), GPIO6), ((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6), ((TOUCH7, TOUCHn, 7), + GPIO7), ((ADC1_CH6, ADCn_CHm, 1, 6), GPIO7), ((TOUCH8, TOUCHn, 8), GPIO8), + ((ADC1_CH7, ADCn_CHm, 1, 7), GPIO8), ((TOUCH9, TOUCHn, 9), GPIO9), ((ADC1_CH8, + ADCn_CHm, 1, 8), GPIO9), ((TOUCH10, TOUCHn, 10), GPIO10), ((ADC1_CH9, ADCn_CHm, + 1, 9), GPIO10), ((TOUCH11, TOUCHn, 11), GPIO11), ((ADC2_CH0, ADCn_CHm, 2, 0), + GPIO11), ((TOUCH12, TOUCHn, 12), GPIO12), ((ADC2_CH1, ADCn_CHm, 2, 1), GPIO12), + ((TOUCH13, TOUCHn, 13), GPIO13), ((ADC2_CH2, ADCn_CHm, 2, 2), GPIO13), ((TOUCH14, + TOUCHn, 14), GPIO14), ((ADC2_CH3, ADCn_CHm, 2, 3), GPIO14), ((ADC2_CH4, ADCn_CHm, + 2, 4), GPIO15), ((ADC2_CH5, ADCn_CHm, 2, 5), GPIO16), ((DAC_1, DAC_n, 1), + GPIO17), ((ADC2_CH6, ADCn_CHm, 2, 6), GPIO17), ((DAC_2, DAC_n, 2), GPIO18), + ((ADC2_CH7, ADCn_CHm, 2, 7), GPIO18), ((ADC2_CH8, ADCn_CHm, 2, 8), GPIO19), + ((ADC2_CH9, ADCn_CHm, 2, 9), GPIO20))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((RTC_GPIO0, GPIO0)); + _for_each_inner_lp_function!((RTC_GPIO1, GPIO1)); + _for_each_inner_lp_function!((RTC_GPIO2, GPIO2)); + _for_each_inner_lp_function!((RTC_GPIO3, GPIO3)); + _for_each_inner_lp_function!((RTC_GPIO4, GPIO4)); + _for_each_inner_lp_function!((RTC_GPIO5, GPIO5)); + _for_each_inner_lp_function!((RTC_GPIO6, GPIO6)); + _for_each_inner_lp_function!((RTC_GPIO7, GPIO7)); + _for_each_inner_lp_function!((RTC_GPIO8, GPIO8)); + _for_each_inner_lp_function!((RTC_GPIO9, GPIO9)); + _for_each_inner_lp_function!((RTC_GPIO10, GPIO10)); + _for_each_inner_lp_function!((RTC_GPIO11, GPIO11)); + _for_each_inner_lp_function!((RTC_GPIO12, GPIO12)); + _for_each_inner_lp_function!((RTC_GPIO13, GPIO13)); + _for_each_inner_lp_function!((RTC_GPIO14, GPIO14)); + _for_each_inner_lp_function!((RTC_GPIO15, GPIO15)); + _for_each_inner_lp_function!((RTC_GPIO16, GPIO16)); + _for_each_inner_lp_function!((RTC_GPIO17, GPIO17)); + _for_each_inner_lp_function!((RTC_GPIO18, GPIO18)); + _for_each_inner_lp_function!((RTC_GPIO19, GPIO19)); + _for_each_inner_lp_function!((RTC_GPIO20, GPIO20)); + _for_each_inner_lp_function!((RTC_GPIO21, GPIO21)); + _for_each_inner_lp_function!(((RTC_GPIO0, RTC_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((RTC_GPIO1, RTC_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((RTC_GPIO2, RTC_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((RTC_GPIO3, RTC_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((RTC_GPIO4, RTC_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((RTC_GPIO5, RTC_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!(((RTC_GPIO6, RTC_GPIOn, 6), GPIO6)); + _for_each_inner_lp_function!(((RTC_GPIO7, RTC_GPIOn, 7), GPIO7)); + _for_each_inner_lp_function!(((RTC_GPIO8, RTC_GPIOn, 8), GPIO8)); + _for_each_inner_lp_function!(((RTC_GPIO9, RTC_GPIOn, 9), GPIO9)); + _for_each_inner_lp_function!(((RTC_GPIO10, RTC_GPIOn, 10), GPIO10)); + _for_each_inner_lp_function!(((RTC_GPIO11, RTC_GPIOn, 11), GPIO11)); + _for_each_inner_lp_function!(((RTC_GPIO12, RTC_GPIOn, 12), GPIO12)); + _for_each_inner_lp_function!(((RTC_GPIO13, RTC_GPIOn, 13), GPIO13)); + _for_each_inner_lp_function!(((RTC_GPIO14, RTC_GPIOn, 14), GPIO14)); + _for_each_inner_lp_function!(((RTC_GPIO15, RTC_GPIOn, 15), GPIO15)); + _for_each_inner_lp_function!(((RTC_GPIO16, RTC_GPIOn, 16), GPIO16)); + _for_each_inner_lp_function!(((RTC_GPIO17, RTC_GPIOn, 17), GPIO17)); + _for_each_inner_lp_function!(((RTC_GPIO18, RTC_GPIOn, 18), GPIO18)); + _for_each_inner_lp_function!(((RTC_GPIO19, RTC_GPIOn, 19), GPIO19)); + _for_each_inner_lp_function!(((RTC_GPIO20, RTC_GPIOn, 20), GPIO20)); + _for_each_inner_lp_function!(((RTC_GPIO21, RTC_GPIOn, 21), GPIO21)); + _for_each_inner_lp_function!((all(RTC_GPIO0, GPIO0), (RTC_GPIO1, GPIO1), + (RTC_GPIO2, GPIO2), (RTC_GPIO3, GPIO3), (RTC_GPIO4, GPIO4), (RTC_GPIO5, GPIO5), + (RTC_GPIO6, GPIO6), (RTC_GPIO7, GPIO7), (RTC_GPIO8, GPIO8), (RTC_GPIO9, GPIO9), + (RTC_GPIO10, GPIO10), (RTC_GPIO11, GPIO11), (RTC_GPIO12, GPIO12), (RTC_GPIO13, + GPIO13), (RTC_GPIO14, GPIO14), (RTC_GPIO15, GPIO15), (RTC_GPIO16, GPIO16), + (RTC_GPIO17, GPIO17), (RTC_GPIO18, GPIO18), (RTC_GPIO19, GPIO19), (RTC_GPIO20, + GPIO20), (RTC_GPIO21, GPIO21))); + _for_each_inner_lp_function!((all_expanded((RTC_GPIO0, RTC_GPIOn, 0), GPIO0), + ((RTC_GPIO1, RTC_GPIOn, 1), GPIO1), ((RTC_GPIO2, RTC_GPIOn, 2), GPIO2), + ((RTC_GPIO3, RTC_GPIOn, 3), GPIO3), ((RTC_GPIO4, RTC_GPIOn, 4), GPIO4), + ((RTC_GPIO5, RTC_GPIOn, 5), GPIO5), ((RTC_GPIO6, RTC_GPIOn, 6), GPIO6), + ((RTC_GPIO7, RTC_GPIOn, 7), GPIO7), ((RTC_GPIO8, RTC_GPIOn, 8), GPIO8), + ((RTC_GPIO9, RTC_GPIOn, 9), GPIO9), ((RTC_GPIO10, RTC_GPIOn, 10), GPIO10), + ((RTC_GPIO11, RTC_GPIOn, 11), GPIO11), ((RTC_GPIO12, RTC_GPIOn, 12), GPIO12), + ((RTC_GPIO13, RTC_GPIOn, 13), GPIO13), ((RTC_GPIO14, RTC_GPIOn, 14), GPIO14), + ((RTC_GPIO15, RTC_GPIOn, 15), GPIO15), ((RTC_GPIO16, RTC_GPIOn, 16), GPIO16), + ((RTC_GPIO17, RTC_GPIOn, 17), GPIO17), ((RTC_GPIO18, RTC_GPIOn, 18), GPIO18), + ((RTC_GPIO19, RTC_GPIOn, 19), GPIO19), ((RTC_GPIO20, RTC_GPIOn, 20), GPIO20), + ((RTC_GPIO21, RTC_GPIOn, 21), GPIO21))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPID4 = 7, + SPID5 = 8, + SPID6 = 9, + SPID7 = 10, + SPIDQS = 11, + U0RXD = 14, + U0CTS = 15, + U0DSR = 16, + U1RXD = 17, + U1CTS = 18, + U1DSR = 21, + I2S0O_BCK = 23, + I2S0O_WS = 25, + I2S0I_BCK = 27, + I2S0I_WS = 28, + I2CEXT0_SCL = 29, + I2CEXT0_SDA = 30, + PCNT0_SIG_CH0 = 39, + PCNT0_SIG_CH1 = 40, + PCNT0_CTRL_CH0 = 41, + PCNT0_CTRL_CH1 = 42, + PCNT1_SIG_CH0 = 43, + PCNT1_SIG_CH1 = 44, + PCNT1_CTRL_CH0 = 45, + PCNT1_CTRL_CH1 = 46, + PCNT2_SIG_CH0 = 47, + PCNT2_SIG_CH1 = 48, + PCNT2_CTRL_CH0 = 49, + PCNT2_CTRL_CH1 = 50, + PCNT3_SIG_CH0 = 51, + PCNT3_SIG_CH1 = 52, + PCNT3_CTRL_CH0 = 53, + PCNT3_CTRL_CH1 = 54, + USB_EXTPHY_VP = 61, + USB_EXTPHY_VM = 62, + USB_EXTPHY_RCV = 63, + USB_OTG_IDDIG = 64, + USB_OTG_AVALID = 65, + USB_SRP_BVALID = 66, + USB_OTG_VBUSVALID = 67, + USB_SRP_SESSEND = 68, + SPI3_CLK = 72, + SPI3_Q = 73, + SPI3_D = 74, + SPI3_HD = 75, + SPI3_CS0 = 76, + RMT_SIG_0 = 83, + RMT_SIG_1 = 84, + RMT_SIG_2 = 85, + RMT_SIG_3 = 86, + I2CEXT1_SCL = 95, + I2CEXT1_SDA = 96, + FSPICLK = 108, + FSPIQ = 109, + FSPID = 110, + FSPIHD = 111, + FSPIWP = 112, + FSPIIO4 = 113, + FSPIIO5 = 114, + FSPIIO6 = 115, + FSPIIO7 = 116, + FSPICS0 = 117, + TWAI_RX = 123, + SUBSPIQ = 127, + SUBSPID = 128, + SUBSPIHD = 129, + SUBSPIWP = 130, + I2S0I_DATA_IN15 = 158, + SUBSPID4 = 167, + SUBSPID5 = 168, + SUBSPID6 = 169, + SUBSPID7 = 170, + SUBSPIDQS = 171, + PCMFSYNC = 203, + PCMCLK = 204, + PRO_ALONEGPIO0 = 235, + PRO_ALONEGPIO1 = 236, + PRO_ALONEGPIO2 = 237, + PRO_ALONEGPIO3 = 238, + PRO_ALONEGPIO4 = 239, + PRO_ALONEGPIO5 = 240, + PRO_ALONEGPIO6 = 241, + PRO_ALONEGPIO7 = 242, + MTDI, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPICLK = 4, + SPICS0 = 5, + SPICS1 = 6, + SPID4 = 7, + SPID5 = 8, + SPID6 = 9, + SPID7 = 10, + SPIDQS = 11, + U0TXD = 14, + U0RTS = 15, + U0DTR = 16, + U1TXD = 17, + U1RTS = 18, + U1DTR = 21, + I2S0O_BCK = 23, + I2S0O_WS = 25, + I2S0I_BCK = 27, + I2S0I_WS = 28, + I2CEXT0_SCL = 29, + I2CEXT0_SDA = 30, + SDIO_TOHOST_INT = 31, + USB_EXTPHY_OEN = 61, + USB_EXTPHY_VPO = 63, + USB_EXTPHY_VMO = 64, + SPI3_CLK = 72, + SPI3_Q = 73, + SPI3_D = 74, + SPI3_HD = 75, + SPI3_CS0 = 76, + SPI3_CS1 = 77, + SPI3_CS2 = 78, + LEDC_LS_SIG0 = 79, + LEDC_LS_SIG1 = 80, + LEDC_LS_SIG2 = 81, + LEDC_LS_SIG3 = 82, + LEDC_LS_SIG4 = 83, + LEDC_LS_SIG5 = 84, + LEDC_LS_SIG6 = 85, + LEDC_LS_SIG7 = 86, + RMT_SIG_0 = 87, + RMT_SIG_1 = 88, + RMT_SIG_2 = 89, + RMT_SIG_3 = 90, + I2CEXT1_SCL = 95, + I2CEXT1_SDA = 96, + GPIO_SD0 = 100, + GPIO_SD1 = 101, + GPIO_SD2 = 102, + GPIO_SD3 = 103, + GPIO_SD4 = 104, + GPIO_SD5 = 105, + GPIO_SD6 = 106, + GPIO_SD7 = 107, + FSPICLK = 108, + FSPIQ = 109, + FSPID = 110, + FSPIHD = 111, + FSPIWP = 112, + FSPIIO4 = 113, + FSPIIO5 = 114, + FSPIIO6 = 115, + FSPIIO7 = 116, + FSPICS0 = 117, + FSPICS1 = 118, + FSPICS2 = 119, + FSPICS3 = 120, + FSPICS4 = 121, + FSPICS5 = 122, + TWAI_TX = 123, + SUBSPICLK = 126, + SUBSPIQ = 127, + SUBSPID = 128, + SUBSPIHD = 129, + SUBSPIWP = 130, + SUBSPICS0 = 131, + SUBSPICS1 = 132, + FSPIDQS = 133, + FSPI_HSYNC = 134, + FSPI_VSYNC = 135, + FSPI_DE = 136, + FSPICD = 137, + SPI3_CD = 139, + SPI3_DQS = 140, + I2S0O_DATA_OUT23 = 166, + SUBSPID4 = 167, + SUBSPID5 = 168, + SUBSPID6 = 169, + SUBSPID7 = 170, + SUBSPIDQS = 171, + PCMFSYNC = 209, + PCMCLK = 210, + PRO_ALONEGPIO0 = 235, + PRO_ALONEGPIO1 = 236, + PRO_ALONEGPIO2 = 237, + PRO_ALONEGPIO3 = 238, + PRO_ALONEGPIO4 = 239, + PRO_ALONEGPIO5 = 240, + PRO_ALONEGPIO6 = 241, + PRO_ALONEGPIO7 = 242, + CLK_I2S = 251, + GPIO = 256, + CLK_OUT1, + CLK_OUT2, + CLK_OUT3, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs new file mode 100644 index 00000000000..f748bef4146 --- /dev/null +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -0,0 +1,4962 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +/// The name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip!(); +#[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip { + () => { + "esp32s3" + }; +} +/// The pretty name of the chip as `&str` +/// +/// # Example +/// +/// ```rust, no_run +/// use esp_hal::chip; +/// let chip_name = chip_pretty!(); +#[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")] +/// ``` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! chip_pretty { + () => { + "ESP32-S3" + }; +} +/// The properties of this chip and its drivers. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! property { + ("chip") => { + "esp32s3" + }; + ("arch") => { + "xtensa" + }; + ("cores") => { + 2 + }; + ("cores", str) => { + stringify!(2) + }; + ("trm") => { + "https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf" + }; + ("aes.dma") => { + true + }; + ("aes.has_split_text_registers") => { + true + }; + ("aes.endianness_configurable") => { + false + }; + ("assist_debug.has_sp_monitor") => { + false + }; + ("assist_debug.has_region_monitor") => { + true + }; + ("bt.controller") => { + "btdm" + }; + ("dedicated_gpio.needs_initialization") => { + true + }; + ("dedicated_gpio.channel_count") => { + 8 + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(8) + }; + ("dma.kind") => { + "gdma" + }; + ("dma.supports_mem2mem") => { + true + }; + ("dma.separate_in_out_interrupts") => { + true + }; + ("dma.max_priority") => { + 9 + }; + ("dma.max_priority", str) => { + stringify!(9) + }; + ("dma.gdma_version") => { + 1 + }; + ("dma.gdma_version", str) => { + stringify!(1) + }; + ("gpio.has_bank_1") => { + true + }; + ("gpio.gpio_function") => { + 1 + }; + ("gpio.gpio_function", str) => { + stringify!(1) + }; + ("gpio.constant_0_input") => { + 60 + }; + ("gpio.constant_0_input", str) => { + stringify!(60) + }; + ("gpio.constant_1_input") => { + 56 + }; + ("gpio.constant_1_input", str) => { + stringify!(56) + }; + ("gpio.remap_iomux_pin_registers") => { + false + }; + ("gpio.func_in_sel_offset") => { + 0 + }; + ("gpio.func_in_sel_offset", str) => { + stringify!(0) + }; + ("gpio.input_signal_max") => { + 255 + }; + ("gpio.input_signal_max", str) => { + stringify!(255) + }; + ("gpio.output_signal_max") => { + 256 + }; + ("gpio.output_signal_max", str) => { + stringify!(256) + }; + ("i2c_master.has_fsm_timeouts") => { + true + }; + ("i2c_master.has_hw_bus_clear") => { + false + }; + ("i2c_master.has_bus_timeout_enable") => { + true + }; + ("i2c_master.separate_filter_config_registers") => { + false + }; + ("i2c_master.can_estimate_nack_reason") => { + true + }; + ("i2c_master.has_conf_update") => { + true + }; + ("i2c_master.has_reliable_fsm_reset") => { + false + }; + ("i2c_master.has_arbitration_en") => { + true + }; + ("i2c_master.has_tx_fifo_watermark") => { + true + }; + ("i2c_master.bus_timeout_is_exponential") => { + true + }; + ("i2c_master.max_bus_timeout") => { + 31 + }; + ("i2c_master.max_bus_timeout", str) => { + stringify!(31) + }; + ("i2c_master.ll_intr_mask") => { + 262143 + }; + ("i2c_master.ll_intr_mask", str) => { + stringify!(262143) + }; + ("i2c_master.fifo_size") => { + 32 + }; + ("i2c_master.fifo_size", str) => { + stringify!(32) + }; + ("interrupts.status_registers") => { + 4 + }; + ("interrupts.status_registers", str) => { + stringify!(4) + }; + ("phy.combo_module") => { + true + }; + ("phy.backed_up_digital_register_count") => { + 21 + }; + ("phy.backed_up_digital_register_count", str) => { + stringify!(21) + }; + ("rmt.ram_start") => { + 1610704896 + }; + ("rmt.ram_start", str) => { + stringify!(1610704896) + }; + ("rmt.channel_ram_size") => { + 48 + }; + ("rmt.channel_ram_size", str) => { + stringify!(48) + }; + ("rmt.has_tx_immediate_stop") => { + true + }; + ("rmt.has_tx_loop_count") => { + true + }; + ("rmt.has_tx_loop_auto_stop") => { + true + }; + ("rmt.has_tx_carrier_data_only") => { + true + }; + ("rmt.has_tx_sync") => { + true + }; + ("rmt.has_rx_wrap") => { + true + }; + ("rmt.has_rx_demodulation") => { + true + }; + ("rmt.has_dma") => { + true + }; + ("rmt.has_per_channel_clock") => { + false + }; + ("rng.apb_cycle_wait_num") => { + 16 + }; + ("rng.apb_cycle_wait_num", str) => { + stringify!(16) + }; + ("rng.trng_supported") => { + true + }; + ("rsa.size_increment") => { + 32 + }; + ("rsa.size_increment", str) => { + stringify!(32) + }; + ("rsa.memory_size_bytes") => { + 512 + }; + ("rsa.memory_size_bytes", str) => { + stringify!(512) + }; + ("sha.dma") => { + true + }; + ("sleep.light_sleep") => { + true + }; + ("sleep.deep_sleep") => { + true + }; + ("soc.cpu_has_branch_predictor") => { + false + }; + ("soc.cpu_has_csr_pc") => { + false + }; + ("soc.multi_core_enabled") => { + true + }; + ("soc.rc_fast_clk_default") => { + 17500000 + }; + ("soc.rc_fast_clk_default", str) => { + stringify!(17500000) + }; + ("spi_master.supports_dma") => { + true + }; + ("spi_master.has_octal") => { + true + }; + ("spi_master.has_app_interrupts") => { + true + }; + ("spi_master.has_dma_segmented_transfer") => { + true + }; + ("spi_master.has_clk_pre_div") => { + false + }; + ("spi_slave.supports_dma") => { + true + }; + ("timergroup.timg_has_timer1") => { + true + }; + ("timergroup.timg_has_divcnt_rst") => { + false + }; + ("uart.ram_size") => { + 128 + }; + ("uart.ram_size", str) => { + stringify!(128) + }; + ("uart.peripheral_controls_mem_clk") => { + false + }; + ("uhci.combined_uart_selector_field") => { + false + }; + ("wifi.has_wifi6") => { + false + }; + ("wifi.mac_version") => { + 1 + }; + ("wifi.mac_version", str) => { + stringify!(1) + }; + ("wifi.has_5g") => { + false + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_aes_key_length { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_aes_key_length { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_aes_key_length!((128)); + _for_each_inner_aes_key_length!((256)); _for_each_inner_aes_key_length!((128, 0, + 4)); _for_each_inner_aes_key_length!((256, 2, 6)); + _for_each_inner_aes_key_length!((bits(128), (256))); + _for_each_inner_aes_key_length!((modes(128, 0, 4), (256, 2, 6))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_dedicated_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_dedicated_gpio { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_dedicated_gpio!((0)); + _for_each_inner_dedicated_gpio!((1)); _for_each_inner_dedicated_gpio!((2)); + _for_each_inner_dedicated_gpio!((3)); _for_each_inner_dedicated_gpio!((4)); + _for_each_inner_dedicated_gpio!((5)); _for_each_inner_dedicated_gpio!((6)); + _for_each_inner_dedicated_gpio!((7)); _for_each_inner_dedicated_gpio!((0, 0, + PRO_ALONEGPIO0)); _for_each_inner_dedicated_gpio!((0, 1, PRO_ALONEGPIO1)); + _for_each_inner_dedicated_gpio!((0, 2, PRO_ALONEGPIO2)); + _for_each_inner_dedicated_gpio!((0, 3, PRO_ALONEGPIO3)); + _for_each_inner_dedicated_gpio!((0, 4, PRO_ALONEGPIO4)); + _for_each_inner_dedicated_gpio!((0, 5, PRO_ALONEGPIO5)); + _for_each_inner_dedicated_gpio!((0, 6, PRO_ALONEGPIO6)); + _for_each_inner_dedicated_gpio!((0, 7, PRO_ALONEGPIO7)); + _for_each_inner_dedicated_gpio!((1, 0, CORE1_GPIO0)); + _for_each_inner_dedicated_gpio!((1, 1, CORE1_GPIO1)); + _for_each_inner_dedicated_gpio!((1, 2, CORE1_GPIO2)); + _for_each_inner_dedicated_gpio!((1, 3, CORE1_GPIO3)); + _for_each_inner_dedicated_gpio!((1, 4, CORE1_GPIO4)); + _for_each_inner_dedicated_gpio!((1, 5, CORE1_GPIO5)); + _for_each_inner_dedicated_gpio!((1, 6, CORE1_GPIO6)); + _for_each_inner_dedicated_gpio!((1, 7, CORE1_GPIO7)); + _for_each_inner_dedicated_gpio!((channels(0), (1), (2), (3), (4), (5), (6), + (7))); _for_each_inner_dedicated_gpio!((signals(0, 0, PRO_ALONEGPIO0), (0, 1, + PRO_ALONEGPIO1), (0, 2, PRO_ALONEGPIO2), (0, 3, PRO_ALONEGPIO3), (0, 4, + PRO_ALONEGPIO4), (0, 5, PRO_ALONEGPIO5), (0, 6, PRO_ALONEGPIO6), (0, 7, + PRO_ALONEGPIO7), (1, 0, CORE1_GPIO0), (1, 1, CORE1_GPIO1), (1, 2, CORE1_GPIO2), + (1, 3, CORE1_GPIO3), (1, 4, CORE1_GPIO4), (1, 5, CORE1_GPIO5), (1, 6, + CORE1_GPIO6), (1, 7, CORE1_GPIO7))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sw_interrupt { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sw_interrupt { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sw_interrupt!((0, FROM_CPU_INTR0, + software_interrupt0)); _for_each_inner_sw_interrupt!((1, FROM_CPU_INTR1, + software_interrupt1)); _for_each_inner_sw_interrupt!((2, FROM_CPU_INTR2, + software_interrupt2)); _for_each_inner_sw_interrupt!((3, FROM_CPU_INTR3, + software_interrupt3)); _for_each_inner_sw_interrupt!((all(0, FROM_CPU_INTR0, + software_interrupt0), (1, FROM_CPU_INTR1, software_interrupt1), (2, + FROM_CPU_INTR2, software_interrupt2), (3, FROM_CPU_INTR3, software_interrupt3))); + }; +} +#[macro_export] +macro_rules! sw_interrupt_delay { + () => { + unsafe {} + }; +} +/// This macro can be used to generate code for each channel of the RMT peripheral. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has three options for its "Individual matcher" case: +/// +/// - `all`: `($num:literal)` +/// - `tx`: `($num:literal, $idx:literal)` +/// - `rx`: `($num:literal, $idx:literal)` +/// +/// Macro fragments: +/// +/// - `$num`: number of the channel, e.g. `0` +/// - `$idx`: index of the channel among channels of the same capability, e.g. `0` +/// +/// Example data: +/// +/// - `all`: `(0)` +/// - `tx`: `(1, 1)` +/// - `rx`: `(2, 0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_channel { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_channel { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_rmt_channel!((0)); _for_each_inner_rmt_channel!((1)); + _for_each_inner_rmt_channel!((2)); _for_each_inner_rmt_channel!((3)); + _for_each_inner_rmt_channel!((4)); _for_each_inner_rmt_channel!((5)); + _for_each_inner_rmt_channel!((6)); _for_each_inner_rmt_channel!((7)); + _for_each_inner_rmt_channel!((0, 0)); _for_each_inner_rmt_channel!((1, 1)); + _for_each_inner_rmt_channel!((2, 2)); _for_each_inner_rmt_channel!((3, 3)); + _for_each_inner_rmt_channel!((4, 0)); _for_each_inner_rmt_channel!((5, 1)); + _for_each_inner_rmt_channel!((6, 2)); _for_each_inner_rmt_channel!((7, 3)); + _for_each_inner_rmt_channel!((all(0), (1), (2), (3), (4), (5), (6), (7))); + _for_each_inner_rmt_channel!((tx(0, 0), (1, 1), (2, 2), (3, 3))); + _for_each_inner_rmt_channel!((rx(4, 0), (5, 1), (6, 2), (7, 3))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rmt_clock_source { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rmt_clock_source { $(($pattern) => $code;)* ($other + : tt) => {} } _for_each_inner_rmt_clock_source!((Apb, 1)); + _for_each_inner_rmt_clock_source!((RcFast, 2)); + _for_each_inner_rmt_clock_source!((Xtal, 3)); + _for_each_inner_rmt_clock_source!((Apb)); + _for_each_inner_rmt_clock_source!((all(Apb, 1), (RcFast, 2), (Xtal, 3))); + _for_each_inner_rmt_clock_source!((default(Apb))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_exponentiation { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_exponentiation { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_exponentiation!((32)); + _for_each_inner_rsa_exponentiation!((64)); + _for_each_inner_rsa_exponentiation!((96)); + _for_each_inner_rsa_exponentiation!((128)); + _for_each_inner_rsa_exponentiation!((160)); + _for_each_inner_rsa_exponentiation!((192)); + _for_each_inner_rsa_exponentiation!((224)); + _for_each_inner_rsa_exponentiation!((256)); + _for_each_inner_rsa_exponentiation!((288)); + _for_each_inner_rsa_exponentiation!((320)); + _for_each_inner_rsa_exponentiation!((352)); + _for_each_inner_rsa_exponentiation!((384)); + _for_each_inner_rsa_exponentiation!((416)); + _for_each_inner_rsa_exponentiation!((448)); + _for_each_inner_rsa_exponentiation!((480)); + _for_each_inner_rsa_exponentiation!((512)); + _for_each_inner_rsa_exponentiation!((544)); + _for_each_inner_rsa_exponentiation!((576)); + _for_each_inner_rsa_exponentiation!((608)); + _for_each_inner_rsa_exponentiation!((640)); + _for_each_inner_rsa_exponentiation!((672)); + _for_each_inner_rsa_exponentiation!((704)); + _for_each_inner_rsa_exponentiation!((736)); + _for_each_inner_rsa_exponentiation!((768)); + _for_each_inner_rsa_exponentiation!((800)); + _for_each_inner_rsa_exponentiation!((832)); + _for_each_inner_rsa_exponentiation!((864)); + _for_each_inner_rsa_exponentiation!((896)); + _for_each_inner_rsa_exponentiation!((928)); + _for_each_inner_rsa_exponentiation!((960)); + _for_each_inner_rsa_exponentiation!((992)); + _for_each_inner_rsa_exponentiation!((1024)); + _for_each_inner_rsa_exponentiation!((1056)); + _for_each_inner_rsa_exponentiation!((1088)); + _for_each_inner_rsa_exponentiation!((1120)); + _for_each_inner_rsa_exponentiation!((1152)); + _for_each_inner_rsa_exponentiation!((1184)); + _for_each_inner_rsa_exponentiation!((1216)); + _for_each_inner_rsa_exponentiation!((1248)); + _for_each_inner_rsa_exponentiation!((1280)); + _for_each_inner_rsa_exponentiation!((1312)); + _for_each_inner_rsa_exponentiation!((1344)); + _for_each_inner_rsa_exponentiation!((1376)); + _for_each_inner_rsa_exponentiation!((1408)); + _for_each_inner_rsa_exponentiation!((1440)); + _for_each_inner_rsa_exponentiation!((1472)); + _for_each_inner_rsa_exponentiation!((1504)); + _for_each_inner_rsa_exponentiation!((1536)); + _for_each_inner_rsa_exponentiation!((1568)); + _for_each_inner_rsa_exponentiation!((1600)); + _for_each_inner_rsa_exponentiation!((1632)); + _for_each_inner_rsa_exponentiation!((1664)); + _for_each_inner_rsa_exponentiation!((1696)); + _for_each_inner_rsa_exponentiation!((1728)); + _for_each_inner_rsa_exponentiation!((1760)); + _for_each_inner_rsa_exponentiation!((1792)); + _for_each_inner_rsa_exponentiation!((1824)); + _for_each_inner_rsa_exponentiation!((1856)); + _for_each_inner_rsa_exponentiation!((1888)); + _for_each_inner_rsa_exponentiation!((1920)); + _for_each_inner_rsa_exponentiation!((1952)); + _for_each_inner_rsa_exponentiation!((1984)); + _for_each_inner_rsa_exponentiation!((2016)); + _for_each_inner_rsa_exponentiation!((2048)); + _for_each_inner_rsa_exponentiation!((2080)); + _for_each_inner_rsa_exponentiation!((2112)); + _for_each_inner_rsa_exponentiation!((2144)); + _for_each_inner_rsa_exponentiation!((2176)); + _for_each_inner_rsa_exponentiation!((2208)); + _for_each_inner_rsa_exponentiation!((2240)); + _for_each_inner_rsa_exponentiation!((2272)); + _for_each_inner_rsa_exponentiation!((2304)); + _for_each_inner_rsa_exponentiation!((2336)); + _for_each_inner_rsa_exponentiation!((2368)); + _for_each_inner_rsa_exponentiation!((2400)); + _for_each_inner_rsa_exponentiation!((2432)); + _for_each_inner_rsa_exponentiation!((2464)); + _for_each_inner_rsa_exponentiation!((2496)); + _for_each_inner_rsa_exponentiation!((2528)); + _for_each_inner_rsa_exponentiation!((2560)); + _for_each_inner_rsa_exponentiation!((2592)); + _for_each_inner_rsa_exponentiation!((2624)); + _for_each_inner_rsa_exponentiation!((2656)); + _for_each_inner_rsa_exponentiation!((2688)); + _for_each_inner_rsa_exponentiation!((2720)); + _for_each_inner_rsa_exponentiation!((2752)); + _for_each_inner_rsa_exponentiation!((2784)); + _for_each_inner_rsa_exponentiation!((2816)); + _for_each_inner_rsa_exponentiation!((2848)); + _for_each_inner_rsa_exponentiation!((2880)); + _for_each_inner_rsa_exponentiation!((2912)); + _for_each_inner_rsa_exponentiation!((2944)); + _for_each_inner_rsa_exponentiation!((2976)); + _for_each_inner_rsa_exponentiation!((3008)); + _for_each_inner_rsa_exponentiation!((3040)); + _for_each_inner_rsa_exponentiation!((3072)); + _for_each_inner_rsa_exponentiation!((3104)); + _for_each_inner_rsa_exponentiation!((3136)); + _for_each_inner_rsa_exponentiation!((3168)); + _for_each_inner_rsa_exponentiation!((3200)); + _for_each_inner_rsa_exponentiation!((3232)); + _for_each_inner_rsa_exponentiation!((3264)); + _for_each_inner_rsa_exponentiation!((3296)); + _for_each_inner_rsa_exponentiation!((3328)); + _for_each_inner_rsa_exponentiation!((3360)); + _for_each_inner_rsa_exponentiation!((3392)); + _for_each_inner_rsa_exponentiation!((3424)); + _for_each_inner_rsa_exponentiation!((3456)); + _for_each_inner_rsa_exponentiation!((3488)); + _for_each_inner_rsa_exponentiation!((3520)); + _for_each_inner_rsa_exponentiation!((3552)); + _for_each_inner_rsa_exponentiation!((3584)); + _for_each_inner_rsa_exponentiation!((3616)); + _for_each_inner_rsa_exponentiation!((3648)); + _for_each_inner_rsa_exponentiation!((3680)); + _for_each_inner_rsa_exponentiation!((3712)); + _for_each_inner_rsa_exponentiation!((3744)); + _for_each_inner_rsa_exponentiation!((3776)); + _for_each_inner_rsa_exponentiation!((3808)); + _for_each_inner_rsa_exponentiation!((3840)); + _for_each_inner_rsa_exponentiation!((3872)); + _for_each_inner_rsa_exponentiation!((3904)); + _for_each_inner_rsa_exponentiation!((3936)); + _for_each_inner_rsa_exponentiation!((3968)); + _for_each_inner_rsa_exponentiation!((4000)); + _for_each_inner_rsa_exponentiation!((4032)); + _for_each_inner_rsa_exponentiation!((4064)); + _for_each_inner_rsa_exponentiation!((4096)); + _for_each_inner_rsa_exponentiation!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048), (2080), (2112), (2144), (2176), + (2208), (2240), (2272), (2304), (2336), (2368), (2400), (2432), (2464), (2496), + (2528), (2560), (2592), (2624), (2656), (2688), (2720), (2752), (2784), (2816), + (2848), (2880), (2912), (2944), (2976), (3008), (3040), (3072), (3104), (3136), + (3168), (3200), (3232), (3264), (3296), (3328), (3360), (3392), (3424), (3456), + (3488), (3520), (3552), (3584), (3616), (3648), (3680), (3712), (3744), (3776), + (3808), (3840), (3872), (3904), (3936), (3968), (4000), (4032), (4064), (4096))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_rsa_multiplication { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_rsa_multiplication { $(($pattern) => $code;)* + ($other : tt) => {} } _for_each_inner_rsa_multiplication!((32)); + _for_each_inner_rsa_multiplication!((64)); + _for_each_inner_rsa_multiplication!((96)); + _for_each_inner_rsa_multiplication!((128)); + _for_each_inner_rsa_multiplication!((160)); + _for_each_inner_rsa_multiplication!((192)); + _for_each_inner_rsa_multiplication!((224)); + _for_each_inner_rsa_multiplication!((256)); + _for_each_inner_rsa_multiplication!((288)); + _for_each_inner_rsa_multiplication!((320)); + _for_each_inner_rsa_multiplication!((352)); + _for_each_inner_rsa_multiplication!((384)); + _for_each_inner_rsa_multiplication!((416)); + _for_each_inner_rsa_multiplication!((448)); + _for_each_inner_rsa_multiplication!((480)); + _for_each_inner_rsa_multiplication!((512)); + _for_each_inner_rsa_multiplication!((544)); + _for_each_inner_rsa_multiplication!((576)); + _for_each_inner_rsa_multiplication!((608)); + _for_each_inner_rsa_multiplication!((640)); + _for_each_inner_rsa_multiplication!((672)); + _for_each_inner_rsa_multiplication!((704)); + _for_each_inner_rsa_multiplication!((736)); + _for_each_inner_rsa_multiplication!((768)); + _for_each_inner_rsa_multiplication!((800)); + _for_each_inner_rsa_multiplication!((832)); + _for_each_inner_rsa_multiplication!((864)); + _for_each_inner_rsa_multiplication!((896)); + _for_each_inner_rsa_multiplication!((928)); + _for_each_inner_rsa_multiplication!((960)); + _for_each_inner_rsa_multiplication!((992)); + _for_each_inner_rsa_multiplication!((1024)); + _for_each_inner_rsa_multiplication!((1056)); + _for_each_inner_rsa_multiplication!((1088)); + _for_each_inner_rsa_multiplication!((1120)); + _for_each_inner_rsa_multiplication!((1152)); + _for_each_inner_rsa_multiplication!((1184)); + _for_each_inner_rsa_multiplication!((1216)); + _for_each_inner_rsa_multiplication!((1248)); + _for_each_inner_rsa_multiplication!((1280)); + _for_each_inner_rsa_multiplication!((1312)); + _for_each_inner_rsa_multiplication!((1344)); + _for_each_inner_rsa_multiplication!((1376)); + _for_each_inner_rsa_multiplication!((1408)); + _for_each_inner_rsa_multiplication!((1440)); + _for_each_inner_rsa_multiplication!((1472)); + _for_each_inner_rsa_multiplication!((1504)); + _for_each_inner_rsa_multiplication!((1536)); + _for_each_inner_rsa_multiplication!((1568)); + _for_each_inner_rsa_multiplication!((1600)); + _for_each_inner_rsa_multiplication!((1632)); + _for_each_inner_rsa_multiplication!((1664)); + _for_each_inner_rsa_multiplication!((1696)); + _for_each_inner_rsa_multiplication!((1728)); + _for_each_inner_rsa_multiplication!((1760)); + _for_each_inner_rsa_multiplication!((1792)); + _for_each_inner_rsa_multiplication!((1824)); + _for_each_inner_rsa_multiplication!((1856)); + _for_each_inner_rsa_multiplication!((1888)); + _for_each_inner_rsa_multiplication!((1920)); + _for_each_inner_rsa_multiplication!((1952)); + _for_each_inner_rsa_multiplication!((1984)); + _for_each_inner_rsa_multiplication!((2016)); + _for_each_inner_rsa_multiplication!((2048)); + _for_each_inner_rsa_multiplication!((all(32), (64), (96), (128), (160), (192), + (224), (256), (288), (320), (352), (384), (416), (448), (480), (512), (544), + (576), (608), (640), (672), (704), (736), (768), (800), (832), (864), (896), + (928), (960), (992), (1024), (1056), (1088), (1120), (1152), (1184), (1216), + (1248), (1280), (1312), (1344), (1376), (1408), (1440), (1472), (1504), (1536), + (1568), (1600), (1632), (1664), (1696), (1728), (1760), (1792), (1824), (1856), + (1888), (1920), (1952), (1984), (2016), (2048))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_sha_algorithm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_sha_algorithm { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_sha_algorithm!((Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0)); + _for_each_inner_sha_algorithm!((Sha224, "SHA-224"(sizes : 64, 28, 8) + (insecure_against : "length extension"), 1)); + _for_each_inner_sha_algorithm!((Sha256, "SHA-256"(sizes : 64, 32, 8) + (insecure_against : "length extension"), 2)); + _for_each_inner_sha_algorithm!((Sha384, "SHA-384"(sizes : 128, 48, 16) + (insecure_against :), 3)); _for_each_inner_sha_algorithm!((Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 4)); + _for_each_inner_sha_algorithm!((Sha512_224, "SHA-512/224"(sizes : 128, 28, 16) + (insecure_against :), 5)); _for_each_inner_sha_algorithm!((Sha512_256, + "SHA-512/256"(sizes : 128, 32, 16) (insecure_against :), 6)); + _for_each_inner_sha_algorithm!((algos(Sha1, "SHA-1"(sizes : 64, 20, 8) + (insecure_against : "collision", "length extension"), 0), (Sha224, + "SHA-224"(sizes : 64, 28, 8) (insecure_against : "length extension"), 1), + (Sha256, "SHA-256"(sizes : 64, 32, 8) (insecure_against : "length extension"), + 2), (Sha384, "SHA-384"(sizes : 128, 48, 16) (insecure_against :), 3), (Sha512, + "SHA-512"(sizes : 128, 64, 16) (insecure_against : "length extension"), 4), + (Sha512_224, "SHA-512/224"(sizes : 128, 28, 16) (insecure_against :), 5), + (Sha512_256, "SHA-512/256"(sizes : 128, 32, 16) (insecure_against :), 6))); + }; +} +#[macro_export] +/// ESP-HAL must provide implementation for the following functions: +/// ```rust, no_run +/// // XTAL_CLK +/// +/// fn configure_xtal_clk_impl(_clocks: &mut ClockTree, _config: XtalClkConfig) { +/// todo!() +/// } +/// +/// // PLL_CLK +/// +/// fn enable_pll_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_pll_clk_impl(_clocks: &mut ClockTree, _config: PllClkConfig) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK +/// +/// fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // XTAL32K_CLK +/// +/// fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_SLOW_CLK +/// +/// fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_DIV_CLK +/// +/// fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV_IN +/// +/// fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_in_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: SystemPreDivInConfig, +/// ) { +/// todo!() +/// } +/// +/// // SYSTEM_PRE_DIV +/// +/// fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_system_pre_div_impl(_clocks: &mut ClockTree, _new_config: SystemPreDivConfig) { +/// todo!() +/// } +/// +/// // CPU_PLL_DIV_OUT +/// +/// fn enable_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_cpu_pll_div_out_impl(_clocks: &mut ClockTree, _config: CpuPllDivOutConfig) { +/// todo!() +/// } +/// +/// // APB_CLK +/// +/// fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_apb_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: ApbClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CRYPTO_PWM_CLK +/// +/// fn enable_crypto_pwm_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_crypto_pwm_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CryptoPwmClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // CPU_CLK +/// +/// fn configure_cpu_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: CpuClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // PLL_D2 +/// +/// fn enable_pll_d2_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // PLL_160M +/// +/// fn enable_pll_160m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // APB_80M +/// +/// fn enable_apb_80m_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RC_FAST_CLK_DIV_N +/// +/// fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _new_config: RcFastClkDivNConfig) { +/// todo!() +/// } +/// +/// // XTAL_DIV_CLK +/// +/// fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // RTC_SLOW_CLK +/// +/// fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_slow_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcSlowClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // RTC_FAST_CLK +/// +/// fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rtc_fast_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RtcFastClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // LOW_POWER_CLK +/// +/// fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_low_power_clk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LowPowerClkConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART_MEM_CLK +/// +/// fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// // MCPWM0_FUNCTION_CLOCK +/// +/// fn enable_mcpwm0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // MCPWM1_FUNCTION_CLOCK +/// +/// fn enable_mcpwm1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_mcpwm1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Mcpwm0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // RMT_SCLK +/// +/// fn enable_rmt_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_rmt_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: RmtSclkConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_FUNCTION_CLOCK +/// +/// fn enable_timg0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG0_CALIBRATION_CLOCK +/// +/// fn enable_timg0_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg0_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_FUNCTION_CLOCK +/// +/// fn enable_timg1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // TIMG1_CALIBRATION_CLOCK +/// +/// fn enable_timg1_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_timg1_calibration_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Timg0CalibrationClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_FUNCTION_CLOCK +/// +/// fn enable_uart0_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART0_MEM_CLOCK +/// +/// fn enable_uart0_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart0_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_FUNCTION_CLOCK +/// +/// fn enable_uart1_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART1_MEM_CLOCK +/// +/// fn enable_uart1_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart1_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART2_FUNCTION_CLOCK +/// +/// fn enable_uart2_function_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart2_function_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0FunctionClockConfig, +/// ) { +/// todo!() +/// } +/// +/// // UART2_MEM_CLOCK +/// +/// fn enable_uart2_mem_clock_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_uart2_mem_clock_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: Uart0MemClockConfig, +/// ) { +/// todo!() +/// } +/// ``` +macro_rules! define_clock_tree_types { + () => { + /// Selects the output frequency of `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum XtalClkConfig { + /// 40 MHz + _40, + } + impl XtalClkConfig { + pub fn value(&self) -> u32 { + match self { + XtalClkConfig::_40 => 40000000, + } + } + } + /// Selects the output frequency of `PLL_CLK`. Depends on `XTAL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PllClkConfig { + /// 320 MHz + _320, + /// 480 MHz + _480, + } + impl PllClkConfig { + pub fn value(&self) -> u32 { + match self { + PllClkConfig::_320 => 320000000, + PllClkConfig::_480 => 480000000, + } + } + } + /// The list of clock signals that the `SYSTEM_PRE_DIV_IN` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SystemPreDivInConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + } + /// Configures the `SYSTEM_PRE_DIV` clock divider. + /// + /// The output is calculated as `OUTPUT = SYSTEM_PRE_DIV_IN / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SystemPreDivConfig { + divisor: u32, + } + impl SystemPreDivConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 1023). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 1023u32, + "`SYSTEM_PRE_DIV` divisor value must be between 0 and 1023 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// Selects the output frequency of `CPU_PLL_DIV_OUT`. Depends on `PLL_CLK`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuPllDivOutConfig { + /// 80 MHz + _80, + /// 160 MHz + _160, + /// 240 MHz + _240, + } + impl CpuPllDivOutConfig { + pub fn value(&self) -> u32 { + match self { + CpuPllDivOutConfig::_80 => 80000000, + CpuPllDivOutConfig::_160 => 160000000, + CpuPllDivOutConfig::_240 => 240000000, + } + } + } + /// The list of clock signals that the `APB_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ApbClkConfig { + /// Selects `APB_80M`. + Pll, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CRYPTO_PWM_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CryptoPwmClkConfig { + /// Selects `PLL_160M`. + Pll, + /// Selects `CPU_CLK`. + Cpu, + } + /// The list of clock signals that the `CPU_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum CpuClkConfig { + /// Selects `SYSTEM_PRE_DIV`. + Xtal, + /// Selects `SYSTEM_PRE_DIV`. + RcFast, + /// Selects `CPU_PLL_DIV_OUT`. + Pll, + } + /// Configures the `RC_FAST_CLK_DIV_N` clock divider. + /// + /// The output is calculated as `OUTPUT = RC_FAST_CLK / (divisor + 1)`. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RcFastClkDivNConfig { + divisor: u32, + } + impl RcFastClkDivNConfig { + /// Creates a new divider configuration. + /// ## Panics + /// + /// Panics if the divisor value is outside the + /// valid range (0 ..= 3). + pub const fn new(divisor: u32) -> Self { + ::core::assert!( + divisor <= 3u32, + "`RC_FAST_CLK_DIV_N` divisor value must be between 0 and 3 (inclusive)." + ); + Self { divisor } + } + fn divisor(self) -> u32 { + self.divisor + } + fn value(self) -> u32 { + self.divisor() + } + } + /// The list of clock signals that the `RTC_SLOW_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcSlowClkConfig { + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RC_SLOW_CLK`. + RcSlow, + /// Selects `RC_FAST_DIV_CLK`. + RcFast, + } + /// The list of clock signals that the `RTC_FAST_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RtcFastClkConfig { + /// Selects `XTAL_DIV_CLK`. + Xtal, + /// Selects `RC_FAST_CLK_DIV_N`. + Rc, + } + /// The list of clock signals that the `LOW_POWER_CLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LowPowerClkConfig { + /// Selects `XTAL_CLK`. + Xtal, + /// Selects `RC_FAST_CLK`. + RcFast, + /// Selects `XTAL32K_CLK`. + Xtal32k, + /// Selects `RTC_SLOW_CLK`. + RtcSlow, + } + /// The list of clock signals that the `MCPWM0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Mcpwm0FunctionClockConfig { + #[default] + /// Selects `CRYPTO_PWM_CLK`. + CryptoPwmClk, + } + /// The list of clock signals that the `RMT_SCLK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum RmtSclkConfig { + #[default] + /// Selects `APB_CLK`. + ApbClk, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } + /// The list of clock signals that the `TIMG0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0FunctionClockConfig { + #[default] + /// Selects `XTAL_CLK`. + XtalClk, + /// Selects `APB_CLK`. + ApbClk, + } + /// The list of clock signals that the `TIMG0_CALIBRATION_CLOCK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Timg0CalibrationClockConfig { + /// Selects `RC_SLOW_CLK`. + RcSlowClk, + /// Selects `RC_FAST_DIV_CLK`. + RcFastDivClk, + /// Selects `XTAL32K_CLK`. + Xtal32kClk, + } + /// The list of clock signals that the `UART0_FUNCTION_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0FunctionClockConfig { + /// Selects `APB_CLK`. + Apb, + /// Selects `RC_FAST_CLK`. + RcFast, + #[default] + /// Selects `XTAL_CLK`. + Xtal, + } + /// The list of clock signals that the `UART0_MEM_CLOCK` multiplexer can output. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Uart0MemClockConfig { + #[default] + /// Selects `UART_MEM_CLK`. + Mem, + } + /// Represents the device's clock tree. + pub struct ClockTree { + xtal_clk: Option, + pll_clk: Option, + system_pre_div_in: Option, + system_pre_div: Option, + cpu_pll_div_out: Option, + apb_clk: Option, + crypto_pwm_clk: Option, + cpu_clk: Option, + rc_fast_clk_div_n: Option, + rtc_slow_clk: Option, + rtc_fast_clk: Option, + low_power_clk: Option, + mcpwm0_function_clock: Option, + mcpwm1_function_clock: Option, + rmt_sclk: Option, + timg0_function_clock: Option, + timg0_calibration_clock: Option, + timg1_function_clock: Option, + timg1_calibration_clock: Option, + uart0_function_clock: Option, + uart0_mem_clock: Option, + uart1_function_clock: Option, + uart1_mem_clock: Option, + uart2_function_clock: Option, + uart2_mem_clock: Option, + pll_clk_refcount: u32, + rc_fast_clk_refcount: u32, + xtal32k_clk_refcount: u32, + rc_slow_clk_refcount: u32, + rc_fast_div_clk_refcount: u32, + apb_clk_refcount: u32, + crypto_pwm_clk_refcount: u32, + pll_d2_refcount: u32, + rtc_fast_clk_refcount: u32, + low_power_clk_refcount: u32, + uart_mem_clk_refcount: u32, + mcpwm0_function_clock_refcount: u32, + mcpwm1_function_clock_refcount: u32, + rmt_sclk_refcount: u32, + timg0_function_clock_refcount: u32, + timg0_calibration_clock_refcount: u32, + timg1_function_clock_refcount: u32, + timg1_calibration_clock_refcount: u32, + uart0_function_clock_refcount: u32, + uart0_mem_clock_refcount: u32, + uart1_function_clock_refcount: u32, + uart1_mem_clock_refcount: u32, + uart2_function_clock_refcount: u32, + uart2_mem_clock_refcount: u32, + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + /// Returns the current configuration of the XTAL_CLK clock tree node + pub fn xtal_clk(&self) -> Option { + self.xtal_clk + } + /// Returns the current configuration of the PLL_CLK clock tree node + pub fn pll_clk(&self) -> Option { + self.pll_clk + } + /// Returns the current configuration of the SYSTEM_PRE_DIV_IN clock tree node + pub fn system_pre_div_in(&self) -> Option { + self.system_pre_div_in + } + /// Returns the current configuration of the SYSTEM_PRE_DIV clock tree node + pub fn system_pre_div(&self) -> Option { + self.system_pre_div + } + /// Returns the current configuration of the CPU_PLL_DIV_OUT clock tree node + pub fn cpu_pll_div_out(&self) -> Option { + self.cpu_pll_div_out + } + /// Returns the current configuration of the APB_CLK clock tree node + pub fn apb_clk(&self) -> Option { + self.apb_clk + } + /// Returns the current configuration of the CRYPTO_PWM_CLK clock tree node + pub fn crypto_pwm_clk(&self) -> Option { + self.crypto_pwm_clk + } + /// Returns the current configuration of the CPU_CLK clock tree node + pub fn cpu_clk(&self) -> Option { + self.cpu_clk + } + /// Returns the current configuration of the RC_FAST_CLK_DIV_N clock tree node + pub fn rc_fast_clk_div_n(&self) -> Option { + self.rc_fast_clk_div_n + } + /// Returns the current configuration of the RTC_SLOW_CLK clock tree node + pub fn rtc_slow_clk(&self) -> Option { + self.rtc_slow_clk + } + /// Returns the current configuration of the RTC_FAST_CLK clock tree node + pub fn rtc_fast_clk(&self) -> Option { + self.rtc_fast_clk + } + /// Returns the current configuration of the LOW_POWER_CLK clock tree node + pub fn low_power_clk(&self) -> Option { + self.low_power_clk + } + /// Returns the current configuration of the MCPWM0_FUNCTION_CLOCK clock tree node + pub fn mcpwm0_function_clock(&self) -> Option { + self.mcpwm0_function_clock + } + /// Returns the current configuration of the MCPWM1_FUNCTION_CLOCK clock tree node + pub fn mcpwm1_function_clock(&self) -> Option { + self.mcpwm1_function_clock + } + /// Returns the current configuration of the RMT_SCLK clock tree node + pub fn rmt_sclk(&self) -> Option { + self.rmt_sclk + } + /// Returns the current configuration of the TIMG0_FUNCTION_CLOCK clock tree node + pub fn timg0_function_clock(&self) -> Option { + self.timg0_function_clock + } + /// Returns the current configuration of the TIMG0_CALIBRATION_CLOCK clock tree node + pub fn timg0_calibration_clock(&self) -> Option { + self.timg0_calibration_clock + } + /// Returns the current configuration of the TIMG1_FUNCTION_CLOCK clock tree node + pub fn timg1_function_clock(&self) -> Option { + self.timg1_function_clock + } + /// Returns the current configuration of the TIMG1_CALIBRATION_CLOCK clock tree node + pub fn timg1_calibration_clock(&self) -> Option { + self.timg1_calibration_clock + } + /// Returns the current configuration of the UART0_FUNCTION_CLOCK clock tree node + pub fn uart0_function_clock(&self) -> Option { + self.uart0_function_clock + } + /// Returns the current configuration of the UART0_MEM_CLOCK clock tree node + pub fn uart0_mem_clock(&self) -> Option { + self.uart0_mem_clock + } + /// Returns the current configuration of the UART1_FUNCTION_CLOCK clock tree node + pub fn uart1_function_clock(&self) -> Option { + self.uart1_function_clock + } + /// Returns the current configuration of the UART1_MEM_CLOCK clock tree node + pub fn uart1_mem_clock(&self) -> Option { + self.uart1_mem_clock + } + /// Returns the current configuration of the UART2_FUNCTION_CLOCK clock tree node + pub fn uart2_function_clock(&self) -> Option { + self.uart2_function_clock + } + /// Returns the current configuration of the UART2_MEM_CLOCK clock tree node + pub fn uart2_mem_clock(&self) -> Option { + self.uart2_mem_clock + } + } + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + xtal_clk: None, + pll_clk: None, + system_pre_div_in: None, + system_pre_div: None, + cpu_pll_div_out: None, + apb_clk: None, + crypto_pwm_clk: None, + cpu_clk: None, + rc_fast_clk_div_n: None, + rtc_slow_clk: None, + rtc_fast_clk: None, + low_power_clk: None, + mcpwm0_function_clock: None, + mcpwm1_function_clock: None, + rmt_sclk: None, + timg0_function_clock: None, + timg0_calibration_clock: None, + timg1_function_clock: None, + timg1_calibration_clock: None, + uart0_function_clock: None, + uart0_mem_clock: None, + uart1_function_clock: None, + uart1_mem_clock: None, + uart2_function_clock: None, + uart2_mem_clock: None, + pll_clk_refcount: 0, + rc_fast_clk_refcount: 0, + xtal32k_clk_refcount: 0, + rc_slow_clk_refcount: 0, + rc_fast_div_clk_refcount: 0, + apb_clk_refcount: 0, + crypto_pwm_clk_refcount: 0, + pll_d2_refcount: 0, + rtc_fast_clk_refcount: 0, + low_power_clk_refcount: 0, + uart_mem_clk_refcount: 0, + mcpwm0_function_clock_refcount: 0, + mcpwm1_function_clock_refcount: 0, + rmt_sclk_refcount: 0, + timg0_function_clock_refcount: 0, + timg0_calibration_clock_refcount: 0, + timg1_function_clock_refcount: 0, + timg1_calibration_clock_refcount: 0, + uart0_function_clock_refcount: 0, + uart0_mem_clock_refcount: 0, + uart1_function_clock_refcount: 0, + uart1_mem_clock_refcount: 0, + uart2_function_clock_refcount: 0, + uart2_mem_clock_refcount: 0, + }); + pub fn configure_xtal_clk(clocks: &mut ClockTree, config: XtalClkConfig) { + clocks.xtal_clk = Some(config); + configure_xtal_clk_impl(clocks, config); + } + pub fn xtal_clk_config(clocks: &mut ClockTree) -> Option { + clocks.xtal_clk + } + fn request_xtal_clk(_clocks: &mut ClockTree) {} + fn release_xtal_clk(_clocks: &mut ClockTree) {} + pub fn xtal_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.xtal_clk).value() + } + pub fn configure_pll_clk(clocks: &mut ClockTree, config: PllClkConfig) { + if let Some(cpu_pll_div_out) = clocks.cpu_pll_div_out { + assert!(!((config.value() == 320000000) && (cpu_pll_div_out.value() == 240000000))); + } + clocks.pll_clk = Some(config); + configure_pll_clk_impl(clocks, config); + } + pub fn pll_clk_config(clocks: &mut ClockTree) -> Option { + clocks.pll_clk + } + pub fn request_pll_clk(clocks: &mut ClockTree) { + trace!("Requesting PLL_CLK"); + if increment_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Enabling PLL_CLK"); + request_xtal_clk(clocks); + enable_pll_clk_impl(clocks, true); + } + } + pub fn release_pll_clk(clocks: &mut ClockTree) { + trace!("Releasing PLL_CLK"); + if decrement_reference_count(&mut clocks.pll_clk_refcount) { + trace!("Disabling PLL_CLK"); + enable_pll_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn pll_clk_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.pll_clk).value() + } + pub fn request_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK"); + if increment_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Enabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_clk_refcount) { + trace!("Disabling RC_FAST_CLK"); + enable_rc_fast_clk_impl(clocks, false); + } + } + pub fn rc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + 17500000 + } + pub fn request_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL32K_CLK"); + if increment_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Enabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, true); + } + } + pub fn release_xtal32k_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL32K_CLK"); + if decrement_reference_count(&mut clocks.xtal32k_clk_refcount) { + trace!("Disabling XTAL32K_CLK"); + enable_xtal32k_clk_impl(clocks, false); + } + } + pub fn xtal32k_clk_frequency(clocks: &mut ClockTree) -> u32 { + 32768 + } + pub fn request_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_SLOW_CLK"); + if increment_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Enabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, true); + } + } + pub fn release_rc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_SLOW_CLK"); + if decrement_reference_count(&mut clocks.rc_slow_clk_refcount) { + trace!("Disabling RC_SLOW_CLK"); + enable_rc_slow_clk_impl(clocks, false); + } + } + pub fn rc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + 136000 + } + pub fn request_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_DIV_CLK"); + if increment_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Enabling RC_FAST_DIV_CLK"); + request_rc_fast_clk(clocks); + enable_rc_fast_div_clk_impl(clocks, true); + } + } + pub fn release_rc_fast_div_clk(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_DIV_CLK"); + if decrement_reference_count(&mut clocks.rc_fast_div_clk_refcount) { + trace!("Disabling RC_FAST_DIV_CLK"); + enable_rc_fast_div_clk_impl(clocks, false); + release_rc_fast_clk(clocks); + } + } + pub fn rc_fast_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / 256) + } + pub fn configure_system_pre_div_in( + clocks: &mut ClockTree, + new_selector: SystemPreDivInConfig, + ) { + let old_selector = clocks.system_pre_div_in.replace(new_selector); + match new_selector { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + configure_system_pre_div_in_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + } + pub fn system_pre_div_in_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div_in + } + pub fn request_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV_IN"); + trace!("Enabling SYSTEM_PRE_DIV_IN"); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => request_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => request_rc_fast_clk(clocks), + } + enable_system_pre_div_in_impl(clocks, true); + } + pub fn release_system_pre_div_in(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV_IN"); + trace!("Disabling SYSTEM_PRE_DIV_IN"); + enable_system_pre_div_in_impl(clocks, false); + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => release_xtal_clk(clocks), + SystemPreDivInConfig::RcFast => release_rc_fast_clk(clocks), + } + } + pub fn system_pre_div_in_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.system_pre_div_in) { + SystemPreDivInConfig::Xtal => xtal_clk_frequency(clocks), + SystemPreDivInConfig::RcFast => rc_fast_clk_frequency(clocks), + } + } + pub fn configure_system_pre_div(clocks: &mut ClockTree, config: SystemPreDivConfig) { + clocks.system_pre_div = Some(config); + configure_system_pre_div_impl(clocks, config); + } + pub fn system_pre_div_config(clocks: &mut ClockTree) -> Option { + clocks.system_pre_div + } + pub fn request_system_pre_div(clocks: &mut ClockTree) { + trace!("Requesting SYSTEM_PRE_DIV"); + trace!("Enabling SYSTEM_PRE_DIV"); + request_system_pre_div_in(clocks); + enable_system_pre_div_impl(clocks, true); + } + pub fn release_system_pre_div(clocks: &mut ClockTree) { + trace!("Releasing SYSTEM_PRE_DIV"); + trace!("Disabling SYSTEM_PRE_DIV"); + enable_system_pre_div_impl(clocks, false); + release_system_pre_div_in(clocks); + } + pub fn system_pre_div_frequency(clocks: &mut ClockTree) -> u32 { + (system_pre_div_in_frequency(clocks) / (unwrap!(clocks.system_pre_div).divisor() + 1)) + } + pub fn configure_cpu_pll_div_out(clocks: &mut ClockTree, config: CpuPllDivOutConfig) { + if let Some(pll_clk) = clocks.pll_clk { + assert!(!((config.value() == 240000000) && (pll_clk.value() == 320000000))); + } + clocks.cpu_pll_div_out = Some(config); + configure_cpu_pll_div_out_impl(clocks, config); + } + pub fn cpu_pll_div_out_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_pll_div_out + } + pub fn request_cpu_pll_div_out(clocks: &mut ClockTree) { + trace!("Requesting CPU_PLL_DIV_OUT"); + trace!("Enabling CPU_PLL_DIV_OUT"); + request_pll_clk(clocks); + enable_cpu_pll_div_out_impl(clocks, true); + } + pub fn release_cpu_pll_div_out(clocks: &mut ClockTree) { + trace!("Releasing CPU_PLL_DIV_OUT"); + trace!("Disabling CPU_PLL_DIV_OUT"); + enable_cpu_pll_div_out_impl(clocks, false); + release_pll_clk(clocks); + } + pub fn cpu_pll_div_out_frequency(clocks: &mut ClockTree) -> u32 { + unwrap!(clocks.cpu_pll_div_out).value() + } + pub fn configure_apb_clk(clocks: &mut ClockTree, new_selector: ApbClkConfig) { + let old_selector = clocks.apb_clk.replace(new_selector); + if clocks.apb_clk_refcount > 0 { + match new_selector { + ApbClkConfig::Pll => request_apb_80m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_apb_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + ApbClkConfig::Pll => release_apb_80m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_apb_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn apb_clk_config(clocks: &mut ClockTree) -> Option { + clocks.apb_clk + } + pub fn request_apb_clk(clocks: &mut ClockTree) { + trace!("Requesting APB_CLK"); + if increment_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Enabling APB_CLK"); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => request_apb_80m(clocks), + ApbClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_apb_clk_impl(clocks, true); + } + } + pub fn release_apb_clk(clocks: &mut ClockTree) { + trace!("Releasing APB_CLK"); + if decrement_reference_count(&mut clocks.apb_clk_refcount) { + trace!("Disabling APB_CLK"); + enable_apb_clk_impl(clocks, false); + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => release_apb_80m(clocks), + ApbClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn apb_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.apb_clk) { + ApbClkConfig::Pll => apb_80m_frequency(clocks), + ApbClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_crypto_pwm_clk(clocks: &mut ClockTree, new_selector: CryptoPwmClkConfig) { + let old_selector = clocks.crypto_pwm_clk.replace(new_selector); + if clocks.crypto_pwm_clk_refcount > 0 { + match new_selector { + CryptoPwmClkConfig::Pll => request_pll_160m(clocks), + CryptoPwmClkConfig::Cpu => request_cpu_clk(clocks), + } + configure_crypto_pwm_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CryptoPwmClkConfig::Pll => release_pll_160m(clocks), + CryptoPwmClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } else { + configure_crypto_pwm_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn crypto_pwm_clk_config(clocks: &mut ClockTree) -> Option { + clocks.crypto_pwm_clk + } + pub fn request_crypto_pwm_clk(clocks: &mut ClockTree) { + trace!("Requesting CRYPTO_PWM_CLK"); + if increment_reference_count(&mut clocks.crypto_pwm_clk_refcount) { + trace!("Enabling CRYPTO_PWM_CLK"); + match unwrap!(clocks.crypto_pwm_clk) { + CryptoPwmClkConfig::Pll => request_pll_160m(clocks), + CryptoPwmClkConfig::Cpu => request_cpu_clk(clocks), + } + enable_crypto_pwm_clk_impl(clocks, true); + } + } + pub fn release_crypto_pwm_clk(clocks: &mut ClockTree) { + trace!("Releasing CRYPTO_PWM_CLK"); + if decrement_reference_count(&mut clocks.crypto_pwm_clk_refcount) { + trace!("Disabling CRYPTO_PWM_CLK"); + enable_crypto_pwm_clk_impl(clocks, false); + match unwrap!(clocks.crypto_pwm_clk) { + CryptoPwmClkConfig::Pll => release_pll_160m(clocks), + CryptoPwmClkConfig::Cpu => release_cpu_clk(clocks), + } + } + } + pub fn crypto_pwm_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.crypto_pwm_clk) { + CryptoPwmClkConfig::Pll => pll_160m_frequency(clocks), + CryptoPwmClkConfig::Cpu => cpu_clk_frequency(clocks), + } + } + pub fn configure_cpu_clk(clocks: &mut ClockTree, new_selector: CpuClkConfig) { + let old_selector = clocks.cpu_clk.replace(new_selector); + match new_selector { + CpuClkConfig::Xtal => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_pwm_clk(clocks, CryptoPwmClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::Xtal); + } + CpuClkConfig::RcFast => { + configure_apb_clk(clocks, ApbClkConfig::Cpu); + configure_crypto_pwm_clk(clocks, CryptoPwmClkConfig::Cpu); + configure_system_pre_div_in(clocks, SystemPreDivInConfig::RcFast); + } + CpuClkConfig::Pll => { + configure_apb_clk(clocks, ApbClkConfig::Pll); + configure_crypto_pwm_clk(clocks, CryptoPwmClkConfig::Pll); + } + } + match new_selector { + CpuClkConfig::Xtal => request_system_pre_div(clocks), + CpuClkConfig::RcFast => request_system_pre_div(clocks), + CpuClkConfig::Pll => request_cpu_pll_div_out(clocks), + } + configure_cpu_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + CpuClkConfig::Xtal => release_system_pre_div(clocks), + CpuClkConfig::RcFast => release_system_pre_div(clocks), + CpuClkConfig::Pll => release_cpu_pll_div_out(clocks), + } + } + } + pub fn cpu_clk_config(clocks: &mut ClockTree) -> Option { + clocks.cpu_clk + } + fn request_cpu_clk(_clocks: &mut ClockTree) {} + fn release_cpu_clk(_clocks: &mut ClockTree) {} + pub fn cpu_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.cpu_clk) { + CpuClkConfig::Xtal => system_pre_div_frequency(clocks), + CpuClkConfig::RcFast => system_pre_div_frequency(clocks), + CpuClkConfig::Pll => cpu_pll_div_out_frequency(clocks), + } + } + pub fn request_pll_d2(clocks: &mut ClockTree) { + trace!("Requesting PLL_D2"); + if increment_reference_count(&mut clocks.pll_d2_refcount) { + trace!("Enabling PLL_D2"); + request_pll_clk(clocks); + enable_pll_d2_impl(clocks, true); + } + } + pub fn release_pll_d2(clocks: &mut ClockTree) { + trace!("Releasing PLL_D2"); + if decrement_reference_count(&mut clocks.pll_d2_refcount) { + trace!("Disabling PLL_D2"); + enable_pll_d2_impl(clocks, false); + release_pll_clk(clocks); + } + } + pub fn pll_d2_frequency(clocks: &mut ClockTree) -> u32 { + (pll_clk_frequency(clocks) / 2) + } + pub fn request_pll_160m(clocks: &mut ClockTree) { + trace!("Requesting PLL_160M"); + trace!("Enabling PLL_160M"); + request_cpu_clk(clocks); + enable_pll_160m_impl(clocks, true); + } + pub fn release_pll_160m(clocks: &mut ClockTree) { + trace!("Releasing PLL_160M"); + trace!("Disabling PLL_160M"); + enable_pll_160m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn pll_160m_frequency(clocks: &mut ClockTree) -> u32 { + 160000000 + } + pub fn request_apb_80m(clocks: &mut ClockTree) { + trace!("Requesting APB_80M"); + trace!("Enabling APB_80M"); + request_cpu_clk(clocks); + enable_apb_80m_impl(clocks, true); + } + pub fn release_apb_80m(clocks: &mut ClockTree) { + trace!("Releasing APB_80M"); + trace!("Disabling APB_80M"); + enable_apb_80m_impl(clocks, false); + release_cpu_clk(clocks); + } + pub fn apb_80m_frequency(clocks: &mut ClockTree) -> u32 { + 80000000 + } + pub fn configure_rc_fast_clk_div_n(clocks: &mut ClockTree, config: RcFastClkDivNConfig) { + clocks.rc_fast_clk_div_n = Some(config); + configure_rc_fast_clk_div_n_impl(clocks, config); + } + pub fn rc_fast_clk_div_n_config(clocks: &mut ClockTree) -> Option { + clocks.rc_fast_clk_div_n + } + pub fn request_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Requesting RC_FAST_CLK_DIV_N"); + trace!("Enabling RC_FAST_CLK_DIV_N"); + request_rc_fast_clk(clocks); + enable_rc_fast_clk_div_n_impl(clocks, true); + } + pub fn release_rc_fast_clk_div_n(clocks: &mut ClockTree) { + trace!("Releasing RC_FAST_CLK_DIV_N"); + trace!("Disabling RC_FAST_CLK_DIV_N"); + enable_rc_fast_clk_div_n_impl(clocks, false); + release_rc_fast_clk(clocks); + } + pub fn rc_fast_clk_div_n_frequency(clocks: &mut ClockTree) -> u32 { + (rc_fast_clk_frequency(clocks) / (unwrap!(clocks.rc_fast_clk_div_n).divisor() + 1)) + } + pub fn request_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Requesting XTAL_DIV_CLK"); + trace!("Enabling XTAL_DIV_CLK"); + request_xtal_clk(clocks); + enable_xtal_div_clk_impl(clocks, true); + } + pub fn release_xtal_div_clk(clocks: &mut ClockTree) { + trace!("Releasing XTAL_DIV_CLK"); + trace!("Disabling XTAL_DIV_CLK"); + enable_xtal_div_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + pub fn xtal_div_clk_frequency(clocks: &mut ClockTree) -> u32 { + (xtal_clk_frequency(clocks) / 2) + } + pub fn configure_rtc_slow_clk(clocks: &mut ClockTree, new_selector: RtcSlowClkConfig) { + let old_selector = clocks.rtc_slow_clk.replace(new_selector); + match new_selector { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + configure_rtc_slow_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + } + pub fn rtc_slow_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_slow_clk + } + pub fn request_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_SLOW_CLK"); + trace!("Enabling RTC_SLOW_CLK"); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => request_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => request_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => request_rc_fast_div_clk(clocks), + } + enable_rtc_slow_clk_impl(clocks, true); + } + pub fn release_rtc_slow_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_SLOW_CLK"); + trace!("Disabling RTC_SLOW_CLK"); + enable_rtc_slow_clk_impl(clocks, false); + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => release_xtal32k_clk(clocks), + RtcSlowClkConfig::RcSlow => release_rc_slow_clk(clocks), + RtcSlowClkConfig::RcFast => release_rc_fast_div_clk(clocks), + } + } + pub fn rtc_slow_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_slow_clk) { + RtcSlowClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + RtcSlowClkConfig::RcSlow => rc_slow_clk_frequency(clocks), + RtcSlowClkConfig::RcFast => rc_fast_div_clk_frequency(clocks), + } + } + pub fn configure_rtc_fast_clk(clocks: &mut ClockTree, new_selector: RtcFastClkConfig) { + let old_selector = clocks.rtc_fast_clk.replace(new_selector); + if clocks.rtc_fast_clk_refcount > 0 { + match new_selector { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } else { + configure_rtc_fast_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn rtc_fast_clk_config(clocks: &mut ClockTree) -> Option { + clocks.rtc_fast_clk + } + pub fn request_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Requesting RTC_FAST_CLK"); + if increment_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Enabling RTC_FAST_CLK"); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => request_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => request_rc_fast_clk_div_n(clocks), + } + enable_rtc_fast_clk_impl(clocks, true); + } + } + pub fn release_rtc_fast_clk(clocks: &mut ClockTree) { + trace!("Releasing RTC_FAST_CLK"); + if decrement_reference_count(&mut clocks.rtc_fast_clk_refcount) { + trace!("Disabling RTC_FAST_CLK"); + enable_rtc_fast_clk_impl(clocks, false); + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => release_xtal_div_clk(clocks), + RtcFastClkConfig::Rc => release_rc_fast_clk_div_n(clocks), + } + } + } + pub fn rtc_fast_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rtc_fast_clk) { + RtcFastClkConfig::Xtal => xtal_div_clk_frequency(clocks), + RtcFastClkConfig::Rc => rc_fast_clk_div_n_frequency(clocks), + } + } + pub fn configure_low_power_clk(clocks: &mut ClockTree, new_selector: LowPowerClkConfig) { + let old_selector = clocks.low_power_clk.replace(new_selector); + if clocks.low_power_clk_refcount > 0 { + match new_selector { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + configure_low_power_clk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } else { + configure_low_power_clk_impl(clocks, old_selector, new_selector); + } + } + pub fn low_power_clk_config(clocks: &mut ClockTree) -> Option { + clocks.low_power_clk + } + pub fn request_low_power_clk(clocks: &mut ClockTree) { + trace!("Requesting LOW_POWER_CLK"); + if increment_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Enabling LOW_POWER_CLK"); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => request_xtal_clk(clocks), + LowPowerClkConfig::RcFast => request_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => request_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => request_rtc_slow_clk(clocks), + } + enable_low_power_clk_impl(clocks, true); + } + } + pub fn release_low_power_clk(clocks: &mut ClockTree) { + trace!("Releasing LOW_POWER_CLK"); + if decrement_reference_count(&mut clocks.low_power_clk_refcount) { + trace!("Disabling LOW_POWER_CLK"); + enable_low_power_clk_impl(clocks, false); + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => release_xtal_clk(clocks), + LowPowerClkConfig::RcFast => release_rc_fast_clk(clocks), + LowPowerClkConfig::Xtal32k => release_xtal32k_clk(clocks), + LowPowerClkConfig::RtcSlow => release_rtc_slow_clk(clocks), + } + } + } + pub fn low_power_clk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.low_power_clk) { + LowPowerClkConfig::Xtal => xtal_clk_frequency(clocks), + LowPowerClkConfig::RcFast => rc_fast_clk_frequency(clocks), + LowPowerClkConfig::Xtal32k => xtal32k_clk_frequency(clocks), + LowPowerClkConfig::RtcSlow => rtc_slow_clk_frequency(clocks), + } + } + pub fn request_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Requesting UART_MEM_CLK"); + if increment_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Enabling UART_MEM_CLK"); + request_xtal_clk(clocks); + enable_uart_mem_clk_impl(clocks, true); + } + } + pub fn release_uart_mem_clk(clocks: &mut ClockTree) { + trace!("Releasing UART_MEM_CLK"); + if decrement_reference_count(&mut clocks.uart_mem_clk_refcount) { + trace!("Disabling UART_MEM_CLK"); + enable_uart_mem_clk_impl(clocks, false); + release_xtal_clk(clocks); + } + } + pub fn uart_mem_clk_frequency(clocks: &mut ClockTree) -> u32 { + xtal_clk_frequency(clocks) + } + pub fn configure_mcpwm0_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm0_function_clock.replace(new_selector); + if clocks.mcpwm0_function_clock_refcount > 0 { + request_crypto_pwm_clk(clocks); + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_crypto_pwm_clk(clocks); + } + } else { + configure_mcpwm0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm0_function_clock + } + pub fn request_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Enabling MCPWM0_FUNCTION_CLOCK"); + request_crypto_pwm_clk(clocks); + enable_mcpwm0_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm0_function_clock_refcount) { + trace!("Disabling MCPWM0_FUNCTION_CLOCK"); + enable_mcpwm0_function_clock_impl(clocks, false); + release_crypto_pwm_clk(clocks); + } + } + pub fn mcpwm0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + crypto_pwm_clk_frequency(clocks) + } + pub fn configure_mcpwm1_function_clock( + clocks: &mut ClockTree, + new_selector: Mcpwm0FunctionClockConfig, + ) { + let old_selector = clocks.mcpwm1_function_clock.replace(new_selector); + if clocks.mcpwm1_function_clock_refcount > 0 { + request_crypto_pwm_clk(clocks); + configure_mcpwm1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_crypto_pwm_clk(clocks); + } + } else { + configure_mcpwm1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn mcpwm1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.mcpwm1_function_clock + } + pub fn request_mcpwm1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting MCPWM1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.mcpwm1_function_clock_refcount) { + trace!("Enabling MCPWM1_FUNCTION_CLOCK"); + request_crypto_pwm_clk(clocks); + enable_mcpwm1_function_clock_impl(clocks, true); + } + } + pub fn release_mcpwm1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing MCPWM1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.mcpwm1_function_clock_refcount) { + trace!("Disabling MCPWM1_FUNCTION_CLOCK"); + enable_mcpwm1_function_clock_impl(clocks, false); + release_crypto_pwm_clk(clocks); + } + } + pub fn mcpwm1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + crypto_pwm_clk_frequency(clocks) + } + pub fn configure_rmt_sclk(clocks: &mut ClockTree, new_selector: RmtSclkConfig) { + let old_selector = clocks.rmt_sclk.replace(new_selector); + if clocks.rmt_sclk_refcount > 0 { + match new_selector { + RmtSclkConfig::ApbClk => request_apb_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + RmtSclkConfig::ApbClk => release_apb_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_rmt_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn rmt_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.rmt_sclk + } + pub fn request_rmt_sclk(clocks: &mut ClockTree) { + trace!("Requesting RMT_SCLK"); + if increment_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Enabling RMT_SCLK"); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => request_apb_clk(clocks), + RmtSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => request_xtal_clk(clocks), + } + enable_rmt_sclk_impl(clocks, true); + } + } + pub fn release_rmt_sclk(clocks: &mut ClockTree) { + trace!("Releasing RMT_SCLK"); + if decrement_reference_count(&mut clocks.rmt_sclk_refcount) { + trace!("Disabling RMT_SCLK"); + enable_rmt_sclk_impl(clocks, false); + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => release_apb_clk(clocks), + RmtSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + RmtSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn rmt_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.rmt_sclk) { + RmtSclkConfig::ApbClk => apb_clk_frequency(clocks), + RmtSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + RmtSclkConfig::XtalClk => xtal_clk_frequency(clocks), + } + } + pub fn configure_timg0_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg0_function_clock.replace(new_selector); + if clocks.timg0_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_function_clock + } + pub fn request_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Enabling TIMG0_FUNCTION_CLOCK"); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg0_function_clock_impl(clocks, true); + } + } + pub fn release_timg0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_function_clock_refcount) { + trace!("Disabling TIMG0_FUNCTION_CLOCK"); + enable_timg0_function_clock_impl(clocks, false); + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg0_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg0_calibration_clock.replace(new_selector); + if clocks.timg0_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg0_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg0_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg0_calibration_clock + } + pub fn request_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG0_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Enabling TIMG0_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg0_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg0_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG0_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg0_calibration_clock_refcount) { + trace!("Disabling TIMG0_CALIBRATION_CLOCK"); + enable_timg0_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg0_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg0_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_timg1_function_clock( + clocks: &mut ClockTree, + new_selector: Timg0FunctionClockConfig, + ) { + let old_selector = clocks.timg1_function_clock.replace(new_selector); + if clocks.timg1_function_clock_refcount > 0 { + match new_selector { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } else { + configure_timg1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_function_clock + } + pub fn request_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Enabling TIMG1_FUNCTION_CLOCK"); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => request_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => request_apb_clk(clocks), + } + enable_timg1_function_clock_impl(clocks, true); + } + } + pub fn release_timg1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_function_clock_refcount) { + trace!("Disabling TIMG1_FUNCTION_CLOCK"); + enable_timg1_function_clock_impl(clocks, false); + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => release_xtal_clk(clocks), + Timg0FunctionClockConfig::ApbClk => release_apb_clk(clocks), + } + } + } + pub fn timg1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_function_clock) { + Timg0FunctionClockConfig::XtalClk => xtal_clk_frequency(clocks), + Timg0FunctionClockConfig::ApbClk => apb_clk_frequency(clocks), + } + } + pub fn configure_timg1_calibration_clock( + clocks: &mut ClockTree, + new_selector: Timg0CalibrationClockConfig, + ) { + let old_selector = clocks.timg1_calibration_clock.replace(new_selector); + if clocks.timg1_calibration_clock_refcount > 0 { + match new_selector { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => { + release_rc_fast_div_clk(clocks) + } + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } else { + configure_timg1_calibration_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn timg1_calibration_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.timg1_calibration_clock + } + pub fn request_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Requesting TIMG1_CALIBRATION_CLOCK"); + if increment_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Enabling TIMG1_CALIBRATION_CLOCK"); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => request_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => request_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => request_xtal32k_clk(clocks), + } + enable_timg1_calibration_clock_impl(clocks, true); + } + } + pub fn release_timg1_calibration_clock(clocks: &mut ClockTree) { + trace!("Releasing TIMG1_CALIBRATION_CLOCK"); + if decrement_reference_count(&mut clocks.timg1_calibration_clock_refcount) { + trace!("Disabling TIMG1_CALIBRATION_CLOCK"); + enable_timg1_calibration_clock_impl(clocks, false); + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => release_rc_slow_clk(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => release_rc_fast_div_clk(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => release_xtal32k_clk(clocks), + } + } + } + pub fn timg1_calibration_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.timg1_calibration_clock) { + Timg0CalibrationClockConfig::RcSlowClk => rc_slow_clk_frequency(clocks), + Timg0CalibrationClockConfig::RcFastDivClk => rc_fast_div_clk_frequency(clocks), + Timg0CalibrationClockConfig::Xtal32kClk => xtal32k_clk_frequency(clocks), + } + } + pub fn configure_uart0_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart0_function_clock.replace(new_selector); + if clocks.uart0_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart0_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart0_function_clock + } + pub fn request_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Enabling UART0_FUNCTION_CLOCK"); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart0_function_clock_impl(clocks, true); + } + } + pub fn release_uart0_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_function_clock_refcount) { + trace!("Disabling UART0_FUNCTION_CLOCK"); + enable_uart0_function_clock_impl(clocks, false); + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart0_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart0_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart0_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart0_mem_clock.replace(new_selector); + if clocks.uart0_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart0_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart0_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart0_mem_clock + } + pub fn request_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART0_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Enabling UART0_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart0_mem_clock_impl(clocks, true); + } + } + pub fn release_uart0_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART0_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart0_mem_clock_refcount) { + trace!("Disabling UART0_MEM_CLOCK"); + enable_uart0_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart0_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart1_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart1_function_clock.replace(new_selector); + if clocks.uart1_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart1_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart1_function_clock + } + pub fn request_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Enabling UART1_FUNCTION_CLOCK"); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart1_function_clock_impl(clocks, true); + } + } + pub fn release_uart1_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_function_clock_refcount) { + trace!("Disabling UART1_FUNCTION_CLOCK"); + enable_uart1_function_clock_impl(clocks, false); + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart1_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart1_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart1_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart1_mem_clock.replace(new_selector); + if clocks.uart1_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart1_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart1_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart1_mem_clock + } + pub fn request_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART1_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Enabling UART1_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart1_mem_clock_impl(clocks, true); + } + } + pub fn release_uart1_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART1_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart1_mem_clock_refcount) { + trace!("Disabling UART1_MEM_CLOCK"); + enable_uart1_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart1_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + pub fn configure_uart2_function_clock( + clocks: &mut ClockTree, + new_selector: Uart0FunctionClockConfig, + ) { + let old_selector = clocks.uart2_function_clock.replace(new_selector); + if clocks.uart2_function_clock_refcount > 0 { + match new_selector { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + configure_uart2_function_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } else { + configure_uart2_function_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart2_function_clock_config( + clocks: &mut ClockTree, + ) -> Option { + clocks.uart2_function_clock + } + pub fn request_uart2_function_clock(clocks: &mut ClockTree) { + trace!("Requesting UART2_FUNCTION_CLOCK"); + if increment_reference_count(&mut clocks.uart2_function_clock_refcount) { + trace!("Enabling UART2_FUNCTION_CLOCK"); + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => request_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => request_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => request_xtal_clk(clocks), + } + enable_uart2_function_clock_impl(clocks, true); + } + } + pub fn release_uart2_function_clock(clocks: &mut ClockTree) { + trace!("Releasing UART2_FUNCTION_CLOCK"); + if decrement_reference_count(&mut clocks.uart2_function_clock_refcount) { + trace!("Disabling UART2_FUNCTION_CLOCK"); + enable_uart2_function_clock_impl(clocks, false); + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => release_apb_clk(clocks), + Uart0FunctionClockConfig::RcFast => release_rc_fast_clk(clocks), + Uart0FunctionClockConfig::Xtal => release_xtal_clk(clocks), + } + } + } + pub fn uart2_function_clock_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.uart2_function_clock) { + Uart0FunctionClockConfig::Apb => apb_clk_frequency(clocks), + Uart0FunctionClockConfig::RcFast => rc_fast_clk_frequency(clocks), + Uart0FunctionClockConfig::Xtal => xtal_clk_frequency(clocks), + } + } + pub fn configure_uart2_mem_clock( + clocks: &mut ClockTree, + new_selector: Uart0MemClockConfig, + ) { + let old_selector = clocks.uart2_mem_clock.replace(new_selector); + if clocks.uart2_mem_clock_refcount > 0 { + request_uart_mem_clk(clocks); + configure_uart2_mem_clock_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + release_uart_mem_clk(clocks); + } + } else { + configure_uart2_mem_clock_impl(clocks, old_selector, new_selector); + } + } + pub fn uart2_mem_clock_config(clocks: &mut ClockTree) -> Option { + clocks.uart2_mem_clock + } + pub fn request_uart2_mem_clock(clocks: &mut ClockTree) { + trace!("Requesting UART2_MEM_CLOCK"); + if increment_reference_count(&mut clocks.uart2_mem_clock_refcount) { + trace!("Enabling UART2_MEM_CLOCK"); + request_uart_mem_clk(clocks); + enable_uart2_mem_clock_impl(clocks, true); + } + } + pub fn release_uart2_mem_clock(clocks: &mut ClockTree) { + trace!("Releasing UART2_MEM_CLOCK"); + if decrement_reference_count(&mut clocks.uart2_mem_clock_refcount) { + trace!("Disabling UART2_MEM_CLOCK"); + enable_uart2_mem_clock_impl(clocks, false); + release_uart_mem_clk(clocks); + } + } + pub fn uart2_mem_clock_frequency(clocks: &mut ClockTree) -> u32 { + uart_mem_clk_frequency(clocks) + } + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + /// `XTAL_CLK` configuration. + pub xtal_clk: Option, + /// `PLL_CLK` configuration. + pub pll_clk: Option, + /// `SYSTEM_PRE_DIV` configuration. + pub system_pre_div: Option, + /// `CPU_PLL_DIV_OUT` configuration. + pub cpu_pll_div_out: Option, + /// `CPU_CLK` configuration. + pub cpu_clk: Option, + /// `RC_FAST_CLK_DIV_N` configuration. + pub rc_fast_clk_div_n: Option, + /// `RTC_SLOW_CLK` configuration. + pub rtc_slow_clk: Option, + /// `RTC_FAST_CLK` configuration. + pub rtc_fast_clk: Option, + /// `LOW_POWER_CLK` configuration. + pub low_power_clk: Option, + } + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + if let Some(config) = self.xtal_clk { + configure_xtal_clk(clocks, config); + } + if let Some(config) = self.pll_clk { + configure_pll_clk(clocks, config); + } + if let Some(config) = self.system_pre_div { + configure_system_pre_div(clocks, config); + } + if let Some(config) = self.cpu_pll_div_out { + configure_cpu_pll_div_out(clocks, config); + } + if let Some(config) = self.cpu_clk { + configure_cpu_clk(clocks, config); + } + if let Some(config) = self.rc_fast_clk_div_n { + configure_rc_fast_clk_div_n(clocks, config); + } + if let Some(config) = self.rtc_slow_clk { + configure_rtc_slow_clk(clocks, config); + } + if let Some(config) = self.rtc_fast_clk { + configure_rtc_fast_clk(clocks, config); + } + if let Some(config) = self.low_power_clk { + configure_low_power_clk(clocks, config); + } + }); + } + } + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + last + } + }; +} +/// Implement the `Peripheral` enum and enable/disable/reset functions. +/// +/// This macro is intended to be placed in `esp_hal::system`. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! implement_peripheral_clocks { + () => { + #[doc(hidden)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Peripheral { + /// AES peripheral clock signal + Aes, + /// APB_SAR_ADC peripheral clock signal + ApbSarAdc, + /// DEDICATED_GPIO peripheral clock signal + DedicatedGpio, + /// DMA peripheral clock signal + Dma, + /// DS peripheral clock signal + Ds, + /// HMAC peripheral clock signal + Hmac, + /// I2C_EXT0 peripheral clock signal + I2cExt0, + /// I2C_EXT1 peripheral clock signal + I2cExt1, + /// I2S0 peripheral clock signal + I2s0, + /// I2S1 peripheral clock signal + I2s1, + /// LCD_CAM peripheral clock signal + LcdCam, + /// LEDC peripheral clock signal + Ledc, + /// MCPWM0 peripheral clock signal + Mcpwm0, + /// MCPWM1 peripheral clock signal + Mcpwm1, + /// PCNT peripheral clock signal + Pcnt, + /// PERI_BACKUP peripheral clock signal + PeriBackup, + /// RMT peripheral clock signal + Rmt, + /// RSA peripheral clock signal + Rsa, + /// SDIO_HOST peripheral clock signal + SdioHost, + /// SHA peripheral clock signal + Sha, + /// SPI2 peripheral clock signal + Spi2, + /// SPI3 peripheral clock signal + Spi3, + /// SYSTIMER peripheral clock signal + Systimer, + /// TIMG0 peripheral clock signal + Timg0, + /// TIMG1 peripheral clock signal + Timg1, + /// TWAI0 peripheral clock signal + Twai0, + /// UART0 peripheral clock signal + Uart0, + /// UART1 peripheral clock signal + Uart1, + /// UART2 peripheral clock signal + Uart2, + /// UART_MEM peripheral clock signal + UartMem, + /// UHCI0 peripheral clock signal + Uhci0, + /// USB peripheral clock signal + Usb, + /// USB_DEVICE peripheral clock signal + UsbDevice, + } + impl Peripheral { + const KEEP_ENABLED: &[Peripheral] = &[ + Self::Systimer, + Self::Timg0, + Self::Uart0, + Self::UartMem, + Self::UsbDevice, + ]; + const COUNT: usize = Self::ALL.len(); + const ALL: &[Self] = &[ + Self::Aes, + Self::ApbSarAdc, + Self::DedicatedGpio, + Self::Dma, + Self::Ds, + Self::Hmac, + Self::I2cExt0, + Self::I2cExt1, + Self::I2s0, + Self::I2s1, + Self::LcdCam, + Self::Ledc, + Self::Mcpwm0, + Self::Mcpwm1, + Self::Pcnt, + Self::PeriBackup, + Self::Rmt, + Self::Rsa, + Self::SdioHost, + Self::Sha, + Self::Spi2, + Self::Spi3, + Self::Systimer, + Self::Timg0, + Self::Timg1, + Self::Twai0, + Self::Uart0, + Self::Uart1, + Self::Uart2, + Self::UartMem, + Self::Uhci0, + Self::Usb, + Self::UsbDevice, + ]; + } + unsafe fn enable_internal_racey(peripheral: Peripheral, enable: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_aes_clk_en().bit(enable)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.apb_saradc_clk_en().bit(enable)); + } + Peripheral::DedicatedGpio => { + crate::peripherals::SYSTEM::regs() + .cpu_peri_clk_en() + .modify(|_, w| w.dedicated_gpio_clk_en().bit(enable)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.dma_clk_en().bit(enable)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_ds_clk_en().bit(enable)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_hmac_clk_en().bit(enable)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext0_clk_en().bit(enable)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2c_ext1_clk_en().bit(enable)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2s0_clk_en().bit(enable)); + } + Peripheral::I2s1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.i2s1_clk_en().bit(enable)); + } + Peripheral::LcdCam => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.lcd_cam_clk_en().bit(enable)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pwm0_clk_en().bit(enable)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pwm1_clk_en().bit(enable)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.pcnt_clk_en().bit(enable)); + } + Peripheral::PeriBackup => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.peri_backup_clk_en().bit(enable)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.rmt_clk_en().bit(enable)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_rsa_clk_en().bit(enable)); + } + Peripheral::SdioHost => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.sdio_host_clk_en().bit(enable)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.crypto_sha_clk_en().bit(enable)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi2_clk_en().bit(enable)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.spi3_clk_en().bit(enable)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.systimer_clk_en().bit(enable)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup_clk_en().bit(enable)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.timergroup1_clk_en().bit(enable)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.twai_clk_en().bit(enable)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_clk_en().bit(enable)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart1_clk_en().bit(enable)); + } + Peripheral::Uart2 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.uart2_clk_en().bit(enable)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uart_mem_clk_en().bit(enable)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.uhci0_clk_en().bit(enable)); + } + Peripheral::Usb => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en0() + .modify(|_, w| w.usb_clk_en().bit(enable)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .perip_clk_en1() + .modify(|_, w| w.usb_device_clk_en().bit(enable)); + } + } + } + unsafe fn assert_peri_reset_racey(peripheral: Peripheral, reset: bool) { + match peripheral { + Peripheral::Aes => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_aes_rst().bit(reset)); + } + Peripheral::ApbSarAdc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.apb_saradc_rst().bit(reset)); + } + Peripheral::DedicatedGpio => { + crate::peripherals::SYSTEM::regs() + .cpu_peri_rst_en() + .modify(|_, w| w.dedicated_gpio_rst().bit(reset)); + } + Peripheral::Dma => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.dma_rst().bit(reset)); + } + Peripheral::Ds => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_ds_rst().bit(reset)); + } + Peripheral::Hmac => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_hmac_rst().bit(reset)); + } + Peripheral::I2cExt0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext0_rst().bit(reset)); + } + Peripheral::I2cExt1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2c_ext1_rst().bit(reset)); + } + Peripheral::I2s0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2s0_rst().bit(reset)); + } + Peripheral::I2s1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.i2s1_rst().bit(reset)); + } + Peripheral::LcdCam => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.lcd_cam_rst().bit(reset)); + } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.ledc_rst().bit(reset)); + } + Peripheral::Mcpwm0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pwm0_rst().bit(reset)); + } + Peripheral::Mcpwm1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pwm1_rst().bit(reset)); + } + Peripheral::Pcnt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.pcnt_rst().bit(reset)); + } + Peripheral::PeriBackup => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.peri_backup_rst().bit(reset)); + } + Peripheral::Rmt => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.rmt_rst().bit(reset)); + } + Peripheral::Rsa => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_rsa_rst().bit(reset)); + } + Peripheral::SdioHost => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.sdio_host_rst().bit(reset)); + } + Peripheral::Sha => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.crypto_sha_rst().bit(reset)); + } + Peripheral::Spi2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi2_rst().bit(reset)); + } + Peripheral::Spi3 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.spi3_rst().bit(reset)); + } + Peripheral::Systimer => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.systimer_rst().bit(reset)); + } + Peripheral::Timg0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup_rst().bit(reset)); + } + Peripheral::Timg1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.timergroup1_rst().bit(reset)); + } + Peripheral::Twai0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.twai_rst().bit(reset)); + } + Peripheral::Uart0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_rst().bit(reset)); + } + Peripheral::Uart1 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart1_rst().bit(reset)); + } + Peripheral::Uart2 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.uart2_rst().bit(reset)); + } + Peripheral::UartMem => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uart_mem_rst().bit(reset)); + } + Peripheral::Uhci0 => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.uhci0_rst().bit(reset)); + } + Peripheral::Usb => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en0() + .modify(|_, w| w.usb_rst().bit(reset)); + } + Peripheral::UsbDevice => { + crate::peripherals::SYSTEM::regs() + .perip_rst_en1() + .modify(|_, w| w.usb_device_rst().bit(reset)); + } + } + } + }; +} +/// Macro to get the address range of the given memory region. +/// +/// This macro provides two syntax options for each memory region: +/// +/// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). +/// - `memory_range!(size as str, "region_name")` returns the size of the region as a string +/// literal. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! memory_range { + ("DRAM") => { + 0x3FC88000..0x3FD00000 + }; + (size as str, "DRAM") => { + "491520" + }; + ("DRAM2_UNINIT") => { + 0x3FCDB700..0x3FCED710 + }; + (size as str, "DRAM2_UNINIT") => { + "73744" + }; +} +/// This macro can be used to generate code for each peripheral instance of the I2C master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` +/// +/// Macro fragments: +/// - `$id`: the index of the I2C instance +/// - `$instance`: the name of the I2C instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$scl`, `$sda`: peripheral signal names. +/// +/// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_i2c_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_i2c_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_i2c_master!((0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA)); _for_each_inner_i2c_master!((1, I2C1, I2cExt1, I2CEXT1_SCL, + I2CEXT1_SDA)); _for_each_inner_i2c_master!((all(0, I2C0, I2cExt0, I2CEXT0_SCL, + I2CEXT0_SDA), (1, I2C1, I2cExt1, I2CEXT1_SCL, I2CEXT1_SDA))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the UART driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident, $rx:ident, $tx:ident, $cts:ident, +/// $rts:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the UART instance +/// - `$instance`: the name of the UART instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$rx`, `$tx`, `$cts`, `$rts`: signal names. +/// +/// Example data: `(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_uart { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_uart { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_uart!((0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS)); + _for_each_inner_uart!((1, UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS)); + _for_each_inner_uart!((2, UART2, Uart2, U2RXD, U2TXD, U2CTS, U2RTS)); + _for_each_inner_uart!((all(0, UART0, Uart0, U0RXD, U0TXD, U0CTS, U0RTS), (1, + UART1, Uart1, U1RXD, U1TXD, U1CTS, U1RTS), (2, UART2, Uart2, U2RXD, U2TXD, U2CTS, + U2RTS))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI master driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident [$($cs:ident),*] [$($sio:ident),*] +/// $($is_qspi:literal)?)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$cs`, `$sio`: chip select and SIO signal names. +/// - `$is_qspi`: a `true` literal present if the SPI instance supports QSPI. +/// +/// Example data: +/// - `(SPI2, Spi2, FSPICLK [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, +/// FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7], true)` +/// - `(SPI3, Spi3, SPI3_CLK [SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q])` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_master { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_master { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_master!((SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD, FSPIIO4, + FSPIIO5, FSPIIO6, FSPIIO7], true)); _for_each_inner_spi_master!((SPI3, Spi3, + SPI3_CLK[SPI3_CS0, SPI3_CS1, SPI3_CS2] [SPI3_D, SPI3_Q, SPI3_WP, SPI3_HD], + true)); _for_each_inner_spi_master!((all(SPI2, Spi2, FSPICLK[FSPICS0, FSPICS1, + FSPICS2, FSPICS3, FSPICS4, FSPICS5] [FSPID, FSPIQ, FSPIWP, FSPIHD, FSPIIO4, + FSPIIO5, FSPIIO6, FSPIIO7], true), (SPI3, Spi3, SPI3_CLK[SPI3_CS0, SPI3_CS1, + SPI3_CS2] [SPI3_D, SPI3_Q, SPI3_WP, SPI3_HD], true))); + }; +} +/// This macro can be used to generate code for each peripheral instance of the SPI slave driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($instance:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident)` +/// +/// Macro fragments: +/// +/// - `$instance`: the name of the SPI instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// - `$sclk`, `$mosi`, `$miso`, `$cs`: signal names. +/// +/// Example data: `(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_spi_slave { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_spi_slave { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_spi_slave!((SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0)); + _for_each_inner_spi_slave!((SPI3, Spi3, SPI3_CLK, SPI3_D, SPI3_Q, SPI3_CS0)); + _for_each_inner_spi_slave!((all(SPI2, Spi2, FSPICLK, FSPID, FSPIQ, FSPICS0), + (SPI3, Spi3, SPI3_CLK, SPI3_D, SPI3_Q, SPI3_CS0))); + }; +} +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_peripheral { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_peripheral { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO1 peripheral singleton"] + GPIO1 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO3 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO4 peripheral singleton"] + GPIO4 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO5 peripheral singleton"] GPIO5 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO6 peripheral singleton"] + GPIO6 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO7 peripheral singleton"] GPIO7 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO8 peripheral singleton"] + GPIO8 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO9 peripheral singleton"] GPIO9 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO10 peripheral singleton"] + GPIO10 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO12 peripheral singleton"] + GPIO12 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO14 peripheral singleton"] + GPIO14 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO15 peripheral singleton"] GPIO15 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO16 peripheral singleton"] + GPIO16 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO17 peripheral singleton"] GPIO17 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO18 peripheral singleton"] + GPIO18 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO20 peripheral singleton"] + GPIO20 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO31 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO31 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO32 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO32 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO33 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO33 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO34 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO35 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO36 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO37 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO38 peripheral singleton"] + GPIO38 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO39 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO40 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO40 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO41 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO41 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO42 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO42 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO43 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO43 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO44 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO44 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO45 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO45 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO46 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO46 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO47 peripheral singleton"] + GPIO47 <= virtual())); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO48 peripheral singleton"] GPIO48 <= virtual())); + _for_each_inner_peripheral!((@ peri_type #[doc = "AES peripheral singleton"] AES + <= AES(AES : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ASSIST_DEBUG peripheral singleton"] ASSIST_DEBUG <= ASSIST_DEBUG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA peripheral singleton"] DMA + <= DMA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "DS peripheral singleton"] DS <= DS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "EFUSE peripheral singleton"] + EFUSE <= EFUSE() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "GPIO peripheral singleton"] + GPIO <= GPIO() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "HMAC peripheral singleton"] + HMAC <= HMAC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2C0 peripheral singleton"] + I2C0 <= I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "I2C1 peripheral singleton"] I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "I2S0 peripheral singleton"] + I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "I2S1 peripheral singleton"] I2S1 <= I2S1(I2S1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "INTERRUPT_CORE1 peripheral singleton"] INTERRUPT_CORE1 <= INTERRUPT_CORE1() + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LCD_CAM peripheral singleton"] + LCD_CAM <= LCD_CAM() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LPWR peripheral singleton"] + LPWR <= RTC_CNTL() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "MCPWM1 peripheral singleton"] + MCPWM1 <= MCPWM1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "PCNT peripheral singleton"] PCNT <= PCNT() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "PERI_BACKUP peripheral singleton"] PERI_BACKUP <= PERI_BACKUP() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RNG peripheral singleton"] RNG <= RNG() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RSA peripheral singleton"] RSA + <= RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "RTC_CNTL peripheral singleton"] RTC_CNTL <= RTC_CNTL() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "RTC_I2C peripheral singleton"] + RTC_I2C <= RTC_I2C() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "RTC_IO peripheral singleton"] RTC_IO <= RTC_IO() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SDHOST peripheral singleton"] + SDHOST <= SDHOST() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SENS peripheral singleton"] SENS <= SENS() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "SENSITIVE peripheral singleton"] SENSITIVE <= SENSITIVE() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SHA peripheral singleton"] SHA + <= SHA(SHA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt + }) (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI0 peripheral singleton"] SPI0 <= SPI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI1 peripheral singleton"] + SPI1 <= SPI1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SPI3 peripheral singleton"] + SPI3 <= SPI3(SPI3 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "SYSTIMER peripheral singleton"] + SYSTIMER <= SYSTIMER() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "TIMG1 peripheral singleton"] + TIMG1 <= TIMG1() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART0 peripheral singleton"] + UART0 <= UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UART1 peripheral singleton"] UART1 <= UART1(UART1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }))); + _for_each_inner_peripheral!((@ peri_type #[doc = "UART2 peripheral singleton"] + UART2 <= UART2(UART2 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }))); _for_each_inner_peripheral!((@ peri_type #[doc = + "UHCI0 peripheral singleton"] UHCI0 <= UHCI0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "USB0 peripheral singleton"] + USB0 <= USB0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "USB_WRAP peripheral singleton"] USB_WRAP <= USB_WRAP() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WCL peripheral singleton"] WCL + <= WCL() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "XTS_AES peripheral singleton"] XTS_AES <= XTS_AES() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH0 peripheral singleton"] + DMA_CH0 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH1 peripheral singleton"] DMA_CH1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH2 peripheral singleton"] + DMA_CH2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "DMA_CH3 peripheral singleton"] DMA_CH3 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "DMA_CH4 peripheral singleton"] + DMA_CH4 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc + = "ADC1 peripheral singleton"] ADC1 <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "ADC2 peripheral singleton"] + ADC2 <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "BT peripheral singleton"] BT <= virtual(BT_BB : { bind_bb_interrupt, + enable_bb_interrupt, disable_bb_interrupt }, RWBLE : { bind_rwble_interrupt, + enable_rwble_interrupt, disable_rwble_interrupt }) (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "CPU_CTRL peripheral singleton"] + CPU_CTRL <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type + #[doc = "FLASH peripheral singleton"] FLASH <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "PSRAM peripheral singleton"] + PSRAM <= virtual() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "SW_INTERRUPT peripheral singleton"] SW_INTERRUPT <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "ULP_RISCV_CORE peripheral singleton"] ULP_RISCV_CORE <= virtual() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "WIFI peripheral singleton"] + WIFI <= virtual(WIFI_MAC : { bind_mac_interrupt, enable_mac_interrupt, + disable_mac_interrupt }, WIFI_PWR : { bind_pwr_interrupt, enable_pwr_interrupt, + disable_pwr_interrupt }))); _for_each_inner_peripheral!((GPIO0)); + _for_each_inner_peripheral!((GPIO1)); _for_each_inner_peripheral!((GPIO2)); + _for_each_inner_peripheral!((GPIO3)); _for_each_inner_peripheral!((GPIO4)); + _for_each_inner_peripheral!((GPIO5)); _for_each_inner_peripheral!((GPIO6)); + _for_each_inner_peripheral!((GPIO7)); _for_each_inner_peripheral!((GPIO8)); + _for_each_inner_peripheral!((GPIO9)); _for_each_inner_peripheral!((GPIO10)); + _for_each_inner_peripheral!((GPIO11)); _for_each_inner_peripheral!((GPIO12)); + _for_each_inner_peripheral!((GPIO13)); _for_each_inner_peripheral!((GPIO14)); + _for_each_inner_peripheral!((GPIO15)); _for_each_inner_peripheral!((GPIO16)); + _for_each_inner_peripheral!((GPIO17)); _for_each_inner_peripheral!((GPIO18)); + _for_each_inner_peripheral!((GPIO19)); _for_each_inner_peripheral!((GPIO20)); + _for_each_inner_peripheral!((GPIO21)); _for_each_inner_peripheral!((GPIO26)); + _for_each_inner_peripheral!((GPIO27)); _for_each_inner_peripheral!((GPIO28)); + _for_each_inner_peripheral!((GPIO29)); _for_each_inner_peripheral!((GPIO30)); + _for_each_inner_peripheral!((GPIO31)); _for_each_inner_peripheral!((GPIO32)); + _for_each_inner_peripheral!((GPIO33)); _for_each_inner_peripheral!((GPIO34)); + _for_each_inner_peripheral!((GPIO35)); _for_each_inner_peripheral!((GPIO36)); + _for_each_inner_peripheral!((GPIO37)); _for_each_inner_peripheral!((GPIO38)); + _for_each_inner_peripheral!((GPIO39)); _for_each_inner_peripheral!((GPIO40)); + _for_each_inner_peripheral!((GPIO41)); _for_each_inner_peripheral!((GPIO42)); + _for_each_inner_peripheral!((GPIO43)); _for_each_inner_peripheral!((GPIO44)); + _for_each_inner_peripheral!((GPIO45)); _for_each_inner_peripheral!((GPIO46)); + _for_each_inner_peripheral!((GPIO47)); _for_each_inner_peripheral!((GPIO48)); + _for_each_inner_peripheral!((AES(unstable))); + _for_each_inner_peripheral!((APB_CTRL(unstable))); + _for_each_inner_peripheral!((APB_SARADC(unstable))); + _for_each_inner_peripheral!((ASSIST_DEBUG(unstable))); + _for_each_inner_peripheral!((DMA(unstable))); + _for_each_inner_peripheral!((DS(unstable))); + _for_each_inner_peripheral!((EXTMEM(unstable))); + _for_each_inner_peripheral!((GPIO(unstable))); + _for_each_inner_peripheral!((GPIO_SD(unstable))); + _for_each_inner_peripheral!((HMAC(unstable))); + _for_each_inner_peripheral!((I2C_ANA_MST(unstable))); + _for_each_inner_peripheral!((I2C0)); _for_each_inner_peripheral!((I2C1)); + _for_each_inner_peripheral!((I2S0(unstable))); + _for_each_inner_peripheral!((I2S1(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE0(unstable))); + _for_each_inner_peripheral!((INTERRUPT_CORE1(unstable))); + _for_each_inner_peripheral!((IO_MUX(unstable))); + _for_each_inner_peripheral!((LCD_CAM(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); + _for_each_inner_peripheral!((LPWR(unstable))); + _for_each_inner_peripheral!((MCPWM0(unstable))); + _for_each_inner_peripheral!((MCPWM1(unstable))); + _for_each_inner_peripheral!((PCNT(unstable))); + _for_each_inner_peripheral!((PERI_BACKUP(unstable))); + _for_each_inner_peripheral!((RMT(unstable))); + _for_each_inner_peripheral!((RNG(unstable))); + _for_each_inner_peripheral!((RSA(unstable))); + _for_each_inner_peripheral!((RTC_CNTL(unstable))); + _for_each_inner_peripheral!((RTC_I2C(unstable))); + _for_each_inner_peripheral!((RTC_IO(unstable))); + _for_each_inner_peripheral!((SDHOST(unstable))); + _for_each_inner_peripheral!((SENS(unstable))); + _for_each_inner_peripheral!((SENSITIVE(unstable))); + _for_each_inner_peripheral!((SHA(unstable))); + _for_each_inner_peripheral!((SPI0(unstable))); + _for_each_inner_peripheral!((SPI1(unstable))); + _for_each_inner_peripheral!((SPI2)); _for_each_inner_peripheral!((SPI3)); + _for_each_inner_peripheral!((SYSTEM(unstable))); + _for_each_inner_peripheral!((SYSTIMER(unstable))); + _for_each_inner_peripheral!((TIMG0(unstable))); + _for_each_inner_peripheral!((TIMG1(unstable))); + _for_each_inner_peripheral!((TWAI0(unstable))); + _for_each_inner_peripheral!((UART0)); _for_each_inner_peripheral!((UART1)); + _for_each_inner_peripheral!((UART2)); + _for_each_inner_peripheral!((UHCI0(unstable))); + _for_each_inner_peripheral!((USB0(unstable))); + _for_each_inner_peripheral!((USB_DEVICE(unstable))); + _for_each_inner_peripheral!((USB_WRAP(unstable))); + _for_each_inner_peripheral!((WCL(unstable))); + _for_each_inner_peripheral!((XTS_AES(unstable))); + _for_each_inner_peripheral!((DMA_CH0(unstable))); + _for_each_inner_peripheral!((DMA_CH1(unstable))); + _for_each_inner_peripheral!((DMA_CH2(unstable))); + _for_each_inner_peripheral!((DMA_CH3(unstable))); + _for_each_inner_peripheral!((DMA_CH4(unstable))); + _for_each_inner_peripheral!((ADC1(unstable))); + _for_each_inner_peripheral!((ADC2(unstable))); + _for_each_inner_peripheral!((BT(unstable))); + _for_each_inner_peripheral!((CPU_CTRL(unstable))); + _for_each_inner_peripheral!((FLASH(unstable))); + _for_each_inner_peripheral!((GPIO_DEDICATED(unstable))); + _for_each_inner_peripheral!((PSRAM(unstable))); + _for_each_inner_peripheral!((SW_INTERRUPT(unstable))); + _for_each_inner_peripheral!((ULP_RISCV_CORE(unstable))); + _for_each_inner_peripheral!((WIFI)); _for_each_inner_peripheral!((SPI2, Spi2, + 0)); _for_each_inner_peripheral!((SPI3, Spi3, 1)); + _for_each_inner_peripheral!((UHCI0, Uhci0, 2)); + _for_each_inner_peripheral!((I2S0, I2s0, 3)); _for_each_inner_peripheral!((I2S1, + I2s1, 4)); _for_each_inner_peripheral!((LCD_CAM, LcdCam, 5)); + _for_each_inner_peripheral!((AES, Aes, 6)); _for_each_inner_peripheral!((SHA, + Sha, 7)); _for_each_inner_peripheral!((APB_SARADC, ApbSaradc, 8)); + _for_each_inner_peripheral!((RMT, Rmt, 9)); _for_each_inner_peripheral!((all(@ + peri_type #[doc = "GPIO0 peripheral singleton (Limitations exist)"] #[doc = ""] + #[doc = "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO0 <= virtual()), (@ peri_type #[doc = + "GPIO1 peripheral singleton"] GPIO1 <= virtual()), (@ peri_type #[doc = + "GPIO2 peripheral singleton"] GPIO2 <= virtual()), (@ peri_type #[doc = + "GPIO3 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO3 <= virtual()), (@ peri_type #[doc = + "GPIO4 peripheral singleton"] GPIO4 <= virtual()), (@ peri_type #[doc = + "GPIO5 peripheral singleton"] GPIO5 <= virtual()), (@ peri_type #[doc = + "GPIO6 peripheral singleton"] GPIO6 <= virtual()), (@ peri_type #[doc = + "GPIO7 peripheral singleton"] GPIO7 <= virtual()), (@ peri_type #[doc = + "GPIO8 peripheral singleton"] GPIO8 <= virtual()), (@ peri_type #[doc = + "GPIO9 peripheral singleton"] GPIO9 <= virtual()), (@ peri_type #[doc = + "GPIO10 peripheral singleton"] GPIO10 <= virtual()), (@ peri_type #[doc = + "GPIO11 peripheral singleton"] GPIO11 <= virtual()), (@ peri_type #[doc = + "GPIO12 peripheral singleton"] GPIO12 <= virtual()), (@ peri_type #[doc = + "GPIO13 peripheral singleton"] GPIO13 <= virtual()), (@ peri_type #[doc = + "GPIO14 peripheral singleton"] GPIO14 <= virtual()), (@ peri_type #[doc = + "GPIO15 peripheral singleton"] GPIO15 <= virtual()), (@ peri_type #[doc = + "GPIO16 peripheral singleton"] GPIO16 <= virtual()), (@ peri_type #[doc = + "GPIO17 peripheral singleton"] GPIO17 <= virtual()), (@ peri_type #[doc = + "GPIO18 peripheral singleton"] GPIO18 <= virtual()), (@ peri_type #[doc = + "GPIO19 peripheral singleton"] GPIO19 <= virtual()), (@ peri_type #[doc = + "GPIO20 peripheral singleton"] GPIO20 <= virtual()), (@ peri_type #[doc = + "GPIO21 peripheral singleton"] GPIO21 <= virtual()), (@ peri_type #[doc = + "GPIO26 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO26 <= virtual()), (@ peri_type #[doc = + "GPIO27 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO27 <= virtual()), (@ peri_type #[doc = + "GPIO28 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO28 <= virtual()), (@ peri_type #[doc = + "GPIO29 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO29 <= virtual()), (@ peri_type #[doc = + "GPIO30 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO30 <= virtual()), (@ peri_type #[doc = + "GPIO31 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    • This pin may be reserved for interfacing with SPI PSRAM.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO31 <= virtual()), (@ peri_type #[doc = + "GPIO32 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with SPI flash.
    • "] #[doc = + "
    "] #[doc = "
    "] GPIO32 <= virtual()), (@ peri_type #[doc = + "GPIO33 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO33 <= virtual()), (@ peri_type #[doc = + "GPIO34 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO34 <= virtual()), (@ peri_type #[doc = + "GPIO35 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO35 <= virtual()), (@ peri_type #[doc = + "GPIO36 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO36 <= virtual()), (@ peri_type #[doc = + "GPIO37 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin may be reserved for interfacing with Octal SPI flash.
    • "] #[doc + = "
    • This pin may be reserved for interfacing with Octal SPI PSRAM.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO37 <= virtual()), (@ peri_type #[doc = + "GPIO38 peripheral singleton"] GPIO38 <= virtual()), (@ peri_type #[doc = + "GPIO39 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO39 <= virtual()), (@ peri_type #[doc = + "GPIO40 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO40 <= virtual()), (@ peri_type #[doc = + "GPIO41 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO41 <= virtual()), (@ peri_type #[doc = + "GPIO42 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • These pins may be used to debug the chip using an external JTAG debugger.
    • "] + #[doc = "
    "] #[doc = "
    "] GPIO42 <= virtual()), (@ peri_type #[doc = + "GPIO43 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO43 <= virtual()), (@ peri_type #[doc = + "GPIO44 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • By default, this pin is used by the UART programming interface.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO44 <= virtual()), (@ peri_type #[doc = + "GPIO45 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO45 <= virtual()), (@ peri_type #[doc = + "GPIO46 peripheral singleton (Limitations exist)"] #[doc = ""] #[doc = + "
    "] #[doc = + "This pin may be available with certain limitations. Check your hardware to make sure whether you can use it."] + #[doc = "
      "] #[doc = + "
    • This pin is a strapping pin, it determines how the chip boots.
    • "] #[doc + = "
    "] #[doc = "
    "] GPIO46 <= virtual()), (@ peri_type #[doc = + "GPIO47 peripheral singleton"] GPIO47 <= virtual()), (@ peri_type #[doc = + "GPIO48 peripheral singleton"] GPIO48 <= virtual()), (@ peri_type #[doc = + "AES peripheral singleton"] AES <= AES(AES : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "APB_CTRL peripheral singleton"] APB_CTRL <= APB_CTRL() (unstable)), (@ + peri_type #[doc = "APB_SARADC peripheral singleton"] APB_SARADC <= APB_SARADC() + (unstable)), (@ peri_type #[doc = "ASSIST_DEBUG peripheral singleton"] + ASSIST_DEBUG <= ASSIST_DEBUG() (unstable)), (@ peri_type #[doc = + "DMA peripheral singleton"] DMA <= DMA() (unstable)), (@ peri_type #[doc = + "DS peripheral singleton"] DS <= DS() (unstable)), (@ peri_type #[doc = + "EFUSE peripheral singleton"] EFUSE <= EFUSE() (unstable)), (@ peri_type #[doc = + "EXTMEM peripheral singleton"] EXTMEM <= EXTMEM() (unstable)), (@ peri_type #[doc + = "GPIO peripheral singleton"] GPIO <= GPIO() (unstable)), (@ peri_type #[doc = + "GPIO_SD peripheral singleton"] GPIO_SD <= GPIO_SD() (unstable)), (@ peri_type + #[doc = "HMAC peripheral singleton"] HMAC <= HMAC() (unstable)), (@ peri_type + #[doc = "I2C_ANA_MST peripheral singleton"] I2C_ANA_MST <= I2C_ANA_MST() + (unstable)), (@ peri_type #[doc = "I2C0 peripheral singleton"] I2C0 <= + I2C0(I2C_EXT0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "I2C1 peripheral singleton"] + I2C1 <= I2C1(I2C_EXT1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "I2S0 peripheral singleton"] + I2S0 <= I2S0(I2S0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt }) (unstable)), (@ peri_type #[doc = + "I2S1 peripheral singleton"] I2S1 <= I2S1(I2S1 : { bind_peri_interrupt, + enable_peri_interrupt, disable_peri_interrupt }) (unstable)), (@ peri_type #[doc + = "INTERRUPT_CORE0 peripheral singleton"] INTERRUPT_CORE0 <= INTERRUPT_CORE0() + (unstable)), (@ peri_type #[doc = "INTERRUPT_CORE1 peripheral singleton"] + INTERRUPT_CORE1 <= INTERRUPT_CORE1() (unstable)), (@ peri_type #[doc = + "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type #[doc + = "LCD_CAM peripheral singleton"] LCD_CAM <= LCD_CAM() (unstable)), (@ peri_type + #[doc = "LEDC peripheral singleton"] LEDC <= LEDC() (unstable)), (@ peri_type + #[doc = "LPWR peripheral singleton"] LPWR <= RTC_CNTL() (unstable)), (@ peri_type + #[doc = "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() (unstable)), (@ + peri_type #[doc = "MCPWM1 peripheral singleton"] MCPWM1 <= MCPWM1() (unstable)), + (@ peri_type #[doc = "PCNT peripheral singleton"] PCNT <= PCNT() (unstable)), (@ + peri_type #[doc = "PERI_BACKUP peripheral singleton"] PERI_BACKUP <= + PERI_BACKUP() (unstable)), (@ peri_type #[doc = "RMT peripheral singleton"] RMT + <= RMT() (unstable)), (@ peri_type #[doc = "RNG peripheral singleton"] RNG <= + RNG() (unstable)), (@ peri_type #[doc = "RSA peripheral singleton"] RSA <= + RSA(RSA : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "RTC_CNTL peripheral singleton"] RTC_CNTL <= + RTC_CNTL() (unstable)), (@ peri_type #[doc = "RTC_I2C peripheral singleton"] + RTC_I2C <= RTC_I2C() (unstable)), (@ peri_type #[doc = + "RTC_IO peripheral singleton"] RTC_IO <= RTC_IO() (unstable)), (@ peri_type #[doc + = "SDHOST peripheral singleton"] SDHOST <= SDHOST() (unstable)), (@ peri_type + #[doc = "SENS peripheral singleton"] SENS <= SENS() (unstable)), (@ peri_type + #[doc = "SENSITIVE peripheral singleton"] SENSITIVE <= SENSITIVE() (unstable)), + (@ peri_type #[doc = "SHA peripheral singleton"] SHA <= SHA(SHA : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "SPI0 peripheral singleton"] SPI0 <= SPI0() + (unstable)), (@ peri_type #[doc = "SPI1 peripheral singleton"] SPI1 <= SPI1() + (unstable)), (@ peri_type #[doc = "SPI2 peripheral singleton"] SPI2 <= SPI2(SPI2 + : { bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SPI3 peripheral singleton"] SPI3 <= SPI3(SPI3 : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt })), (@ + peri_type #[doc = "SYSTEM peripheral singleton"] SYSTEM <= SYSTEM() (unstable)), + (@ peri_type #[doc = "SYSTIMER peripheral singleton"] SYSTIMER <= SYSTIMER() + (unstable)), (@ peri_type #[doc = "TIMG0 peripheral singleton"] TIMG0 <= TIMG0() + (unstable)), (@ peri_type #[doc = "TIMG1 peripheral singleton"] TIMG1 <= TIMG1() + (unstable)), (@ peri_type #[doc = "TWAI0 peripheral singleton"] TWAI0 <= TWAI0() + (unstable)), (@ peri_type #[doc = "UART0 peripheral singleton"] UART0 <= + UART0(UART0 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART1 peripheral singleton"] + UART1 <= UART1(UART1 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UART2 peripheral singleton"] + UART2 <= UART2(UART2 : { bind_peri_interrupt, enable_peri_interrupt, + disable_peri_interrupt })), (@ peri_type #[doc = "UHCI0 peripheral singleton"] + UHCI0 <= UHCI0() (unstable)), (@ peri_type #[doc = "USB0 peripheral singleton"] + USB0 <= USB0() (unstable)), (@ peri_type #[doc = + "USB_DEVICE peripheral singleton"] USB_DEVICE <= USB_DEVICE(USB_DEVICE : { + bind_peri_interrupt, enable_peri_interrupt, disable_peri_interrupt }) + (unstable)), (@ peri_type #[doc = "USB_WRAP peripheral singleton"] USB_WRAP <= + USB_WRAP() (unstable)), (@ peri_type #[doc = "WCL peripheral singleton"] WCL <= + WCL() (unstable)), (@ peri_type #[doc = "XTS_AES peripheral singleton"] XTS_AES + <= XTS_AES() (unstable)), (@ peri_type #[doc = "DMA_CH0 peripheral singleton"] + DMA_CH0 <= virtual() (unstable)), (@ peri_type #[doc = + "DMA_CH1 peripheral singleton"] DMA_CH1 <= virtual() (unstable)), (@ peri_type + #[doc = "DMA_CH2 peripheral singleton"] DMA_CH2 <= virtual() (unstable)), (@ + peri_type #[doc = "DMA_CH3 peripheral singleton"] DMA_CH3 <= virtual() + (unstable)), (@ peri_type #[doc = "DMA_CH4 peripheral singleton"] DMA_CH4 <= + virtual() (unstable)), (@ peri_type #[doc = "ADC1 peripheral singleton"] ADC1 <= + virtual() (unstable)), (@ peri_type #[doc = "ADC2 peripheral singleton"] ADC2 <= + virtual() (unstable)), (@ peri_type #[doc = "BT peripheral singleton"] BT <= + virtual(BT_BB : { bind_bb_interrupt, enable_bb_interrupt, disable_bb_interrupt }, + RWBLE : { bind_rwble_interrupt, enable_rwble_interrupt, disable_rwble_interrupt + }) (unstable)), (@ peri_type #[doc = "CPU_CTRL peripheral singleton"] CPU_CTRL <= + virtual() (unstable)), (@ peri_type #[doc = "FLASH peripheral singleton"] FLASH + <= virtual() (unstable)), (@ peri_type #[doc = + "GPIO_DEDICATED peripheral singleton"] GPIO_DEDICATED <= virtual() (unstable)), + (@ peri_type #[doc = "PSRAM peripheral singleton"] PSRAM <= virtual() + (unstable)), (@ peri_type #[doc = "SW_INTERRUPT peripheral singleton"] + SW_INTERRUPT <= virtual() (unstable)), (@ peri_type #[doc = + "ULP_RISCV_CORE peripheral singleton"] ULP_RISCV_CORE <= virtual() (unstable)), + (@ peri_type #[doc = "WIFI peripheral singleton"] WIFI <= virtual(WIFI_MAC : { + bind_mac_interrupt, enable_mac_interrupt, disable_mac_interrupt }, WIFI_PWR : { + bind_pwr_interrupt, enable_pwr_interrupt, disable_pwr_interrupt })))); + _for_each_inner_peripheral!((singletons(GPIO0), (GPIO1), (GPIO2), (GPIO3), + (GPIO4), (GPIO5), (GPIO6), (GPIO7), (GPIO8), (GPIO9), (GPIO10), (GPIO11), + (GPIO12), (GPIO13), (GPIO14), (GPIO15), (GPIO16), (GPIO17), (GPIO18), (GPIO19), + (GPIO20), (GPIO21), (GPIO26), (GPIO27), (GPIO28), (GPIO29), (GPIO30), (GPIO31), + (GPIO32), (GPIO33), (GPIO34), (GPIO35), (GPIO36), (GPIO37), (GPIO38), (GPIO39), + (GPIO40), (GPIO41), (GPIO42), (GPIO43), (GPIO44), (GPIO45), (GPIO46), (GPIO47), + (GPIO48), (AES(unstable)), (APB_CTRL(unstable)), (APB_SARADC(unstable)), + (ASSIST_DEBUG(unstable)), (DMA(unstable)), (DS(unstable)), (EXTMEM(unstable)), + (GPIO(unstable)), (GPIO_SD(unstable)), (HMAC(unstable)), (I2C_ANA_MST(unstable)), + (I2C0), (I2C1), (I2S0(unstable)), (I2S1(unstable)), (INTERRUPT_CORE0(unstable)), + (INTERRUPT_CORE1(unstable)), (IO_MUX(unstable)), (LCD_CAM(unstable)), + (LEDC(unstable)), (LPWR(unstable)), (MCPWM0(unstable)), (MCPWM1(unstable)), + (PCNT(unstable)), (PERI_BACKUP(unstable)), (RMT(unstable)), (RNG(unstable)), + (RSA(unstable)), (RTC_CNTL(unstable)), (RTC_I2C(unstable)), (RTC_IO(unstable)), + (SDHOST(unstable)), (SENS(unstable)), (SENSITIVE(unstable)), (SHA(unstable)), + (SPI0(unstable)), (SPI1(unstable)), (SPI2), (SPI3), (SYSTEM(unstable)), + (SYSTIMER(unstable)), (TIMG0(unstable)), (TIMG1(unstable)), (TWAI0(unstable)), + (UART0), (UART1), (UART2), (UHCI0(unstable)), (USB0(unstable)), + (USB_DEVICE(unstable)), (USB_WRAP(unstable)), (WCL(unstable)), + (XTS_AES(unstable)), (DMA_CH0(unstable)), (DMA_CH1(unstable)), + (DMA_CH2(unstable)), (DMA_CH3(unstable)), (DMA_CH4(unstable)), (ADC1(unstable)), + (ADC2(unstable)), (BT(unstable)), (CPU_CTRL(unstable)), (FLASH(unstable)), + (GPIO_DEDICATED(unstable)), (PSRAM(unstable)), (SW_INTERRUPT(unstable)), + (ULP_RISCV_CORE(unstable)), (WIFI))); + _for_each_inner_peripheral!((dma_eligible(SPI2, Spi2, 0), (SPI3, Spi3, 1), + (UHCI0, Uhci0, 2), (I2S0, I2s0, 3), (I2S1, I2s1, 4), (LCD_CAM, LcdCam, 5), (AES, + Aes, 6), (SHA, Sha, 7), (APB_SARADC, ApbSaradc, 8), (RMT, Rmt, 9))); + }; +} +/// This macro can be used to generate code for each `GPIOn` instance. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => +/// $digital_input_signal:ident)*) ($($digital_output_function:ident => +/// $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` +/// +/// Macro fragments: +/// +/// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. +/// - `$gpio`: the name of the GPIO. +/// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_input_function`: the name of the digital function, as an identifier. +/// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for +/// function 0 this is `_0`). +/// - `$digital_output_function`: the name of the digital function, as an identifier. +/// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. +/// Bracketed so that they can also be matched as optional fragments. Order is always Input first. +/// +/// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] +/// [Output]))` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_gpio { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_gpio { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_gpio!((0, GPIO0() () ([Input] [Output]))); + _for_each_inner_gpio!((1, GPIO1() () ([Input] [Output]))); + _for_each_inner_gpio!((2, GPIO2() () ([Input] [Output]))); + _for_each_inner_gpio!((3, GPIO3() () ([Input] [Output]))); + _for_each_inner_gpio!((4, GPIO4() () ([Input] [Output]))); + _for_each_inner_gpio!((5, GPIO5() () ([Input] [Output]))); + _for_each_inner_gpio!((6, GPIO6() () ([Input] [Output]))); + _for_each_inner_gpio!((7, GPIO7() () ([Input] [Output]))); + _for_each_inner_gpio!((8, GPIO8() (_3 => SUBSPICS1) ([Input] [Output]))); + _for_each_inner_gpio!((9, GPIO9(_3 => SUBSPIHD _4 => FSPIHD) (_3 => SUBSPIHD _4 + => FSPIHD) ([Input] [Output]))); _for_each_inner_gpio!((10, GPIO10(_2 => FSPIIO4 + _4 => FSPICS0) (_2 => FSPIIO4 _3 => SUBSPICS0 _4 => FSPICS0) ([Input] + [Output]))); _for_each_inner_gpio!((11, GPIO11(_2 => FSPIIO5 _3 => SUBSPID _4 => + FSPID) (_2 => FSPIIO5 _3 => SUBSPID _4 => FSPID) ([Input] [Output]))); + _for_each_inner_gpio!((12, GPIO12(_2 => FSPIIO6 _4 => FSPICLK) (_2 => FSPIIO6 _3 + => SUBSPICLK _4 => FSPICLK) ([Input] [Output]))); _for_each_inner_gpio!((13, + GPIO13(_2 => FSPIIO7 _3 => SUBSPIQ _4 => FSPIQ) (_2 => FSPIIO7 _3 => SUBSPIQ _4 + => FSPIQ) ([Input] [Output]))); _for_each_inner_gpio!((14, GPIO14(_3 => SUBSPIWP + _4 => FSPIWP) (_2 => FSPIDQS _3 => SUBSPIWP _4 => FSPIWP) ([Input] [Output]))); + _for_each_inner_gpio!((15, GPIO15() (_2 => U0RTS) ([Input] [Output]))); + _for_each_inner_gpio!((16, GPIO16(_2 => U0CTS) () ([Input] [Output]))); + _for_each_inner_gpio!((17, GPIO17() (_2 => U1TXD) ([Input] [Output]))); + _for_each_inner_gpio!((18, GPIO18(_2 => U1RXD) (_3 => CLK_OUT3) ([Input] + [Output]))); _for_each_inner_gpio!((19, GPIO19() (_2 => U1RTS _3 => CLK_OUT2) + ([Input] [Output]))); _for_each_inner_gpio!((20, GPIO20(_2 => U1CTS) (_3 => + CLK_OUT1) ([Input] [Output]))); _for_each_inner_gpio!((21, GPIO21() () ([Input] + [Output]))); _for_each_inner_gpio!((26, GPIO26() (_0 => SPICS1) ([Input] + [Output]))); _for_each_inner_gpio!((27, GPIO27(_0 => SPIHD) (_0 => SPIHD) + ([Input] [Output]))); _for_each_inner_gpio!((28, GPIO28(_0 => SPIWP) (_0 => + SPIWP) ([Input] [Output]))); _for_each_inner_gpio!((29, GPIO29() (_0 => SPICS0) + ([Input] [Output]))); _for_each_inner_gpio!((30, GPIO30() (_0 => SPICLK) ([Input] + [Output]))); _for_each_inner_gpio!((31, GPIO31(_0 => SPIQ) (_0 => SPIQ) ([Input] + [Output]))); _for_each_inner_gpio!((32, GPIO32(_0 => SPID) (_0 => SPID) ([Input] + [Output]))); _for_each_inner_gpio!((33, GPIO33(_2 => FSPIHD _3 => SUBSPIHD _4 => + SPIIO4) (_2 => FSPIHD _3 => SUBSPIHD _4 => SPIIO4) ([Input] [Output]))); + _for_each_inner_gpio!((34, GPIO34(_2 => FSPICS0 _4 => SPIIO5) (_2 => FSPICS0 _3 + => SUBSPICS0 _4 => SPIIO5) ([Input] [Output]))); _for_each_inner_gpio!((35, + GPIO35(_2 => FSPID _3 => SUBSPID _4 => SPIIO6) (_2 => FSPID _3 => SUBSPID _4 => + SPIIO6) ([Input] [Output]))); _for_each_inner_gpio!((36, GPIO36(_2 => FSPICLK _4 + => SPIIO7) (_2 => FSPICLK _3 => SUBSPICLK _4 => SPIIO7) ([Input] [Output]))); + _for_each_inner_gpio!((37, GPIO37(_2 => FSPIQ _3 => SUBSPIQ _4 => SPIDQS) (_2 => + FSPIQ _3 => SUBSPIQ _4 => SPIDQS) ([Input] [Output]))); + _for_each_inner_gpio!((38, GPIO38(_2 => FSPIWP _3 => SUBSPIWP) (_2 => FSPIWP _3 + => SUBSPIWP) ([Input] [Output]))); _for_each_inner_gpio!((39, GPIO39(_0 => MTCK) + (_2 => CLK_OUT3 _3 => SUBSPICS1) ([Input] [Output]))); _for_each_inner_gpio!((40, + GPIO40() (_0 => MTDO _2 => CLK_OUT2) ([Input] [Output]))); + _for_each_inner_gpio!((41, GPIO41(_0 => MTDI) (_2 => CLK_OUT1) ([Input] + [Output]))); _for_each_inner_gpio!((42, GPIO42(_0 => MTMS) () ([Input] + [Output]))); _for_each_inner_gpio!((43, GPIO43() (_0 => U0TXD _2 => CLK_OUT1) + ([Input] [Output]))); _for_each_inner_gpio!((44, GPIO44(_0 => U0RXD) (_2 => + CLK_OUT2) ([Input] [Output]))); _for_each_inner_gpio!((45, GPIO45() () ([Input] + [Output]))); _for_each_inner_gpio!((46, GPIO46() () ([Input] [Output]))); + _for_each_inner_gpio!((47, GPIO47() (_0 => SPICLK_P_DIFF _2 => SUBSPICLK_P_DIFF) + ([Input] [Output]))); _for_each_inner_gpio!((48, GPIO48() (_0 => SPICLK_N_DIFF _2 + => SUBSPICLK_N_DIFF) ([Input] [Output]))); _for_each_inner_gpio!((all(0, GPIO0() + () ([Input] [Output])), (1, GPIO1() () ([Input] [Output])), (2, GPIO2() () + ([Input] [Output])), (3, GPIO3() () ([Input] [Output])), (4, GPIO4() () ([Input] + [Output])), (5, GPIO5() () ([Input] [Output])), (6, GPIO6() () ([Input] + [Output])), (7, GPIO7() () ([Input] [Output])), (8, GPIO8() (_3 => SUBSPICS1) + ([Input] [Output])), (9, GPIO9(_3 => SUBSPIHD _4 => FSPIHD) (_3 => SUBSPIHD _4 => + FSPIHD) ([Input] [Output])), (10, GPIO10(_2 => FSPIIO4 _4 => FSPICS0) (_2 => + FSPIIO4 _3 => SUBSPICS0 _4 => FSPICS0) ([Input] [Output])), (11, GPIO11(_2 => + FSPIIO5 _3 => SUBSPID _4 => FSPID) (_2 => FSPIIO5 _3 => SUBSPID _4 => FSPID) + ([Input] [Output])), (12, GPIO12(_2 => FSPIIO6 _4 => FSPICLK) (_2 => FSPIIO6 _3 + => SUBSPICLK _4 => FSPICLK) ([Input] [Output])), (13, GPIO13(_2 => FSPIIO7 _3 => + SUBSPIQ _4 => FSPIQ) (_2 => FSPIIO7 _3 => SUBSPIQ _4 => FSPIQ) ([Input] + [Output])), (14, GPIO14(_3 => SUBSPIWP _4 => FSPIWP) (_2 => FSPIDQS _3 => + SUBSPIWP _4 => FSPIWP) ([Input] [Output])), (15, GPIO15() (_2 => U0RTS) ([Input] + [Output])), (16, GPIO16(_2 => U0CTS) () ([Input] [Output])), (17, GPIO17() (_2 => + U1TXD) ([Input] [Output])), (18, GPIO18(_2 => U1RXD) (_3 => CLK_OUT3) ([Input] + [Output])), (19, GPIO19() (_2 => U1RTS _3 => CLK_OUT2) ([Input] [Output])), (20, + GPIO20(_2 => U1CTS) (_3 => CLK_OUT1) ([Input] [Output])), (21, GPIO21() () + ([Input] [Output])), (26, GPIO26() (_0 => SPICS1) ([Input] [Output])), (27, + GPIO27(_0 => SPIHD) (_0 => SPIHD) ([Input] [Output])), (28, GPIO28(_0 => SPIWP) + (_0 => SPIWP) ([Input] [Output])), (29, GPIO29() (_0 => SPICS0) ([Input] + [Output])), (30, GPIO30() (_0 => SPICLK) ([Input] [Output])), (31, GPIO31(_0 => + SPIQ) (_0 => SPIQ) ([Input] [Output])), (32, GPIO32(_0 => SPID) (_0 => SPID) + ([Input] [Output])), (33, GPIO33(_2 => FSPIHD _3 => SUBSPIHD _4 => SPIIO4) (_2 => + FSPIHD _3 => SUBSPIHD _4 => SPIIO4) ([Input] [Output])), (34, GPIO34(_2 => + FSPICS0 _4 => SPIIO5) (_2 => FSPICS0 _3 => SUBSPICS0 _4 => SPIIO5) ([Input] + [Output])), (35, GPIO35(_2 => FSPID _3 => SUBSPID _4 => SPIIO6) (_2 => FSPID _3 + => SUBSPID _4 => SPIIO6) ([Input] [Output])), (36, GPIO36(_2 => FSPICLK _4 => + SPIIO7) (_2 => FSPICLK _3 => SUBSPICLK _4 => SPIIO7) ([Input] [Output])), (37, + GPIO37(_2 => FSPIQ _3 => SUBSPIQ _4 => SPIDQS) (_2 => FSPIQ _3 => SUBSPIQ _4 => + SPIDQS) ([Input] [Output])), (38, GPIO38(_2 => FSPIWP _3 => SUBSPIWP) (_2 => + FSPIWP _3 => SUBSPIWP) ([Input] [Output])), (39, GPIO39(_0 => MTCK) (_2 => + CLK_OUT3 _3 => SUBSPICS1) ([Input] [Output])), (40, GPIO40() (_0 => MTDO _2 => + CLK_OUT2) ([Input] [Output])), (41, GPIO41(_0 => MTDI) (_2 => CLK_OUT1) ([Input] + [Output])), (42, GPIO42(_0 => MTMS) () ([Input] [Output])), (43, GPIO43() (_0 => + U0TXD _2 => CLK_OUT1) ([Input] [Output])), (44, GPIO44(_0 => U0RXD) (_2 => + CLK_OUT2) ([Input] [Output])), (45, GPIO45() () ([Input] [Output])), (46, + GPIO46() () ([Input] [Output])), (47, GPIO47() (_0 => SPICLK_P_DIFF _2 => + SUBSPICLK_P_DIFF) ([Input] [Output])), (48, GPIO48() (_0 => SPICLK_N_DIFF _2 => + SUBSPICLK_N_DIFF) ([Input] [Output])))); + }; +} +/// This macro can be used to generate code for each analog function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like +/// `(ADC2_CH3, ADCn_CHm, 2, 3)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(ADC2_CH5, GPIO12)` +/// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_analog_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_analog_function { $(($pattern) => $code;)* ($other : + tt) => {} } _for_each_inner_analog_function!((TOUCH1, GPIO1)); + _for_each_inner_analog_function!((ADC1_CH0, GPIO1)); + _for_each_inner_analog_function!((TOUCH2, GPIO2)); + _for_each_inner_analog_function!((ADC1_CH1, GPIO2)); + _for_each_inner_analog_function!((TOUCH3, GPIO3)); + _for_each_inner_analog_function!((ADC1_CH2, GPIO3)); + _for_each_inner_analog_function!((TOUCH4, GPIO4)); + _for_each_inner_analog_function!((ADC1_CH3, GPIO4)); + _for_each_inner_analog_function!((TOUCH5, GPIO5)); + _for_each_inner_analog_function!((ADC1_CH4, GPIO5)); + _for_each_inner_analog_function!((TOUCH6, GPIO6)); + _for_each_inner_analog_function!((ADC1_CH5, GPIO6)); + _for_each_inner_analog_function!((TOUCH7, GPIO7)); + _for_each_inner_analog_function!((ADC1_CH6, GPIO7)); + _for_each_inner_analog_function!((TOUCH8, GPIO8)); + _for_each_inner_analog_function!((ADC1_CH7, GPIO8)); + _for_each_inner_analog_function!((TOUCH9, GPIO9)); + _for_each_inner_analog_function!((ADC1_CH8, GPIO9)); + _for_each_inner_analog_function!((TOUCH10, GPIO10)); + _for_each_inner_analog_function!((ADC1_CH9, GPIO10)); + _for_each_inner_analog_function!((TOUCH11, GPIO11)); + _for_each_inner_analog_function!((ADC2_CH0, GPIO11)); + _for_each_inner_analog_function!((TOUCH12, GPIO12)); + _for_each_inner_analog_function!((ADC2_CH1, GPIO12)); + _for_each_inner_analog_function!((TOUCH13, GPIO13)); + _for_each_inner_analog_function!((ADC2_CH2, GPIO13)); + _for_each_inner_analog_function!((TOUCH14, GPIO14)); + _for_each_inner_analog_function!((ADC2_CH3, GPIO14)); + _for_each_inner_analog_function!((XTAL_32K_P, GPIO15)); + _for_each_inner_analog_function!((ADC2_CH4, GPIO15)); + _for_each_inner_analog_function!((XTAL_32K_N, GPIO16)); + _for_each_inner_analog_function!((ADC2_CH5, GPIO16)); + _for_each_inner_analog_function!((ADC2_CH6, GPIO17)); + _for_each_inner_analog_function!((ADC2_CH7, GPIO18)); + _for_each_inner_analog_function!((USB_DM, GPIO19)); + _for_each_inner_analog_function!((ADC2_CH8, GPIO19)); + _for_each_inner_analog_function!((USB_DP, GPIO20)); + _for_each_inner_analog_function!((ADC2_CH9, GPIO20)); + _for_each_inner_analog_function!(((TOUCH1, TOUCHn, 1), GPIO1)); + _for_each_inner_analog_function!(((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1)); + _for_each_inner_analog_function!(((TOUCH2, TOUCHn, 2), GPIO2)); + _for_each_inner_analog_function!(((ADC1_CH1, ADCn_CHm, 1, 1), GPIO2)); + _for_each_inner_analog_function!(((TOUCH3, TOUCHn, 3), GPIO3)); + _for_each_inner_analog_function!(((ADC1_CH2, ADCn_CHm, 1, 2), GPIO3)); + _for_each_inner_analog_function!(((TOUCH4, TOUCHn, 4), GPIO4)); + _for_each_inner_analog_function!(((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4)); + _for_each_inner_analog_function!(((TOUCH5, TOUCHn, 5), GPIO5)); + _for_each_inner_analog_function!(((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5)); + _for_each_inner_analog_function!(((TOUCH6, TOUCHn, 6), GPIO6)); + _for_each_inner_analog_function!(((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6)); + _for_each_inner_analog_function!(((TOUCH7, TOUCHn, 7), GPIO7)); + _for_each_inner_analog_function!(((ADC1_CH6, ADCn_CHm, 1, 6), GPIO7)); + _for_each_inner_analog_function!(((TOUCH8, TOUCHn, 8), GPIO8)); + _for_each_inner_analog_function!(((ADC1_CH7, ADCn_CHm, 1, 7), GPIO8)); + _for_each_inner_analog_function!(((TOUCH9, TOUCHn, 9), GPIO9)); + _for_each_inner_analog_function!(((ADC1_CH8, ADCn_CHm, 1, 8), GPIO9)); + _for_each_inner_analog_function!(((TOUCH10, TOUCHn, 10), GPIO10)); + _for_each_inner_analog_function!(((ADC1_CH9, ADCn_CHm, 1, 9), GPIO10)); + _for_each_inner_analog_function!(((TOUCH11, TOUCHn, 11), GPIO11)); + _for_each_inner_analog_function!(((ADC2_CH0, ADCn_CHm, 2, 0), GPIO11)); + _for_each_inner_analog_function!(((TOUCH12, TOUCHn, 12), GPIO12)); + _for_each_inner_analog_function!(((ADC2_CH1, ADCn_CHm, 2, 1), GPIO12)); + _for_each_inner_analog_function!(((TOUCH13, TOUCHn, 13), GPIO13)); + _for_each_inner_analog_function!(((ADC2_CH2, ADCn_CHm, 2, 2), GPIO13)); + _for_each_inner_analog_function!(((TOUCH14, TOUCHn, 14), GPIO14)); + _for_each_inner_analog_function!(((ADC2_CH3, ADCn_CHm, 2, 3), GPIO14)); + _for_each_inner_analog_function!(((ADC2_CH4, ADCn_CHm, 2, 4), GPIO15)); + _for_each_inner_analog_function!(((ADC2_CH5, ADCn_CHm, 2, 5), GPIO16)); + _for_each_inner_analog_function!(((ADC2_CH6, ADCn_CHm, 2, 6), GPIO17)); + _for_each_inner_analog_function!(((ADC2_CH7, ADCn_CHm, 2, 7), GPIO18)); + _for_each_inner_analog_function!(((ADC2_CH8, ADCn_CHm, 2, 8), GPIO19)); + _for_each_inner_analog_function!(((ADC2_CH9, ADCn_CHm, 2, 9), GPIO20)); + _for_each_inner_analog_function!((all(TOUCH1, GPIO1), (ADC1_CH0, GPIO1), (TOUCH2, + GPIO2), (ADC1_CH1, GPIO2), (TOUCH3, GPIO3), (ADC1_CH2, GPIO3), (TOUCH4, GPIO4), + (ADC1_CH3, GPIO4), (TOUCH5, GPIO5), (ADC1_CH4, GPIO5), (TOUCH6, GPIO6), + (ADC1_CH5, GPIO6), (TOUCH7, GPIO7), (ADC1_CH6, GPIO7), (TOUCH8, GPIO8), + (ADC1_CH7, GPIO8), (TOUCH9, GPIO9), (ADC1_CH8, GPIO9), (TOUCH10, GPIO10), + (ADC1_CH9, GPIO10), (TOUCH11, GPIO11), (ADC2_CH0, GPIO11), (TOUCH12, GPIO12), + (ADC2_CH1, GPIO12), (TOUCH13, GPIO13), (ADC2_CH2, GPIO13), (TOUCH14, GPIO14), + (ADC2_CH3, GPIO14), (XTAL_32K_P, GPIO15), (ADC2_CH4, GPIO15), (XTAL_32K_N, + GPIO16), (ADC2_CH5, GPIO16), (ADC2_CH6, GPIO17), (ADC2_CH7, GPIO18), (USB_DM, + GPIO19), (ADC2_CH8, GPIO19), (USB_DP, GPIO20), (ADC2_CH9, GPIO20))); + _for_each_inner_analog_function!((all_expanded((TOUCH1, TOUCHn, 1), GPIO1), + ((ADC1_CH0, ADCn_CHm, 1, 0), GPIO1), ((TOUCH2, TOUCHn, 2), GPIO2), ((ADC1_CH1, + ADCn_CHm, 1, 1), GPIO2), ((TOUCH3, TOUCHn, 3), GPIO3), ((ADC1_CH2, ADCn_CHm, 1, + 2), GPIO3), ((TOUCH4, TOUCHn, 4), GPIO4), ((ADC1_CH3, ADCn_CHm, 1, 3), GPIO4), + ((TOUCH5, TOUCHn, 5), GPIO5), ((ADC1_CH4, ADCn_CHm, 1, 4), GPIO5), ((TOUCH6, + TOUCHn, 6), GPIO6), ((ADC1_CH5, ADCn_CHm, 1, 5), GPIO6), ((TOUCH7, TOUCHn, 7), + GPIO7), ((ADC1_CH6, ADCn_CHm, 1, 6), GPIO7), ((TOUCH8, TOUCHn, 8), GPIO8), + ((ADC1_CH7, ADCn_CHm, 1, 7), GPIO8), ((TOUCH9, TOUCHn, 9), GPIO9), ((ADC1_CH8, + ADCn_CHm, 1, 8), GPIO9), ((TOUCH10, TOUCHn, 10), GPIO10), ((ADC1_CH9, ADCn_CHm, + 1, 9), GPIO10), ((TOUCH11, TOUCHn, 11), GPIO11), ((ADC2_CH0, ADCn_CHm, 2, 0), + GPIO11), ((TOUCH12, TOUCHn, 12), GPIO12), ((ADC2_CH1, ADCn_CHm, 2, 1), GPIO12), + ((TOUCH13, TOUCHn, 13), GPIO13), ((ADC2_CH2, ADCn_CHm, 2, 2), GPIO13), ((TOUCH14, + TOUCHn, 14), GPIO14), ((ADC2_CH3, ADCn_CHm, 2, 3), GPIO14), ((ADC2_CH4, ADCn_CHm, + 2, 4), GPIO15), ((ADC2_CH5, ADCn_CHm, 2, 5), GPIO16), ((ADC2_CH6, ADCn_CHm, 2, + 6), GPIO17), ((ADC2_CH7, ADCn_CHm, 2, 7), GPIO18), ((ADC2_CH8, ADCn_CHm, 2, 8), + GPIO19), ((ADC2_CH9, ADCn_CHm, 2, 9), GPIO20))); + }; +} +/// This macro can be used to generate code for each LP/RTC function of each GPIO. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has two options for its "Individual matcher" case: +/// +/// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers +/// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - +/// expanded signal case, where you need the number(s) of a signal, or the general group to which +/// the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like +/// `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. +/// +/// Macro fragments: +/// +/// - `$signal`: the name of the signal. +/// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this +/// is `ADCn_CHm`. +/// - `$number`: the numbers extracted from `$signal`. +/// - `$gpio`: the name of the GPIO. +/// +/// Example data: +/// - `(RTC_GPIO15, GPIO12)` +/// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` +/// +/// The expanded syntax is only available when the signal has at least one numbered component. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_lp_function { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_lp_function { $(($pattern) => $code;)* ($other : tt) + => {} } _for_each_inner_lp_function!((RTC_GPIO0, GPIO0)); + _for_each_inner_lp_function!((SAR_I2C_SCL_0, GPIO0)); + _for_each_inner_lp_function!((RTC_GPIO1, GPIO1)); + _for_each_inner_lp_function!((SAR_I2C_SDA_0, GPIO1)); + _for_each_inner_lp_function!((RTC_GPIO2, GPIO2)); + _for_each_inner_lp_function!((SAR_I2C_SCL_1, GPIO2)); + _for_each_inner_lp_function!((RTC_GPIO3, GPIO3)); + _for_each_inner_lp_function!((SAR_I2C_SDA_1, GPIO3)); + _for_each_inner_lp_function!((RTC_GPIO4, GPIO4)); + _for_each_inner_lp_function!((RTC_GPIO5, GPIO5)); + _for_each_inner_lp_function!((RTC_GPIO6, GPIO6)); + _for_each_inner_lp_function!((RTC_GPIO7, GPIO7)); + _for_each_inner_lp_function!((RTC_GPIO8, GPIO8)); + _for_each_inner_lp_function!((RTC_GPIO9, GPIO9)); + _for_each_inner_lp_function!((RTC_GPIO10, GPIO10)); + _for_each_inner_lp_function!((RTC_GPIO11, GPIO11)); + _for_each_inner_lp_function!((RTC_GPIO12, GPIO12)); + _for_each_inner_lp_function!((RTC_GPIO13, GPIO13)); + _for_each_inner_lp_function!((RTC_GPIO14, GPIO14)); + _for_each_inner_lp_function!((RTC_GPIO15, GPIO15)); + _for_each_inner_lp_function!((RTC_GPIO16, GPIO16)); + _for_each_inner_lp_function!((RTC_GPIO17, GPIO17)); + _for_each_inner_lp_function!((RTC_GPIO18, GPIO18)); + _for_each_inner_lp_function!((RTC_GPIO19, GPIO19)); + _for_each_inner_lp_function!((RTC_GPIO20, GPIO20)); + _for_each_inner_lp_function!((RTC_GPIO21, GPIO21)); + _for_each_inner_lp_function!(((RTC_GPIO0, RTC_GPIOn, 0), GPIO0)); + _for_each_inner_lp_function!(((SAR_I2C_SCL_0, SAR_I2C_SCL_n, 0), GPIO0)); + _for_each_inner_lp_function!(((RTC_GPIO1, RTC_GPIOn, 1), GPIO1)); + _for_each_inner_lp_function!(((SAR_I2C_SDA_0, SAR_I2C_SDA_n, 0), GPIO1)); + _for_each_inner_lp_function!(((RTC_GPIO2, RTC_GPIOn, 2), GPIO2)); + _for_each_inner_lp_function!(((SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1), GPIO2)); + _for_each_inner_lp_function!(((RTC_GPIO3, RTC_GPIOn, 3), GPIO3)); + _for_each_inner_lp_function!(((SAR_I2C_SDA_1, SAR_I2C_SDA_n, 1), GPIO3)); + _for_each_inner_lp_function!(((RTC_GPIO4, RTC_GPIOn, 4), GPIO4)); + _for_each_inner_lp_function!(((RTC_GPIO5, RTC_GPIOn, 5), GPIO5)); + _for_each_inner_lp_function!(((RTC_GPIO6, RTC_GPIOn, 6), GPIO6)); + _for_each_inner_lp_function!(((RTC_GPIO7, RTC_GPIOn, 7), GPIO7)); + _for_each_inner_lp_function!(((RTC_GPIO8, RTC_GPIOn, 8), GPIO8)); + _for_each_inner_lp_function!(((RTC_GPIO9, RTC_GPIOn, 9), GPIO9)); + _for_each_inner_lp_function!(((RTC_GPIO10, RTC_GPIOn, 10), GPIO10)); + _for_each_inner_lp_function!(((RTC_GPIO11, RTC_GPIOn, 11), GPIO11)); + _for_each_inner_lp_function!(((RTC_GPIO12, RTC_GPIOn, 12), GPIO12)); + _for_each_inner_lp_function!(((RTC_GPIO13, RTC_GPIOn, 13), GPIO13)); + _for_each_inner_lp_function!(((RTC_GPIO14, RTC_GPIOn, 14), GPIO14)); + _for_each_inner_lp_function!(((RTC_GPIO15, RTC_GPIOn, 15), GPIO15)); + _for_each_inner_lp_function!(((RTC_GPIO16, RTC_GPIOn, 16), GPIO16)); + _for_each_inner_lp_function!(((RTC_GPIO17, RTC_GPIOn, 17), GPIO17)); + _for_each_inner_lp_function!(((RTC_GPIO18, RTC_GPIOn, 18), GPIO18)); + _for_each_inner_lp_function!(((RTC_GPIO19, RTC_GPIOn, 19), GPIO19)); + _for_each_inner_lp_function!(((RTC_GPIO20, RTC_GPIOn, 20), GPIO20)); + _for_each_inner_lp_function!(((RTC_GPIO21, RTC_GPIOn, 21), GPIO21)); + _for_each_inner_lp_function!((all(RTC_GPIO0, GPIO0), (SAR_I2C_SCL_0, GPIO0), + (RTC_GPIO1, GPIO1), (SAR_I2C_SDA_0, GPIO1), (RTC_GPIO2, GPIO2), (SAR_I2C_SCL_1, + GPIO2), (RTC_GPIO3, GPIO3), (SAR_I2C_SDA_1, GPIO3), (RTC_GPIO4, GPIO4), + (RTC_GPIO5, GPIO5), (RTC_GPIO6, GPIO6), (RTC_GPIO7, GPIO7), (RTC_GPIO8, GPIO8), + (RTC_GPIO9, GPIO9), (RTC_GPIO10, GPIO10), (RTC_GPIO11, GPIO11), (RTC_GPIO12, + GPIO12), (RTC_GPIO13, GPIO13), (RTC_GPIO14, GPIO14), (RTC_GPIO15, GPIO15), + (RTC_GPIO16, GPIO16), (RTC_GPIO17, GPIO17), (RTC_GPIO18, GPIO18), (RTC_GPIO19, + GPIO19), (RTC_GPIO20, GPIO20), (RTC_GPIO21, GPIO21))); + _for_each_inner_lp_function!((all_expanded((RTC_GPIO0, RTC_GPIOn, 0), GPIO0), + ((SAR_I2C_SCL_0, SAR_I2C_SCL_n, 0), GPIO0), ((RTC_GPIO1, RTC_GPIOn, 1), GPIO1), + ((SAR_I2C_SDA_0, SAR_I2C_SDA_n, 0), GPIO1), ((RTC_GPIO2, RTC_GPIOn, 2), GPIO2), + ((SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1), GPIO2), ((RTC_GPIO3, RTC_GPIOn, 3), GPIO3), + ((SAR_I2C_SDA_1, SAR_I2C_SDA_n, 1), GPIO3), ((RTC_GPIO4, RTC_GPIOn, 4), GPIO4), + ((RTC_GPIO5, RTC_GPIOn, 5), GPIO5), ((RTC_GPIO6, RTC_GPIOn, 6), GPIO6), + ((RTC_GPIO7, RTC_GPIOn, 7), GPIO7), ((RTC_GPIO8, RTC_GPIOn, 8), GPIO8), + ((RTC_GPIO9, RTC_GPIOn, 9), GPIO9), ((RTC_GPIO10, RTC_GPIOn, 10), GPIO10), + ((RTC_GPIO11, RTC_GPIOn, 11), GPIO11), ((RTC_GPIO12, RTC_GPIOn, 12), GPIO12), + ((RTC_GPIO13, RTC_GPIOn, 13), GPIO13), ((RTC_GPIO14, RTC_GPIOn, 14), GPIO14), + ((RTC_GPIO15, RTC_GPIOn, 15), GPIO15), ((RTC_GPIO16, RTC_GPIOn, 16), GPIO16), + ((RTC_GPIO17, RTC_GPIOn, 17), GPIO17), ((RTC_GPIO18, RTC_GPIOn, 18), GPIO18), + ((RTC_GPIO19, RTC_GPIOn, 19), GPIO19), ((RTC_GPIO20, RTC_GPIOn, 20), GPIO20), + ((RTC_GPIO21, RTC_GPIOn, 21), GPIO21))); + }; +} +/// Defines the `InputSignal` and `OutputSignal` enums. +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_signals { + () => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum InputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPID4 = 7, + SPID5 = 8, + SPID6 = 9, + SPID7 = 10, + SPIDQS = 11, + U0RXD = 12, + U0CTS = 13, + U0DSR = 14, + U1RXD = 15, + U1CTS = 16, + U1DSR = 17, + U2RXD = 18, + U2CTS = 19, + U2DSR = 20, + I2S1_MCLK = 21, + I2S0O_BCK = 22, + I2S0_MCLK = 23, + I2S0O_WS = 24, + I2S0I_SD = 25, + I2S0I_BCK = 26, + I2S0I_WS = 27, + I2S1O_BCK = 28, + I2S1O_WS = 29, + I2S1I_SD = 30, + I2S1I_BCK = 31, + I2S1I_WS = 32, + PCNT0_SIG_CH0 = 33, + PCNT0_SIG_CH1 = 34, + PCNT0_CTRL_CH0 = 35, + PCNT0_CTRL_CH1 = 36, + PCNT1_SIG_CH0 = 37, + PCNT1_SIG_CH1 = 38, + PCNT1_CTRL_CH0 = 39, + PCNT1_CTRL_CH1 = 40, + PCNT2_SIG_CH0 = 41, + PCNT2_SIG_CH1 = 42, + PCNT2_CTRL_CH0 = 43, + PCNT2_CTRL_CH1 = 44, + PCNT3_SIG_CH0 = 45, + PCNT3_SIG_CH1 = 46, + PCNT3_CTRL_CH0 = 47, + PCNT3_CTRL_CH1 = 48, + I2S0I_SD1 = 51, + I2S0I_SD2 = 52, + I2S0I_SD3 = 53, + CORE1_GPIO7 = 54, + USB_EXTPHY_VP = 55, + USB_EXTPHY_VM = 56, + USB_EXTPHY_RCV = 57, + USB_OTG_IDDIG = 58, + USB_OTG_AVALID = 59, + USB_SRP_BVALID = 60, + USB_OTG_VBUSVALID = 61, + USB_SRP_SESSEND = 62, + SPI3_CLK = 66, + SPI3_Q = 67, + SPI3_D = 68, + SPI3_HD = 69, + SPI3_WP = 70, + SPI3_CS0 = 71, + RMT_SIG_0 = 81, + RMT_SIG_1 = 82, + RMT_SIG_2 = 83, + RMT_SIG_3 = 84, + I2CEXT0_SCL = 89, + I2CEXT0_SDA = 90, + I2CEXT1_SCL = 91, + I2CEXT1_SDA = 92, + FSPICLK = 101, + FSPIQ = 102, + FSPID = 103, + FSPIHD = 104, + FSPIWP = 105, + FSPIIO4 = 106, + FSPIIO5 = 107, + FSPIIO6 = 108, + FSPIIO7 = 109, + FSPICS0 = 110, + TWAI_RX = 116, + SUBSPIQ = 120, + SUBSPID = 121, + SUBSPIHD = 122, + SUBSPIWP = 123, + CORE1_GPIO0 = 129, + CORE1_GPIO1 = 130, + CORE1_GPIO2 = 131, + CAM_DATA_0 = 133, + CAM_DATA_1 = 134, + CAM_DATA_2 = 135, + CAM_DATA_3 = 136, + CAM_DATA_4 = 137, + CAM_DATA_5 = 138, + CAM_DATA_6 = 139, + CAM_DATA_7 = 140, + CAM_DATA_8 = 141, + CAM_DATA_9 = 142, + CAM_DATA_10 = 143, + CAM_DATA_11 = 144, + CAM_DATA_12 = 145, + CAM_DATA_13 = 146, + CAM_DATA_14 = 147, + CAM_DATA_15 = 148, + CAM_PCLK = 149, + CAM_H_ENABLE = 150, + CAM_H_SYNC = 151, + CAM_V_SYNC = 152, + SUBSPID4 = 155, + SUBSPID5 = 156, + SUBSPID6 = 157, + SUBSPID7 = 158, + SUBSPIDQS = 159, + PWM0_SYNC0 = 160, + PWM0_SYNC1 = 161, + PWM0_SYNC2 = 162, + PWM0_F0 = 163, + PWM0_F1 = 164, + PWM0_F2 = 165, + PWM0_CAP0 = 166, + PWM0_CAP1 = 167, + PWM0_CAP2 = 168, + PWM1_SYNC0 = 169, + PWM1_SYNC1 = 170, + PWM1_SYNC2 = 171, + PWM1_F0 = 172, + PWM1_F1 = 173, + PWM1_F2 = 174, + PWM1_CAP0 = 175, + PWM1_CAP1 = 176, + PWM1_CAP2 = 177, + SDHOST_CCMD_IN_1 = 178, + SDHOST_CCMD_IN_2 = 179, + SDHOST_CDATA_IN_10 = 180, + SDHOST_CDATA_IN_11 = 181, + SDHOST_CDATA_IN_12 = 182, + SDHOST_CDATA_IN_13 = 183, + SDHOST_CDATA_IN_14 = 184, + SDHOST_CDATA_IN_15 = 185, + SDHOST_CDATA_IN_16 = 186, + SDHOST_CDATA_IN_17 = 187, + SDHOST_DATA_STROBE_1 = 192, + SDHOST_DATA_STROBE_2 = 193, + SDHOST_CARD_DETECT_N_1 = 194, + SDHOST_CARD_DETECT_N_2 = 195, + SDHOST_CARD_WRITE_PRT_1 = 196, + SDHOST_CARD_WRITE_PRT_2 = 197, + SDHOST_CARD_INT_N_1 = 198, + SDHOST_CARD_INT_N_2 = 199, + SDHOST_CDATA_IN_20 = 213, + SDHOST_CDATA_IN_21 = 214, + SDHOST_CDATA_IN_22 = 215, + SDHOST_CDATA_IN_23 = 216, + SDHOST_CDATA_IN_24 = 217, + SDHOST_CDATA_IN_25 = 218, + SDHOST_CDATA_IN_26 = 219, + SDHOST_CDATA_IN_27 = 220, + PRO_ALONEGPIO0 = 221, + PRO_ALONEGPIO1 = 222, + PRO_ALONEGPIO2 = 223, + PRO_ALONEGPIO3 = 224, + PRO_ALONEGPIO4 = 225, + PRO_ALONEGPIO5 = 226, + PRO_ALONEGPIO6 = 227, + PRO_ALONEGPIO7 = 228, + USB_JTAG_TDO_BRIDGE = 251, + CORE1_GPIO3 = 252, + CORE1_GPIO4 = 253, + CORE1_GPIO5 = 254, + CORE1_GPIO6 = 255, + SPIIO4, + SPIIO5, + SPIIO6, + SPIIO7, + MTDI, + MTCK, + MTMS, + } + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum OutputSignal { + SPIQ = 0, + SPID = 1, + SPIHD = 2, + SPIWP = 3, + SPICLK = 4, + SPICS0 = 5, + SPICS1 = 6, + SPID4 = 7, + SPID5 = 8, + SPID6 = 9, + SPID7 = 10, + SPIDQS = 11, + U0TXD = 12, + U0RTS = 13, + U0DTR = 14, + U1TXD = 15, + U1RTS = 16, + U1DTR = 17, + U2TXD = 18, + U2RTS = 19, + U2DTR = 20, + I2S1_MCLK = 21, + I2S0O_BCK = 22, + I2S0_MCLK = 23, + I2S0O_WS = 24, + I2S0O_SD = 25, + I2S0I_BCK = 26, + I2S0I_WS = 27, + I2S1O_BCK = 28, + I2S1O_WS = 29, + I2S1O_SD = 30, + I2S1I_BCK = 31, + I2S1I_WS = 32, + CORE1_GPIO7 = 54, + USB_EXTPHY_OEN = 55, + USB_EXTPHY_VPO = 57, + USB_EXTPHY_VMO = 58, + SPI3_CLK = 66, + SPI3_Q = 67, + SPI3_D = 68, + SPI3_HD = 69, + SPI3_WP = 70, + SPI3_CS0 = 71, + SPI3_CS1 = 72, + LEDC_LS_SIG0 = 73, + LEDC_LS_SIG1 = 74, + LEDC_LS_SIG2 = 75, + LEDC_LS_SIG3 = 76, + LEDC_LS_SIG4 = 77, + LEDC_LS_SIG5 = 78, + LEDC_LS_SIG6 = 79, + LEDC_LS_SIG7 = 80, + RMT_SIG_0 = 81, + RMT_SIG_1 = 82, + RMT_SIG_2 = 83, + RMT_SIG_3 = 84, + I2CEXT0_SCL = 89, + I2CEXT0_SDA = 90, + I2CEXT1_SCL = 91, + I2CEXT1_SDA = 92, + GPIO_SD0 = 93, + GPIO_SD1 = 94, + GPIO_SD2 = 95, + GPIO_SD3 = 96, + GPIO_SD4 = 97, + GPIO_SD5 = 98, + GPIO_SD6 = 99, + GPIO_SD7 = 100, + FSPICLK = 101, + FSPIQ = 102, + FSPID = 103, + FSPIHD = 104, + FSPIWP = 105, + FSPIIO4 = 106, + FSPIIO5 = 107, + FSPIIO6 = 108, + FSPIIO7 = 109, + FSPICS0 = 110, + FSPICS1 = 111, + FSPICS2 = 112, + FSPICS3 = 113, + FSPICS4 = 114, + FSPICS5 = 115, + TWAI_TX = 116, + SUBSPICLK = 119, + SUBSPIQ = 120, + SUBSPID = 121, + SUBSPIHD = 122, + SUBSPIWP = 123, + SUBSPICS0 = 124, + SUBSPICS1 = 125, + FSPIDQS = 126, + SPI3_CS2 = 127, + I2S0O_SD1 = 128, + CORE1_GPIO0 = 129, + CORE1_GPIO1 = 130, + CORE1_GPIO2 = 131, + LCD_CS = 132, + LCD_DATA_0 = 133, + LCD_DATA_1 = 134, + LCD_DATA_2 = 135, + LCD_DATA_3 = 136, + LCD_DATA_4 = 137, + LCD_DATA_5 = 138, + LCD_DATA_6 = 139, + LCD_DATA_7 = 140, + LCD_DATA_8 = 141, + LCD_DATA_9 = 142, + LCD_DATA_10 = 143, + LCD_DATA_11 = 144, + LCD_DATA_12 = 145, + LCD_DATA_13 = 146, + LCD_DATA_14 = 147, + LCD_DATA_15 = 148, + CAM_CLK = 149, + LCD_H_ENABLE = 150, + LCD_H_SYNC = 151, + LCD_V_SYNC = 152, + LCD_DC = 153, + LCD_PCLK = 154, + SUBSPID4 = 155, + SUBSPID5 = 156, + SUBSPID6 = 157, + SUBSPID7 = 158, + SUBSPIDQS = 159, + PWM0_0A = 160, + PWM0_0B = 161, + PWM0_1A = 162, + PWM0_1B = 163, + PWM0_2A = 164, + PWM0_2B = 165, + PWM1_0A = 166, + PWM1_0B = 167, + PWM1_1A = 168, + PWM1_1B = 169, + PWM1_2A = 170, + PWM1_2B = 171, + SDHOST_CCLK_OUT_1 = 172, + SDHOST_CCLK_OUT_2 = 173, + SDHOST_RST_N_1 = 174, + SDHOST_RST_N_2 = 175, + SDHOST_CCMD_OD_PULLUP_EN_N = 176, + SDIO_TOHOST_INT = 177, + SDHOST_CCMD_OUT_1 = 178, + SDHOST_CCMD_OUT_2 = 179, + SDHOST_CDATA_OUT_10 = 180, + SDHOST_CDATA_OUT_11 = 181, + SDHOST_CDATA_OUT_12 = 182, + SDHOST_CDATA_OUT_13 = 183, + SDHOST_CDATA_OUT_14 = 184, + SDHOST_CDATA_OUT_15 = 185, + SDHOST_CDATA_OUT_16 = 186, + SDHOST_CDATA_OUT_17 = 187, + SDHOST_CDATA_OUT_20 = 213, + SDHOST_CDATA_OUT_21 = 214, + SDHOST_CDATA_OUT_22 = 215, + SDHOST_CDATA_OUT_23 = 216, + SDHOST_CDATA_OUT_24 = 217, + SDHOST_CDATA_OUT_25 = 218, + SDHOST_CDATA_OUT_26 = 219, + SDHOST_CDATA_OUT_27 = 220, + PRO_ALONEGPIO0 = 221, + PRO_ALONEGPIO1 = 222, + PRO_ALONEGPIO2 = 223, + PRO_ALONEGPIO3 = 224, + PRO_ALONEGPIO4 = 225, + PRO_ALONEGPIO5 = 226, + PRO_ALONEGPIO6 = 227, + PRO_ALONEGPIO7 = 228, + USB_JTAG_TRST = 251, + CORE1_GPIO3 = 252, + CORE1_GPIO4 = 253, + CORE1_GPIO5 = 254, + CORE1_GPIO6 = 255, + GPIO = 256, + SPIIO4, + SPIIO5, + SPIIO6, + SPIIO7, + CLK_OUT1, + CLK_OUT2, + CLK_OUT3, + SPICLK_P_DIFF, + SPICLK_N_DIFF, + SUBSPICLK_P_DIFF, + SUBSPICLK_N_DIFF, + MTDO, + } + }; +} +/// Defines and implements the `io_mux_reg` function. +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { +/// // ... +/// # unimplemented!() +/// } +/// ``` +/// +/// This macro is intended to be called in esp-hal only. +#[macro_export] +#[expect(clippy::crate_in_macro_def)] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! define_io_mux_reg { + () => { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + }; +} diff --git a/esp-metadata-generated/src/lib.rs b/esp-metadata-generated/src/lib.rs new file mode 100644 index 00000000000..c835c054115 --- /dev/null +++ b/esp-metadata-generated/src/lib.rs @@ -0,0 +1,119 @@ +// Do NOT edit this file directly. Make your changes to esp-metadata, +// then run `cargo xtask update-metadata`. + +//! # (Generated) metadata for Espressif MCUs. +//! +//! This crate provides properties that are specific to various Espressif microcontrollers, +//! and provides macros to work with peripherals, pins, and various other parts of the chips. +//! +//! This crate can be used both in firmware, as well as in build scripts, but the usage is +//! different. +//! +//! ## Usage in build scripts +//! +//! To use the `Chip` enum, add the crate to your `Cargo.toml` build +//! dependencies, with the `build-script` feature: +//! +//! ```toml +//! [build-dependencies] +//! esp-metadata-generated = { version = "...", features = ["build-script"] } +//! ``` +//! +//! ## Usage in firmware +//! +//! To use the various macros, add the crate to your `Cargo.toml` dependencies. +//! A device-specific feature needs to be enabled in order to use the crate, usually +//! picked by the user: +//! +//! ```toml +//! [dependencies] +//! esp-metadata-generated = { version = "..." } +//! # ... +//! +//! [features] +//! esp32 = ["esp-metadata-generated/esp32"] +//! esp32c2 = ["esp-metadata-generated/esp32c2"] +//! # ... +//! ``` +//! +//! ## `for_each` macros +//! +//! The basic syntax of this macro looks like a macro definition with two distinct syntax options: +//! +//! ```rust, no_run +//! for_each_peripherals! { +//! // Individual matcher, invoked separately for each peripheral instance +//! ( ) => { /* some code */ }; +//! +//! // Repeated matcher, invoked once with all peripheral instances +//! ( all $( () ),* ) => { /* some code */ }; +//! } +//! ``` +//! +//! You can specify any number of matchers in the same invocation. +//! +//! > The way code is generated, you will need to use the full `return` syntax to return any +//! > values from code generated with these macros. +//! +//! ### Using the individual matcher +//! +//! In this use case, each item's data is individually passed through the macro. This can be used to +//! generate code for each item separately, allowing specializing the implementation where needed. +//! +//! ```rust,no_run +//! for_each_gpio! { +//! // Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))` +//! ($n:literal, $gpio:ident ($($digital_input_function:ident => $digital_input_signal:ident)*) ($($digital_output_function:ident => $digital_output_signal:ident)*) ($($pin_attribute:ident)*)) => { /* some code */ }; +//! +//! // You can create matchers with data filled in. This example will specifically match GPIO2 +//! ($n:literal, GPIO2 $input_af:tt $output_af:tt $attributes:tt) => { /* Additional case only for GPIO2 */ }; +//! } +//! ``` +//! +//! Different macros can have multiple different syntax options for their individual matchers, +//! usually to provide more detailed information, while preserving simpler syntax for more basic use +//! cases. Consult each macro's documentation for available options. +//! +//! ### Repeated matcher +//! +//! With this option, all data is passed through the macro all at once. This form can be used to, +//! for example, generate struct fields. If the macro has multiple individual matcher options, +//! there are separate repeated matchers for each of the options. +//! +//! To use this option, start the match pattern with the name of the individual matcher option. When +//! there is only a single individual matcher option, its repeated matcher is named `all` unless +//! otherwise specified by the macro. +//! +//! ```rust,no_run +//! // Example usage to create a struct containing all GPIOs: +//! for_each_gpio! { +//! (all $( ($n:literal, $gpio:ident $_af_ins:tt $_af_outs:tt $_attrs:tt) ),*) => { +//! struct Gpios { +//! $( +//! #[doc = concat!(" The ", stringify!($n), "th GPIO pin")] +//! pub $gpio: Gpio<$n>, +//! )* +//! } +//! }; +//! } +//! ``` +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(not(feature = "build-script"), no_std)] +#[cfg(feature = "esp32")] +include!("_generated_esp32.rs"); +#[cfg(feature = "esp32c2")] +include!("_generated_esp32c2.rs"); +#[cfg(feature = "esp32c3")] +include!("_generated_esp32c3.rs"); +#[cfg(feature = "esp32c5")] +include!("_generated_esp32c5.rs"); +#[cfg(feature = "esp32c6")] +include!("_generated_esp32c6.rs"); +#[cfg(feature = "esp32h2")] +include!("_generated_esp32h2.rs"); +#[cfg(feature = "esp32s2")] +include!("_generated_esp32s2.rs"); +#[cfg(feature = "esp32s3")] +include!("_generated_esp32s3.rs"); +#[cfg(any(feature = "build-script", docsrs))] +include!("_build_script_utils.rs"); diff --git a/esp-metadata/CHANGELOG.md b/esp-metadata/CHANGELOG.md new file mode 100644 index 00000000000..f23e7446fc3 --- /dev/null +++ b/esp-metadata/CHANGELOG.md @@ -0,0 +1,97 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Clock tree modeling (#4501) +- Encode pin restrictions in the generated data (#4790) +- Initial ESP32-C5 support (#4859, #4866) +- DMA properties and peripheral selector values (#4944) + +### Changed + +- Symbols generated from driver names are now suffixed by `_driver_supported` (#4929) + +### Fixed + + +### Removed + +- `Config` no longer implements `serde::Serialize` (#4464) + +## [v0.10.0] - 2025-10-30 + +## [v0.9.0] - 2025-10-13 + +### Fixed + +- Output: Include firmware-side metadata macros even if the build-script feature is set. (#3906) + +## [v0.8.0] - 2025-07-16 + +### Added + +- Added `Config::generate_metadata` to generate code for firmware crates. (#3604) + +### Removed + +- Removed the firmware-side component of the crate. (#3604) + +## [v0.7.0] - 2025-06-03 + +### Added + +- Add ability to define memory regions, have DRAM defined there (#3300) +- Provide macros to get the start/end of a memory region, make it possible to use the macros in a no-std project (#3300) + +### Changed + +- Bump Rust edition to 2024, bump MSRV to 1.86. (#3391, #3560) + +## [0.6.0] - 2025-02-24 + +### Added + +- Introduced the `adc1` and `adc2` symbols (#3082) + +### Removed + +- Removed the `adc` symbol (#3082) + +## 0.5.0 - 2025-01-15 + +### Added + +- Introduced the `wifi6` symbol (#2612) +- Introduced the `gpio_bank_1` symbol (#2625) + +### Changed + +- Bump MSRV to 1.84 (#2951) + +## 0.4.0 - 2024-10-10 + +## 0.3.0 - 2024-08-29 + +## 0.2.0 - 2024-07-15 + +## 0.1.1 - 2024-06-04 + +## 0.1.0 - 2024-04-17 + +### Added + +- Initial release (#2518) + +[0.6.0]: https://github.com/esp-rs/esp-hal/releases/tag/esp-metadata-v0.6.0 +[v0.7.0]: https://github.com/esp-rs/esp-hal/compare/esp-metadata-v0.6.0...esp-metadata-v0.7.0 +[v0.8.0]: https://github.com/esp-rs/esp-hal/compare/esp-metadata-v0.7.0...esp-metadata-v0.8.0 +[v0.9.0]: https://github.com/esp-rs/esp-hal/compare/esp-metadata-v0.8.0...esp-metadata-v0.9.0 +[v0.10.0]: https://github.com/esp-rs/esp-hal/compare/esp-metadata-v0.9.0...esp-metadata-v0.10.0 +[Unreleased]: https://github.com/esp-rs/esp-hal/compare/esp-metadata-v0.10.0...HEAD diff --git a/esp-metadata/Cargo.toml b/esp-metadata/Cargo.toml new file mode 100644 index 00000000000..d4d6387365c --- /dev/null +++ b/esp-metadata/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "esp-metadata" +version = "0.10.0" +edition = "2024" +rust-version = "1.86.0" +description = "Metadata for Espressif devices" +documentation = "https://docs.espressif.com/projects/rust/esp-metadata/latest/" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.espressif] +forever-unstable = true +check-configs = [ + { features = [] }, + { features = ["clap"] }, +] +clippy-configs = [] # don't waste time on this + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"], optional = true } +convert_case = "0.8" +basic-toml = "0.1" +serde = { version = "1.0", default-features = false, features = ["derive"] } +somni-parser = "0.2.1" +somni-expr = "0.2" +strum = { version = "0.27", features = ["derive"] } +proc-macro2 = "1" +quote = "1" +indexmap = { version = "2", features = ["serde"] } + +[features] +default = [] +clap = ["dep:clap"] diff --git a/esp-metadata/README.md b/esp-metadata/README.md new file mode 100644 index 00000000000..bc6815b70d7 --- /dev/null +++ b/esp-metadata/README.md @@ -0,0 +1,34 @@ +# esp-metadata + +[![Crates.io](https://img.shields.io/crates/v/esp-metadata?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-metadata) +[![docs.rs](https://img.shields.io/docsrs/esp-metadata?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.espressif.com/projects/rust/esp-metadata/latest/) +![MSRV](https://img.shields.io/badge/MSRV-1.86.0-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-metadata?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +Metadata for Espressif devices, intended for use in [build scripts]. + +Firmware crates are meant to depend on `esp-metadata-generated`, not on this crate directly. To update `esp-metadata-generated`, make your changes in `esp-metadata`, then run `cargo xtask update-metadata`. + +[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html + +## [Documentation](https://docs.espressif.com/projects/rust/esp-metadata/latest/) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile when using the latest stable Rust version at the time of the crate's release. It _might_ compile with older versions, but that may change in any new release, including patches. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-metadata/devices/esp32.toml b/esp-metadata/devices/esp32.toml new file mode 100644 index 00000000000..aa156715924 --- /dev/null +++ b/esp-metadata/devices/esp32.toml @@ -0,0 +1,906 @@ +# ESP32 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32" +arch = "xtensa" +target = "xtensa-esp32-none-elf" +cores = 2 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES" }, + { name = "APB_CTRL" }, + { name = "BB" }, + { name = "DPORT" }, + { name = "SYSTEM", pac = "DPORT" }, + { name = "EFUSE", hidden = true }, + { name = "EMAC_DMA" }, + { name = "EMAC_EXT" }, + { name = "EMAC_MAC" }, + { name = "FLASH_ENCRYPTION" }, + { name = "FRC_TIMER" }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HINF" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2C1", interrupts = { peri = "I2C_EXT1" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 0 }, + { name = "I2S1", interrupts = { peri = "I2S1" }, dma_peripheral = 1 }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "MCPWM0" }, + { name = "MCPWM1" }, + { name = "NRX" }, + { name = "PCNT" }, + { name = "RMT" }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "LPWR", pac = "RTC_CNTL" }, + { name = "RTC_I2C" }, + { name = "RTC_IO" }, + { name = "SDHOST" }, + { name = "SENS" }, + { name = "SHA" }, + { name = "SLC" }, + { name = "SLCHOST" }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2", dma = "SPI2_DMA" }, dma_peripheral = 2, stable = true }, + { name = "SPI3", interrupts = { peri = "SPI3", dma = "SPI3_DMA" }, dma_peripheral = 3, stable = true }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TWAI0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UART2", interrupts = { peri = "UART2" }, stable = true }, + { name = "UHCI0", dma_peripheral = 4 }, + { name = "UHCI1", dma_peripheral = 5 }, + { name = "WIFI", interrupts = { mac = "WIFI_MAC" }, stable = true }, + + { name = "DMA_SPI2", pac = "SPI2" }, + { name = "DMA_SPI3", pac = "SPI3" }, + { name = "DMA_I2S0", pac = "I2S0" }, + { name = "DMA_I2S1", pac = "I2S1" }, + + { name = "ADC1", virtual = true }, + { name = "ADC2", virtual = true }, + { name = "BT", virtual = true, interrupts = { rwbt = "RWBT", rwble = "RWBLE", bb = "BT_BB" } }, + { name = "CPU_CTRL", virtual = true }, + { name = "DAC1", virtual = true }, + { name = "DAC2", virtual = true }, + { name = "FLASH", virtual = true }, + { name = "PSRAM", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "TOUCH", virtual = true }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "psram", + "touch", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + + # Wakeup SOC based on ESP-IDF: + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "ulp_supported", +] + +[device.soc] +multi_core_enabled = true +rc_fast_clk_default = 8_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x3FFA_E000, end = 0x4000_0000 }, + { name = "dram2_uninit", start = 0x3FFE_7E30, end = 0x4000_0000 }, +] } + + + +# PeripheralClockControl templates and peripheral clock signals. +# +# Clock control code is generated by recursively substituting fields into the `clk_en_template` and `rst_template` templates. +# Each peripheral clock can overwrite any of the default fields, including the templates. +# +# `clocks` in a peripheral clock definition can either be a different `peripheral_clocks` entry (string), or a new definition (array) that uses the same syntax as `system_clocks` above. +clocks = { system_clocks = { clock_tree = [ + { name = "XTAL_CLK", type = "source", values = "26, 40", output = "VALUE * 1_000_000", always_on = true }, # 2-40MHz, always on? sw ability to measure using an internal RC clock + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", values = "320, 480", output = "VALUE * 1_000_000", reject = "VALUE == 480_000_000 && CPU_PLL_DIV == 4" }, + { name = "APLL_CLK", type = "derived", from = "PLL_CLK", values = "16_000_000 ..= 128_000_000", output = "VALUE" }, + { name = "RC_FAST_CLK", type = "source", output = "8_000_000" }, # this is supposed to be adjustable using RTC_CNTL_CK8M_DIV_SEL + { name = "PLL_F160M_CLK", type = "derived", from = "PLL_CLK", output = "160_000_000" }, + + # CPU and related clocks + # + # Not all elements here correspond 1:1 to the hardware, these are more logical representations of the clock tree. + # + # `source` is a clock source. + # `mux` is a multiplexer that outputs one of several input signals. May cause additional configurations to be applied. + # - `variants` describe the different options available for the multiplexer. + # - `outputs` is the name of the clock signal that will be output. Only a name is accepted, expressions are not supported. + # `divider` is a clock divider, either fixed or configurable. + # - `params` is a map that lists the possible values for variables in the output expression. + # `variable = "from .. to(inclusive)"` or `variable = "option, option, option"` + # - `output` is an expression describing how the output frequency is calculated. `divisor` is the divisor value. + # `derived` is a custom element that depends on another clock signal. + # - `from` describes a dependency on another clock signal, if the output expression does not indicate it. + # - `output` is an expression describing how the output frequency is calculated. + # + # Each item may have a `reject` list of conditions to validate the clock tree config. + # + # Free parameters: + # - value of XTAL_CLK (can be fixed or "measured") + # - value of PLL_CLK (320/480) + # - value of APLL_CLK (target frequency, maybe?) + # - value of SYSCON_PRE_DIV (divisor) + # - value of CPU_PLL_DIV (divisor) + # - value of CPU_CLK (XTAL, FOSC, PLL, APLL) + # - RTC_SLOW_CLK mux + # - RTC_FAST_CLK mux + # - LOW_POWER_CLK mux + # + # In the clock config, everything is optional. The setup functions should select suitable defaults. + # The HAL is responsible for calling the right setup functions for root clocks (e.g. CPU_CLK), but dependencies are configured automatically. + # + # Clock configuration should be fallible: + # - Clocks should not be changed while in use. This should be enforced externally, as we need to set the CPU clock source and the CPU is always in use. + # - Validation errors should be reported to the user. + # + # Based on this data, the following code will be generated: + # - A clock configuration struct, containing the free parameters. + # - A struct for each node with a `configure` and `shut_down` method. + # - These methods will contain code to propagate the configuration to other nodes. + # - These methods will contain calls to `configure_impl` and `shut_down_impl`, which need to be implemented in the HAL. + # These methods take a type-specific value, as well as a reference to the clock configuration struct to observe other options. + # - Reference counting code to only call `configure_impl` and `shut_down_impl` when actually needed. + # But not every node needs a separate refcount. + + # DIVA Divisor for high-speed clock sources (PLL, APLL) + { name = "CPU_PLL_DIV_IN", type = "mux", variants = [ + { name = "PLL", outputs = "PLL_CLK" }, + { name = "APLL", outputs = "APLL_CLK" }, + ] }, + { name = "CPU_PLL_DIV", type = "divider", params = { divisor = "2, 4" }, output = "CPU_PLL_DIV_IN / divisor", reject = "PLL_CLK == 480_000_000 && divisor == 4" }, + + # DIVB Divisor for low-speed clock sources (XTAL, FOSC) + { name = "SYSCON_PRE_DIV_IN", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + { name = "SYSCON_PRE_DIV", type = "divider", params = { divisor = "0 .. 1024" }, output = "SYSCON_PRE_DIV_IN / (divisor + 1)" }, + + # The meta-switch + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + # source clock CPU clock additional config (mux = variant name, divider = divisor expression) + { name = "XTAL", outputs = "SYSCON_PRE_DIV", configures = [ "APB_CLK = CPU", "SYSCON_PRE_DIV_IN = XTAL", "REF_TICK = XTAL", "REF_TICK_XTAL = APB_CLK / 1_000_000 - 1" ] }, + { name = "RC_FAST", outputs = "SYSCON_PRE_DIV", configures = [ "APB_CLK = CPU", "SYSCON_PRE_DIV_IN = RC_FAST", "REF_TICK = FOSC", "REF_TICK_FOSC = APB_CLK / 1_000_000 - 1" ] }, + { name = "APLL", outputs = "CPU_PLL_DIV", configures = [ "APB_CLK = CPU_DIV2", "CPU_PLL_DIV_IN = APLL", "REF_TICK = APLL", "REF_TICK_APLL = APB_CLK / 1_000_000 - 1" ] }, + { name = "PLL", outputs = "CPU_PLL_DIV", configures = [ "APB_CLK = PLL_80M", "CPU_PLL_DIV_IN = PLL", "REF_TICK = PLL", "REF_TICK_PLL = APB_CLK / 1_000_000 - 1" ] }, + ] }, + + # APB options + { name = "APB_CLK_CPU_DIV2", type = "divider", output = "CPU_CLK / 2" }, + { name = "APB_CLK_80M", type = "derived", from = "CPU_CLK", output = "80_000_000" }, + { name = "APB_CLK", type = "mux", variants = [ + { name = "PLL_80M", outputs = "APB_CLK_80M" }, + { name = "CPU_DIV2", outputs = "APB_CLK_CPU_DIV2" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + + # REF_TICK, configuring CPU clock needs to ensure this is 1MHz + { name = "REF_TICK_PLL", type = "divider", params = { divisor = "0 .. 256" }, output = "APB_CLK / (divisor + 1)" }, + { name = "REF_TICK_APLL", type = "divider", params = { divisor = "0 .. 256" }, output = "APB_CLK / (divisor + 1)" }, + { name = "REF_TICK_XTAL", type = "divider", params = { divisor = "0 .. 256" }, output = "APB_CLK / (divisor + 1)" }, + { name = "REF_TICK_FOSC", type = "divider", params = { divisor = "0 .. 256" }, output = "APB_CLK / (divisor + 1)" }, + { name = "REF_TICK", type = "mux", variants = [ + { name = "PLL", outputs = "REF_TICK_PLL" }, + { name = "APLL", outputs = "REF_TICK_APLL" }, + { name = "XTAL", outputs = "REF_TICK_XTAL" }, + { name = "FOSC", outputs = "REF_TICK_FOSC" }, + ] }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "150_000" }, + { name = "RC_FAST_DIV_CLK", type = "divider", output = "RC_FAST_CLK / 256" }, + { name = "XTAL_DIV_CLK", type = "divider", output = "XTAL_CLK / 4" }, # source: esp-idf SOC_RTC_FAST_CLK_SRC_XTAL_D4 + + { name = "RTC_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_DIV_CLK" }, + ] }, + { name = "RTC_FAST_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_DIV_CLK" }, + { name = "RC", outputs = "RC_FAST_CLK" }, + ] }, + # Wireless low-power clock. Unsure how to configure this or if it's needed at all, esp-idf does not seem to use this MUX. + #{ name = "LOW_POWER_CLK", type = "mux", variants = [ + # { name = "XTAL", outputs = "XTAL_CLK" }, + # { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + # { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + # { name = "RTC_SLOW", outputs = "RTC_SLOW_CLK" }, + #] } + + # System-wide peripheral clocks + # LEDC_SCLK: The TRM is slightly confusing, there is a single mux in the peripheral selecting between APB and RTC_FAST. + { name = "UART_MEM_CLK", type = "mux", default = "XTAL", variants = [ + # The actual source clock is unknown, but it also doesn't matter. + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "clk_en_template", value = "{{control}}::regs().{{clk_en_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "rst_template", value = "{{control}}::regs().{{rst_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "reg_group", value = "perip" }, + { name = "clk_en_register", value = "{{reg_group}}_clk_en" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_register", value = "{{reg_group}}_rst_en" }, + { name = "rst_field", value = "{{peripheral}}_rst" }, + # {{peripheral}} is derived automatically (e.g. SpiDma -> spi_dma) +], peripheral_clocks = [ + # TODO: peripheral_clocks are implicit APB clock gates, we will need to tie them into the system to track APB use. + { name = "Aes", template_params = { reg_group = "peri", peripheral = "crypto_aes" } }, + { name = "Rsa", template_params = { reg_group = "peri", peripheral = "crypto_rsa" } }, + { name = "Sha", template_params = { reg_group = "peri", peripheral = "crypto_sha" } }, + + #{ name = "Emac", template_params = { reg_group = "wifi_clk" } }, + #{ name = "SdioHost", template_params = { reg_group = "wifi_clk" } }, + #{ name = "SdioSlave", template_params = { reg_group = "wifi_clk" } }, + + { name = "UartMem", keep_enabled = true }, # TODO: keep_enabled can be removed once esp-println needs explicit initialization + { name = "Uart2", clocks = "Uart0" }, + { name = "SpiDma" }, + { name = "I2s1" }, + { name = "Mcpwm1", template_params = { peripheral = "pwm1" }, clocks = "Mcpwm0" }, + { name = "Twai0", template_params = { peripheral = "twai" } }, + { name = "I2cExt1" }, + { name = "Mcpwm0", template_params = { peripheral = "pwm0" }, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "PLL_F160M", variants = [ + { name = "PLL_F160M", outputs = "PLL_F160M_CLK" } + ] } + ] }, + { name = "Spi3" }, + { name = "Timg1", template_params = { peripheral = "timergroup1" }, clocks = "Timg0" }, # same clock options, but different instance + #{ name = "Efuse" }, + { name = "Timg0", template_params = { peripheral = "timergroup" }, keep_enabled = true, clocks = [ + { name = "CALIBRATION_CLOCK", type = "mux", default = "RC_SLOW_CLK", variants = [ + { name = "RC_SLOW_CLK", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_DIV_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + ] }, + { name = "Uhci1" }, + { name = "Ledc" }, + { name = "Pcnt" }, + { name = "Rmt" }, + { name = "Uhci0" }, + { name = "I2cExt0" }, + { name = "Spi2" }, + { name = "Uart1", clocks = "Uart0" }, + { name = "I2s0" }, + { name = "Uart0", template_params = { peripheral = "uart" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "APB", variants = [ + { name = "APB", outputs = "APB_CLK" }, + { name = "REF_TICK", outputs = "REF_TICK" } + ] }, + { name = "MEM_CLOCK", type = "mux", default = "MEM", variants = [ + { name = "MEM", outputs = "UART_MEM_CLK" }, + ] }, + ] }, + #{ name = "Spi01" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, + { name = "adc2" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = false +endianness_configurable = true +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 192, encrypt_mode = 1, decrypt_mode = 5 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.dac] +support_status = "partial" +instances = [ + { name = "dac1" }, + { name = "dac2" }, +] + +[device.dma] +support_status = "partial" +kind = "pdma" +supports_mem2mem = false + +[device.gpio] +support_status = "supported" +has_bank_1 = true +gpio_function = 2 +constant_0_input = 0x30 +constant_1_input = 0x38 +remap_iomux_pin_registers = true +pins = [ + { pin = 0, functions = { 1 = "CLK_OUT1", 5 = "EMAC_TX_CLK" }, analog = { 1 = "ADC2_CH1", 2 = "TOUCH1" }, rtc = { 0 = "RTC_GPIO11", 1 = "SAR_I2C_SDA" }, limitations = ["strapping"] }, + { pin = 1, functions = { 0 = "U0TXD", 1 = "CLK_OUT3", 5 = "EMAC_RXD2" } }, + { pin = 2, functions = { 1 = "HSPIWP", 3 = "HS2_DATA0", 4 = "SD_DATA0" }, analog = { 1 = "ADC2_CH2", 2 = "TOUCH2" }, rtc = { 0 = "RTC_GPIO12", 1 = "SAR_I2C_SCL" }, limitations = ["strapping"] }, + { pin = 3, functions = { 0 = "U0RXD", 1 = "CLK_OUT2" } }, + { pin = 4, functions = { 1 = "HSPIHD", 3 = "HS2_DATA1", 4 = "SD_DATA1", 5 = "EMAC_TX_ER" }, analog = { 1 = "ADC2_CH0", 2 = "TOUCH0" }, rtc = { 0 = "RTC_GPIO10", 1 = "SAR_I2C_SCL" } }, + { pin = 5, functions = { 1 = "VSPICS0", 3 = "HS1_DATA6", 5 = "EMAC_RX_CLK" }, limitations = ["strapping"] }, + { pin = 6, functions = { 0 = "SD_CLK", 1 = "SPICLK", 3 = "HS1_CLK", 4 = "U1CTS" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 7, functions = { 0 = "SD_DATA0", 1 = "SPIQ", 3 = "HS1_DATA0", 4 = "U2RTS" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 8, functions = { 0 = "SD_DATA1", 1 = "SPID", 3 = "HS1_DATA1", 4 = "U2CTS" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 9, functions = { 0 = "SD_DATA2", 1 = "SPIHD", 3 = "HS1_DATA2", 4 = "U1RXD" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 10, functions = { 0 = "SD_DATA3", 1 = "SPIWP", 3 = "HS1_DATA3", 4 = "U1TXD" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 11, functions = { 0 = "SD_CMD", 1 = "SPICS0", 3 = "HS1_CMD", 4 = "U1RTS" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 12, functions = { 0 = "MTDI", 1 = "HSPIQ", 3 = "HS2_DATA2", 4 = "SD_DATA2", 5 = "EMAC_TXD3" }, analog = { 1 = "ADC2_CH5", 2 = "TOUCH5" }, rtc = { 0 = "RTC_GPIO15" }, limitations = ["strapping"] }, + { pin = 13, functions = { 0 = "MTCK", 1 = "HSPID", 3 = "HS2_DATA3", 4 = "SD_DATA3", 5 = "EMAC_RX_ER" }, analog = { 1 = "ADC2_CH4", 2 = "TOUCH4" }, rtc = { 0 = "RTC_GPIO14" } }, + { pin = 14, functions = { 0 = "MTMS", 1 = "HSPICLK", 3 = "HS2_CLK", 4 = "SD_CLK", 5 = "EMAC_TXD2" }, analog = { 1 = "ADC2_CH6", 2 = "TOUCH6" }, rtc = { 0 = "RTC_GPIO16" } }, + { pin = 15, functions = { 0 = "MTDO", 1 = "HSPICS0", 3 = "HS2_CMD", 4 = "SD_CMD", 5 = "EMAC_RXD3" }, analog = { 1 = "ADC2_CH3", 2 = "TOUCH3" }, rtc = { 0 = "RTC_GPIO13", 1 = "SAR_I2C_SDA" }, limitations = ["strapping"] }, + { pin = 16, functions = { 3 = "HS1_DATA4", 4 = "U2RXD", 5 = "EMAC_CLK_OUT" }, analog = {}, rtc = {}, limitations = ["spi_flash", "spi_psram"] }, + { pin = 17, functions = { 3 = "HS1_DATA5", 4 = "U2TXD", 5 = "EMAC_CLK_180" }, analog = {}, rtc = {}, limitations = ["spi_psram"] }, + { pin = 18, functions = { 1 = "VSPICLK", 3 = "HS1_DATA7" }, analog = {}, rtc = {} }, + { pin = 19, functions = { 1 = "VSPIQ", 3 = "U0CTS", 5 = "EMAC_TXD0" }, analog = {}, rtc = {} }, + { pin = 20, limitations = ["esp32_pico_v3"] }, + { pin = 21, functions = { 1 = "VSPIHD", 5 = "EMAC_TX_EN" } }, + { pin = 22, functions = { 1 = "VSPIWP", 3 = "U0RTS", 5 = "EMAC_TXD1" } }, + { pin = 23, functions = { 1 = "VSPID", 3 = "HS1_STROBE" } }, + { pin = 25, functions = { 5 = "EMAC_RXD0" }, analog = { 0 = "DAC1", 1 = "ADC2_CH8" }, rtc = { 0 = "RTC_GPIO6" } }, + { pin = 26, functions = { 5 = "EMAC_RXD1" }, analog = { 0 = "DAC2", 1 = "ADC2_CH9" }, rtc = { 0 = "RTC_GPIO7" } }, + { pin = 27, functions = { 5 = "EMAC_RX_DV" }, analog = { 1 = "ADC2_CH7", 2 = "TOUCH7" }, rtc = { 0 = "RTC_GPIO17" } }, + { pin = 32, analog = { 0 = "XTAL_32K_P", 1 = "ADC1_CH4", 2 = "TOUCH9" }, rtc = { 0 = "RTC_GPIO9" } }, + { pin = 33, analog = { 0 = "XTAL_32K_N", 1 = "ADC1_CH5", 2 = "TOUCH8" }, rtc = { 0 = "RTC_GPIO8" } }, + { pin = 34, limitations = ["input_only"], analog = { 1 = "ADC1_CH6" }, rtc = { 0 = "RTC_GPIO4" } }, + { pin = 35, limitations = ["input_only"], analog = { 1 = "ADC1_CH7" }, rtc = { 0 = "RTC_GPIO5" } }, + { pin = 36, limitations = ["input_only"], analog = { 0 = "ADC_H", 1 = "ADC1_CH0" }, rtc = { 0 = "RTC_GPIO0" } }, + { pin = 37, limitations = ["input_only"], analog = { 0 = "ADC_H", 1 = "ADC1_CH1" }, rtc = { 0 = "RTC_GPIO1" } }, + { pin = 38, limitations = ["input_only"], analog = { 0 = "ADC_H", 1 = "ADC1_CH2" }, rtc = { 0 = "RTC_GPIO2" } }, + { pin = 39, limitations = ["input_only"], analog = { 0 = "ADC_H", 1 = "ADC1_CH3" }, rtc = { 0 = "RTC_GPIO3" } }, +] +input_signals = [ + { name = "SPICLK", id = 0 }, + { name = "SPIQ", id = 1 }, + { name = "SPID", id = 2 }, + { name = "SPIHD", id = 3 }, + { name = "SPIWP", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "SPICS1", id = 6 }, + { name = "SPICS2", id = 7 }, + { name = "HSPICLK", id = 8 }, + { name = "HSPIQ", id = 9 }, + { name = "HSPID", id = 10 }, + { name = "HSPICS0", id = 11 }, + { name = "HSPIHD", id = 12 }, + { name = "HSPIWP", id = 13 }, + { name = "U0RXD", id = 14 }, + { name = "U0CTS", id = 15 }, + { name = "U0DSR", id = 16 }, + { name = "U1RXD", id = 17 }, + { name = "U1CTS", id = 18 }, + { name = "I2S0O_BCK", id = 23 }, + { name = "I2S1O_BCK", id = 24 }, + { name = "I2S0O_WS", id = 25 }, + { name = "I2S1O_WS", id = 26 }, + { name = "I2S0I_BCK", id = 27 }, + { name = "I2S0I_WS", id = 28 }, + { name = "I2CEXT0_SCL", id = 29 }, + { name = "I2CEXT0_SDA", id = 30 }, + { name = "PWM0_SYNC0", id = 31 }, + { name = "PWM0_SYNC1", id = 32 }, + { name = "PWM0_SYNC2", id = 33 }, + { name = "PWM0_F0", id = 34 }, + { name = "PWM0_F1", id = 35 }, + { name = "PWM0_F2", id = 36 }, + { name = "PCNT0_SIG_CH0", id = 39 }, + { name = "PCNT0_SIG_CH1", id = 40 }, + { name = "PCNT0_CTRL_CH0", id = 41 }, + { name = "PCNT0_CTRL_CH1", id = 42 }, + { name = "PCNT1_SIG_CH0", id = 43 }, + { name = "PCNT1_SIG_CH1", id = 44 }, + { name = "PCNT1_CTRL_CH0", id = 45 }, + { name = "PCNT1_CTRL_CH1", id = 46 }, + { name = "PCNT2_SIG_CH0", id = 47 }, + { name = "PCNT2_SIG_CH1", id = 48 }, + { name = "PCNT2_CTRL_CH0", id = 49 }, + { name = "PCNT2_CTRL_CH1", id = 50 }, + { name = "PCNT3_SIG_CH0", id = 51 }, + { name = "PCNT3_SIG_CH1", id = 52 }, + { name = "PCNT3_CTRL_CH0", id = 53 }, + { name = "PCNT3_CTRL_CH1", id = 54 }, + { name = "PCNT4_SIG_CH0", id = 55 }, + { name = "PCNT4_SIG_CH1", id = 56 }, + { name = "PCNT4_CTRL_CH0", id = 57 }, + { name = "PCNT4_CTRL_CH1", id = 58 }, + { name = "HSPICS1", id = 61 }, + { name = "HSPICS2", id = 62 }, + { name = "VSPICLK", id = 63 }, + { name = "VSPIQ", id = 64 }, + { name = "VSPID", id = 65 }, + { name = "VSPIHD", id = 66 }, + { name = "VSPIWP", id = 67 }, + { name = "VSPICS0", id = 68 }, + { name = "VSPICS1", id = 69 }, + { name = "VSPICS2", id = 70 }, + { name = "PCNT5_SIG_CH0", id = 71 }, + { name = "PCNT5_SIG_CH1", id = 72 }, + { name = "PCNT5_CTRL_CH0", id = 73 }, + { name = "PCNT5_CTRL_CH1", id = 74 }, + { name = "PCNT6_SIG_CH0", id = 75 }, + { name = "PCNT6_SIG_CH1", id = 76 }, + { name = "PCNT6_CTRL_CH0", id = 77 }, + { name = "PCNT6_CTRL_CH1", id = 78 }, + { name = "PCNT7_SIG_CH0", id = 79 }, + { name = "PCNT7_SIG_CH1", id = 80 }, + { name = "PCNT7_CTRL_CH0", id = 81 }, + { name = "PCNT7_CTRL_CH1", id = 82 }, + { name = "RMT_SIG_0", id = 83 }, + { name = "RMT_SIG_1", id = 84 }, + { name = "RMT_SIG_2", id = 85 }, + { name = "RMT_SIG_3", id = 86 }, + { name = "RMT_SIG_4", id = 87 }, + { name = "RMT_SIG_5", id = 88 }, + { name = "RMT_SIG_6", id = 89 }, + { name = "RMT_SIG_7", id = 90 }, + { name = "TWAI_RX", id = 94 }, + { name = "I2CEXT1_SCL", id = 95 }, + { name = "I2CEXT1_SDA", id = 96 }, + { name = "HOST_CARD_DETECT_N_1", id = 97 }, + { name = "HOST_CARD_DETECT_N_2", id = 98 }, + { name = "HOST_CARD_WRITE_PRT_1",id = 99 }, + { name = "HOST_CARD_WRITE_PRT_2",id = 100 }, + { name = "HOST_CARD_INT_N_1", id = 101 }, + { name = "HOST_CARD_INT_N_2", id = 102 }, + { name = "PWM1_SYNC0", id = 103 }, + { name = "PWM1_SYNC1", id = 104 }, + { name = "PWM1_SYNC2", id = 105 }, + { name = "PWM1_F0", id = 106 }, + { name = "PWM1_F1", id = 107 }, + { name = "PWM1_F2", id = 108 }, + { name = "PWM0_CAP0", id = 109 }, + { name = "PWM0_CAP1", id = 110 }, + { name = "PWM0_CAP2", id = 111 }, + { name = "PWM1_CAP0", id = 112 }, + { name = "PWM1_CAP1", id = 113 }, + { name = "PWM1_CAP2", id = 114 }, + { name = "I2S0I_DATA_0", id = 140 }, + { name = "I2S0I_DATA_1", id = 141 }, + { name = "I2S0I_DATA_2", id = 142 }, + { name = "I2S0I_DATA_3", id = 143 }, + { name = "I2S0I_DATA_4", id = 144 }, + { name = "I2S0I_DATA_5", id = 145 }, + { name = "I2S0I_DATA_6", id = 146 }, + { name = "I2S0I_DATA_7", id = 147 }, + { name = "I2S0I_DATA_8", id = 148 }, + { name = "I2S0I_DATA_9", id = 149 }, + { name = "I2S0I_DATA_10", id = 150 }, + { name = "I2S0I_DATA_11", id = 151 }, + { name = "I2S0I_DATA_12", id = 152 }, + { name = "I2S0I_DATA_13", id = 153 }, + { name = "I2S0I_DATA_14", id = 154 }, + { name = "I2S0I_DATA_15", id = 155 }, + { name = "I2S1I_BCK", id = 164 }, + { name = "I2S1I_WS", id = 165 }, + { name = "I2S1I_DATA_0", id = 166 }, + { name = "I2S1I_DATA_1", id = 167 }, + { name = "I2S1I_DATA_2", id = 168 }, + { name = "I2S1I_DATA_3", id = 169 }, + { name = "I2S1I_DATA_4", id = 170 }, + { name = "I2S1I_DATA_5", id = 171 }, + { name = "I2S1I_DATA_6", id = 172 }, + { name = "I2S1I_DATA_7", id = 173 }, + { name = "I2S1I_DATA_8", id = 174 }, + { name = "I2S1I_DATA_9", id = 175 }, + { name = "I2S1I_DATA_10", id = 176 }, + { name = "I2S1I_DATA_11", id = 177 }, + { name = "I2S1I_DATA_12", id = 178 }, + { name = "I2S1I_DATA_13", id = 179 }, + { name = "I2S1I_DATA_14", id = 180 }, + { name = "I2S1I_DATA_15", id = 181 }, + { name = "I2S0I_H_SYNC", id = 190 }, + { name = "I2S0I_V_SYNC", id = 191 }, + { name = "I2S0I_H_ENABLE", id = 192 }, + { name = "I2S1I_H_SYNC", id = 193 }, + { name = "I2S1I_V_SYNC", id = 194 }, + { name = "I2S1I_H_ENABLE", id = 195 }, + { name = "U2RXD", id = 198 }, + { name = "U2CTS", id = 199 }, + { name = "EMAC_MDC", id = 200 }, + { name = "EMAC_MDI", id = 201 }, + { name = "EMAC_CRS", id = 202 }, + { name = "EMAC_COL", id = 203 }, + { name = "PCMFSYNC", id = 204 }, + { name = "PCMCLK", id = 205 }, + { name = "PCMDIN", id = 206 }, + + { name = "SD_CMD" }, + { name = "SD_DATA0" }, + { name = "SD_DATA1" }, + { name = "SD_DATA2" }, + { name = "SD_DATA3" }, + { name = "HS1_DATA0" }, + { name = "HS1_DATA1" }, + { name = "HS1_DATA2" }, + { name = "HS1_DATA3" }, + { name = "HS1_DATA4" }, + { name = "HS1_DATA5" }, + { name = "HS1_DATA6" }, + { name = "HS1_DATA7" }, + { name = "HS2_DATA0" }, + { name = "HS2_DATA1" }, + { name = "HS2_DATA2" }, + { name = "HS2_DATA3" }, + + { name = "EMAC_TX_CLK" }, + { name = "EMAC_RXD2" }, + { name = "EMAC_TX_ER" }, + { name = "EMAC_RX_CLK" }, + { name = "EMAC_RX_ER" }, + { name = "EMAC_RXD3" }, + { name = "EMAC_RXD0" }, + { name = "EMAC_RXD1" }, + { name = "EMAC_RX_DV" }, + + { name = "MTDI" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "SPICLK", id = 0 }, + { name = "SPIQ", id = 1 }, + { name = "SPID", id = 2 }, + { name = "SPIHD", id = 3 }, + { name = "SPIWP", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "SPICS1", id = 6 }, + { name = "SPICS2", id = 7 }, + { name = "HSPICLK", id = 8 }, + { name = "HSPIQ", id = 9 }, + { name = "HSPID", id = 10 }, + { name = "HSPICS0", id = 11 }, + { name = "HSPIHD", id = 12 }, + { name = "HSPIWP", id = 13 }, + { name = "U0TXD", id = 14 }, + { name = "U0RTS", id = 15 }, + { name = "U0DTR", id = 16 }, + { name = "U1TXD", id = 17 }, + { name = "U1RTS", id = 18 }, + { name = "I2S0O_BCK", id = 23 }, + { name = "I2S1O_BCK", id = 24 }, + { name = "I2S0O_WS", id = 25 }, + { name = "I2S1O_WS", id = 26 }, + { name = "I2S0I_BCK", id = 27 }, + { name = "I2S0I_WS", id = 28 }, + { name = "I2CEXT0_SCL", id = 29 }, + { name = "I2CEXT0_SDA", id = 30 }, + { name = "SDIO_TOHOSTT", id = 31 }, + { name = "PWM0_0A", id = 32 }, + { name = "PWM0_0B", id = 33 }, + { name = "PWM0_1A", id = 34 }, + { name = "PWM0_1B", id = 35 }, + { name = "PWM0_2A", id = 36 }, + { name = "PWM0_2B", id = 37 }, + { name = "HSPICS1", id = 61 }, + { name = "HSPICS2", id = 62 }, + { name = "VSPICLK", id = 63 }, + { name = "VSPIQ", id = 64 }, + { name = "VSPID", id = 65 }, + { name = "VSPIHD", id = 66 }, + { name = "VSPIWP", id = 67 }, + { name = "VSPICS0", id = 68 }, + { name = "VSPICS1", id = 69 }, + { name = "VSPICS2", id = 70 }, + { name = "LEDC_HS_SIG0", id = 71 }, + { name = "LEDC_HS_SIG1", id = 72 }, + { name = "LEDC_HS_SIG2", id = 73 }, + { name = "LEDC_HS_SIG3", id = 74 }, + { name = "LEDC_HS_SIG4", id = 75 }, + { name = "LEDC_HS_SIG5", id = 76 }, + { name = "LEDC_HS_SIG6", id = 77 }, + { name = "LEDC_HS_SIG7", id = 78 }, + { name = "LEDC_LS_SIG0", id = 79 }, + { name = "LEDC_LS_SIG1", id = 80 }, + { name = "LEDC_LS_SIG2", id = 81 }, + { name = "LEDC_LS_SIG3", id = 82 }, + { name = "LEDC_LS_SIG4", id = 83 }, + { name = "LEDC_LS_SIG5", id = 84 }, + { name = "LEDC_LS_SIG6", id = 85 }, + { name = "LEDC_LS_SIG7", id = 86 }, + { name = "RMT_SIG_0", id = 87 }, + { name = "RMT_SIG_1", id = 88 }, + { name = "RMT_SIG_2", id = 89 }, + { name = "RMT_SIG_3", id = 90 }, + { name = "RMT_SIG_4", id = 91 }, + { name = "RMT_SIG_5", id = 92 }, + { name = "RMT_SIG_6", id = 93 }, + { name = "RMT_SIG_7", id = 94 }, + { name = "I2CEXT1_SCL", id = 95 }, + { name = "I2CEXT1_SDA", id = 96 }, + { name = "HOST_CCMD_OD_PULLUP_EN_N", id = 97 }, + { name = "HOST_RST_N_1", id = 98 }, + { name = "HOST_RST_N_2", id = 99 }, + { name = "GPIO_SD0", id = 100 }, + { name = "GPIO_SD1", id = 101 }, + { name = "GPIO_SD2", id = 102 }, + { name = "GPIO_SD3", id = 103 }, + { name = "GPIO_SD4", id = 104 }, + { name = "GPIO_SD5", id = 105 }, + { name = "GPIO_SD6", id = 106 }, + { name = "GPIO_SD7", id = 107 }, + { name = "PWM1_0A", id = 108 }, + { name = "PWM1_0B", id = 109 }, + { name = "PWM1_1A", id = 110 }, + { name = "PWM1_1B", id = 111 }, + { name = "PWM1_2A", id = 112 }, + { name = "PWM1_2B", id = 113 }, + { name = "TWAI_TX", id = 123 }, + { name = "TWAI_BUS_OFF_ON", id = 124 }, + { name = "TWAI_CLKOUT", id = 125 }, + { name = "I2S0O_DATA_0", id = 140 }, + { name = "I2S0O_DATA_1", id = 141 }, + { name = "I2S0O_DATA_2", id = 142 }, + { name = "I2S0O_DATA_3", id = 143 }, + { name = "I2S0O_DATA_4", id = 144 }, + { name = "I2S0O_DATA_5", id = 145 }, + { name = "I2S0O_DATA_6", id = 146 }, + { name = "I2S0O_DATA_7", id = 147 }, + { name = "I2S0O_DATA_8", id = 148 }, + { name = "I2S0O_DATA_9", id = 149 }, + { name = "I2S0O_DATA_10", id = 150 }, + { name = "I2S0O_DATA_11", id = 151 }, + { name = "I2S0O_DATA_12", id = 152 }, + { name = "I2S0O_DATA_13", id = 153 }, + { name = "I2S0O_DATA_14", id = 154 }, + { name = "I2S0O_DATA_15", id = 155 }, + { name = "I2S0O_DATA_16", id = 156 }, + { name = "I2S0O_DATA_17", id = 157 }, + { name = "I2S0O_DATA_18", id = 158 }, + { name = "I2S0O_DATA_19", id = 159 }, + { name = "I2S0O_DATA_20", id = 160 }, + { name = "I2S0O_DATA_21", id = 161 }, + { name = "I2S0O_DATA_22", id = 162 }, + { name = "I2S0O_DATA_23", id = 163 }, + { name = "I2S1I_BCK", id = 164 }, + { name = "I2S1I_WS", id = 165 }, + { name = "I2S1O_DATA_0", id = 166 }, + { name = "I2S1O_DATA_1", id = 167 }, + { name = "I2S1O_DATA_2", id = 168 }, + { name = "I2S1O_DATA_3", id = 169 }, + { name = "I2S1O_DATA_4", id = 170 }, + { name = "I2S1O_DATA_5", id = 171 }, + { name = "I2S1O_DATA_6", id = 172 }, + { name = "I2S1O_DATA_7", id = 173 }, + { name = "I2S1O_DATA_8", id = 174 }, + { name = "I2S1O_DATA_9", id = 175 }, + { name = "I2S1O_DATA_10", id = 176 }, + { name = "I2S1O_DATA_11", id = 177 }, + { name = "I2S1O_DATA_12", id = 178 }, + { name = "I2S1O_DATA_13", id = 179 }, + { name = "I2S1O_DATA_14", id = 180 }, + { name = "I2S1O_DATA_15", id = 181 }, + { name = "I2S1O_DATA_16", id = 182 }, + { name = "I2S1O_DATA_17", id = 183 }, + { name = "I2S1O_DATA_18", id = 184 }, + { name = "I2S1O_DATA_19", id = 185 }, + { name = "I2S1O_DATA_20", id = 186 }, + { name = "I2S1O_DATA_21", id = 187 }, + { name = "I2S1O_DATA_22", id = 188 }, + { name = "I2S1O_DATA_23", id = 189 }, + { name = "U2TXD", id = 198 }, + { name = "U2RTS", id = 199 }, + { name = "EMAC_MDC", id = 200 }, + { name = "EMAC_MDO", id = 201 }, + { name = "EMAC_CRS", id = 202 }, + { name = "EMAC_COL", id = 203 }, + { name = "BT_AUDIO0RQ", id = 204 }, + { name = "BT_AUDIO1RQ", id = 205 }, + { name = "BT_AUDIO2RQ", id = 206 }, + { name = "BLE_AUDIO0RQ", id = 207 }, + { name = "BLE_AUDIO1RQ", id = 208 }, + { name = "BLE_AUDIO2RQ", id = 209 }, + { name = "PCMFSYNC", id = 210 }, + { name = "PCMCLK", id = 211 }, + { name = "PCMDOUT", id = 212 }, + { name = "BLE_AUDIO_SYNC0_P", id = 213 }, + { name = "BLE_AUDIO_SYNC1_P", id = 214 }, + { name = "BLE_AUDIO_SYNC2_P", id = 215 }, + { name = "ANT_SEL0", id = 216 }, + { name = "ANT_SEL1", id = 217 }, + { name = "ANT_SEL2", id = 218 }, + { name = "ANT_SEL3", id = 219 }, + { name = "ANT_SEL4", id = 220 }, + { name = "ANT_SEL5", id = 221 }, + { name = "ANT_SEL6", id = 222 }, + { name = "ANT_SEL7", id = 223 }, + { name = "SIGNAL_224", id = 224 }, + { name = "SIGNAL_225", id = 225 }, + { name = "SIGNAL_226", id = 226 }, + { name = "SIGNAL_227", id = 227 }, + { name = "SIGNAL_228", id = 228 }, + { name = "GPIO", id = 256 }, + + { name = "CLK_OUT1" }, + { name = "CLK_OUT2" }, + { name = "CLK_OUT3" }, + { name = "SD_CLK" }, + { name = "SD_CMD" }, + { name = "SD_DATA0" }, + { name = "SD_DATA1" }, + { name = "SD_DATA2" }, + { name = "SD_DATA3" }, + { name = "HS1_CLK" }, + { name = "HS1_CMD" }, + { name = "HS1_DATA0" }, + { name = "HS1_DATA1" }, + { name = "HS1_DATA2" }, + { name = "HS1_DATA3" }, + { name = "HS1_DATA4" }, + { name = "HS1_DATA5" }, + { name = "HS1_DATA6" }, + { name = "HS1_DATA7" }, + { name = "HS1_STROBE" }, + { name = "HS2_CLK" }, + { name = "HS2_CMD" }, + { name = "HS2_DATA0" }, + { name = "HS2_DATA1" }, + { name = "HS2_DATA2" }, + { name = "HS2_DATA3" }, + + { name = "EMAC_TX_CLK" }, + { name = "EMAC_TX_ER" }, + { name = "EMAC_TXD3" }, + { name = "EMAC_RX_ER" }, + { name = "EMAC_TXD2" }, + { name = "EMAC_CLK_OUT" }, + { name = "EMAC_CLK_180" }, + { name = "EMAC_TXD0" }, + { name = "EMAC_TX_EN" }, + { name = "EMAC_TXD1" }, + + { name = "MTDO" }, +] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, + { name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" }, +] +ll_intr_mask = 0x3ffff +fifo_size = 32 +max_bus_timeout = 0xFFFFF +separate_filter_config_registers = true +i2c0_data_register_ahb_address = 0x6001301c + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 3 +software_interrupt_count = 4 +software_interrupt_delay = 0 +controller = "Xtensa" + +[device.rmt] +support_status = "partial" +ram_start = 0x3ff56800 +channel_ram_size = 64 +channels = ["RxTx", "RxTx", "RxTx", "RxTx", "RxTx", "RxTx", "RxTx", "RxTx"] +has_per_channel_clock = true +clock_sources.supported = [ "RefTick", "Apb" ] +# Power-on value is RefTick! +clock_sources.default = "Apb" + +[device.rsa] +support_status = "partial" +size_increment = 512 +memory_size_bytes = 512 + +[device.sha] +support_status = "partial" +algo = { sha1 = 0, sha256 = 0, sha384 = 0, sha512 = 0 } # fake mode bits, ESP32 has separate register sets + +[device.spi_master] +support_status = "supported" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "HSPICLK", sio = ["HSPID", "HSPIQ", "HSPIWP", "HSPIHD"], cs = ["HSPICS0", "HSPICS1", "HSPICS2"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "VSPICLK", sio = ["VSPID", "VSPIQ", "VSPIWP", "VSPIHD"], cs = ["VSPICS0", "VSPICS1", "VSPICS2"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "HSPICLK", mosi = "HSPID", miso = "HSPIQ", cs = "HSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "VSPICLK", mosi = "VSPID", miso = "VSPIQ", cs = "VSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +timg_has_timer1 = true +timg_has_divcnt_rst = false +instances = [{ name = "timg0" }, { name = "timg1" }] + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, + { name = "uart2", sys_instance = "Uart2", tx = "U2TXD", rx = "U2RXD", cts = "U2CTS", rts = "U2RTS" }, +] +ram_size = 128 + +[device.uhci] +support_status = "not_supported" + +[device.ethernet] +support_status = "not_supported" + +[device.camera] +support_status = "not_supported" +[device.rgb_display] +support_status = "partial" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Interfaces +[device.i2s] +[device.ledc] +[device.mcpwm] +[device.pcnt] +[device.sd_host] +[device.sd_slave] +[device.touch] +[device.twai] + +## Miscellaneous +[device.io_mux] +[device.psram] +[device.temp_sensor] +[device.lp_timer] + +[device.ulp_fsm] + +## Radio +[device.wifi] +mac_version = 1 + +[device.bt] +support_status = "partial" +controller = "btdm" + +[device.phy] +combo_module = true diff --git a/esp-metadata/devices/esp32c2.toml b/esp-metadata/devices/esp32c2.toml new file mode 100644 index 00000000000..3e351dca9cf --- /dev/null +++ b/esp-metadata/devices/esp32c2.toml @@ -0,0 +1,473 @@ +# ESP32-C2 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32c2" +arch = "riscv" +target = "riscv32imc-unknown-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp8684_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "APB_CTRL" }, + { name = "APB_SARADC" }, + { name = "BB" }, + { name = "ASSIST_DEBUG" }, + { name = "DMA" }, + { name = "ECC" }, + { name = "EFUSE", hidden = true }, + { name = "EXTMEM" }, + { name = "GPIO" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "INTERRUPT_CORE0" }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "RNG" }, + { name = "LPWR", pac = "RTC_CNTL" }, + { name = "MODEM_CLKRST" }, + { name = "SENSITIVE" }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 0, stable = true }, + { name = "SYSTEM" }, + { name = "SYSTIMER" }, + { name = "TIMG0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "XTS_AES" }, + + { name = "DMA_CH0", virtual = true }, + + { name = "ADC1", virtual = true }, + { name = "BT", virtual = true, interrupts = { mac = "BT_MAC", lp_timer = "LP_TIMER" } }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "WIFI", virtual = true, interrupts = { mac = "WIFI_MAC", pwr = "WIFI_PWR" }, stable = true }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "swd", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_mbedtls", + + # Wakeup SOC based on ESP-IDF: + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "gpio_support_deepsleep_wakeup", +] + +[device.soc] +cpu_has_csr_pc = true +rc_fast_clk_default = 17_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x3FCA_0000, end = 0x3FCE_0000 }, + { name = "dram2_uninit", start = 0x3FCC_E800, end = 0x3FCD_EB70 }, +] } + + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "26, 40", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", output = "480_000_000" }, + { name = "RC_FAST_CLK", type = "source", output = "17_500_000" }, + + # Low-speed clocks + { name = "OSC_SLOW_CLK", type = "source", output = "32768" }, # external slow clock from GPIO0 + { name = "RC_SLOW_CLK", type = "source", output = "136_000" }, # adjustable via RTC_CNTL_ANA_CLK_DIV + { name = "RC_FAST_DIV_CLK", type = "divider", output = "RC_FAST_CLK / 256" }, # 256 is just the default value? RTC_CNTL_FOSC_DIV + + # CPU clock source dividers + { name = "SYSTEM_PRE_DIV_IN", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + { name = "SYSTEM_PRE_DIV", type = "divider", params = { divisor = "0 .. 1024" }, output = "SYSTEM_PRE_DIV_IN / (divisor + 1)" }, + { name = "CPU_PLL_DIV", type = "divider", params = { divisor = "4, 6" }, output = "PLL_CLK / divisor" }, + + # The meta-switch + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + # source clock CPU clock additional config (mux = variant name, divider = divisor expression) + { name = "XTAL", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_CLK = CPU", "MSPI_CLK = CPU", "SYSTEM_PRE_DIV_IN = XTAL" ] }, + { name = "RC_FAST", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_CLK = CPU", "MSPI_CLK = CPU", "SYSTEM_PRE_DIV_IN = RC_FAST" ] }, + { name = "PLL", outputs = "CPU_PLL_DIV", configures = [ "APB_CLK = PLL_40M", "CRYPTO_CLK = PLL_80M", "MSPI_CLK = CPU_DIV2" ] }, + ] }, + + # CPU-dependent clock signals + { name = "PLL_40M", type = "derived", from = "CPU_CLK", output = "40_000_000" }, # PLL / 12 + { name = "PLL_60M", type = "derived", from = "CPU_CLK", output = "60_000_000" }, # PLL / 8 + { name = "PLL_80M", type = "derived", from = "CPU_CLK", output = "80_000_000" }, # PLL / 6 + { name = "CPU_DIV2", type = "divider", output = "CPU_CLK / 2" }, + + { name = "APB_CLK", type = "mux", variants = [ + { name = "PLL_40M", outputs = "PLL_40M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + { name = "CRYPTO_CLK", type = "mux", variants = [ + { name = "PLL_80M", outputs = "PLL_80M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + { name = "MSPI_CLK", type = "mux", variants = [ + { name = "CPU_DIV2", outputs = "CPU_DIV2" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + + # Low-power clocks + { name = "RC_FAST_CLK_DIV_N", type = "divider", params = { divisor = "0 ..= 3" }, output = "RC_FAST_CLK / (divisor + 1)" }, # RC_DIV + { name = "XTAL_DIV_CLK", type = "divider", output = "XTAL_CLK / 2" }, # XTAL_DIV + { name = "RTC_SLOW_CLK", type = "mux", variants = [ # RTC_CNTL_ANA_CLK_RTC_SEL + { name = "OSC_SLOW", outputs = "OSC_SLOW_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_DIV_CLK" }, + ] }, + { name = "RTC_FAST_CLK", type = "mux", variants = [ # RTC_CNTL_FAST_CLK_RTC_SEL + { name = "XTAL", outputs = "XTAL_DIV_CLK" }, + { name = "RC", outputs = "RC_FAST_CLK_DIV_N" }, + ] }, + + # Low-power wireless clock source + { name = "LOW_POWER_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "OSC_SLOW", outputs = "OSC_SLOW_CLK" }, + { name = "RTC_SLOW", outputs = "RTC_SLOW_CLK" }, + ] }, + + # System-wide peripheral clocks + { name = "UART_MEM_CLK", type = "mux", default = "XTAL", variants = [ + # The actual source clock is unknown, but it also doesn't matter. + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "clk_en_template", value = "{{control}}::regs().{{clk_en_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "rst_template", value = "{{control}}::regs().{{rst_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "reg_group", value = "en0" }, + { name = "clk_en_register", value = "perip_clk_{{reg_group}}" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_register", value = "perip_rst_{{reg_group}}" }, + { name = "rst_field", value = "{{peripheral}}_rst" }, +], peripheral_clocks = [ + # TODO: peripheral_clocks are implicit APB clock gates, we will need to tie them into the system to track APB use. + { name = "Tsens", template_params = { reg_group = "en1" } }, + { name = "Dma", template_params = { reg_group = "en1" } }, + { name = "Sha", template_params = { reg_group = "en1", peripheral = "crypto_sha" } }, + { name = "Ecc", template_params = { reg_group = "en1", peripheral = "crypto_ecc" } }, + + #{ name = "Adc2Arb" }, + { name = "Systimer", keep_enabled = true }, + { name = "ApbSarAdc", template_params = { peripheral = "apb_saradc" } }, + { name = "UartMem", keep_enabled = true }, # TODO: keep_enabled can be removed once esp-println needs explicit initialization + { name = "Timg0", template_params = { peripheral = "timergroup" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "PLL_40M", outputs = "PLL_40M" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", variants = [ + { name = "RC_SLOW_CLK", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_DIV_CLK" }, + { name = "OSC32K_CLK", outputs = "OSC_SLOW_CLK" }, + ] }, + { name = "WDT_CLOCK", type = "mux", default = "PLL_40M", variants = [ + { name = "PLL_40M", outputs = "PLL_40M" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Ledc" }, + { name = "I2cExt0" }, + { name = "Spi2" }, + { name = "Uart1", clocks = "Uart0" }, + { name = "Uart0", template_params = { peripheral = "uart" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "PLL_F40M", outputs = "PLL_40M" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + { name = "MEM_CLOCK", type = "mux", default = "MEM", variants = [ + { name = "MEM", outputs = "UART_MEM_CLK" }, + ] }, + ] }, + #{ name = "Spi01" }, + #{ name = "Timers" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, +] + +[device.assist_debug] +support_status = "partial" +has_sp_monitor = true + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 1 +supports_mem2mem = true +max_priority = 9 + +[device.ecc] +support_status = "partial" +working_modes = [ + { id = 0, mode = "affine_point_multiplication" }, + { id = 1, mode = "finite_field_division" }, + { id = 2, mode = "affine_point_verification" }, + { id = 3, mode = "affine_point_verification_and_multiplication" }, + { id = 4, mode = "jacobian_point_multiplication" }, + { id = 6, mode = "jacobian_point_verification" }, + { id = 7, mode = "affine_point_verification_and_jacobian_point_multiplication" } +] +curves = [ + { id = 0, curve = 192 }, + { id = 1, curve = 256 } +] +zero_extend_writes = true + +[device.gpio] +support_status = "supported" +gpio_function = 1 +constant_0_input = 0x1f +constant_1_input = 0x1e +pins = [ + { pin = 0, analog = { 1 = "ADC1_CH0" }, rtc = { 0 = "RTC_GPIO0" } }, + { pin = 1, analog = { 1 = "ADC1_CH1" }, rtc = { 0 = "RTC_GPIO1" } }, + { pin = 2, functions = { 2 = "FSPIQ" }, analog = { 1 = "ADC1_CH2" }, rtc = { 0 = "RTC_GPIO2" } }, + { pin = 3, analog = { 1 = "ADC1_CH3" }, rtc = { 0 = "RTC_GPIO3" } }, + { pin = 4, functions = { 0 = "MTMS", 2 = "FSPIHD" }, analog = { 1 = "ADC1_CH4" }, rtc = { 0 = "RTC_GPIO4" } }, + { pin = 5, functions = { 0 = "MTDI", 2 = "FSPIWP" }, rtc = { 0 = "RTC_GPIO5" } }, + { pin = 6, functions = { 0 = "MTCK", 2 = "FSPICLK" } }, + { pin = 7, functions = { 0 = "MTDO", 2 = "FSPID" } }, + { pin = 8, limitations = ["strapping"] }, + { pin = 9, limitations = ["strapping"] }, + { pin = 10, functions = { 2 = "FSPICS0" } }, + { pin = 11, functions = { 0 = "SPIHD" }, limitations = ["spi_flash"] }, + { pin = 12, functions = { 0 = "SPIHD" }, limitations = ["spi_flash"] }, + { pin = 13, functions = { 0 = "SPIWP" }, limitations = ["spi_flash"] }, + { pin = 14, functions = { 0 = "SPICS0" }, limitations = ["spi_flash"] }, + { pin = 15, functions = { 0 = "SPICLK" }, limitations = ["spi_flash"] }, + { pin = 16, functions = { 0 = "SPID" }, limitations = ["spi_flash"] }, + { pin = 17, functions = { 0 = "SPIQ" }, limitations = ["spi_flash"] }, + { pin = 18 }, + { pin = 19, functions = { 0 = "U0RXD" } }, + { pin = 20, functions = { 0 = "U0TXD" } }, +] +input_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "U0RXD", id = 6 }, + { name = "U0CTS", id = 7 }, + { name = "U0DSR", id = 8 }, + { name = "U1RXD", id = 9 }, + { name = "U1CTS", id = 10 }, + { name = "U1DSR", id = 11 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "EXT_ADC_START", id = 45 }, + { name = "RMT_SIG_0", id = 51 }, + { name = "RMT_SIG_1", id = 52 }, + { name = "I2CEXT0_SCL", id = 53 }, + { name = "I2CEXT0_SDA", id = 54 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "SIG_FUNC_97", id = 97 }, + { name = "SIG_FUNC_98", id = 98 }, + { name = "SIG_FUNC_99", id = 99 }, + { name = "SIG_FUNC_100", id = 100 }, + + { name = "MTCK" }, + { name = "MTMS" }, + { name = "MTDI" }, +] +output_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPICLK", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "U0TXD", id = 6 }, + { name = "U0RTS", id = 7 }, + { name = "U0DTR", id = 8 }, + { name = "U1TXD", id = 9 }, + { name = "U1RTS", id = 10 }, + { name = "U1DTR", id = 11 }, + { name = "SPIQ_MONITOR", id = 15 }, + { name = "SPID_MONITOR", id = 16 }, + { name = "SPIHD_MONITOR", id = 17 }, + { name = "SPIWP_MONITOR", id = 18 }, + { name = "SPICS1", id = 19 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "LEDC_LS_SIG0", id = 45 }, + { name = "LEDC_LS_SIG1", id = 46 }, + { name = "LEDC_LS_SIG2", id = 47 }, + { name = "LEDC_LS_SIG3", id = 48 }, + { name = "LEDC_LS_SIG4", id = 49 }, + { name = "LEDC_LS_SIG5", id = 50 }, + { name = "RMT_SIG_0", id = 51 }, + { name = "RMT_SIG_1", id = 52 }, + { name = "I2CEXT0_SCL", id = 53 }, + { name = "I2CEXT0_SDA", id = 54 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "FSPICS1", id = 69 }, + { name = "FSPICS3", id = 70 }, + { name = "FSPICS2", id = 71 }, + { name = "FSPICS4", id = 72 }, + { name = "FSPICS5", id = 73 }, + { name = "ANT_SEL0", id = 89 }, + { name = "ANT_SEL1", id = 90 }, + { name = "ANT_SEL2", id = 91 }, + { name = "ANT_SEL3", id = 92 }, + { name = "ANT_SEL4", id = 93 }, + { name = "ANT_SEL5", id = 94 }, + { name = "ANT_SEL6", id = 95 }, + { name = "ANT_SEL7", id = 96 }, + { name = "SIG_FUNC_97", id = 97 }, + { name = "SIG_FUNC_98", id = 98 }, + { name = "SIG_FUNC_99", id = 99 }, + { name = "SIG_FUNC_100", id = 100 }, + { name = "CLK_OUT1", id = 123 }, + { name = "CLK_OUT2", id = 124 }, + { name = "CLK_OUT3", id = 125 }, + { name = "GPIO", id = 128 }, + + { name = "MTDO" }, +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["CPU_GPIO_0", "CPU_GPIO_1", "CPU_GPIO_2", "CPU_GPIO_3", "CPU_GPIO_4", "CPU_GPIO_5", "CPU_GPIO_6", "CPU_GPIO_7"]] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, +] +has_fsm_timeouts = true +has_hw_bus_clear = true +ll_intr_mask = 0x3ffff +fifo_size = 16 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +has_conf_update = true +has_arbitration_en = true +has_tx_fifo_watermark = true +bus_timeout_is_exponential = true + +[device.interrupts] +support_status = "partial" +status_registers = 2 +software_interrupt_count = 4 +software_interrupt_delay = 5 +controller = { Riscv = { flavour = "basic", interrupts = 32, priority_levels = 15 } } + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_app_interrupts = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +instances = [{ name = "timg0" }] +timg_has_divcnt_rst = true + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +# [device.aes] Product portfolio lists AES, but TRM only has XTS_AES? + +## Interfaces +[device.ledc] + +## Miscellaneous +[device.io_mux] +[device.temp_sensor] +[device.systimer] +[device.lp_timer] + +## Radio +[device.wifi] +mac_version = 1 + +[device.bt] +support_status = "partial" +controller = "npl" + +[device.phy] +combo_module = true diff --git a/esp-metadata/devices/esp32c3.toml b/esp-metadata/devices/esp32c3.toml new file mode 100644 index 00000000000..d9661cb6b32 --- /dev/null +++ b/esp-metadata/devices/esp32c3.toml @@ -0,0 +1,553 @@ +# ESP32-C3 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32c3" +arch = "riscv" +target = "riscv32imc-unknown-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 6 }, + { name = "APB_CTRL" }, + { name = "APB_SARADC", dma_peripheral = 8 }, + { name = "ASSIST_DEBUG" }, + { name = "BB" }, + { name = "DMA" }, + { name = "DS" }, + { name = "EFUSE", hidden = true }, + { name = "EXTMEM" }, + { name = "FE" }, + { name = "FE2" }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HMAC" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 3 }, + { name = "INTERRUPT_CORE0" }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "NRX" }, + { name = "RMT" }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "LPWR", pac = "RTC_CNTL" }, + { name = "SENSITIVE" }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 0, stable = true }, + { name = "SYSTEM" }, + { name = "SYSTIMER" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TWAI0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UHCI0", dma_peripheral = 2 }, + { name = "USB_DEVICE", interrupts = { peri = "USB_DEVICE" } }, + { name = "XTS_AES" }, + + { name = "DMA_CH0", virtual = true }, + { name = "DMA_CH1", virtual = true }, + { name = "DMA_CH2", virtual = true }, + + { name = "ADC1", virtual = true }, + { name = "ADC2", virtual = true }, + { name = "BT", virtual = true, interrupts = { rwbt = "RWBT", rwble = "RWBLE", bb = "BT_BB" } }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "TSENS", virtual = true }, + { name = "WIFI", virtual = true, interrupts = { mac = "WIFI_MAC", pwr = "WIFI_PWR" }, stable = true }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "swd", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + + # Wakeup SOC based on ESP-IDF: + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "gpio_support_deepsleep_wakeup", +] + +[device.soc] +cpu_has_csr_pc = true +rc_fast_clk_default = 17_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x3FC8_0000, end = 0x3FCE_0000 }, + { name = "dram2_uninit", start = 0x3FCCE400, end = 0x3FCD_E710 }, +] } + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "40", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", values = "320, 480", output = "VALUE * 1_000_000" }, + { name = "RC_FAST_CLK", type = "source", output = "17_500_000" }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "136_000" }, + { name = "RC_FAST_DIV_CLK", type = "divider", output = "RC_FAST_CLK / 256" }, + + # CPU clock source dividers + { name = "SYSTEM_PRE_DIV_IN", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + { name = "SYSTEM_PRE_DIV", type = "divider", params = { divisor = "0 .. 1024" }, output = "SYSTEM_PRE_DIV_IN / (divisor + 1)" }, + # PLL CLK divider, modeled as a generic derived source because valid divider values depend on the PLL frequency + { name = "CPU_PLL_DIV_OUT", type = "derived", from = "PLL_CLK", values = "80, 160", output = "VALUE * 1_000_000" }, + + # The meta-switch + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + # source clock CPU clock additional config (mux = variant name, divider = divisor expression) + { name = "XTAL", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_CLK = CPU", "SYSTEM_PRE_DIV_IN = XTAL" ] }, + { name = "RC_FAST", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_CLK = CPU", "SYSTEM_PRE_DIV_IN = RC_FAST" ] }, + { name = "PLL", outputs = "CPU_PLL_DIV_OUT", configures = [ "APB_CLK = PLL_80M", "CRYPTO_CLK = PLL_160M" ] }, + ] }, + + # CPU-dependent clock signals + { name = "PLL_80M", type = "derived", from = "CPU_CLK", output = "80_000_000" }, + { name = "PLL_160M", type = "derived", from = "CPU_CLK", output = "160_000_000" }, + + { name = "APB_CLK", type = "mux", variants = [ + { name = "PLL_80M", outputs = "PLL_80M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + { name = "CRYPTO_CLK", type = "mux", variants = [ + { name = "PLL_160M", outputs = "PLL_160M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + + # Low-power clocks + { name = "RC_FAST_CLK_DIV_N", type = "divider", params = { divisor = "0 ..= 3" }, output = "RC_FAST_CLK / (divisor + 1)" }, + { name = "XTAL_DIV_CLK", type = "divider", output = "XTAL_CLK / 2" }, + { name = "RTC_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_DIV_CLK" }, + ] }, + { name = "RTC_FAST_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_DIV_CLK" }, + { name = "RC", outputs = "RC_FAST_CLK_DIV_N" }, + ] }, + + # Low-power wireless clock source + { name = "LOW_POWER_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RTC_SLOW", outputs = "RTC_SLOW_CLK" }, + ] }, + + # System-wide peripheral clocks + { name = "UART_MEM_CLK", type = "mux", default = "XTAL", variants = [ + # The actual source clock is unknown, but it also doesn't matter. + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "clk_en_template", value = "{{control}}::regs().{{clk_en_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "rst_template", value = "{{control}}::regs().{{rst_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "reg_group", value = "en0" }, + { name = "clk_en_register", value = "perip_clk_{{reg_group}}" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_register", value = "perip_rst_{{reg_group}}" }, + { name = "rst_field", value = "{{peripheral}}_rst" }, + # {{peripheral}} is derived automatically (e.g. SpiDma -> spi_dma) +], peripheral_clocks = [ + { name = "Tsens", template_params = { reg_group = "en1" } }, + { name = "Dma", template_params = { reg_group = "en1" } }, + { name = "Hmac", template_params = { reg_group = "en1", peripheral = "crypto_hmac" } }, + { name = "Ds", template_params = { reg_group = "en1", peripheral = "crypto_ds" } }, + { name = "Rsa", template_params = { reg_group = "en1", peripheral = "crypto_rsa" } }, + { name = "Sha", template_params = { reg_group = "en1", peripheral = "crypto_sha" } }, + { name = "Aes", template_params = { reg_group = "en1", peripheral = "crypto_aes" } }, + + #{ name = "Adc2Arb" }, + { name = "Systimer", keep_enabled = true }, + { name = "ApbSarAdc", template_params = { peripheral = "apb_saradc" } }, + #{ name = "Spi3Dma" }, + { name = "UartMem", keep_enabled = true }, # TODO: keep_enabled can be removed once esp-println needs explicit initialization + { name = "UsbDevice", keep_enabled = true }, + { name = "I2s0" }, + { name = "Twai0", template_params = { peripheral = "twai" } }, + { name = "Timg1", template_params = { peripheral = "timergroup1" }, clocks = "Timg0" }, + { name = "Timg0", template_params = { peripheral = "timergroup" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "APB_CLK", outputs = "APB_CLK" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", variants = [ + { name = "RC_SLOW_CLK", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_DIV_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + { name = "WDT_CLOCK", type = "mux", default = "APB_CLK", variants = [ + { name = "APB_CLK", outputs = "APB_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Ledc" }, + { name = "Rmt", clocks = [ + { name = "SCLK", type = "mux", default = "APB_CLK", variants = [ + { name = "APB_CLK", outputs = "APB_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Uhci0" }, + { name = "I2cExt0" }, + { name = "Spi2" }, + { name = "Uart1", clocks = "Uart0" }, + { name = "Uart0", template_params = { peripheral = "uart" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "APB", outputs = "APB_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + { name = "MEM_CLOCK", type = "mux", default = "MEM", variants = [ + { name = "MEM", outputs = "UART_MEM_CLK" }, + ] }, + ] }, + #{ name = "Spi01" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, + { name = "adc2" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = false +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.assist_debug] +support_status = "partial" +has_sp_monitor = true +has_region_monitor = true + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 1 +supports_mem2mem = true +max_priority = 9 + +[device.gpio] +support_status = "supported" +gpio_function = 1 +constant_0_input = 0x1f +constant_1_input = 0x1e +pins = [ + { pin = 0, analog = { 1 = "ADC1_CH0" }, rtc = { 0 = "RTC_GPIO0" } }, + { pin = 1, analog = { 1 = "ADC1_CH1" }, rtc = { 0 = "RTC_GPIO1" } }, + { pin = 2, functions = { 2 = "FSPIQ" }, limitations = ["strapping"], analog = { 1 = "ADC1_CH2" }, rtc = { 0 = "RTC_GPIO2" } }, + { pin = 3, analog = { 1 = "ADC1_CH3" }, rtc = { 0 = "RTC_GPIO3" } }, + { pin = 4, functions = { 0 = "MTMS", 2 = "FSPIHD" }, analog = { 1 = "ADC1_CH4" }, rtc = { 0 = "RTC_GPIO4" } }, + { pin = 5, functions = { 0 = "MTDI", 2 = "FSPIWP" }, analog = { 1 = "ADC2_CH0" }, rtc = { 0 = "RTC_GPIO5" } }, + { pin = 6, functions = { 0 = "MTCK", 2 = "FSPICLK" } }, + { pin = 7, functions = { 0 = "MTDO", 2 = "FSPID" } }, + { pin = 8, limitations = ["strapping"] }, + { pin = 9, limitations = ["strapping"] }, + { pin = 10, functions = { 2 = "FSPICS0" } }, + { pin = 11, limitations = ["spi_flash"] }, + { pin = 12, functions = { 0 = "SPIHD" }, limitations = ["spi_flash"] }, + { pin = 13, functions = { 0 = "SPIWP" }, limitations = ["spi_flash"] }, + { pin = 14, functions = { 0 = "SPICS0" }, limitations = ["spi_flash"] }, + { pin = 15, functions = { 0 = "SPICLK" }, limitations = ["spi_flash"] }, + { pin = 16, functions = { 0 = "SPID" }, limitations = ["spi_flash"] }, + { pin = 17, functions = { 0 = "SPIQ" }, limitations = ["spi_flash"] }, + { pin = 18, analog = { 0 = "USB_DM" } }, + { pin = 19, analog = { 0 = "USB_DP" } }, + { pin = 20, functions = { 0 = "U0RXD" } }, + { pin = 21, functions = { 0 = "U0TXD" } }, +] +input_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "U0RXD", id = 6 }, + { name = "U0CTS", id = 7 }, + { name = "U0DSR", id = 8 }, + { name = "U1RXD", id = 9 }, + { name = "U1CTS", id = 10 }, + { name = "U1DSR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SI_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "GPIO_BT_PRIORITY", id = 18 }, + { name = "GPIO_BT_ACTIVE", id = 19 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "EXT_ADC_START", id = 45 }, + { name = "RMT_SIG_0", id = 51 }, + { name = "RMT_SIG_1", id = 52 }, + { name = "I2CEXT0_SCL", id = 53 }, + { name = "I2CEXT0_SDA", id = 54 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "TWAI_RX", id = 74 }, + { name = "SIG_FUNC_97", id = 97 }, + { name = "SIG_FUNC_98", id = 98 }, + { name = "SIG_FUNC_99", id = 99 }, + { name = "SIG_FUNC_100", id = 100 }, + + { name = "MTCK" }, + { name = "MTMS" }, + { name = "MTDI" }, +] +output_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPICLK", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "U0TXD", id = 6 }, + { name = "U0RTS", id = 7 }, + { name = "U0DTR", id = 8 }, + { name = "U1TXD", id = 9 }, + { name = "U1RTS", id = 10 }, + { name = "U1DTR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SO_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "GPIO_WLAN_PRIO", id = 18 }, + { name = "GPIO_WLAN_ACTIVE", id = 19 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "USB_JTAG_TCK", id = 36 }, + { name = "USB_JTAG_TMS", id = 37 }, + { name = "USB_JTAG_TDI", id = 38 }, + { name = "USB_JTAG_TDO", id = 39 }, + { name = "LEDC_LS_SIG0", id = 45 }, + { name = "LEDC_LS_SIG1", id = 46 }, + { name = "LEDC_LS_SIG2", id = 47 }, + { name = "LEDC_LS_SIG3", id = 48 }, + { name = "LEDC_LS_SIG4", id = 49 }, + { name = "LEDC_LS_SIG5", id = 50 }, + { name = "RMT_SIG_0", id = 51 }, + { name = "RMT_SIG_1", id = 52 }, + { name = "I2CEXT0_SCL", id = 53 }, + { name = "I2CEXT0_SDA", id = 54 }, + { name = "GPIO_SD0", id = 55 }, + { name = "GPIO_SD1", id = 56 }, + { name = "GPIO_SD2", id = 57 }, + { name = "GPIO_SD3", id = 58 }, + { name = "I2SO_SD1", id = 59 }, + { name = "FSPICLK" , id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "FSPICS1", id = 69 }, + { name = "FSPICS3", id = 70 }, + { name = "FSPICS2", id = 71 }, + { name = "FSPICS4", id = 72 }, + { name = "FSPICS5", id = 73 }, + { name = "TWAI_TX", id = 74 }, + { name = "TWAI_BUS_OFF_ON", id = 75 }, + { name = "TWAI_CLKOUT", id = 76 }, + { name = "ANT_SEL0", id = 89 }, + { name = "ANT_SEL1", id = 90 }, + { name = "ANT_SEL2", id = 91 }, + { name = "ANT_SEL3", id = 92 }, + { name = "ANT_SEL4", id = 93 }, + { name = "ANT_SEL5", id = 94 }, + { name = "ANT_SEL6", id = 95 }, + { name = "ANT_SEL7", id = 96 }, + { name = "SIG_FUNC_97", id = 97 }, + { name = "SIG_FUNC_98", id = 98 }, + { name = "SIG_FUNC_99", id = 99 }, + { name = "SIG_FUNC_100", id = 100 }, + { name = "CLK_OUT1", id = 123 }, + { name = "CLK_OUT2", id = 124 }, + { name = "CLK_OUT3", id = 125 }, + { name = "SPICS1", id = 126 }, + { name = "USB_JTAG_TRST", id = 127 }, + { name = "GPIO", id = 128 }, + + { name = "MTDO" }, +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["CPU_GPIO_0", "CPU_GPIO_1", "CPU_GPIO_2", "CPU_GPIO_3", "CPU_GPIO_4", "CPU_GPIO_5", "CPU_GPIO_6", "CPU_GPIO_7"]] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, +] +has_fsm_timeouts = true +has_hw_bus_clear = true +ll_intr_mask = 0x3ffff +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +has_conf_update = true +has_arbitration_en = true +has_tx_fifo_watermark = true +bus_timeout_is_exponential = true + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 2 +software_interrupt_count = 4 +software_interrupt_delay = 5 +controller = { Riscv = { flavour = "basic", interrupts = 32, priority_levels = 15 } } + +[device.rmt] +support_status = "partial" +ram_start = 0x60016400 +channel_ram_size = 48 +channels = ["Tx", "Tx", "Rx", "Rx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_wrap = true +has_rx_demodulation = true +clock_sources.supported = [ "None", "Apb", "RcFast", "Xtal" ] +clock_sources.default = "Apb" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 384 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_app_interrupts = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +instances = [{ name = "timg0" }, { name = "timg1" }] +timg_has_divcnt_rst = true + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 + +[device.uhci] +support_status = "partial" + +[device.ds] +support_status = "not_supported" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +[device.hmac] + +## Interfaces +[device.i2s] +[device.ledc] +[device.twai] +[device.usb_serial_jtag] + +## Miscellaneous +[device.io_mux] +[device.temp_sensor] +[device.systimer] +[device.lp_timer] + +## Radio +[device.wifi] +mac_version = 1 + +[device.bt] +support_status = "partial" +controller = "btdm" + +[device.phy] +combo_module = true +backed_up_digital_register_count = 21 diff --git a/esp-metadata/devices/esp32c5.toml b/esp-metadata/devices/esp32c5.toml new file mode 100644 index 00000000000..3bfe33143e1 --- /dev/null +++ b/esp-metadata/devices/esp32c5.toml @@ -0,0 +1,706 @@ +# ESP32-C5 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-chip-support-table` to +# update the table in the esp-hal README. + +[device] +name = "esp32c5" +arch = "riscv" +target = "riscv32imac-unknown-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-c5_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 6 }, + { name = "ASSIST_DEBUG" }, + { name = "APB_SARADC", dma_peripheral = 8 }, + # { name = "AVC" }, + # { name = "BITSCRAMBLER" }, + # { name = "CACHE" }, + { name = "CLINT" }, + # { name = "CPU_APM" }, # TODO: verify we need it + { name = "DMA" }, + { name = "DS" }, + { name = "ECC" }, + { name = "ECDSA" }, + { name = "EFUSE", hidden = true }, + { name = "ETM", pac = "SOC_ETM" }, + { name = "GPIO" }, + { name = "GPIO_SD", pac = "GPIO_EXT" }, + # { name = "HINF" }, + { name = "HMAC" }, + { name = "HP_APM" }, + { name = "HP_SYS" }, + { name = "HUK" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 3 }, + { name = "IEEE802154", interrupts = { mac = "ZB_MAC" } }, + { name = "INTERRUPT_CORE0" }, + { name = "INTPRI" }, + { name = "IO_MUX" }, + { name = "KEYMNG" }, + # { name = "LEDC" }, # TODO: define later + { name = "LP_ANA" }, + { name = "LP_AON" }, + # { name = "LP_APM" }, + { name = "LP_APM0" }, + { name = "LP_CLKRST" }, + # { name = "LP_GPIO" }, # TODO: rename LP_IO? + # { name = "LP_I2C0" }, + { name = "LP_I2C_ANA_MST" }, + { name = "LP_IO_MUX" }, + { name = "LP_PERI", pac = "LPPERI" }, + { name = "LP_TEE" }, + { name = "LP_TIMER" }, + { name = "LP_UART" }, + { name = "LP_WDT" }, + { name = "LPWR", pac = "LP_CLKRST" }, + { name = "MCPWM0" }, + { name = "MEM_MONITOR" }, + { name = "MODEM_LPCON" }, + { name = "MODEM_SYSCON" }, + # { name = "MODEM0" }, + # { name = "MODEM1" }, + # { name = "MODEM_PWR0" }, + # { name = "MODEM_PWR1" }, + # { name = "OTP_DEBUG" }, + { name = "PARL_IO", interrupts = { tx = "PARL_IO_TX", rx = "PARL_IO_RX" }, dma_peripheral = 9 }, + { name = "PAU" }, + { name = "PCNT" }, + { name = "PCR" }, + { name = "PMU" }, + # { name = "PSRAM_MEM_MONITOR" }, + { name = "PVT_MONITOR", pac = "PVT" }, + { name = "RMT" }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "SLC" }, + # { name = "SLCHOST" }, TODO: add PAC manually later + # { name = "SPI0" }, + # { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 1, stable = true }, + { name = "SYSTEM", pac = "PCR" }, + { name = "SYSTIMER" }, + { name = "TEE" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + # { name = "TRACE0", pac = "TRACE" }, + # { name = "TWAI0" }, # TODO: too different from what we know + # { name = "TWAI1" }, # TODO: too different from what we know + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UHCI0", dma_peripheral = 2 }, + { name = "USB_DEVICE", interrupts = { peri = "USB_DEVICE" } }, + + { name = "DMA_CH0", virtual = true }, + { name = "DMA_CH1", virtual = true }, + { name = "DMA_CH2", virtual = true }, + + { name = "BT", virtual = true, interrupts = { mac = "BT_MAC", lp_timer = "LP_TIMER" } }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "LP_CORE", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "WIFI", virtual = true, interrupts = { bb = "WIFI_BB", mac = "WIFI_MAC", pwr = "WIFI_PWR" }, stable = true }, + { name = "MEM2MEM0", virtual = true, dma_peripheral = 0 }, + { name = "MEM2MEM1", virtual = true, dma_peripheral = 4 }, + { name = "MEM2MEM2", virtual = true, dma_peripheral = 5 }, + { name = "MEM2MEM3", virtual = true, dma_peripheral = 10 }, + { name = "MEM2MEM4", virtual = true, dma_peripheral = 11 }, + { name = "MEM2MEM5", virtual = true, dma_peripheral = 12 }, + { name = "MEM2MEM6", virtual = true, dma_peripheral = 13 }, + { name = "MEM2MEM7", virtual = true, dma_peripheral = 14 }, + { name = "MEM2MEM8", virtual = true, dma_peripheral = 15 }, +] + + +symbols = [ + # Additional peripherals defined by us (the developers): + # "lp_core", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", +] + +[device.soc] +cpu_has_branch_predictor = true +cpu_has_csr_pc = false +cpu_csr_prv_mode = 0x810 +rc_fast_clk_default = 17_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x4080_0000, end = 0x4086_0000 }, + { name = "dram2_uninit", start = 0, end = 0x4085_E5A0 }, # todo +] } + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "40, 48", output = "VALUE * 1_000_000", always_on = true }, # TODO: not user-configurable + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", output = "480_000_000" }, + { name = "RC_FAST_CLK", type = "source", output = "20_000_000" }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "OSC_SLOW_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "130_000" }, + # { name = "EXT32K_CLK", type = "source", output = "32768" }, # currently unimplemented + + # PLL divider taps + { name = "PLL_F12M", type = "divider", output = "PLL_CLK / 40" }, + { name = "PLL_F20M", type = "divider", output = "PLL_CLK / 24" }, + { name = "PLL_F40M", type = "divider", output = "PLL_CLK / 12" }, + { name = "PLL_F48M", type = "divider", output = "PLL_CLK / 10" }, + { name = "PLL_F60M", type = "divider", output = "PLL_CLK / 8" }, + { name = "PLL_F80M", type = "divider", output = "PLL_CLK / 6" }, + { name = "PLL_F120M", type = "divider", output = "PLL_CLK / 4" }, + { name = "PLL_F160M", type = "divider", output = "PLL_CLK / 3" }, + { name = "PLL_F240M", type = "divider", output = "PLL_CLK / 2" }, + + # HP SOC clock root (PCR_SOC_CLK_SEL: XTAL, RC_FAST, PLL_F160M, PLL_F240M) + { name = "HP_ROOT_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "PLL_F160M", outputs = "PLL_F160M" }, + { name = "PLL_F240M", outputs = "PLL_F240M" }, + ] }, + # CPU_CLK divider: divider = (CPU_DIV_NUM + 1), n*F_AHB + { name = "CPU_CLK", type = "divider", params = { divisor = "0..256" }, output = "HP_ROOT_CLK / (divisor + 1)", always_on = true }, + + # AHB_CLK divider: divider = (AHB_DIV_NUM + 1), <= 48MHz + { name = "AHB_CLK", type = "divider", params = { divisor = "0..256" }, output = "HP_ROOT_CLK / (divisor + 1)", always_on = true }, + + # APB_CLK divider: divider = (APB_DIV_NUM + 1) + { name = "APB_CLK", type = "divider", params = { divisor = "0..256" }, output = "AHB_CLK / (divisor + 1)", always_on = true }, + + # LP clocks (RTC domain) + { name = "XTAL_D2_CLK", type = "divider", output = "XTAL_CLK / 2" }, + { name = "LP_FAST_CLK", type = "mux", variants = [ + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL_D2", outputs = "XTAL_D2_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + { name = "LP_SLOW_CLK", type = "mux", variants = [ + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + #{ name = "EXT32K", outputs = "EXT32K_CLK" }, + { name = "OSC_SLOW", outputs = "OSC_SLOW_CLK" }, + ] }, + + # Global peripheral-related clocks + { name = "CRYPTO_CLK", type = "mux", variants = [ # PCR_SEC_CONF + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "FOSC", outputs = "RC_FAST_CLK" }, + { name = "PLL_F480M", outputs = "PLL_CLK" }, + ] }, + { name = "TIMG_CALIBRATION_CLOCK", type = "mux", variants = [ # PCR_32K_SEL / clk_tg_xtal32k + { name = "OSC_SLOW_CLK", outputs = "OSC_SLOW_CLK" }, + { name = "RC_SLOW_CLK", outputs = "RC_SLOW_CLK" }, + # EXT32K not modeled + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "default_clk_en_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "clk_en_template", value = "{{default_clk_en_template}}" }, + { name = "rst_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "conf_register", value = "{{peripheral}}_conf" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_field", value = "{{peripheral}}_rst_en" }, +], peripheral_clocks = [ + { name = "Uart0", template_params = { conf_register = "uart(0).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + ] }, + { name = "Uart1", template_params = { conf_register = "uart(1).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, keep_enabled = true, clocks = "Uart0" }, + { name = "Timg0", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup0_conf", peripheral = "tg0", peri_clk_template = "{{control}}::regs().timergroup0_timer_clk_conf().modify(|_, w| w.tg0_timer_clk_en().bit(enable));" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + ] }, + { name = "WDT_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + ] }, + ] }, + { name = "Timg1", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup1_conf", peripheral = "tg1", peri_clk_template = "{{control}}::regs().timergroup1_timer_clk_conf().modify(|_, w| w.tg1_timer_clk_en().bit(enable));" }, clocks = "Timg0" }, + { name = "I2cExt0", template_params = { peripheral = "i2c0" } }, + { name = "Pcnt" }, + { name = "Rmt", clocks = [ + { name = "SCLK", type = "mux", default = "PLL_F80M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + ] }, + ] }, + { name = "Systimer", keep_enabled = true }, # can be clocked from XTAL_CLK or RC_FAST_CLK, the latter has no divider? + { name = "ParlIo", template_params = { clk_en_field = "parl_clk_en", rst_field = "parl_rst_en" }, clocks = [ + { name = "RX_CLOCK", type = "mux", default = "PLL_F240M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F240M", outputs = "PLL_F240M" }, + # TODO: external clock + ] }, + { name = "TX_CLOCK", type = "mux", default = "PLL_F240M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F240M", outputs = "PLL_F240M" }, + ] }, + ] }, + { name = "Dma", template_params = { peripheral = "gdma" } }, + { name = "Uhci0", template_params = { peripheral = "uhci" } }, + { name = "Spi2" }, + { name = "Aes" }, + { name = "Sha" }, + { name = "Rsa" }, + { name = "Ecc" }, + { name = "UsbDevice", keep_enabled = true }, +] } } + +[device.adc] +support_status = "not_supported" + +# TODO: define functionality for PSEUDO register +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = false +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 2 +separate_in_out_interrupts = true +supports_mem2mem = true +max_priority = 5 +# TODO: DMA can access flash +# TODO: weight-based arbitration + +[device.ecc] +support_status = "partial" +working_modes = [ + { id = 0, mode = "affine_point_multiplication" }, + { id = 2, mode = "affine_point_verification" }, + { id = 3, mode = "affine_point_verification_and_multiplication" }, + { id = 4, mode = "jacobian_point_multiplication" }, + { id = 5, mode = "affine_point_addition" }, + { id = 6, mode = "jacobian_point_verification" }, + { id = 7, mode = "affine_point_verification_and_jacobian_point_multiplication" }, + { id = 8, mode = "modular_addition" }, + { id = 9, mode = "modular_subtraction" }, + { id = 10, mode = "modular_multiplication" }, + { id = 11, mode = "modular_division" } +] +curves = [ + { id = 0, curve = 192 }, + { id = 1, curve = 256 }, + { id = 2, curve = 384 } +] +separate_jacobian_point_memory = true +has_memory_clock_gate = true +supports_enhanced_security = true + +[device.gpio] +support_status = "partial" +gpio_function = 1 +constant_0_input = 0x60 +constant_1_input = 0x40 +pins = [ + { pin = 0, analog = { 0 = "XTAL_32K_P" }, lp = { 0 = "LP_UART_DTRN", 1 = "LP_GPIO0" } }, + { pin = 1, analog = { 0 = "XTAL_32K_N", 1 = "ADC1_CH0" }, lp = { 0 = "LP_UART_DSRN", 1 = "LP_GPIO1" } }, + { pin = 2, functions = { 0 = "MTMS", 2 = "FSPIQ" }, limitations = ["strapping"], analog = { 1 = "ADC1_CH1" }, lp = { 0 = "LP_UART_RTSN", 1 = "LP_GPIO2", 3 = "LP_I2C_SDA" } }, + { pin = 3, functions = { 0 = "MTDI" }, limitations = ["strapping"], analog = { 1 = "ADC1_CH2" }, lp = { 0 = "LP_UART_CTSN", 1 = "LP_GPIO3", 3 = "LP_I2C_SCL" } }, + { pin = 4, functions = { 0 = "MTCK", 2 = "FSPIHD" }, analog = { 1 = "ADC1_CH3" }, lp = { 0 = "LP_UART_RXD_PAD", 1 = "LP_GPIO4" } }, + { pin = 5, functions = { 0 = "MTDO", 2 = "FSPIWP" }, analog = { 1 = "ADC1_CH4" }, lp = { 0 = "LP_UART_TXD_PAD", 1 = "LP_GPIO5" } }, + { pin = 6, functions = { 2 = "FSPICLK" }, analog = { 1 = "ADC1_CH5" }, lp = { 1 = "LP_GPIO6" } }, + { pin = 7, functions = { 0 = "SDIO_DATA1", 2 = "FSPID" }, limitations = ["strapping"] }, + { pin = 8, functions = { 0 = "SDIO_DATA0" }, analog = { 0 = "ZCD0" } }, + { pin = 9, functions = { 0 = "SDIO_CLK" }, analog = { 0 = "ZCD1" } }, + { pin = 10, functions = { 0 = "SDIO_CMD", 2 = "FSPICS0" } }, + { pin = 11, functions = { 0 = "U0TXD" } }, + { pin = 12, functions = { 0 = "U0RXD" } }, + { pin = 13, functions = { 0 = "SDIO_DATA3" }, analog = { 0 = "USB_DM" } }, + { pin = 14, functions = { 0 = "SDIO_DATA2" }, analog = { 0 = "USB_DP" } }, + { pin = 23 }, + { pin = 24 }, + { pin = 25, limitations = ["strapping"] }, + { pin = 26, limitations = ["strapping"] }, + { pin = 27, limitations = ["strapping"] }, + { pin = 28, limitations = ["strapping"] }, +] +input_signals = [ + { name = "U0RXD", id = 6 }, + { name = "U0CTS", id = 7 }, + { name = "U0DSR", id = 8 }, + { name = "U1RXD", id = 9 }, + { name = "U1CTS", id = 10 }, + { name = "U1DSR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SI_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "CPU_GPIO_0", id = 27 }, + { name = "CPU_GPIO_1", id = 28 }, + { name = "CPU_GPIO_2", id = 29 }, + { name = "CPU_GPIO_3", id = 30 }, + { name = "CPU_GPIO_4", id = 31 }, + { name = "CPU_GPIO_5", id = 32 }, + { name = "CPU_GPIO_6", id = 33 }, + { name = "CPU_GPIO_7", id = 34 }, + { name = "USB_JTAG_TDO_BRIDGE", id = 35 }, + { name = "I2CEXT0_SCL", id = 46 }, + { name = "I2CEXT0_SDA", id = 47 }, + { name = "PARL_RX_DATA0", id = 48 }, + { name = "PARL_RX_DATA1", id = 49 }, + { name = "PARL_RX_DATA2", id = 50 }, + { name = "PARL_RX_DATA3", id = 51 }, + { name = "PARL_RX_DATA4", id = 52 }, + { name = "PARL_RX_DATA5", id = 53 }, + { name = "PARL_RX_DATA6", id = 54 }, + { name = "PARL_RX_DATA7", id = 55 }, + { name = "FSPICLK", id = 56 }, + { name = "FSPIQ", id = 57 }, + { name = "FSPID", id = 58 }, + { name = "FSPIHD", id = 59 }, + { name = "FSPIWP", id = 60 }, + { name = "FSPICS0", id = 61 }, + { name = "PARL_RX_CLK", id = 62 }, + { name = "PARL_TX_CLK", id = 63 }, + { name = "RMT_SIG_0", id = 64 }, + { name = "RMT_SIG_1", id = 65 }, + { name = "TWAI0_RX", id = 66 }, + { name = "TWAI1_RX", id = 70 }, + { name = "PCNT0_RST", id = 76 }, + { name = "PCNT1_RST", id = 77 }, + { name = "PCNT2_RST", id = 78 }, + { name = "PCNT3_RST", id = 79 }, + { name = "PWM0_SYNC0", id = 80 }, + { name = "PWM0_SYNC1", id = 81 }, + { name = "PWM0_SYNC2", id = 82 }, + { name = "PWM0_F0", id = 83 }, + { name = "PWM0_F1", id = 84 }, + { name = "PWM0_F2", id = 85 }, + { name = "PWM0_CAP0", id = 86 }, + { name = "PWM0_CAP1", id = 87 }, + { name = "PWM0_CAP2", id = 88 }, + { name = "SIG_IN_FUNC97", id = 97 }, + { name = "SIG_IN_FUNC98", id = 98 }, + { name = "SIG_IN_FUNC99", id = 99 }, + { name = "SIG_IN_FUNC100", id = 100 }, + { name = "PCNT0_SIG_CH0", id = 101 }, + { name = "PCNT0_SIG_CH1", id = 102 }, + { name = "PCNT0_CTRL_CH0", id = 103 }, + { name = "PCNT0_CTRL_CH1", id = 104 }, + { name = "PCNT1_SIG_CH0", id = 105 }, + { name = "PCNT1_SIG_CH1", id = 106 }, + { name = "PCNT1_CTRL_CH0", id = 107 }, + { name = "PCNT1_CTRL_CH1", id = 108 }, + { name = "PCNT2_SIG_CH0", id = 109 }, + { name = "PCNT2_SIG_CH1", id = 110 }, + { name = "PCNT2_CTRL_CH0", id = 111 }, + { name = "PCNT2_CTRL_CH1", id = 112 }, + { name = "PCNT3_SIG_CH0", id = 113 }, + { name = "PCNT3_SIG_CH1", id = 114 }, + { name = "PCNT3_CTRL_CH0", id = 115 }, + { name = "PCNT3_CTRL_CH1", id = 116 }, + + { name = "SDIO_CLK" }, + { name = "SDIO_CMD" }, + { name = "SDIO_DATA0" }, + { name = "SDIO_DATA1" }, + { name = "SDIO_DATA2" }, + { name = "SDIO_DATA3" }, + + { name = "MTDI" }, + { name = "MTDO" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "LEDC_LS_SIG0", id = 0 }, + { name = "LEDC_LS_SIG1", id = 1 }, + { name = "LEDC_LS_SIG2", id = 2 }, + { name = "LEDC_LS_SIG3", id = 3 }, + { name = "LEDC_LS_SIG4", id = 4 }, + { name = "LEDC_LS_SIG5", id = 5 }, + { name = "U0TXD", id = 6 }, + { name = "U0RTS", id = 7 }, + { name = "U0DTR", id = 8 }, + { name = "U1TXD", id = 9 }, + { name = "U1RTS", id = 10 }, + { name = "U1DTR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SO_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "I2SO_SD1", id = 18 }, + { name = "CPU_GPIO_0", id = 27 }, + { name = "CPU_GPIO_1", id = 28 }, + { name = "CPU_GPIO_2", id = 29 }, + { name = "CPU_GPIO_3", id = 30 }, + { name = "CPU_GPIO_4", id = 31 }, + { name = "CPU_GPIO_5", id = 32 }, + { name = "CPU_GPIO_6", id = 33 }, + { name = "CPU_GPIO_7", id = 34 }, + { name = "I2CEXT0_SCL", id = 46 }, + { name = "I2CEXT0_SDA", id = 47 }, + { name = "PARL_TX_DATA0", id = 48 }, + { name = "PARL_TX_DATA1", id = 49 }, + { name = "PARL_TX_DATA2", id = 50 }, + { name = "PARL_TX_DATA3", id = 51 }, + { name = "PARL_TX_DATA4", id = 52 }, + { name = "PARL_TX_DATA5", id = 53 }, + { name = "PARL_TX_DATA6", id = 54 }, + { name = "PARL_TX_DATA7", id = 55 }, + { name = "FSPICLK", id = 56 }, + { name = "FSPIQ", id = 57 }, + { name = "FSPID", id = 58 }, + { name = "FSPIHD", id = 59 }, + { name = "FSPIWP", id = 60 }, + { name = "FSPICS0", id = 61 }, + { name = "PARL_RX_CLK", id = 62 }, + { name = "PARL_TX_CLK", id = 63 }, + { name = "RMT_SIG_0", id = 64 }, + { name = "RMT_SIG_1", id = 65 }, + { name = "TWAI0_TX", id = 66 }, + { name = "TWAI0_BUS_OFF_ON", id = 67 }, + { name = "TWAI0_CLKOUT", id = 68 }, + { name = "TWAI0_STANDBY", id = 69 }, + { name = "TWAI1_TX", id = 70 }, + { name = "TWAI1_BUS_OFF_ON", id = 71 }, + { name = "TWAI1_CLKOUT", id = 72 }, + { name = "TWAI1_STANDBY", id = 73 }, + { name = "GPIO_SD0", id = 76 }, + { name = "GPIO_SD1", id = 77 }, + { name = "GPIO_SD2", id = 78 }, + { name = "GPIO_SD3", id = 79 }, + { name = "PWM0_0A", id = 80 }, + { name = "PWM0_0B", id = 81 }, + { name = "PWM0_1A", id = 82 }, + { name = "PWM0_1B", id = 83 }, + { name = "PWM0_2A", id = 84 }, + { name = "PWM0_2B", id = 85 }, + { name = "PARL_TX_CS", id = 86 }, + { name = "SIG_IN_FUNC97", id = 97 }, + { name = "SIG_IN_FUNC98", id = 98 }, + { name = "SIG_IN_FUNC99", id = 99 }, + { name = "SIG_IN_FUNC100", id = 100 }, + { name = "FSPICS1", id = 101 }, + { name = "FSPICS2", id = 102 }, + { name = "FSPICS3", id = 103 }, + { name = "FSPICS4", id = 104 }, + { name = "FSPICS5", id = 105 }, + { name = "GPIO", id = 256 } +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["CPU_GPIO_0", "CPU_GPIO_1", "CPU_GPIO_2", "CPU_GPIO_3", "CPU_GPIO_4", "CPU_GPIO_5", "CPU_GPIO_6", "CPU_GPIO_7"]] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, +] +has_fsm_timeouts = true # why not S2? +# has_hw_bus_clear = true +ll_intr_mask = 0x3ffff +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +can_estimate_nack_reason = true # not documented +has_conf_update = true +has_reliable_fsm_reset = true +has_arbitration_en = true +has_tx_fifo_watermark = true # why not s2 +bus_timeout_is_exponential = true + +[device.i2c_slave] +support_status = "not_supported" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 384 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2 } + +[device.interrupts] +support_status = "partial" +status_registers = 3 +software_interrupt_count = 4 +software_interrupt_delay = 24 # CPU speed dependent +controller = { Riscv = { flavour = "clic", interrupts = 48, priority_levels = 8 } } + +[device.rmt] +support_status = "partial" +ram_start = 0x60006400 +channel_ram_size = 48 +channels = ["Tx", "Tx", "Rx", "Rx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_loop_auto_stop = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_wrap = true +has_rx_demodulation = true +clock_sources.supported = ["Xtal", "RcFast", "Pll80MHz"] +clock_sources.default = "Pll80MHz" + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_app_interrupts = true +has_dma_segmented_transfer = true +has_clk_pre_div = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +instances = [{ name = "timg0" }, { name = "timg1" }] +timg_has_divcnt_rst = true + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 +peripheral_controls_mem_clk = true + +[device.uhci] +support_status = "partial" +combined_uart_selector_field = true + +[device.bit_scrambler] +support_status = "not_supported" + +[device.assist_debug] +support_status = "partial" +has_sp_monitor = true +has_region_monitor = true + +[device.avc] +support_status = "not_supported" + +[device.twai] +support_status = "not_supported" + +[device.key_manager] +support_status = "not_supported" + +[device.touch] +support_status = "not_supported" + +[device.ds] +support_status = "not_supported" + +[device.lp_uart] +support_status = "not_supported" +ram_size = 32 + +[device.lp_i2c_master] +support_status = "partial" +fifo_size = 16 + +[device.rng] +support_status = "partial" +trng_supported = false # TODO +apb_cycle_wait_num = 16 # TODO + +## Radio +[device.wifi] +mac_version = 3 +has_5g = true + +[device.bt] +support_status = "partial" +controller = "npl" + +[device.ieee802154] + +[device.phy] +support_status = "partial" +combo_module = true + +# ## Miscellaneous +[device.systimer] +[device.pcnt] +[device.usb_serial_jtag] + +[device.parl_io] +support_status = "partial" +version = 2 +# TODO: model chip select signal (active low) + +[device.ledc] +support_status = "not_supported" + +[device.mcpwm] +support_status = "not_supported" + +[device.sleep] +support_status = "not_supported" + +[device.etm] +support_status = "not_supported" + +[device.hmac] +support_status = "not_supported" + +# [device.ecdsa] +# support_status = "not_supported" + +[device.i2s] +support_status = "not_supported" + +[device.io_mux] + +[device.psram] +support_status = "not_supported" + +[device.sd_slave] +support_status = "not_supported" + +[device.ulp_riscv] +support_status = "not_supported" + +[device.lp_timer] +support_status = "not_supported" + +[device.temp_sensor] +support_status = "not_supported" diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml new file mode 100644 index 00000000000..c442a90d29f --- /dev/null +++ b/esp-metadata/devices/esp32c6.toml @@ -0,0 +1,773 @@ +# ESP32-C6 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32c6" +arch = "riscv" +target = "riscv32imac-unknown-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 6 }, + { name = "APB_SARADC", dma_peripheral = 8 }, + { name = "ASSIST_DEBUG" }, + { name = "ATOMIC" }, + { name = "DMA" }, + { name = "DS" }, + { name = "ECC" }, + { name = "EFUSE", hidden = true }, + { name = "EXTMEM" }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HINF" }, + { name = "HMAC" }, + { name = "HP_APM" }, + { name = "HP_SYS" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 3 }, + { name = "IEEE802154", interrupts = { mac = "ZB_MAC" } }, + { name = "INTERRUPT_CORE0" }, + { name = "INTPRI" }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "LP_ANA" }, + { name = "LP_AON" }, + { name = "LP_APM" }, + { name = "LP_APM0" }, + { name = "LP_CLKRST" }, + { name = "LP_I2C0" }, + { name = "LP_I2C_ANA_MST" }, + { name = "LP_IO" }, + { name = "LP_PERI" }, + { name = "LP_TEE" }, + { name = "LP_TIMER" }, + { name = "LP_UART" }, + { name = "LP_WDT" }, + { name = "LPWR", pac = "LP_CLKRST" }, + { name = "MCPWM0" }, + { name = "MEM_MONITOR" }, + { name = "MODEM_LPCON" }, + { name = "MODEM_SYSCON" }, + { name = "OTP_DEBUG" }, + { name = "PARL_IO", interrupts = { peri = "PARL_IO" }, dma_peripheral = 9 }, + { name = "PAU" }, + { name = "PCNT" }, + { name = "PCR" }, + { name = "PLIC_MX" }, + { name = "PMU" }, + { name = "RMT" }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "SLCHOST" }, + { name = "ETM", pac = "SOC_ETM" }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 0, stable = true }, + { name = "SYSTEM", pac = "PCR" }, + { name = "SYSTIMER" }, + { name = "TEE" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TRACE0", pac = "TRACE" }, + { name = "TWAI0" }, + { name = "TWAI1" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UHCI0", dma_peripheral = 2 }, + { name = "USB_DEVICE", interrupts = { peri = "USB_DEVICE" } }, + + { name = "DMA_CH0", virtual = true }, + { name = "DMA_CH1", virtual = true }, + { name = "DMA_CH2", virtual = true }, + + { name = "ADC1", virtual = true }, + { name = "BT", virtual = true, interrupts = { mac = "BT_MAC", lp_timer = "LP_TIMER" } }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "LP_CORE", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "TSENS", virtual = true }, + { name = "WIFI", virtual = true, interrupts = { bb = "WIFI_BB", mac = "WIFI_MAC", pwr = "WIFI_PWR", modem_peri_timeout = "MODEM_PERI_TIMEOUT" }, stable = true }, + { name = "MEM2MEM0", virtual = true, dma_peripheral = 1 }, + { name = "MEM2MEM1", virtual = true, dma_peripheral = 4 }, + { name = "MEM2MEM2", virtual = true, dma_peripheral = 5 }, + { name = "MEM2MEM3", virtual = true, dma_peripheral = 10 }, + { name = "MEM2MEM4", virtual = true, dma_peripheral = 11 }, + { name = "MEM2MEM5", virtual = true, dma_peripheral = 12 }, + { name = "MEM2MEM6", virtual = true, dma_peripheral = 13 }, + { name = "MEM2MEM7", virtual = true, dma_peripheral = 14 }, + { name = "MEM2MEM8", virtual = true, dma_peripheral = 15 }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "lp_core", + "swd", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + + # Wakeup SOC based on ESP-IDF: + "pm_support_wifi_wakeup", + "pm_support_beacon_wakeup", + "pm_support_bt_wakeup", + "gpio_support_deepsleep_wakeup", + "uart_support_wakeup_int", + "pm_support_ext1_wakeup", +] + +[device.soc] +cpu_has_csr_pc = true +cpu_csr_prv_mode = 0xC10 +rc_fast_clk_default = 17_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x4080_0000, end = 0x4088_0000 }, + { name = "dram2_uninit", start = 0x4086_E610, end = 0x4087_E610 }, +] } + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "40", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", output = "480_000_000" }, + { name = "RC_FAST_CLK", type = "source", output = "17_500_000" }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "OSC_SLOW_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "136_000" }, + + { name = "SOC_ROOT_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK", configures = ["HP_ROOT_CLK = 1", "CPU_CLK = LS", "AHB_CLK = LS", "MSPI_FAST_CLK = LS"] }, + { name = "RC_FAST", outputs = "RC_FAST_CLK", configures = ["HP_ROOT_CLK = 1", "CPU_CLK = LS", "AHB_CLK = LS", "MSPI_FAST_CLK = LS"] }, + { name = "PLL", outputs = "PLL_CLK", configures = ["HP_ROOT_CLK = 3", "CPU_CLK = HS", "AHB_CLK = HS", "MSPI_FAST_CLK = HS"] }, + ] }, + { name = "HP_ROOT_CLK", type = "divider", params = { divisor = "1, 3" }, output = "SOC_ROOT_CLK / divisor" }, # AUTO_DIV, only divides when using PLL_CLK + + # TODO: model by-4 divider for 120MHz CPU clock + { name = "CPU_HS_DIV", type = "divider", params = { divisor = "0, 1, 3" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "CPU_LS_DIV", type = "divider", params = { divisor = "0, 1, 3, 7, 15, 31" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + { name = "HS", outputs = "CPU_HS_DIV" }, + { name = "LS", outputs = "CPU_LS_DIV" }, + ] }, + + { name = "AHB_HS_DIV", type = "divider", params = { divisor = "3, 7, 15" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "AHB_LS_DIV", type = "divider", params = { divisor = "0, 1, 3, 7, 15, 31" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "AHB_CLK", type = "mux", always_on = true, variants = [ + { name = "HS", outputs = "AHB_HS_DIV" }, + { name = "LS", outputs = "AHB_LS_DIV" }, + ] }, + { name = "APB_CLK", type = "divider", params = { divisor = "0, 1, 3" }, output = "AHB_CLK / (divisor + 1)" }, + # configurable, but automatic - there's no point in making APB_DECREASE_DIV part of the model as consumers will always see APB_CLK + # { name = "APB_DECREASE_DIV", type = "divider", output = "APB_DIV / (divisor + 1)" }, + + { name = "MSPI_FAST_HS_CLK", type = "divider", params = { divisor = "3, 4, 5" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "MSPI_FAST_LS_CLK", type = "divider", params = { divisor = "0, 1, 2" }, output = "HP_ROOT_CLK / (divisor + 1)" }, + { name = "MSPI_FAST_CLK", type = "mux", variants = [ + { name = "HS", outputs = "MSPI_FAST_HS_CLK" }, + { name = "LS", outputs = "MSPI_FAST_LS_CLK" }, + ] }, + + { name = "PLL_F48M", type = "derived", from = "PLL_CLK", output = "48_000_000" }, + { name = "PLL_F80M", type = "derived", from = "PLL_CLK", output = "80_000_000" }, + { name = "PLL_F160M", type = "derived", from = "PLL_CLK", output = "160_000_000" }, + { name = "PLL_F240M", type = "derived", from = "PLL_CLK", output = "240_000_000" }, + { name = "LEDC_SCLK", type = "mux", variants = [ + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + + # LP clocks + { name = "XTAL_D2_CLK", type = "divider", output = "XTAL_CLK / 2" }, + { name = "LP_FAST_CLK", type = "mux", variants = [ + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_D2_CLK", outputs = "XTAL_D2_CLK" }, + ] }, + { name = "LP_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "OSC_SLOW", outputs = "OSC_SLOW_CLK" }, + ] }, + # LP_DYN_SLOW_CLK is LP_SLOW_CLK + # LP_DYN_FAST_CLK is automatically managed based on power mode. Not sure how to model it, some LP peripherals need it. +] }, peripheral_clocks = { templates = [ + # templates + { name = "default_clk_en_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "clk_en_template", value = "{{default_clk_en_template}}" }, # Indirection allows appending to the template for TWAI + { name = "rst_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "conf_register", value = "{{peripheral}}_conf" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_field", value = "{{peripheral}}_rst_en" }, + # {{peripheral}} is derived automatically (e.g. SpiDma -> spi_dma) +], peripheral_clocks = [ + # This list is based on TRM (v1.1) - 8.4.1 PCR Registers + { name = "Uart0", template_params = { conf_register = "uart(0).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Uart1", template_params = { conf_register = "uart(1).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, clocks = "Uart0" }, + { name = "I2cExt0", template_params = { peripheral = "i2c0" } }, + { name = "Uhci0", template_params = { peripheral = "uhci" } }, + { name = "Rmt", clocks = [ + { name = "SCLK", type = "mux", default = "PLL_F80M", variants = [ + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Ledc" }, + { name = "Timg0", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup0_conf", peripheral = "tg0", peri_clk_template = "{{control}}::regs().timergroup0_timer_clk_conf().modify(|_, w| w.tg0_timer_clk_en().bit(enable));" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", variants = [ + { name = "RC_SLOW_CLK", outputs = "LP_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + { name = "WDT_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + ] }, + ] }, + { name = "Timg1", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup1_conf", peripheral = "tg1", peri_clk_template = "{{control}}::regs().timergroup1_timer_clk_conf().modify(|_, w| w.tg1_timer_clk_en().bit(enable));" }, clocks = "Timg0" }, + { name = "Systimer", keep_enabled = true }, + { name = "Twai0", template_params = { clk_en_template = "{{default_clk_en_template}} {{func_clk_template}}", func_clk_template = "{{control}}::regs().twai0_func_clk_conf().modify(|_, w| w.twai0_func_clk_en().bit(enable));" } }, + { name = "Twai1", template_params = { clk_en_template = "{{default_clk_en_template}} {{func_clk_template}}", func_clk_template = "{{control}}::regs().twai1_func_clk_conf().modify(|_, w| w.twai1_func_clk_en().bit(enable));" } }, + { name = "I2s0", template_params = { peripheral = "i2s" } }, + { name = "ApbSarAdc", template_params = { peripheral = "saradc", clk_en_field = "saradc_reg_clk_en", rst_field = "saradc_reg_rst_en" }, keep_enabled = true }, # used by some wifi calibration steps. + { name = "Tsens", template_params = { conf_register = "tsens_clk_conf" } }, + { name = "UsbDevice", keep_enabled = true }, + # { name = "Intmtx" }, + { name = "Pcnt" }, + { name = "Etm" }, + { name = "Mcpwm0", template_params = { peripheral = "pwm" }, clocks = [ + # TODO: MCPWM divider? + { name = "FUNCTION_CLOCK", type = "mux", default = "PLL_F160M", variants = [ + { name = "PLL_F160M", outputs = "PLL_F160M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "ParlIo", template_params = { clk_en_field = "parl_clk_en", rst_field = "parl_rst_en" }, clocks = [ + { name = "RX_CLOCK", type = "mux", default = "PLL_F240M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F240M", outputs = "PLL_F240M" }, + # TODO: external clock + ] }, + { name = "TX_CLOCK", type = "mux", default = "PLL_F240M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F240M", outputs = "PLL_F240M" }, + ] }, + ] }, + { name = "SdioSlave" }, + { name = "Dma", template_params = { peripheral = "gdma" } }, + { name = "Spi2" }, + { name = "Aes" }, + { name = "Sha" }, + { name = "Rsa" }, + { name = "Ecc" }, + { name = "Ds" }, + { name = "Hmac" }, + #{ name = "Iomux" }, + #{ name = "MemMonitor" }, + { name = "Trace0", template_params = { peripheral = "trace" } }, + #{ name = "Assist" }, + #{ name = "Cache" }, + #{ name = "ModemApb" }, + #{ name = "Timeout" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = false +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.assist_debug] +support_status = "partial" +has_sp_monitor = true +has_region_monitor = true + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 1 +separate_in_out_interrupts = true +supports_mem2mem = true +max_priority = 9 + +[device.ecc] +support_status = "partial" +working_modes = [ + { id = 0, mode = "affine_point_multiplication" }, + { id = 2, mode = "affine_point_verification" }, + { id = 3, mode = "affine_point_verification_and_multiplication" }, + { id = 4, mode = "jacobian_point_multiplication" }, + { id = 6, mode = "jacobian_point_verification" }, + { id = 7, mode = "affine_point_verification_and_jacobian_point_multiplication" } +] +curves = [ + { id = 0, curve = 192 }, + { id = 1, curve = 256 } +] +zero_extend_writes = true +has_memory_clock_gate = true + +[device.gpio] +support_status = "supported" +gpio_function = 1 +constant_0_input = 0x3c +constant_1_input = 0x38 +pins = [ + { pin = 0, analog = { 0 = "XTAL_32K_P", 1 = "ADC0_CH0" }, lp = { 0 = "LP_GPIO0", 1 = "LP_UART_DTRN" } }, + { pin = 1, analog = { 0 = "XTAL_32K_N", 1 = "ADC0_CH1" }, lp = { 0 = "LP_GPIO1", 1 = "LP_UART_DSRN" } }, + { pin = 2, functions = { 2 = "FSPIQ" }, analog = { 1 = "ADC0_CH2" }, lp = { 0 = "LP_GPIO2", 1 = "LP_UART_RTSN" } }, + { pin = 3, analog = { 1 = "ADC0_CH3" }, lp = { 0 = "LP_GPIO3", 1 = "LP_UART_CTSN" } }, + { pin = 4, functions = { 0 = "MTMS", 2 = "FSPIHD" }, limitations = ["strapping"], analog = { 1 = "ADC0_CH4" }, lp = { 0 = "LP_GPIO4", 1 = "LP_UART_RXD" } }, + { pin = 5, functions = { 0 = "MTDI", 2 = "FSPIWP" }, limitations = ["strapping"], analog = { 1 = "ADC0_CH5" }, lp = { 0 = "LP_GPIO5", 1 = "LP_UART_TXD" } }, + { pin = 6, functions = { 0 = "MTCK", 2 = "FSPICLK" }, analog = { 1 = "ADC0_CH6" }, lp = { 0 = "LP_GPIO6", 1 = "LP_I2C_SDA" } }, + { pin = 7, functions = { 0 = "MTDO", 2 = "FSPID" }, lp = { 0 = "LP_GPIO7", 1 = "LP_I2C_SCL" } }, + { pin = 8, limitations = ["strapping"] }, + { pin = 9, limitations = ["strapping"] }, + { pin = 10 }, + { pin = 11 }, + { pin = 12, analog = { 0 = "USB_DM" } }, + { pin = 13, analog = { 0 = "USB_DP" } }, + { pin = 14 }, + { pin = 15, limitations = ["strapping"] }, + { pin = 16, functions = { 0 = "U0TXD", 2 = "FSPICS0" } }, + { pin = 17, functions = { 0 = "U0RXD", 2 = "FSPICS1" } }, + { pin = 18, functions = { 0 = "SDIO_CMD", 2 = "FSPICS2" } }, + { pin = 19, functions = { 0 = "SDIO_CLK", 2 = "FSPICS3" } }, + { pin = 20, functions = { 0 = "SDIO_DATA0", 2 = "FSPICS4" } }, + { pin = 21, functions = { 0 = "SDIO_DATA1", 2 = "FSPICS5" } }, + { pin = 22, functions = { 0 = "SDIO_DATA2" } }, + { pin = 23, functions = { 0 = "SDIO_DATA3" } }, + { pin = 24, functions = { 0 = "SPICS0" }, limitations = ["spi_flash"] }, + { pin = 25, functions = { 0 = "SPIQ" }, limitations = ["spi_flash"] }, + { pin = 26, functions = { 0 = "SPIWP" }, limitations = ["spi_flash"] }, + { pin = 27, limitations = ["spi_flash"] }, + { pin = 28, functions = { 0 = "SPIHD" }, limitations = ["spi_flash"] }, + { pin = 29, functions = { 0 = "SPICLK" }, limitations = ["spi_flash"] }, + { pin = 30, functions = { 0 = "SPID" }, limitations = ["spi_flash"] }, +] +input_signals = [ + { name = "EXT_ADC_START", id = 0 }, + { name = "U0RXD", id = 6 }, + { name = "U0CTS", id = 7 }, + { name = "U0DSR", id = 8 }, + { name = "U1RXD", id = 9 }, + { name = "U1CTS", id = 10 }, + { name = "U1DSR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SI_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "USB_JTAG_TDO_BRIDGE", id = 19 }, + { name = "CPU_TESTBUS0", id = 20 }, + { name = "CPU_TESTBUS1", id = 21 }, + { name = "CPU_TESTBUS2", id = 22 }, + { name = "CPU_TESTBUS3", id = 23 }, + { name = "CPU_TESTBUS4", id = 24 }, + { name = "CPU_TESTBUS5", id = 25 }, + { name = "CPU_TESTBUS6", id = 26 }, + { name = "CPU_TESTBUS7", id = 27 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "USB_JTAG_TMS", id = 37 }, + { name = "USB_EXTPHY_OEN", id = 40 }, + { name = "USB_EXTPHY_VM", id = 41 }, + { name = "USB_EXTPHY_VPO", id = 42 }, + { name = "I2CEXT0_SCL", id = 45 }, + { name = "I2CEXT0_SDA", id = 46 }, + { name = "PARL_RX_DATA0", id = 47 }, + { name = "PARL_RX_DATA1", id = 48 }, + { name = "PARL_RX_DATA2", id = 49 }, + { name = "PARL_RX_DATA3", id = 50 }, + { name = "PARL_RX_DATA4", id = 51 }, + { name = "PARL_RX_DATA5", id = 52 }, + { name = "PARL_RX_DATA6", id = 53 }, + { name = "PARL_RX_DATA7", id = 54 }, + { name = "PARL_RX_DATA8", id = 55 }, + { name = "PARL_RX_DATA9", id = 56 }, + { name = "PARL_RX_DATA10", id = 57 }, + { name = "PARL_RX_DATA11", id = 58 }, + { name = "PARL_RX_DATA12", id = 59 }, + { name = "PARL_RX_DATA13", id = 60 }, + { name = "PARL_RX_DATA14", id = 61 }, + { name = "PARL_RX_DATA15", id = 62 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "PARL_RX_CLK", id = 69 }, + { name = "PARL_TX_CLK", id = 70 }, + { name = "RMT_SIG_0", id = 71 }, + { name = "RMT_SIG_1", id = 72 }, + { name = "TWAI0_RX", id = 73 }, + { name = "TWAI1_RX", id = 77 }, + { name = "PWM0_SYNC0", id = 87 }, + { name = "PWM0_SYNC1", id = 88 }, + { name = "PWM0_SYNC2", id = 89 }, + { name = "PWM0_F0", id = 90 }, + { name = "PWM0_F1", id = 91 }, + { name = "PWM0_F2", id = 92 }, + { name = "PWM0_CAP0", id = 93 }, + { name = "PWM0_CAP1", id = 94 }, + { name = "PWM0_CAP2", id = 95 }, + { name = "SIG_IN_FUNC97", id = 97 }, + { name = "SIG_IN_FUNC98", id = 98 }, + { name = "SIG_IN_FUNC99", id = 99 }, + { name = "SIG_IN_FUNC100", id = 100 }, + { name = "PCNT0_SIG_CH0", id = 101 }, + { name = "PCNT0_SIG_CH1", id = 102 }, + { name = "PCNT0_CTRL_CH0", id = 103 }, + { name = "PCNT0_CTRL_CH1", id = 104 }, + { name = "PCNT1_SIG_CH0", id = 105 }, + { name = "PCNT1_SIG_CH1", id = 106 }, + { name = "PCNT1_CTRL_CH0", id = 107 }, + { name = "PCNT1_CTRL_CH1", id = 108 }, + { name = "PCNT2_SIG_CH0", id = 109 }, + { name = "PCNT2_SIG_CH1", id = 110 }, + { name = "PCNT2_CTRL_CH0", id = 111 }, + { name = "PCNT2_CTRL_CH1", id = 112 }, + { name = "PCNT3_SIG_CH0", id = 113 }, + { name = "PCNT3_SIG_CH1", id = 114 }, + { name = "PCNT3_CTRL_CH0", id = 115 }, + { name = "PCNT3_CTRL_CH1", id = 116 }, + { name = "SPIQ", id = 121 }, + { name = "SPID", id = 122 }, + { name = "SPIHD", id = 123 }, + { name = "SPIWP", id = 124 }, + + { name = "SDIO_CMD" }, + { name = "SDIO_DATA0" }, + { name = "SDIO_DATA1" }, + { name = "SDIO_DATA2" }, + { name = "SDIO_DATA3" }, + + { name = "MTDI" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "LEDC_LS_SIG0", id = 0 }, + { name = "LEDC_LS_SIG1", id = 1 }, + { name = "LEDC_LS_SIG2", id = 2 }, + { name = "LEDC_LS_SIG3", id = 3 }, + { name = "LEDC_LS_SIG4", id = 4 }, + { name = "LEDC_LS_SIG5", id = 5 }, + { name = "U0TXD", id = 6 }, + { name = "U0RTS", id = 7 }, + { name = "U0DTR", id = 8 }, + { name = "U1TXD", id = 9 }, + { name = "U1RTS", id = 10 }, + { name = "U1DTR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SO_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "I2SO_SD1", id = 18 }, + { name = "USB_JTAG_TDO_BRIDGE", id = 19 }, + { name = "CPU_TESTBUS0", id = 20 }, + { name = "CPU_TESTBUS1", id = 21 }, + { name = "CPU_TESTBUS2", id = 22 }, + { name = "CPU_TESTBUS3", id = 23 }, + { name = "CPU_TESTBUS4", id = 24 }, + { name = "CPU_TESTBUS5", id = 25 }, + { name = "CPU_TESTBUS6", id = 26 }, + { name = "CPU_TESTBUS7", id = 27 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "USB_JTAG_TCK", id = 36 }, + { name = "USB_JTAG_TMS", id = 37 }, + { name = "USB_JTAG_TDI", id = 38 }, + { name = "USB_JTAG_TDO", id = 39 }, + { name = "I2CEXT0_SCL", id = 45 }, + { name = "I2CEXT0_SDA", id = 46 }, + { name = "PARL_TX_DATA0", id = 47 }, + { name = "PARL_TX_DATA1", id = 48 }, + { name = "PARL_TX_DATA2", id = 49 }, + { name = "PARL_TX_DATA3", id = 50 }, + { name = "PARL_TX_DATA4", id = 51 }, + { name = "PARL_TX_DATA5", id = 52 }, + { name = "PARL_TX_DATA6", id = 53 }, + { name = "PARL_TX_DATA7", id = 54 }, + { name = "PARL_TX_DATA8", id = 55 }, + { name = "PARL_TX_DATA9", id = 56 }, + { name = "PARL_TX_DATA10", id = 57 }, + { name = "PARL_TX_DATA11", id = 58 }, + { name = "PARL_TX_DATA12", id = 59 }, + { name = "PARL_TX_DATA13", id = 60 }, + { name = "PARL_TX_DATA14", id = 61 }, + { name = "PARL_TX_DATA15", id = 62 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "SDIO_TOHOST_INT", id = 69 }, + { name = "PARL_TX_CLK", id = 70 }, + { name = "RMT_SIG_0", id = 71 }, + { name = "RMT_SIG_1", id = 72 }, + { name = "TWAI0_TX", id = 73 }, + { name = "TWAI0_BUS_OFF_ON", id = 74 }, + { name = "TWAI0_CLKOUT", id = 75 }, + { name = "TWAI0_STANDBY", id = 76 }, + { name = "TWAI1_TX", id = 77 }, + { name = "TWAI1_BUS_OFF_ON", id = 78 }, + { name = "TWAI1_CLKOUT", id = 79 }, + { name = "TWAI1_STANDBY", id = 80 }, + { name = "GPIO_SD0", id = 83 }, + { name = "GPIO_SD1", id = 84 }, + { name = "GPIO_SD2", id = 85 }, + { name = "GPIO_SD3", id = 86 }, + { name = "PWM0_0A", id = 87 }, + { name = "PWM0_0B", id = 88 }, + { name = "PWM0_1A", id = 89 }, + { name = "PWM0_1B", id = 90 }, + { name = "PWM0_2A", id = 91 }, + { name = "PWM0_2B", id = 92 }, + { name = "SIG_IN_FUNC97", id = 97 }, + { name = "SIG_IN_FUNC98", id = 98 }, + { name = "SIG_IN_FUNC99", id = 99 }, + { name = "SIG_IN_FUNC100", id = 100 }, + { name = "FSPICS1", id = 101 }, + { name = "FSPICS2", id = 102 }, + { name = "FSPICS3", id = 103 }, + { name = "FSPICS4", id = 104 }, + { name = "FSPICS5", id = 105 }, + { name = "SPICLK", id = 114 }, + { name = "SPICS0", id = 115 }, + { name = "SPICS1", id = 116 }, + { name = "GPIO_TASK_MATRIX_OUT0", id = 117 }, + { name = "GPIO_TASK_MATRIX_OUT1", id = 118 }, + { name = "GPIO_TASK_MATRIX_OUT2", id = 119 }, + { name = "GPIO_TASK_MATRIX_OUT3", id = 120 }, + { name = "SPIQ", id = 121 }, + { name = "SPID", id = 122 }, + { name = "SPIHD", id = 123 }, + { name = "SPIWP", id = 124 }, + { name = "CLK_OUT_OUT1", id = 125 }, + { name = "CLK_OUT_OUT2", id = 126 }, + { name = "CLK_OUT_OUT3", id = 127 }, + { name = "GPIO", id = 128 }, + + { name = "SDIO_CLK" }, + { name = "SDIO_CMD" }, + { name = "SDIO_DATA0" }, + { name = "SDIO_DATA1" }, + { name = "SDIO_DATA2" }, + { name = "SDIO_DATA3" }, + + { name = "MTDO" }, +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["CPU_GPIO_0", "CPU_GPIO_1", "CPU_GPIO_2", "CPU_GPIO_3", "CPU_GPIO_4", "CPU_GPIO_5", "CPU_GPIO_6", "CPU_GPIO_7"]] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, +] +has_fsm_timeouts = true +has_hw_bus_clear = true +ll_intr_mask = 0x3ffff +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +can_estimate_nack_reason = true +has_conf_update = true +has_reliable_fsm_reset = true +has_arbitration_en = true +has_tx_fifo_watermark = true +bus_timeout_is_exponential = true + +[device.lp_i2c_master] +support_status = "partial" +fifo_size = 16 + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 3 +software_interrupt_count = 4 +software_interrupt_delay = 14 # CPU-speed sensitive - what's clocked from where? +controller = { Riscv = { flavour = "plic", interrupts = 32, priority_levels = 15 } } + +[device.rmt] +support_status = "partial" +ram_start = 0x60006400 +channel_ram_size = 48 +channels = ["Tx", "Tx", "Rx", "Rx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_loop_auto_stop = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_wrap = true +has_rx_demodulation = true +clock_sources.supported = [ "None", "Pll80MHz", "RcFast", "Xtal" ] +clock_sources.default = "Pll80MHz" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 384 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_app_interrupts = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +instances = [{ name = "timg0" }, { name = "timg1" }] +timg_has_divcnt_rst = true +rc_fast_calibration = { divider = 32, min_rev = 1 } + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 +peripheral_controls_mem_clk = true + +[device.uhci] +support_status = "partial" + +[device.lp_uart] +support_status = "partial" +ram_size = 32 + +[device.ds] +support_status = "not_supported" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +[device.parl_io] +support_status = "partial" +version = 1 + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +[device.hmac] + +## Interfaces +[device.i2s] +[device.ledc] +[device.mcpwm] + +[device.pcnt] +[device.sd_slave] +[device.twai] +[device.usb_serial_jtag] + +## Miscellaneous +[device.etm] +[device.io_mux] +[device.temp_sensor] +[device.systimer] +[device.ulp_riscv] +[device.lp_timer] + +## Radio +[device.wifi] +support_status = "partial" +has_wifi6 = true +mac_version = 2 + +[device.bt] +support_status = "partial" +controller = "npl" + +[device.ieee802154] + +[device.phy] +combo_module = true diff --git a/esp-metadata/devices/esp32h2.toml b/esp-metadata/devices/esp32h2.toml new file mode 100644 index 00000000000..edafd275756 --- /dev/null +++ b/esp-metadata/devices/esp32h2.toml @@ -0,0 +1,659 @@ +# ESP32-H2 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32h2" +arch = "riscv" +target = "riscv32imac-unknown-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-h2_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 6 }, + { name = "APB_SARADC", dma_peripheral = 8 }, + { name = "ASSIST_DEBUG" }, + { name = "DMA" }, + { name = "DS" }, + { name = "ECC" }, + { name = "EFUSE", hidden = true }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HMAC" }, + { name = "HP_APM" }, + { name = "HP_SYS" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2C1", interrupts = { peri = "I2C_EXT1" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 3 }, + { name = "IEEE802154", interrupts = { mac = "ZB_MAC" } }, + { name = "INTERRUPT_CORE0" }, + { name = "INTPRI" }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "LPWR", pac = "LP_CLKRST" }, + { name = "LP_ANA" }, + { name = "LP_AON" }, + { name = "LP_APM" }, + { name = "LP_APM0" }, + { name = "LP_CLKRST" }, + { name = "LP_PERI" }, + { name = "LP_TIMER" }, + { name = "LP_WDT" }, + { name = "MCPWM0" }, + { name = "MEM_MONITOR" }, + { name = "MODEM_LPCON" }, + { name = "MODEM_SYSCON" }, + { name = "OTP_DEBUG" }, + { name = "PARL_IO", interrupts = { tx = "PARL_IO_TX", rx = "PARL_IO_RX" }, dma_peripheral = 9 }, + { name = "PAU" }, + { name = "PCNT" }, + { name = "PCR" }, + { name = "PLIC_MX" }, + { name = "PMU" }, + { name = "RMT" }, + { name = "RNG", virtual = true }, # a) RNG is LP_PERI in truth. b) we need to offset the rng_data register at runtime for newer revisions. + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "ETM", pac = "SOC_ETM" }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 0, stable = true }, + { name = "SYSTEM", pac = "PCR" }, + { name = "SYSTIMER" }, + { name = "TEE" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TRACE0", pac = "TRACE" }, + { name = "TWAI0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UHCI0", dma_peripheral = 2 }, + { name = "USB_DEVICE", interrupts = { peri = "USB_DEVICE" } }, + + { name = "DMA_CH0", virtual = true }, + { name = "DMA_CH1", virtual = true }, + { name = "DMA_CH2", virtual = true }, + + { name = "ADC1", virtual = true }, + { name = "BT", virtual = true, interrupts = { mac = "BT_MAC", lp_timer = "LP_BLE_TIMER" } }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "MEM2MEM0", virtual = true, dma_peripheral = 1 }, + { name = "MEM2MEM1", virtual = true, dma_peripheral = 4 }, + { name = "MEM2MEM2", virtual = true, dma_peripheral = 5 }, + { name = "MEM2MEM3", virtual = true, dma_peripheral = 10 }, + { name = "MEM2MEM4", virtual = true, dma_peripheral = 11 }, + { name = "MEM2MEM5", virtual = true, dma_peripheral = 12 }, + { name = "MEM2MEM6", virtual = true, dma_peripheral = 13 }, + { name = "MEM2MEM7", virtual = true, dma_peripheral = 14 }, + { name = "MEM2MEM8", virtual = true, dma_peripheral = 15 }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "swd", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", +] + +[device.soc] +cpu_has_csr_pc = true +cpu_csr_prv_mode = 0xC10 +rc_fast_clk_default = 8_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x4080_0000, end = 0x4085_0000 }, + { name = "dram2_uninit", start = 0x4083_EFD0, end = 0x4084_FEE0 }, +] } + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "32", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_F96M_CLK", type = "derived", from = "XTAL_CLK", output = "96_000_000" }, + { name = "PLL_F64M_CLK", type = "divider", output = "PLL_F96M_CLK * 2 / 3" }, + { name = "PLL_F48M_CLK", type = "divider", output = "PLL_F96M_CLK / 2" }, + { name = "RC_FAST_CLK", type = "source", output = "8_000_000" }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "OSC_SLOW_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "130_000" }, + { name = "PLL_LP_CLK", type = "derived", from = "XTAL32K_CLK", output = "8_000_000" }, + + { name = "HP_ROOT_CLK", type = "mux", variants = [ + { name = "PLL96", outputs = "PLL_F96M_CLK" }, + { name = "PLL64", outputs = "PLL_F64M_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + + { name = "CPU_CLK", type = "divider", params = { divisor = "0..=255" }, output = "HP_ROOT_CLK / (divisor + 1)", always_on = true }, # AHB_CLK * integer + { name = "AHB_CLK", type = "divider", params = { divisor = "0..=255" }, output = "HP_ROOT_CLK / (divisor + 1)", always_on = true }, # Max 32M + { name = "APB_CLK", type = "divider", params = { divisor = "0..=255" }, output = "AHB_CLK / (divisor + 1)" }, + # configurable, but automatic - there's no point in making APB_DECREASE_DIV part of the model as consumers will always see APB_CLK + # { name = "APB_DECREASE_DIV", type = "divider", output = "APB_DIV / (divisor + 1)" }, + + # LP clocks + { name = "XTAL_D2_CLK", type = "divider", output = "XTAL_CLK / 2" }, + { name = "LP_FAST_CLK", type = "mux", variants = [ + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_LP_CLK", outputs = "PLL_LP_CLK" }, + { name = "XTAL_D2_CLK", outputs = "XTAL_D2_CLK" }, + ] }, + { name = "LP_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "OSC_SLOW", outputs = "OSC_SLOW_CLK" }, + ] }, + # LP_DYN_SLOW_CLK is LP_SLOW_CLK + # LP_DYN_FAST_CLK is automatically managed based on power mode. Not sure how to model it, some LP peripherals need it. +] }, peripheral_clocks = { templates = [ + # templates + { name = "default_clk_en_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "clk_en_template", value = "{{default_clk_en_template}}" }, # Indirection allows appending to the template for TWAI + { name = "rst_template", value = "{{control}}::regs().{{conf_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "conf_register", value = "{{peripheral}}_conf" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_field", value = "{{peripheral}}_rst_en" }, + # {{peripheral}} is derived automatically (e.g. SpiDma -> spi_dma) +], peripheral_clocks = [ + # This list is based on TRM (v1.5) - 7.4.1 PCR Register Summary + { name = "Uart0", template_params = { conf_register = "uart(0).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "PLL_F48M", outputs = "PLL_F48M_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Uart1", template_params = { conf_register = "uart(1).conf", clk_en_field = "clk_en", rst_field = "rst_en" }, clocks = "Uart0" }, + { name = "I2cExt0", template_params = { peripheral = "i2c0" } }, + { name = "I2cExt1", template_params = { peripheral = "i2c1" } }, + { name = "Uhci0", template_params = { peripheral = "uhci" } }, + { name = "Rmt", clocks = [ + { name = "SCLK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + ] }, + ] }, + { name = "Ledc" }, + { name = "Timg0", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup0_conf", peripheral = "tg0", peri_clk_template = "{{control}}::regs().timergroup0_timer_clk_conf().modify(|_, w| w.tg0_timer_clk_en().bit(enable));" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F48M", outputs = "PLL_F48M_CLK" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", variants = [ + { name = "RC_SLOW_CLK", outputs = "LP_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_CLK" }, #? + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + { name = "WDT_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F48M", outputs = "PLL_F48M_CLK" }, + ] }, + ] }, + { name = "Timg1", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup1_conf", peripheral = "tg1", peri_clk_template = "{{control}}::regs().timergroup1_timer_clk_conf().modify(|_, w| w.tg1_timer_clk_en().bit(enable));" }, clocks = "Timg0" }, + { name = "Systimer", keep_enabled = true }, + { name = "Twai0", template_params = { clk_en_template = "{{default_clk_en_template}} {{func_clk_template}}", func_clk_template = "{{control}}::regs().twai0_func_clk_conf().modify(|_, w| w.twai0_func_clk_en().bit(enable));" } }, + { name = "I2s0", template_params = { peripheral = "i2s" } }, + { name = "ApbSarAdc", template_params = { peripheral = "saradc", clk_en_field = "saradc_reg_clk_en", rst_field = "saradc_reg_rst_en" } }, + { name = "Tsens", template_params = { conf_register = "tsens_clk_conf" } }, + { name = "UsbDevice", keep_enabled = true }, + # { name = "Intmtx" }, + { name = "Pcnt" }, + { name = "Etm" }, + { name = "Mcpwm0", template_params = { peripheral = "pwm" }, clocks = [ + # TODO: MCPWM divider? + { name = "FUNCTION_CLOCK", type = "mux", default = "PLL_F96M", variants = [ + { name = "PLL_F96M", outputs = "PLL_F96M_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "ParlIo", template_params = { clk_en_field = "parl_clk_en", rst_field = "parl_rst_en" }, clocks = [ + { name = "RX_CLOCK", type = "mux", default = "PLL_F96M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F96M", outputs = "PLL_F96M_CLK" }, + # TODO: external clock + ] }, + { name = "TX_CLOCK", type = "mux", default = "PLL_F96M", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "PLL_F96M", outputs = "PLL_F96M_CLK" }, + ] }, + ] }, + { name = "Dma", template_params = { peripheral = "gdma" } }, + { name = "Spi2" }, + { name = "Aes" }, + { name = "Sha" }, + { name = "Rsa" }, + { name = "Ecc" }, + { name = "Ds" }, + { name = "Hmac" }, + { name = "Ecdsa" }, + #{ name = "Iomux" }, + #{ name = "MemMonitor" }, + { name = "Trace0", template_params = { peripheral = "trace" } }, + #{ name = "Assist" }, + #{ name = "Cache" }, + #{ name = "ModemApb" }, + #{ name = "Timeout" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = false +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.assist_debug] +support_status = "partial" +has_sp_monitor = true +has_region_monitor = true + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 1 +separate_in_out_interrupts = true +supports_mem2mem = true +max_priority = 5 + +[device.ecc] +support_status = "partial" +working_modes = [ + { id = 0, mode = "affine_point_multiplication" }, + { id = 2, mode = "affine_point_verification" }, + { id = 3, mode = "affine_point_verification_and_multiplication" }, + { id = 4, mode = "jacobian_point_multiplication" }, + { id = 5, mode = "affine_point_addition" }, + { id = 6, mode = "jacobian_point_verification" }, + { id = 7, mode = "affine_point_verification_and_jacobian_point_multiplication" }, + { id = 8, mode = "modular_addition" }, + { id = 9, mode = "modular_subtraction" }, + { id = 10, mode = "modular_multiplication" }, + { id = 11, mode = "modular_division" } +] +curves = [ + { id = 0, curve = 192 }, + { id = 1, curve = 256 } +] +zero_extend_writes = true +separate_jacobian_point_memory = true +has_memory_clock_gate = true +supports_enhanced_security = true # only after revision 1.2 + +[device.gpio] +support_status = "supported" +gpio_function = 1 +constant_0_input = 0x3c +constant_1_input = 0x38 +pins = [ + { pin = 0, functions = { 2 = "FSPIQ" } }, + { pin = 1, functions = { 2 = "FSPICS0" }, analog = { 1 = "ADC1_CH0" } }, + { pin = 2, functions = { 0 = "MTMS", 2 = "FSPIWP" }, analog = { 1 = "ADC1_CH1" } }, + { pin = 3, functions = { 0 = "MTDI", 2 = "FSPIHD" }, analog = { 1 = "ADC1_CH2" } }, + { pin = 4, functions = { 0 = "MTCK", 2 = "FSPICLK" }, analog = { 1 = "ADC1_CH3" } }, + { pin = 5, functions = { 0 = "MTDO", 2 = "FSPID" }, analog = { 1 = "ADC1_CH4" } }, + { pin = 6, limitations = ["spi_flash"] }, + { pin = 7, lp = { 0 = "LP_GPIO0" }, limitations = ["spi_flash"] }, + { pin = 8, lp = { 0 = "LP_GPIO1" }, limitations = ["strapping"] }, + { pin = 9, lp = { 0 = "LP_GPIO2" }, limitations = ["strapping"] }, + { pin = 10, lp = { 0 = "LP_GPIO3" }, analog = { 0 = "ZCD0" } }, + { pin = 11, lp = { 0 = "LP_GPIO4" }, analog = { 0 = "ZCD1" } }, + { pin = 12, lp = { 0 = "LP_GPIO5" } }, + { pin = 13, lp = { 0 = "LP_GPIO6" }, analog = { 0 = "XTAL_32K_P" } }, + { pin = 14, lp = { 0 = "LP_GPIO7" }, analog = { 0 = "XTAL_32K_N" } }, + { pin = 22 }, + { pin = 23, functions = { 0 = "U0RXD", 2 = "FSPICS1" } }, + { pin = 24, functions = { 0 = "U0TXD", 2 = "FSPICS2" } }, + { pin = 25, functions = { 2 = "FSPICS3" }, limitations = ["strapping"] }, + { pin = 26, functions = { 2 = "FSPICS4" }, analog = { 0 = "USB_DM" } }, + { pin = 27, functions = { 2 = "FSPICS5" }, analog = { 0 = "USB_DP" } }, +] +input_signals = [ + { name = "EXT_ADC_START", id = 0 }, + { name = "U0RXD", id = 6 }, + { name = "U0CTS", id = 7 }, + { name = "U0DSR", id = 8 }, + { name = "U1RXD", id = 9 }, + { name = "U1CTS", id = 10 }, + { name = "U1DSR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SI_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "USB_JTAG_TDO_BRIDGE", id = 19 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "I2CEXT0_SCL", id = 45 }, + { name = "I2CEXT0_SDA", id = 46 }, + { name = "PARL_RX_DATA0", id = 47 }, + { name = "PARL_RX_DATA1", id = 48 }, + { name = "PARL_RX_DATA2", id = 49 }, + { name = "PARL_RX_DATA3", id = 50 }, + { name = "PARL_RX_DATA4", id = 51 }, + { name = "PARL_RX_DATA5", id = 52 }, + { name = "PARL_RX_DATA6", id = 53 }, + { name = "PARL_RX_DATA7", id = 54 }, + { name = "I2CEXT1_SCL", id = 55 }, + { name = "I2CEXT1_SDA", id = 56 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "PARL_RX_CLK", id = 69 }, + { name = "PARL_TX_CLK", id = 70 }, + { name = "RMT_SIG_0", id = 71 }, + { name = "RMT_SIG_1", id = 72 }, + { name = "TWAI0_RX", id = 73 }, + { name = "PWM0_SYNC0", id = 87 }, + { name = "PWM0_SYNC1", id = 88 }, + { name = "PWM0_SYNC2", id = 89 }, + { name = "PWM0_F0", id = 90 }, + { name = "PWM0_F1", id = 91 }, + { name = "PWM0_F2", id = 92 }, + { name = "PWM0_CAP0", id = 93 }, + { name = "PWM0_CAP1", id = 94 }, + { name = "PWM0_CAP2", id = 95 }, + { name = "SIG_FUNC_97", id = 97 }, + { name = "SIG_FUNC_98", id = 98 }, + { name = "SIG_FUNC_99", id = 99 }, + { name = "SIG_FUNC_100", id = 100 }, + { name = "PCNT0_SIG_CH0", id = 101 }, + { name = "PCNT0_SIG_CH1", id = 102 }, + { name = "PCNT0_CTRL_CH0", id = 103 }, + { name = "PCNT0_CTRL_CH1", id = 104 }, + { name = "PCNT1_SIG_CH0", id = 105 }, + { name = "PCNT1_SIG_CH1", id = 106 }, + { name = "PCNT1_CTRL_CH0", id = 107 }, + { name = "PCNT1_CTRL_CH1", id = 108 }, + { name = "PCNT2_SIG_CH0", id = 109 }, + { name = "PCNT2_SIG_CH1", id = 110 }, + { name = "PCNT2_CTRL_CH0", id = 111 }, + { name = "PCNT2_CTRL_CH1", id = 112 }, + { name = "PCNT3_SIG_CH0", id = 113 }, + { name = "PCNT3_SIG_CH1", id = 114 }, + { name = "PCNT3_CTRL_CH0", id = 115 }, + { name = "PCNT3_CTRL_CH1", id = 116 }, + { name = "SPIQ", id = 121 }, + { name = "SPID", id = 122 }, + { name = "SPIHD", id = 123 }, + { name = "SPIWP", id = 124 }, + + { name = "MTDI" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "LEDC_LS_SIG0", id = 0 }, + { name = "LEDC_LS_SIG1", id = 1 }, + { name = "LEDC_LS_SIG2", id = 2 }, + { name = "LEDC_LS_SIG3", id = 3 }, + { name = "LEDC_LS_SIG4", id = 4 }, + { name = "LEDC_LS_SIG5", id = 5 }, + { name = "U0TXD", id = 6 }, + { name = "U0RTS", id = 7 }, + { name = "U0DTR", id = 8 }, + { name = "U1TXD", id = 9 }, + { name = "U1RTS", id = 10 }, + { name = "U1DTR", id = 11 }, + { name = "I2S_MCLK", id = 12 }, + { name = "I2SO_BCK", id = 13 }, + { name = "I2SO_WS", id = 14 }, + { name = "I2SO_SD", id = 15 }, + { name = "I2SI_BCK", id = 16 }, + { name = "I2SI_WS", id = 17 }, + { name = "I2SO_SD1", id = 18 }, + { name = "USB_JTAG_TRST", id = 19 }, + { name = "CPU_GPIO_0", id = 28 }, + { name = "CPU_GPIO_1", id = 29 }, + { name = "CPU_GPIO_2", id = 30 }, + { name = "CPU_GPIO_3", id = 31 }, + { name = "CPU_GPIO_4", id = 32 }, + { name = "CPU_GPIO_5", id = 33 }, + { name = "CPU_GPIO_6", id = 34 }, + { name = "CPU_GPIO_7", id = 35 }, + { name = "I2CEXT0_SCL", id = 45 }, + { name = "I2CEXT0_SDA", id = 46 }, + { name = "PARL_TX_DATA0", id = 47 }, + { name = "PARL_TX_DATA1", id = 48 }, + { name = "PARL_TX_DATA2", id = 49 }, + { name = "PARL_TX_DATA3", id = 50 }, + { name = "PARL_TX_DATA4", id = 51 }, + { name = "PARL_TX_DATA5", id = 52 }, + { name = "PARL_TX_DATA6", id = 53 }, + { name = "PARL_TX_DATA7", id = 54 }, + { name = "I2CEXT1_SCL", id = 55 }, + { name = "I2CEXT1_SDA", id = 56 }, + { name = "FSPICLK", id = 63 }, + { name = "FSPIQ", id = 64 }, + { name = "FSPID", id = 65 }, + { name = "FSPIHD", id = 66 }, + { name = "FSPIWP", id = 67 }, + { name = "FSPICS0", id = 68 }, + { name = "PARL_RX_CLK", id = 69 }, + { name = "PARL_TX_CLK", id = 70 }, + { name = "RMT_SIG_0", id = 71 }, + { name = "RMT_SIG_1", id = 72 }, + { name = "TWAI0_TX", id = 73 }, + { name = "TWAI0_BUS_OFF_ON", id = 74 }, + { name = "TWAI0_CLKOUT", id = 75 }, + { name = "TWAI0_STANDBY", id = 76 }, + { name = "CTE_ANT7", id = 78 }, + { name = "CTE_ANT8", id = 79 }, + { name = "CTE_ANT9", id = 80 }, + { name = "GPIO_SD0", id = 83 }, + { name = "GPIO_SD1", id = 84 }, + { name = "GPIO_SD2", id = 85 }, + { name = "GPIO_SD3", id = 86 }, + { name = "PWM0_0A", id = 87 }, + { name = "PWM0_0B", id = 88 }, + { name = "PWM0_1A", id = 89 }, + { name = "PWM0_1B", id = 90 }, + { name = "PWM0_2A", id = 91 }, + { name = "PWM0_2B", id = 92 }, + { name = "SIG_IN_FUNC97", id = 97 }, + { name = "SIG_IN_FUNC98", id = 98 }, + { name = "SIG_IN_FUNC99", id = 99 }, + { name = "SIG_IN_FUNC100", id = 100 }, + { name = "FSPICS1", id = 101 }, + { name = "FSPICS2", id = 102 }, + { name = "FSPICS3", id = 103 }, + { name = "FSPICS4", id = 104 }, + { name = "FSPICS5", id = 105 }, + { name = "CTE_ANT10", id = 106 }, + { name = "CTE_ANT11", id = 107 }, + { name = "CTE_ANT12", id = 108 }, + { name = "CTE_ANT13", id = 109 }, + { name = "CTE_ANT14", id = 110 }, + { name = "CTE_ANT15", id = 111 }, + { name = "SPICLK", id = 114 }, + { name = "SPICS0", id = 115 }, + { name = "SPICS1", id = 116 }, + { name = "SPIQ", id = 121 }, + { name = "SPID", id = 122 }, + { name = "SPIHD", id = 123 }, + { name = "SPIWP", id = 124 }, + { name = "CLK_OUT_OUT1", id = 125 }, + { name = "CLK_OUT_OUT2", id = 126 }, + { name = "CLK_OUT_OUT3", id = 127 }, + { name = "GPIO", id = 128 }, + + { name = "MTDO" } +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["CPU_GPIO_0", "CPU_GPIO_1", "CPU_GPIO_2", "CPU_GPIO_3", "CPU_GPIO_4", "CPU_GPIO_5", "CPU_GPIO_6", "CPU_GPIO_7"]] + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, + { name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" }, +] +has_fsm_timeouts = true +has_hw_bus_clear = true +ll_intr_mask = 0x3ffff +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +can_estimate_nack_reason = true +has_conf_update = true +has_reliable_fsm_reset = true +has_arbitration_en = true +has_tx_fifo_watermark = true +bus_timeout_is_exponential = true + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 2 +software_interrupt_count = 4 +software_interrupt_delay = 5 +controller = { Riscv = { flavour = "plic", interrupts = 32, priority_levels = 15 } } + +[device.rmt] +support_status = "partial" +ram_start = 0x60007400 +channel_ram_size = 48 +channels = ["Tx", "Tx", "Rx", "Rx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_loop_auto_stop = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_wrap = true +has_rx_demodulation = true +clock_sources.supported = ["Xtal", "RcFast" ] +# Power-on value is RcFast! +clock_sources.default = "Xtal" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 384 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_app_interrupts = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] + +[device.timergroup] +support_status = "partial" +instances = [{ name = "timg0" }, { name = "timg1" }] +timg_has_divcnt_rst = true +rc_fast_calibration = { divider = 32, min_rev = 2 } + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 +peripheral_controls_mem_clk = true + +[device.uhci] +support_status = "partial" + +[device.ds] +support_status = "not_supported" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.parl_io] +support_status = "partial" +version = 2 + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +[device.hmac] + +## Interfaces +[device.i2s] +[device.ledc] +[device.mcpwm] +[device.pcnt] +[device.twai] +[device.usb_serial_jtag] + +## Miscellaneous +[device.etm] +[device.io_mux] +[device.systimer] +[device.temp_sensor] +[device.lp_timer] + +## Radio +[device.bt] +support_status = "partial" +controller = "npl" + +[device.ieee802154] + +[device.phy] diff --git a/esp-metadata/devices/esp32p4.toml b/esp-metadata/devices/esp32p4.toml new file mode 100644 index 00000000000..c8ac63be2c4 --- /dev/null +++ b/esp-metadata/devices/esp32p4.toml @@ -0,0 +1,111 @@ +# ESP32-P4 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32p4" +arch = "riscv" +cores = 2 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-p4_technical_reference_manual_en.pdf" + +peripherals = [ + # Peripherals available in the PAC: + # "adc", + # "aes", + # "assist_debug", + # "axi_dma", + # "axi_icm", + # "bitscrambler", + # "cache", + # "dma", + # "ds", + # "ecc", + # "ecdsa", + "efuse", + # "gpio_sd", + "gpio", + # "h264_dma", + # "h264", + # "hmac", + # "hp_sys_clkrst", + "hp_sys", + # "i2c0", + # "i2c1", + # "i2s0", + # "i2s1", + # "i2s2", + # "i3c_mst_mem", + # "i3c_mst", + # "i3c_slv", + "interrupt_core0", + "interrupt_core1", + "io_mux", + # "isp", + # "jpeg", + # "lcd_cam", + # "ledc", + # "lp_adc", + # "lp_ana_peri", + # "lp_aon_clkrst", + # "lp_gpio", + # "lp_huk", + # "lp_i2c_ana_mst", + # "lp_i2c0", + # "lp_i2s0", + # "lp_intr", + # "lp_io_mux", + # "lp_peri", + # "lp_sys", + # "lp_timer", + # "lp_touch", + # "lp_tsens", + # "lp_uart", + # "lp_wdt", + # "mcpwm0", + # "mcpwm1", + # "mipi_csi_bridge", + # "mipi_csi_host", + # "mipi_dsi_bridge", + # "mipi_dsi_host", + # "parl_io", + # "pau", + # "pcnt", + # "pmu", + # "ppa", + # "pvt", + # "rmt", + # "rsa", + # "sdhost", + # "sha", + # "soc_etm", + # "spi0", + # "spi1", + # "spi2", + # "spi3", + # "systimer", + # "timg0", + # "timg1", + # "trace0", + # "trace1", + # "twai0", + # "twai1", + # "twai2", + # "uart0", + # "uhci0", + # "usb_device", + # "usb_wrap", +] + +symbols = [ + # Additional peripherals defined by us (the developers): + # "adc1", + # "adc2", + "very_large_intr_status", + "gpio_bank_1", + "spi_octal", +] + +memory = [{ name = "dram", start = 0x4FF0_0000, end = 0x4FFC_0000 }] diff --git a/esp-metadata/devices/esp32s2.toml b/esp-metadata/devices/esp32s2.toml new file mode 100644 index 00000000000..dcd9db6d1d6 --- /dev/null +++ b/esp-metadata/devices/esp32s2.toml @@ -0,0 +1,687 @@ +# ESP32-S2 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32s2" +arch = "xtensa" +target = "xtensa-esp32s2-none-elf" +cores = 1 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 4 }, + { name = "APB_SARADC" }, + { name = "DEDICATED_GPIO" }, + { name = "DS" }, + { name = "EFUSE", hidden = true }, + { name = "EXTMEM" }, + { name = "FE" }, + { name = "FE2" }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HMAC" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2C1", interrupts = { peri = "I2C_EXT1" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 0 }, + { name = "INTERRUPT_CORE0" }, + { name = "IO_MUX" }, + { name = "LEDC" }, + { name = "NRX" }, + { name = "PCNT" }, + { name = "PMS" }, + { name = "RMT" }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "LPWR", pac = "RTC_CNTL" }, + { name = "RTC_I2C" }, + { name = "RTC_IO" }, + { name = "SENS" }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 5 }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2", dma = "SPI2_DMA" }, dma_peripheral = 1, stable = true }, + { name = "SPI3", interrupts = { peri = "SPI3", dma = "SPI3_DMA" }, dma_peripheral = 2, stable = true }, + { name = "SYSCON" }, + { name = "SYSTEM" }, + { name = "SYSTIMER" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TWAI0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UHCI0", dma_peripheral = 3 }, + { name = "USB0" }, + { name = "USB_WRAP" }, + { name = "XTS_AES" }, + { name = "WIFI", interrupts = { mac = "WIFI_MAC", pwr = "WIFI_PWR" }, stable = true }, + + { name = "DMA_SPI2", pac = "SPI2" }, + { name = "DMA_SPI3", pac = "SPI3" }, + { name = "DMA_I2S0", pac = "I2S0" }, + { name = "DMA_CRYPTO", pac = "CRYPTO_DMA" }, + { name = "DMA_COPY", pac = "COPY_DMA" }, + + { name = "ADC1", virtual = true }, + { name = "ADC2", virtual = true }, + { name = "DAC1", virtual = true }, + { name = "DAC2", virtual = true }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "PSRAM", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "ULP_RISCV_CORE", virtual = true }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "psram", + "psram_dma", + "ulp_riscv_core", + + # ROM capabilities + "rom_crc_le", + "rom_md5_bsd", + + # Wakeup SOC based on ESP-IDF: + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "pm_support_wifi_wakeup", + "uart_support_wakeup_int", + "ulp_supported", + "riscv_coproc_supported", +] + +[device.soc] +rc_fast_clk_default = 8_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x3FFB_0000, end = 0x4000_0000 }, + { name = "dram2_uninit", start = 0x3FFD_E000, end = 0x4000_0000 }, +] } + +clocks = { system_clocks = { clock_tree = [ + { name = "XTAL_CLK", type = "source", values = "40", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", values = "320, 480", output = "VALUE * 1_000_000" }, # TODO reject = "VALUE == 320_000_000 && CPU_PLL_DIV_OUT == 240_000_000" + { name = "APLL_CLK", type = "derived", from = "PLL_CLK", values = "16_000_000 ..= 128_000_000", output = "VALUE" }, + { name = "RC_FAST_CLK", type = "source", output = "8_000_000" }, # this is supposed to be adjustable using RTC_CNTL_CK8M_DIV_SEL + + # Represents SEL0 = 1, 3 + { name = "CPU_PLL_DIV_IN", type = "mux", variants = [ + { name = "PLL", outputs = "PLL_CLK" }, + { name = "APLL", outputs = "APLL_CLK" }, + ] }, + { name = "CPU_PLL_DIV", type = "divider", params = { divisor = "2, 3, 4, 6" }, output = "CPU_PLL_DIV_IN / divisor" }, # TODO reject = "PLL_CLK == 320_000_000 && divisor % 3 == 0" + + # Represents SEL0 = 0, 2 + { name = "SYSTEM_PRE_DIV_IN", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + { name = "SYSTEM_PRE_DIV", type = "divider", params = { divisor = "0 .. 1024" }, output = "SYSTEM_PRE_DIV_IN / (divisor + 1)" }, + + # The meta-switch + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + # source clock CPU clock additional config (mux = variant name, divider = divisor expression) + { name = "XTAL", outputs = "SYSTEM_PRE_DIV", configures = [ "SYSTEM_PRE_DIV_IN = XTAL", "APB_CLK = XTAL", "REF_TICK = XTAL", "REF_TICK_XTAL = XTAL_CLK / 1_000_000 - 1" ] }, + { name = "RC_FAST", outputs = "SYSTEM_PRE_DIV", configures = [ "SYSTEM_PRE_DIV_IN = RC_FAST", "APB_CLK = RC_FAST", "REF_TICK = RC_FAST", "REF_TICK_CK8M = RC_FAST_CLK / 1_000_000 - 1" ] }, + { name = "APLL", outputs = "CPU_PLL_DIV", configures = [ "CPU_PLL_DIV_IN = APLL", "APB_CLK = APLL", "REF_TICK = APLL", "REF_TICK_XTAL = XTAL_CLK / 1_000_000 - 1" ] }, + { name = "PLL", outputs = "CPU_PLL_DIV", configures = [ "CPU_PLL_DIV_IN = PLL", "APB_CLK = PLL", "REF_TICK = PLL", "REF_TICK_XTAL = XTAL_CLK / 1_000_000 - 1" ] }, + ] }, + + # APB options + { name = "APB_CLK_CPU_DIV2", type = "divider", output = "CPU_CLK / 2" }, + { name = "APB_CLK_80M", type = "derived", from = "CPU_CLK", output = "80_000_000" }, + { name = "APB_CLK", type = "mux", variants = [ + { name = "PLL", outputs = "APB_CLK_80M" }, + { name = "APLL", outputs = "APB_CLK_CPU_DIV2" }, + { name = "XTAL", outputs = "CPU_CLK" }, + { name = "RC_FAST", outputs = "CPU_CLK" }, + ] }, + + # REF_TICK, configuring CPU clock needs to ensure this is 1MHz + { name = "REF_TICK_XTAL", type = "divider", params = { divisor = "0 .. 256" }, output = "XTAL_CLK / (divisor + 1)" }, + { name = "REF_TICK_CK8M", type = "divider", params = { divisor = "0 .. 256" }, output = "RC_FAST_CLK / (divisor + 1)" }, + { name = "REF_TICK", type = "mux", variants = [ + { name = "PLL", outputs = "REF_TICK_XTAL" }, + { name = "APLL", outputs = "REF_TICK_XTAL" }, + { name = "XTAL", outputs = "REF_TICK_XTAL" }, + { name = "RC_FAST", outputs = "REF_TICK_CK8M" }, + ] }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "90_000" }, + { name = "RC_FAST_DIV_CLK", type = "divider", output = "RC_FAST_CLK / 256" }, + { name = "XTAL_DIV_CLK", type = "divider", output = "XTAL_CLK / 4" }, # source: esp-idf SOC_RTC_FAST_CLK_SRC_XTAL_D4 + + { name = "RTC_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_DIV_CLK" }, + ] }, + { name = "RTC_FAST_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_DIV_CLK" }, + { name = "RC", outputs = "RC_FAST_CLK" }, + ] }, + + # System-wide peripheral clocks + { name = "UART_MEM_CLK", type = "mux", default = "XTAL", variants = [ + # The actual source clock is unknown, but it also doesn't matter. + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "clk_en_template", value = "{{control}}::regs().{{clk_en_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "rst_template", value = "{{control}}::regs().{{rst_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "reg_group", value = "en0" }, + { name = "clk_en_register", value = "perip_clk_{{reg_group}}" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_register", value = "perip_rst_{{reg_group}}" }, + { name = "rst_field", value = "{{peripheral}}_rst" }, +], peripheral_clocks = [ + { name = "Aes", template_params = { reg_group = "en1", peripheral = "crypto_aes" } }, + { name = "Rsa", template_params = { reg_group = "en1", peripheral = "crypto_rsa" } }, + { name = "Sha", template_params = { reg_group = "en1", peripheral = "crypto_sha" } }, + { name = "Ds", template_params = { reg_group = "en1", peripheral = "crypto_ds" } }, + { name = "Hmac", template_params = { reg_group = "en1", peripheral = "crypto_hmac" } }, + { name = "CryptoDma", template_params = { reg_group = "en1" } }, + + # The exception: CopyDma needs to be enabled in its own register space and its reset is a bit gnarly + { name = "CopyDma", template_params = { clk_en_template = "crate::peripherals::DMA_COPY::regs().conf().modify(|_, w| w.clk_en().bit(enable));", rst_template = "crate::peripherals::DMA_COPY::regs().conf().modify(|_, w| { w.in_rst().bit(reset).out_rst().bit(reset).cmdfifo_rst().bit(reset).fifo_rst().bit(reset) });" } }, + { name = "DedicatedGpio", template_params = { clk_en_register = "cpu_peri_clk_en", rst_register = "cpu_peri_rst_en" } }, + + #{ name = "Adc2Arb" }, + { name = "Systimer", keep_enabled = true }, + { name = "ApbSarAdc", template_params = { peripheral = "apb_saradc" } }, + { name = "Spi3Dma" }, + #{ name = "Pwm3" }, + #{ name = "Pwm2" }, + { name = "UartMem", keep_enabled = true }, # TODO: keep_enabled can be removed once esp-println needs explicit initialization + { name = "Usb" }, + { name = "Spi2Dma" }, + { name = "Mcpwm1", template_params = { peripheral = "pwm1" } }, + { name = "Twai0", template_params = { peripheral = "twai" } }, + { name = "I2cExt1" }, + { name = "Mcpwm0", template_params = { peripheral = "pwm0" } }, + { name = "Spi3" }, + { name = "Timg1", template_params = { peripheral = "timergroup1" }, clocks = "Timg0" }, + #{ name = "Efuse" }, + { name = "Timg0", template_params = { peripheral = "timergroup" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "APB_CLK", outputs = "APB_CLK" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", default = "RTC_CLK", variants = [ + { name = "RTC_CLK", outputs = "RTC_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_DIV_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + ] }, + { name = "Uhci1" }, + { name = "Ledc" }, + { name = "Pcnt" }, + { name = "Rmt" }, + { name = "Uhci0" }, + { name = "I2cExt0" }, + { name = "Spi2" }, + { name = "Uart1", clocks = "Uart0" }, + { name = "I2s0" }, + { name = "Wdg" }, + { name = "Uart0", template_params = { peripheral = "uart" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "APB", variants = [ + { name = "APB", outputs = "APB_CLK" }, + { name = "REF_TICK", outputs = "REF_TICK" } + ] }, + { name = "MEM_CLOCK", type = "mux", default = "MEM", variants = [ + { name = "MEM", outputs = "UART_MEM_CLK" }, + ] }, + ] }, + #{ name = "Spi01" }, + #{ name = "Timers" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, + { name = "adc2" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = true +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128", "GCM"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 192, encrypt_mode = 1, decrypt_mode = 5 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.dac] +support_status = "partial" +instances = [ + { name = "dac1" }, + { name = "dac2" }, +] + +[device.dma] +support_status = "partial" +kind = "pdma" +supports_mem2mem = true + +[device.gpio] +support_status = "supported" +has_bank_1 = true +gpio_function = 1 +input_signal_max = 204 +output_signal_max = 256 +constant_0_input = 0x3c +constant_1_input = 0x38 +pins = [ + { pin = 0, rtc = { 0 = "RTC_GPIO0" }, limitations = ["strapping"] }, + { pin = 1, analog = { 0 = "TOUCH1", 1 = "ADC1_CH0" }, rtc = { 0 = "RTC_GPIO1" } }, + { pin = 2, analog = { 0 = "TOUCH2", 1 = "ADC1_CH1" }, rtc = { 0 = "RTC_GPIO2" } }, + { pin = 3, analog = { 0 = "TOUCH3", 1 = "ADC1_CH2" }, rtc = { 0 = "RTC_GPIO3" } }, + { pin = 4, analog = { 0 = "TOUCH4", 1 = "ADC1_CH3" }, rtc = { 0 = "RTC_GPIO4" } }, + { pin = 5, analog = { 0 = "TOUCH5", 1 = "ADC1_CH4" }, rtc = { 0 = "RTC_GPIO5" } }, + { pin = 6, analog = { 0 = "TOUCH6", 1 = "ADC1_CH5" }, rtc = { 0 = "RTC_GPIO6" } }, + { pin = 7, analog = { 0 = "TOUCH7", 1 = "ADC1_CH6" }, rtc = { 0 = "RTC_GPIO7" } }, + { pin = 8, functions = { 3 = "SUBSPICS1" }, analog = { 0 = "TOUCH8", 1 = "ADC1_CH7" }, rtc = { 0 = "RTC_GPIO8" } }, + { pin = 9, functions = { 3 = "SUBSPIHD", 4 = "FSPIHD" }, analog = { 0 = "TOUCH9", 1 = "ADC1_CH8" }, rtc = { 0 = "RTC_GPIO9" } }, + { pin = 10, functions = { 2 = "FSPIIO4", 3 = "SUBSPICS0", 4 = "FSPICS0" }, analog = { 0 = "TOUCH10", 1 = "ADC1_CH9" }, rtc = { 0 = "RTC_GPIO10" } }, + { pin = 11, functions = { 2 = "FSPIIO5", 3 = "SUBSPID", 4 = "FSPID" }, analog = { 0 = "TOUCH11", 1 = "ADC2_CH0" }, rtc = { 0 = "RTC_GPIO11" } }, + { pin = 12, functions = { 2 = "FSPIIO6", 3 = "SUBSPICLK", 4 = "FSPICLK" }, analog = { 0 = "TOUCH12", 1 = "ADC2_CH1" }, rtc = { 0 = "RTC_GPIO12" } }, + { pin = 13, functions = { 2 = "FSPIIO7", 3 = "SUBSPIQ", 4 = "FSPIQ" }, analog = { 0 = "TOUCH13", 1 = "ADC2_CH2" }, rtc = { 0 = "RTC_GPIO13" } }, + { pin = 14, functions = { 2 = "FSPIDQS", 3 = "SUBSPIWP", 4 = "FSPIWP" }, analog = { 0 = "TOUCH14", 1 = "ADC2_CH3" }, rtc = { 0 = "RTC_GPIO14" } }, + { pin = 15, functions = { 2 = "U0RTS" }, analog = { 0 = "XTAL_32K_P", 1 = "ADC2_CH4" }, rtc = { 0 = "RTC_GPIO15" } }, + { pin = 16, functions = { 2 = "U0CTS" }, analog = { 0 = "XTAL_32K_N", 1 = "ADC2_CH5" }, rtc = { 0 = "RTC_GPIO16" } }, + { pin = 17, functions = { 2 = "U1TXD" }, analog = { 0 = "DAC_1", 1 = "ADC2_CH6" }, rtc = { 0 = "RTC_GPIO17" } }, + { pin = 18, functions = { 2 = "U1RXD", 3 = "CLK_OUT3" }, analog = { 0 = "DAC_2", 1 = "ADC2_CH7" }, rtc = { 0 = "RTC_GPIO18" } }, + { pin = 19, functions = { 2 = "U1RTS", 3 = "CLK_OUT2" }, analog = { 0 = "USB_DM", 1 = "ADC2_CH8" }, rtc = { 0 = "RTC_GPIO19" } }, + { pin = 20, functions = { 2 = "U1CTS", 3 = "CLK_OUT1" }, analog = { 0 = "USB_DP", 1 = "ADC2_CH9" }, rtc = { 0 = "RTC_GPIO20" } }, + { pin = 21, rtc = { 0 = "RTC_GPIO21" } }, + + { pin = 26, functions = { 0 = "SPICS1" }, limitations = ["spi_psram"] }, + { pin = 27, functions = { 0 = "SPIHD" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 28, functions = { 0 = "SPIWP" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 29, functions = { 0 = "SPICS0" }, limitations = ["spi_flash"] }, + { pin = 30, functions = { 0 = "SPICLK" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 31, functions = { 0 = "SPIQ" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 32, functions = { 0 = "SPID" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 33, functions = { 2 = "FSPIHD", 3 = "SUBSPIHD" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 34, functions = { 2 = "FSPICS0", 3 = "SUBSPICS0" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 35, functions = { 2 = "FSPID", 3 = "SUBSPID" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 36, functions = { 2 = "FSPICLK", 3 = "SUBSPICLK" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 37, functions = { 2 = "FSPIQ", 3 = "SUBSPIQ", 4 = "SPIDQS" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 38, functions = { 2 = "FSPIWP", 3 = "SUBSPIWP" } }, + { pin = 39, functions = { 0 = "MTCK", 2 = "CLK_OUT3", 3 = "SUBSPICS1" } }, + { pin = 40, functions = { 0 = "MTDO", 2 = "CLK_OUT2" } }, + { pin = 41, functions = { 0 = "MTDI", 2 = "CLK_OUT1" } }, + { pin = 42, functions = { 0 = "MTMS" } }, + { pin = 43, functions = { 0 = "U0TXD", 2 = "CLK_OUT1" } }, + { pin = 44, functions = { 0 = "U0RXD", 2 = "CLK_OUT2" } }, + { pin = 45, limitations = ["strapping"] }, + { pin = 46, limitations = ["strapping"] }, +] +input_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPID4", id = 7 }, + { name = "SPID5", id = 8 }, + { name = "SPID6", id = 9 }, + { name = "SPID7", id = 10 }, + { name = "SPIDQS", id = 11 }, + { name = "U0RXD", id = 14 }, + { name = "U0CTS", id = 15 }, + { name = "U0DSR", id = 16 }, + { name = "U1RXD", id = 17 }, + { name = "U1CTS", id = 18 }, + { name = "U1DSR", id = 21 }, + { name = "I2S0O_BCK", id = 23 }, + { name = "I2S0O_WS", id = 25 }, + { name = "I2S0I_BCK", id = 27 }, + { name = "I2S0I_WS", id = 28 }, + { name = "I2CEXT0_SCL", id = 29 }, + { name = "I2CEXT0_SDA", id = 30 }, + { name = "PCNT0_SIG_CH0", id = 39 }, + { name = "PCNT0_SIG_CH1", id = 40 }, + { name = "PCNT0_CTRL_CH0", id = 41 }, + { name = "PCNT0_CTRL_CH1", id = 42 }, + { name = "PCNT1_SIG_CH0", id = 43 }, + { name = "PCNT1_SIG_CH1", id = 44 }, + { name = "PCNT1_CTRL_CH0", id = 45 }, + { name = "PCNT1_CTRL_CH1", id = 46 }, + { name = "PCNT2_SIG_CH0", id = 47 }, + { name = "PCNT2_SIG_CH1", id = 48 }, + { name = "PCNT2_CTRL_CH0", id = 49 }, + { name = "PCNT2_CTRL_CH1", id = 50 }, + { name = "PCNT3_SIG_CH0", id = 51 }, + { name = "PCNT3_SIG_CH1", id = 52 }, + { name = "PCNT3_CTRL_CH0", id = 53 }, + { name = "PCNT3_CTRL_CH1", id = 54 }, + { name = "USB_EXTPHY_VP", id = 61 }, + { name = "USB_EXTPHY_VM", id = 62 }, + { name = "USB_EXTPHY_RCV", id = 63 }, + { name = "USB_OTG_IDDIG", id = 64 }, + { name = "USB_OTG_AVALID", id = 65 }, + { name = "USB_SRP_BVALID", id = 66 }, + { name = "USB_OTG_VBUSVALID", id = 67 }, + { name = "USB_SRP_SESSEND", id = 68 }, + { name = "SPI3_CLK", id = 72 }, + { name = "SPI3_Q", id = 73 }, + { name = "SPI3_D", id = 74 }, + { name = "SPI3_HD", id = 75 }, + { name = "SPI3_CS0", id = 76 }, + { name = "RMT_SIG_0", id = 83 }, + { name = "RMT_SIG_1", id = 84 }, + { name = "RMT_SIG_2", id = 85 }, + { name = "RMT_SIG_3", id = 86 }, + { name = "I2CEXT1_SCL", id = 95 }, + { name = "I2CEXT1_SDA", id = 96 }, + { name = "FSPICLK", id = 108 }, + { name = "FSPIQ", id = 109 }, + { name = "FSPID", id = 110 }, + { name = "FSPIHD", id = 111 }, + { name = "FSPIWP", id = 112 }, + { name = "FSPIIO4", id = 113 }, + { name = "FSPIIO5", id = 114 }, + { name = "FSPIIO6", id = 115 }, + { name = "FSPIIO7", id = 116 }, + { name = "FSPICS0", id = 117 }, + { name = "TWAI_RX", id = 123 }, + { name = "SUBSPIQ", id = 127 }, + { name = "SUBSPID", id = 128 }, + { name = "SUBSPIHD", id = 129 }, + { name = "SUBSPIWP", id = 130 }, + { name = "I2S0I_DATA_IN15", id = 158 }, + { name = "SUBSPID4", id = 167 }, + { name = "SUBSPID5", id = 168 }, + { name = "SUBSPID6", id = 169 }, + { name = "SUBSPID7", id = 170 }, + { name = "SUBSPIDQS", id = 171 }, + { name = "PCMFSYNC", id = 203 }, + { name = "PCMCLK", id = 204 }, + + { name = "PRO_ALONEGPIO0", id = 235 }, + { name = "PRO_ALONEGPIO1", id = 236 }, + { name = "PRO_ALONEGPIO2", id = 237 }, + { name = "PRO_ALONEGPIO3", id = 238 }, + { name = "PRO_ALONEGPIO4", id = 239 }, + { name = "PRO_ALONEGPIO5", id = 240 }, + { name = "PRO_ALONEGPIO6", id = 241 }, + { name = "PRO_ALONEGPIO7", id = 242 }, + + { name = "MTDI" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPICLK", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "SPICS1", id = 6 }, + { name = "SPID4", id = 7 }, + { name = "SPID5", id = 8 }, + { name = "SPID6", id = 9 }, + { name = "SPID7", id = 10 }, + { name = "SPIDQS", id = 11 }, + { name = "U0TXD", id = 14 }, + { name = "U0RTS", id = 15 }, + { name = "U0DTR", id = 16 }, + { name = "U1TXD", id = 17 }, + { name = "U1RTS", id = 18 }, + { name = "U1DTR", id = 21 }, + { name = "I2S0O_BCK", id = 23 }, + { name = "I2S0O_WS", id = 25 }, + { name = "I2S0I_BCK", id = 27 }, + { name = "I2S0I_WS", id = 28 }, + { name = "I2CEXT0_SCL", id = 29 }, + { name = "I2CEXT0_SDA", id = 30 }, + { name = "SDIO_TOHOST_INT", id = 31 }, + { name = "USB_EXTPHY_OEN", id = 61 }, + { name = "USB_EXTPHY_VPO", id = 63 }, + { name = "USB_EXTPHY_VMO", id = 64 }, + { name = "SPI3_CLK", id = 72 }, + { name = "SPI3_Q", id = 73 }, + { name = "SPI3_D", id = 74 }, + { name = "SPI3_HD", id = 75 }, + { name = "SPI3_CS0", id = 76 }, + { name = "SPI3_CS1", id = 77 }, + { name = "SPI3_CS2", id = 78 }, + { name = "LEDC_LS_SIG0", id = 79 }, + { name = "LEDC_LS_SIG1", id = 80 }, + { name = "LEDC_LS_SIG2", id = 81 }, + { name = "LEDC_LS_SIG3", id = 82 }, + { name = "LEDC_LS_SIG4", id = 83 }, + { name = "LEDC_LS_SIG5", id = 84 }, + { name = "LEDC_LS_SIG6", id = 85 }, + { name = "LEDC_LS_SIG7", id = 86 }, + { name = "RMT_SIG_0", id = 87 }, + { name = "RMT_SIG_1", id = 88 }, + { name = "RMT_SIG_2", id = 89 }, + { name = "RMT_SIG_3", id = 90 }, + { name = "I2CEXT1_SCL", id = 95 }, + { name = "I2CEXT1_SDA", id = 96 }, + { name = "GPIO_SD0", id = 100 }, + { name = "GPIO_SD1", id = 101 }, + { name = "GPIO_SD2", id = 102 }, + { name = "GPIO_SD3", id = 103 }, + { name = "GPIO_SD4", id = 104 }, + { name = "GPIO_SD5", id = 105 }, + { name = "GPIO_SD6", id = 106 }, + { name = "GPIO_SD7", id = 107 }, + { name = "FSPICLK", id = 108 }, + { name = "FSPIQ", id = 109 }, + { name = "FSPID", id = 110 }, + { name = "FSPIHD", id = 111 }, + { name = "FSPIWP", id = 112 }, + { name = "FSPIIO4", id = 113 }, + { name = "FSPIIO5", id = 114 }, + { name = "FSPIIO6", id = 115 }, + { name = "FSPIIO7", id = 116 }, + { name = "FSPICS0", id = 117 }, + { name = "FSPICS1", id = 118 }, + { name = "FSPICS2", id = 119 }, + { name = "FSPICS3", id = 120 }, + { name = "FSPICS4", id = 121 }, + { name = "FSPICS5", id = 122 }, + { name = "TWAI_TX", id = 123 }, + { name = "SUBSPICLK", id = 126 }, + { name = "SUBSPIQ", id = 127 }, + { name = "SUBSPID", id = 128 }, + { name = "SUBSPIHD", id = 129 }, + { name = "SUBSPIWP", id = 130 }, + { name = "SUBSPICS0", id = 131 }, + { name = "SUBSPICS1", id = 132 }, + { name = "FSPIDQS", id = 133 }, + { name = "FSPI_HSYNC", id = 134 }, + { name = "FSPI_VSYNC", id = 135 }, + { name = "FSPI_DE", id = 136 }, + { name = "FSPICD", id = 137 }, + { name = "SPI3_CD", id = 139 }, + { name = "SPI3_DQS", id = 140 }, + { name = "I2S0O_DATA_OUT23", id = 166 }, + { name = "SUBSPID4", id = 167 }, + { name = "SUBSPID5", id = 168 }, + { name = "SUBSPID6", id = 169 }, + { name = "SUBSPID7", id = 170 }, + { name = "SUBSPIDQS", id = 171 }, + { name = "PCMFSYNC", id = 209 }, + { name = "PCMCLK", id = 210 }, + + { name = "PRO_ALONEGPIO0", id = 235 }, + { name = "PRO_ALONEGPIO1", id = 236 }, + { name = "PRO_ALONEGPIO2", id = 237 }, + { name = "PRO_ALONEGPIO3", id = 238 }, + { name = "PRO_ALONEGPIO4", id = 239 }, + { name = "PRO_ALONEGPIO5", id = 240 }, + { name = "PRO_ALONEGPIO6", id = 241 }, + { name = "PRO_ALONEGPIO7", id = 242 }, + + { name = "CLK_I2S", id = 251 }, + + { name = "GPIO", id = 256 }, + + { name = "CLK_OUT1" }, + { name = "CLK_OUT2" }, + { name = "CLK_OUT3" }, + + { name = "MTDO" }, +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [["PRO_ALONEGPIO0", "PRO_ALONEGPIO1", "PRO_ALONEGPIO2", "PRO_ALONEGPIO3", "PRO_ALONEGPIO4", "PRO_ALONEGPIO5", "PRO_ALONEGPIO6", "PRO_ALONEGPIO7"]] +needs_initialization = true + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, + { name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" }, +] +ll_intr_mask = 0x1ffff +has_hw_bus_clear = false +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0xFFFFFF +separate_filter_config_registers = true +has_arbitration_en = true +i2c0_data_register_ahb_address = 0x6001301c + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 3 +software_interrupt_count = 4 +software_interrupt_delay = 0 +controller = "Xtensa" + +[device.rmt] +support_status = "partial" +ram_start = 0x3f416400 +channel_ram_size = 64 +channels = ["RxTx", "RxTx", "RxTx", "RxTx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_demodulation = true +has_per_channel_clock = true +clock_sources.supported = [ "RefTick", "Apb" ] +# Power-on value is :RefTick +clock_sources.default = "Apb" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 512 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2, sha384 = 3, sha512 = 4, sha512_224 = 5, sha512_256 = 6, sha512_t = 7 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_octal = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD", "FSPIIO4", "FSPIIO5", "FSPIIO6", "FSPIIO7"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", sio = ["SPI3_D", "SPI3_Q"], cs = ["SPI3_CS0", "SPI3_CS1", "SPI3_CS2"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", mosi = "SPI3_D", miso = "SPI3_Q", cs = "SPI3_CS0" }, +] + +[device.timergroup] +support_status = "partial" +timg_has_timer1 = true +timg_has_divcnt_rst = false +instances = [{ name = "timg0" }, { name = "timg1" }] + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, +] +ram_size = 128 + +[device.uhci] +support_status = "not_supported" + +[device.rgb_display] # via SPI and I2S +support_status = "not_supported" + +[device.camera] # via I2S +support_status = "not_supported" + +[device.touch] +support_status = "not_supported" + +[device.ds] +support_status = "not_supported" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +[device.hmac] + +## Interfaces +[device.i2s] +[device.ledc] +[device.pcnt] +[device.twai] +[device.usb_otg] + +## Miscellaneous +[device.io_mux] +[device.psram] +[device.systimer] +[device.temp_sensor] +[device.ulp_fsm] +[device.ulp_riscv] +[device.lp_timer] + +## Radio +[device.wifi] +mac_version = 1 + +[device.phy] diff --git a/esp-metadata/devices/esp32s3.toml b/esp-metadata/devices/esp32s3.toml new file mode 100644 index 00000000000..f4ad67b4c37 --- /dev/null +++ b/esp-metadata/devices/esp32s3.toml @@ -0,0 +1,890 @@ +# ESP32-S3 Device Metadata +# +# Empty [`device.driver`] tables imply `partial` support status. +# +# If you modify a driver support status, run `cargo xtask update-metadata` to +# update the table in the esp-hal README. + +[device] +name = "esp32s3" +arch = "xtensa" +target = "xtensa-esp32s3-none-elf" +cores = 2 +trm = "https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf" + +peripherals = [ + { name = "AES", interrupts = { peri = "AES" }, dma_peripheral = 6 }, + { name = "APB_CTRL" }, + { name = "APB_SARADC", dma_peripheral = 8 }, + { name = "ASSIST_DEBUG" }, + { name = "DMA" }, + { name = "DS" }, + { name = "EFUSE", hidden = true }, + { name = "EXTMEM" }, + { name = "GPIO" }, + { name = "GPIO_SD" }, + { name = "HMAC" }, + { name = "I2C_ANA_MST" }, + { name = "I2C0", interrupts = { peri = "I2C_EXT0" }, stable = true }, + { name = "I2C1", interrupts = { peri = "I2C_EXT1" }, stable = true }, + { name = "I2S0", interrupts = { peri = "I2S0" }, dma_peripheral = 3 }, + { name = "I2S1", interrupts = { peri = "I2S1" }, dma_peripheral = 4 }, + { name = "INTERRUPT_CORE0" }, + { name = "INTERRUPT_CORE1" }, + { name = "IO_MUX" }, + { name = "LCD_CAM", dma_peripheral = 5 }, + { name = "LEDC" }, + { name = "LPWR", pac = "RTC_CNTL" }, + { name = "MCPWM0" }, + { name = "MCPWM1" }, + { name = "PCNT" }, + { name = "PERI_BACKUP" }, + { name = "RMT", dma_peripheral = 9 }, + { name = "RNG" }, + { name = "RSA", interrupts = { peri = "RSA" } }, + { name = "RTC_CNTL" }, + { name = "RTC_I2C" }, + { name = "RTC_IO" }, + { name = "SDHOST" }, + { name = "SENS" }, + { name = "SENSITIVE" }, + { name = "SHA", interrupts = { peri = "SHA" }, dma_peripheral = 7 }, + { name = "SPI0" }, + { name = "SPI1" }, + { name = "SPI2", interrupts = { peri = "SPI2" }, dma_peripheral = 0, stable = true }, + { name = "SPI3", interrupts = { peri = "SPI3" }, dma_peripheral = 1, stable = true }, + { name = "SYSTEM" }, + { name = "SYSTIMER" }, + { name = "TIMG0" }, + { name = "TIMG1" }, + { name = "TWAI0" }, + { name = "UART0", interrupts = { peri = "UART0" }, stable = true }, + { name = "UART1", interrupts = { peri = "UART1" }, stable = true }, + { name = "UART2", interrupts = { peri = "UART2" }, stable = true }, + { name = "UHCI0", dma_peripheral = 2 }, + { name = "USB0" }, + { name = "USB_DEVICE", interrupts = { peri = "USB_DEVICE" } }, + { name = "USB_WRAP" }, + { name = "WCL" }, + { name = "XTS_AES" }, + + { name = "DMA_CH0", virtual = true }, + { name = "DMA_CH1", virtual = true }, + { name = "DMA_CH2", virtual = true }, + { name = "DMA_CH3", virtual = true }, + { name = "DMA_CH4", virtual = true }, + + { name = "ADC1", virtual = true }, + { name = "ADC2", virtual = true }, + { name = "BT", virtual = true, interrupts = { rwble = "RWBLE", bb = "BT_BB" } }, + { name = "CPU_CTRL", virtual = true }, + { name = "FLASH", virtual = true }, + { name = "GPIO_DEDICATED", virtual = true }, + { name = "PSRAM", virtual = true }, + { name = "SW_INTERRUPT", virtual = true }, + { name = "ULP_RISCV_CORE", virtual = true }, + { name = "WIFI", virtual = true, interrupts = { mac = "WIFI_MAC", pwr = "WIFI_PWR" }, stable = true }, +] + +symbols = [ + # Additional peripherals defined by us (the developers): + "phy", + "psram", + "psram_dma", + "octal_psram", + "swd", + "ulp_riscv_core", + + # ROM capabilities + "rom_crc_le", + "rom_crc_be", + "rom_md5_bsd", + + # Wakeup SOC based on ESP-IDF: + "pm_support_ext0_wakeup", + "pm_support_ext1_wakeup", + "pm_support_touch_sensor_wakeup", + "pm_support_wifi_wakeup", + "pm_support_bt_wakeup", + "uart_support_wakeup_int", + "ulp_supported", + "riscv_coproc_supported", +] + +[device.soc] +multi_core_enabled = true +rc_fast_clk_default = 17_500_000 + +memory_map = { ranges = [ + { name = "dram", start = 0x3FC8_8000, end = 0x3FD0_0000 }, + { name = "dram2_uninit", start = 0x3FCD_B700, end = 0x3FCE_D710 }, +] } + +clocks = { system_clocks = { clock_tree = [ + # High-speed clock sources + { name = "XTAL_CLK", type = "source", values = "40", output = "VALUE * 1_000_000", always_on = true }, + { name = "PLL_CLK", type = "derived", from = "XTAL_CLK", values = "320, 480", output = "VALUE * 1_000_000", reject = "VALUE == 320_000_000 && CPU_PLL_DIV_OUT == 240_000_000" }, + { name = "RC_FAST_CLK", type = "source", output = "17_500_000" }, + + # Low-speed clocks + { name = "XTAL32K_CLK", type = "source", output = "32768" }, + { name = "RC_SLOW_CLK", type = "source", output = "136_000" }, + { name = "RC_FAST_DIV_CLK", type = "divider", output = "RC_FAST_CLK / 256" }, + + # CPU clock source dividers + { name = "SYSTEM_PRE_DIV_IN", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + ] }, + { name = "SYSTEM_PRE_DIV", type = "divider", params = { divisor = "0 .. 1024" }, output = "SYSTEM_PRE_DIV_IN / (divisor + 1)" }, + # PLL CLK divider, modeled as a generic derived source because valid divider values depend on the PLL frequency + { name = "CPU_PLL_DIV_OUT", type = "derived", from = "PLL_CLK", values = "80, 160, 240", output = "VALUE * 1_000_000", reject = "VALUE == 240_000_000 && PLL_CLK == 320_000_000" }, + + # The meta-switch + { name = "CPU_CLK", type = "mux", always_on = true, variants = [ + # source clock CPU clock additional config (mux = variant name, divider = divisor expression) + { name = "XTAL", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_PWM_CLK = CPU", "SYSTEM_PRE_DIV_IN = XTAL" ] }, + { name = "RC_FAST", outputs = "SYSTEM_PRE_DIV", configures = [ "APB_CLK = CPU", "CRYPTO_PWM_CLK = CPU", "SYSTEM_PRE_DIV_IN = RC_FAST" ] }, + { name = "PLL", outputs = "CPU_PLL_DIV_OUT", configures = [ "APB_CLK = PLL", "CRYPTO_PWM_CLK = PLL" ] }, + ] }, + + # CPU-dependent clock signals + { name = "PLL_D2", type = "divider", output = "PLL_CLK / 2" }, + { name = "PLL_160M", type = "derived", from = "CPU_CLK", output = "160_000_000" }, + + { name = "APB_80M", type = "derived", from = "CPU_CLK", output = "80_000_000" }, + { name = "APB_CLK", type = "mux", variants = [ + { name = "PLL", outputs = "APB_80M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + { name = "CRYPTO_PWM_CLK", type = "mux", variants = [ + { name = "PLL", outputs = "PLL_160M" }, + { name = "CPU", outputs = "CPU_CLK" }, + ] }, + + # Low-power clocks + { name = "RC_FAST_CLK_DIV_N", type = "divider", params = { divisor = "0 ..= 3" }, output = "RC_FAST_CLK / (divisor + 1)" }, + { name = "XTAL_DIV_CLK", type = "divider", output = "XTAL_CLK / 2" }, + { name = "RTC_SLOW_CLK", type = "mux", variants = [ + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RC_SLOW", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_DIV_CLK" }, + ] }, + { name = "RTC_FAST_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_DIV_CLK" }, + { name = "RC", outputs = "RC_FAST_CLK_DIV_N" }, + ] }, + + # Low-power wireless clock source + { name = "LOW_POWER_CLK", type = "mux", variants = [ + { name = "XTAL", outputs = "XTAL_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL32K", outputs = "XTAL32K_CLK" }, + { name = "RTC_SLOW", outputs = "RTC_SLOW_CLK" }, + ] }, + + # System-wide peripheral clocks + { name = "UART_MEM_CLK", type = "mux", default = "XTAL", variants = [ + # The actual source clock is unknown, but it also doesn't matter. + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, +] }, peripheral_clocks = { templates = [ + # templates + { name = "clk_en_template", value = "{{control}}::regs().{{clk_en_register}}().modify(|_, w| w.{{clk_en_field}}().bit(enable));" }, + { name = "rst_template", value = "{{control}}::regs().{{rst_register}}().modify(|_, w| w.{{rst_field}}().bit(reset));" }, + # substitutions + { name = "control", value = "crate::peripherals::SYSTEM" }, + { name = "reg_group", value = "en0" }, + { name = "clk_en_register", value = "perip_clk_{{reg_group}}" }, + { name = "clk_en_field", value = "{{peripheral}}_clk_en" }, + { name = "rst_register", value = "perip_rst_{{reg_group}}" }, + { name = "rst_field", value = "{{peripheral}}_rst" }, +], peripheral_clocks = [ + { name = "PeriBackup", template_params = { reg_group = "en1", peripheral = "peri_backup" } }, + { name = "Aes", template_params = { reg_group = "en1", peripheral = "crypto_aes" } }, + { name = "Sha", template_params = { reg_group = "en1", peripheral = "crypto_sha" } }, + { name = "Rsa", template_params = { reg_group = "en1", peripheral = "crypto_rsa" } }, + { name = "Ds", template_params = { reg_group = "en1", peripheral = "crypto_ds" } }, + { name = "Hmac", template_params = { reg_group = "en1", peripheral = "crypto_hmac" } }, + { name = "Dma", template_params = { reg_group = "en1" } }, + { name = "SdioHost", template_params = { reg_group = "en1" } }, + { name = "LcdCam", template_params = { reg_group = "en1" } }, + { name = "Uart2", template_params = { reg_group = "en1" }, clocks = "Uart0" }, + { name = "UsbDevice", template_params = { reg_group = "en1" }, keep_enabled = true }, + { name = "DedicatedGpio", template_params = { clk_en_register = "cpu_peri_clk_en", rst_register = "cpu_peri_rst_en" } }, + + #{ name = "Adc2Arb" }, + { name = "Systimer", keep_enabled = true }, + { name = "ApbSarAdc", template_params = { peripheral = "apb_saradc" } }, + { name = "UartMem", keep_enabled = true }, # TODO: keep_enabled can be removed once esp-println needs explicit initialization + { name = "Usb" }, + { name = "I2s1" }, + { name = "Mcpwm1", template_params = { peripheral = "pwm1" }, clocks = "Mcpwm0" }, + { name = "Twai0", template_params = { peripheral = "twai" } }, + { name = "I2cExt1" }, + { name = "Mcpwm0", template_params = { peripheral = "pwm0" }, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "CRYPTO_PWM_CLK", variants = [ + { name = "CRYPTO_PWM_CLK", outputs = "CRYPTO_PWM_CLK" } + ] } + ] }, + { name = "Spi3" }, + { name = "Timg1", template_params = { peripheral = "timergroup1" }, clocks = "Timg0" }, + { name = "Timg0", template_params = { peripheral = "timergroup" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL_CLK", variants = [ + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + { name = "APB_CLK", outputs = "APB_CLK" }, + ] }, + { name = "CALIBRATION_CLOCK", type = "mux", variants = [ + { name = "RC_SLOW_CLK", outputs = "RC_SLOW_CLK" }, + { name = "RC_FAST_DIV_CLK", outputs = "RC_FAST_DIV_CLK" }, + { name = "XTAL32K_CLK", outputs = "XTAL32K_CLK" }, + ] }, + ] }, + { name = "Ledc" }, + { name = "Pcnt" }, + { name = "Rmt", clocks = [ + { name = "SCLK", type = "mux", default = "APB_CLK", variants = [ + { name = "APB_CLK", outputs = "APB_CLK" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, + ] }, + { name = "Uhci0" }, + { name = "I2cExt0" }, + { name = "Spi2" }, + { name = "Uart1", clocks = "Uart0" }, + { name = "I2s0" }, + { name = "Uart0", template_params = { peripheral = "uart" }, keep_enabled = true, clocks = [ + { name = "FUNCTION_CLOCK", type = "mux", default = "XTAL", variants = [ # UART_SCLK + { name = "APB", outputs = "APB_CLK" }, + { name = "RC_FAST", outputs = "RC_FAST_CLK" }, + { name = "XTAL", outputs = "XTAL_CLK" }, + ] }, + { name = "MEM_CLOCK", type = "mux", default = "MEM", variants = [ + { name = "MEM", outputs = "UART_MEM_CLK" }, + ] }, + ] }, + #{ name = "Spi01" }, + #{ name = "Timers" }, + + # Radio clocks not modeled here. +] } } + +[device.adc] +support_status = "partial" +instances = [ + { name = "adc1" }, + { name = "adc2" }, +] + +[device.aes] +support_status = "partial" +has_split_text_registers = true +endianness_configurable = false +dma = true +dma_mode = ["ECB", "CBC", "OFB", "CTR", "CFB8", "CFB128"] +key_length = { options = [ + { bits = 128, encrypt_mode = 0, decrypt_mode = 4 }, + { bits = 256, encrypt_mode = 2, decrypt_mode = 6 } +] } + +[device.assist_debug] +support_status = "partial" +has_region_monitor = true + +[device.dma] +support_status = "partial" +kind = "gdma" +gdma_version = 1 +separate_in_out_interrupts = true +supports_mem2mem = true +max_priority = 9 + +[device.gpio] +support_status = "supported" +has_bank_1 = true +gpio_function = 1 +constant_0_input = 0x3c +constant_1_input = 0x38 +pins = [ + { pin = 0, rtc = { 0 = "RTC_GPIO0", 1 = "SAR_I2C_SCL_0" }, limitations = ["strapping"] }, + { pin = 1, analog = { 0 = "TOUCH1", 1 = "ADC1_CH0" }, rtc = { 0 = "RTC_GPIO1", 1 = "SAR_I2C_SDA_0" } }, + { pin = 2, analog = { 0 = "TOUCH2", 1 = "ADC1_CH1" }, rtc = { 0 = "RTC_GPIO2", 1 = "SAR_I2C_SCL_1" } }, + { pin = 3, analog = { 0 = "TOUCH3", 1 = "ADC1_CH2" }, rtc = { 0 = "RTC_GPIO3", 1 = "SAR_I2C_SDA_1" }, limitations = ["strapping"] }, + { pin = 4, analog = { 0 = "TOUCH4", 1 = "ADC1_CH3" }, rtc = { 0 = "RTC_GPIO4" } }, + { pin = 5, analog = { 0 = "TOUCH5", 1 = "ADC1_CH4" }, rtc = { 0 = "RTC_GPIO5" } }, + { pin = 6, analog = { 0 = "TOUCH6", 1 = "ADC1_CH5" }, rtc = { 0 = "RTC_GPIO6" } }, + { pin = 7, analog = { 0 = "TOUCH7", 1 = "ADC1_CH6" }, rtc = { 0 = "RTC_GPIO7" } }, + { pin = 8, functions = { 3 = "SUBSPICS1" }, analog = { 0 = "TOUCH8", 1 = "ADC1_CH7" }, rtc = { 0 = "RTC_GPIO8" } }, + { pin = 9, functions = { 3 = "SUBSPIHD", 4 = "FSPIHD" }, analog = { 0 = "TOUCH9", 1 = "ADC1_CH8" }, rtc = { 0 = "RTC_GPIO9" } }, + { pin = 10, functions = { 2 = "FSPIIO4", 3 = "SUBSPICS0", 4 = "FSPICS0" }, analog = { 0 = "TOUCH10", 1 = "ADC1_CH9" }, rtc = { 0 = "RTC_GPIO10" } }, + { pin = 11, functions = { 2 = "FSPIIO5", 3 = "SUBSPID", 4 = "FSPID" }, analog = { 0 = "TOUCH11", 1 = "ADC2_CH0" }, rtc = { 0 = "RTC_GPIO11" } }, + { pin = 12, functions = { 2 = "FSPIIO6", 3 = "SUBSPICLK", 4 = "FSPICLK" }, analog = { 0 = "TOUCH12", 1 = "ADC2_CH1" }, rtc = { 0 = "RTC_GPIO12" } }, + { pin = 13, functions = { 2 = "FSPIIO7", 3 = "SUBSPIQ", 4 = "FSPIQ" }, analog = { 0 = "TOUCH13", 1 = "ADC2_CH2" }, rtc = { 0 = "RTC_GPIO13" } }, + { pin = 14, functions = { 2 = "FSPIDQS", 3 = "SUBSPIWP", 4 = "FSPIWP" }, analog = { 0 = "TOUCH14", 1 = "ADC2_CH3" }, rtc = { 0 = "RTC_GPIO14" } }, + { pin = 15, functions = { 2 = "U0RTS" }, analog = { 0 = "XTAL_32K_P", 1 = "ADC2_CH4" }, rtc = { 0 = "RTC_GPIO15" } }, + { pin = 16, functions = { 2 = "U0CTS" }, analog = { 0 = "XTAL_32K_N", 1 = "ADC2_CH5" }, rtc = { 0 = "RTC_GPIO16" } }, + { pin = 17, functions = { 2 = "U1TXD" }, analog = { 1 = "ADC2_CH6" }, rtc = { 0 = "RTC_GPIO17" } }, + { pin = 18, functions = { 2 = "U1RXD", 3 = "CLK_OUT3" }, analog = { 1 = "ADC2_CH7" }, rtc = { 0 = "RTC_GPIO18" } }, + { pin = 19, functions = { 2 = "U1RTS", 3 = "CLK_OUT2" }, analog = { 0 = "USB_DM", 1 = "ADC2_CH8" }, rtc = { 0 = "RTC_GPIO19" } }, + { pin = 20, functions = { 2 = "U1CTS", 3 = "CLK_OUT1" }, analog = { 0 = "USB_DP", 1 = "ADC2_CH9" }, rtc = { 0 = "RTC_GPIO20" } }, + { pin = 21, rtc = { 0 = "RTC_GPIO21" } }, + + { pin = 26, functions = { 0 = "SPICS1" }, limitations = ["spi_psram"] }, + { pin = 27, functions = { 0 = "SPIHD" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 28, functions = { 0 = "SPIWP" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 29, functions = { 0 = "SPICS0" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 30, functions = { 0 = "SPICLK" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 31, functions = { 0 = "SPIQ" }, limitations = ["spi_flash", "spi_psram"] }, + { pin = 32, functions = { 0 = "SPID" }, limitations = ["spi_flash"] }, + { pin = 33, functions = { 2 = "FSPIHD", 3 = "SUBSPIHD", 4 = "SPIIO4" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 34, functions = { 2 = "FSPICS0", 3 = "SUBSPICS0", 4 = "SPIIO5" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 35, functions = { 2 = "FSPID", 3 = "SUBSPID", 4 = "SPIIO6" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 36, functions = { 2 = "FSPICLK", 3 = "SUBSPICLK", 4 = "SPIIO7" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 37, functions = { 2 = "FSPIQ", 3 = "SUBSPIQ", 4 = "SPIDQS" }, limitations = ["octal_flash", "octal_psram"] }, + { pin = 38, functions = { 2 = "FSPIWP", 3 = "SUBSPIWP" } }, + { pin = 39, functions = { 0 = "MTCK", 2 = "CLK_OUT3", 3 = "SUBSPICS1" } }, + { pin = 40, functions = { 0 = "MTDO", 2 = "CLK_OUT2" } }, + { pin = 41, functions = { 0 = "MTDI", 2 = "CLK_OUT1" } }, + { pin = 42, functions = { 0 = "MTMS" } }, + { pin = 43, functions = { 0 = "U0TXD", 2 = "CLK_OUT1" } }, + { pin = 44, functions = { 0 = "U0RXD", 2 = "CLK_OUT2" } }, + { pin = 45, limitations = ["strapping"] }, + { pin = 46, limitations = ["strapping"] }, + { pin = 47, functions = { 0 = "SPICLK_P_DIFF", 2 = "SUBSPICLK_P_DIFF" } }, + { pin = 48, functions = { 0 = "SPICLK_N_DIFF", 2 = "SUBSPICLK_N_DIFF" } }, +] +input_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPID4", id = 7 }, + { name = "SPID5", id = 8 }, + { name = "SPID6", id = 9 }, + { name = "SPID7", id = 10 }, + { name = "SPIDQS", id = 11 }, + { name = "U0RXD", id = 12 }, + { name = "U0CTS", id = 13 }, + { name = "U0DSR", id = 14 }, + { name = "U1RXD", id = 15 }, + { name = "U1CTS", id = 16 }, + { name = "U1DSR", id = 17 }, + { name = "U2RXD", id = 18 }, + { name = "U2CTS", id = 19 }, + { name = "U2DSR", id = 20 }, + { name = "I2S1_MCLK", id = 21 }, + { name = "I2S0O_BCK", id = 22 }, + { name = "I2S0_MCLK", id = 23 }, + { name = "I2S0O_WS", id = 24 }, + { name = "I2S0I_SD", id = 25 }, + { name = "I2S0I_BCK", id = 26 }, + { name = "I2S0I_WS", id = 27 }, + { name = "I2S1O_BCK", id = 28 }, + { name = "I2S1O_WS", id = 29 }, + { name = "I2S1I_SD", id = 30 }, + { name = "I2S1I_BCK", id = 31 }, + { name = "I2S1I_WS", id = 32 }, + { name = "PCNT0_SIG_CH0", id = 33 }, + { name = "PCNT0_SIG_CH1", id = 34 }, + { name = "PCNT0_CTRL_CH0", id = 35 }, + { name = "PCNT0_CTRL_CH1", id = 36 }, + { name = "PCNT1_SIG_CH0", id = 37 }, + { name = "PCNT1_SIG_CH1", id = 38 }, + { name = "PCNT1_CTRL_CH0", id = 39 }, + { name = "PCNT1_CTRL_CH1", id = 40 }, + { name = "PCNT2_SIG_CH0", id = 41 }, + { name = "PCNT2_SIG_CH1", id = 42 }, + { name = "PCNT2_CTRL_CH0", id = 43 }, + { name = "PCNT2_CTRL_CH1", id = 44 }, + { name = "PCNT3_SIG_CH0", id = 45 }, + { name = "PCNT3_SIG_CH1", id = 46 }, + { name = "PCNT3_CTRL_CH0", id = 47 }, + { name = "PCNT3_CTRL_CH1", id = 48 }, + { name = "I2S0I_SD1", id = 51 }, + { name = "I2S0I_SD2", id = 52 }, + { name = "I2S0I_SD3", id = 53 }, + { name = "CORE1_GPIO7", id = 54 }, + { name = "USB_EXTPHY_VP", id = 55 }, + { name = "USB_EXTPHY_VM", id = 56 }, + { name = "USB_EXTPHY_RCV", id = 57 }, + { name = "USB_OTG_IDDIG", id = 58 }, + { name = "USB_OTG_AVALID", id = 59 }, + { name = "USB_SRP_BVALID", id = 60 }, + { name = "USB_OTG_VBUSVALID", id = 61 }, + { name = "USB_SRP_SESSEND", id = 62 }, + { name = "SPI3_CLK", id = 66 }, + { name = "SPI3_Q", id = 67 }, + { name = "SPI3_D", id = 68 }, + { name = "SPI3_HD", id = 69 }, + { name = "SPI3_WP", id = 70 }, + { name = "SPI3_CS0", id = 71 }, + { name = "RMT_SIG_0", id = 81 }, + { name = "RMT_SIG_1", id = 82 }, + { name = "RMT_SIG_2", id = 83 }, + { name = "RMT_SIG_3", id = 84 }, + { name = "I2CEXT0_SCL", id = 89 }, + { name = "I2CEXT0_SDA", id = 90 }, + { name = "I2CEXT1_SCL", id = 91 }, + { name = "I2CEXT1_SDA", id = 92 }, + { name = "FSPICLK", id = 101 }, + { name = "FSPIQ", id = 102 }, + { name = "FSPID", id = 103 }, + { name = "FSPIHD", id = 104 }, + { name = "FSPIWP", id = 105 }, + { name = "FSPIIO4", id = 106 }, + { name = "FSPIIO5", id = 107 }, + { name = "FSPIIO6", id = 108 }, + { name = "FSPIIO7", id = 109 }, + { name = "FSPICS0", id = 110 }, + { name = "TWAI_RX", id = 116 }, + { name = "SUBSPIQ", id = 120 }, + { name = "SUBSPID", id = 121 }, + { name = "SUBSPIHD", id = 122 }, + { name = "SUBSPIWP", id = 123 }, + { name = "CORE1_GPIO0", id = 129 }, + { name = "CORE1_GPIO1", id = 130 }, + { name = "CORE1_GPIO2", id = 131 }, + { name = "CAM_DATA_0", id = 133 }, + { name = "CAM_DATA_1", id = 134 }, + { name = "CAM_DATA_2", id = 135 }, + { name = "CAM_DATA_3", id = 136 }, + { name = "CAM_DATA_4", id = 137 }, + { name = "CAM_DATA_5", id = 138 }, + { name = "CAM_DATA_6", id = 139 }, + { name = "CAM_DATA_7", id = 140 }, + { name = "CAM_DATA_8", id = 141 }, + { name = "CAM_DATA_9", id = 142 }, + { name = "CAM_DATA_10", id = 143 }, + { name = "CAM_DATA_11", id = 144 }, + { name = "CAM_DATA_12", id = 145 }, + { name = "CAM_DATA_13", id = 146 }, + { name = "CAM_DATA_14", id = 147 }, + { name = "CAM_DATA_15", id = 148 }, + { name = "CAM_PCLK", id = 149 }, + { name = "CAM_H_ENABLE", id = 150 }, + { name = "CAM_H_SYNC", id = 151 }, + { name = "CAM_V_SYNC", id = 152 }, + { name = "SUBSPID4", id = 155 }, + { name = "SUBSPID5", id = 156 }, + { name = "SUBSPID6", id = 157 }, + { name = "SUBSPID7", id = 158 }, + { name = "SUBSPIDQS", id = 159 }, + { name = "PWM0_SYNC0", id = 160 }, + { name = "PWM0_SYNC1", id = 161 }, + { name = "PWM0_SYNC2", id = 162 }, + { name = "PWM0_F0", id = 163 }, + { name = "PWM0_F1", id = 164 }, + { name = "PWM0_F2", id = 165 }, + { name = "PWM0_CAP0", id = 166 }, + { name = "PWM0_CAP1", id = 167 }, + { name = "PWM0_CAP2", id = 168 }, + { name = "PWM1_SYNC0", id = 169 }, + { name = "PWM1_SYNC1", id = 170 }, + { name = "PWM1_SYNC2", id = 171 }, + { name = "PWM1_F0", id = 172 }, + { name = "PWM1_F1", id = 173 }, + { name = "PWM1_F2", id = 174 }, + { name = "PWM1_CAP0", id = 175 }, + { name = "PWM1_CAP1", id = 176 }, + { name = "PWM1_CAP2", id = 177 }, + { name = "SDHOST_CCMD_IN_1", id = 178 }, + { name = "SDHOST_CCMD_IN_2", id = 179 }, + { name = "SDHOST_CDATA_IN_10", id = 180 }, + { name = "SDHOST_CDATA_IN_11", id = 181 }, + { name = "SDHOST_CDATA_IN_12", id = 182 }, + { name = "SDHOST_CDATA_IN_13", id = 183 }, + { name = "SDHOST_CDATA_IN_14", id = 184 }, + { name = "SDHOST_CDATA_IN_15", id = 185 }, + { name = "SDHOST_CDATA_IN_16", id = 186 }, + { name = "SDHOST_CDATA_IN_17", id = 187 }, + { name = "SDHOST_DATA_STROBE_1", id = 192 }, + { name = "SDHOST_DATA_STROBE_2", id = 193 }, + { name = "SDHOST_CARD_DETECT_N_1", id = 194 }, + { name = "SDHOST_CARD_DETECT_N_2", id = 195 }, + { name = "SDHOST_CARD_WRITE_PRT_1", id = 196 }, + { name = "SDHOST_CARD_WRITE_PRT_2", id = 197 }, + { name = "SDHOST_CARD_INT_N_1", id = 198 }, + { name = "SDHOST_CARD_INT_N_2", id = 199 }, + { name = "SDHOST_CDATA_IN_20", id = 213 }, + { name = "SDHOST_CDATA_IN_21", id = 214 }, + { name = "SDHOST_CDATA_IN_22", id = 215 }, + { name = "SDHOST_CDATA_IN_23", id = 216 }, + { name = "SDHOST_CDATA_IN_24", id = 217 }, + { name = "SDHOST_CDATA_IN_25", id = 218 }, + { name = "SDHOST_CDATA_IN_26", id = 219 }, + { name = "SDHOST_CDATA_IN_27", id = 220 }, + + { name = "PRO_ALONEGPIO0", id = 221 }, + { name = "PRO_ALONEGPIO1", id = 222 }, + { name = "PRO_ALONEGPIO2", id = 223 }, + { name = "PRO_ALONEGPIO3", id = 224 }, + { name = "PRO_ALONEGPIO4", id = 225 }, + { name = "PRO_ALONEGPIO5", id = 226 }, + { name = "PRO_ALONEGPIO6", id = 227 }, + { name = "PRO_ALONEGPIO7", id = 228 }, + + { name = "USB_JTAG_TDO_BRIDGE", id = 251 }, + { name = "CORE1_GPIO3", id = 252 }, + { name = "CORE1_GPIO4", id = 253 }, + { name = "CORE1_GPIO5", id = 254 }, + { name = "CORE1_GPIO6", id = 255 }, + + { name = "SPIIO4" }, + { name = "SPIIO5" }, + { name = "SPIIO6" }, + { name = "SPIIO7" }, + + { name = "MTDI" }, + { name = "MTCK" }, + { name = "MTMS" }, +] +output_signals = [ + { name = "SPIQ", id = 0 }, + { name = "SPID", id = 1 }, + { name = "SPIHD", id = 2 }, + { name = "SPIWP", id = 3 }, + { name = "SPICLK", id = 4 }, + { name = "SPICS0", id = 5 }, + { name = "SPICS1", id = 6 }, + { name = "SPID4", id = 7 }, + { name = "SPID5", id = 8 }, + { name = "SPID6", id = 9 }, + { name = "SPID7", id = 10 }, + { name = "SPIDQS", id = 11 }, + { name = "U0TXD", id = 12 }, + { name = "U0RTS", id = 13 }, + { name = "U0DTR", id = 14 }, + { name = "U1TXD", id = 15 }, + { name = "U1RTS", id = 16 }, + { name = "U1DTR", id = 17 }, + { name = "U2TXD", id = 18 }, + { name = "U2RTS", id = 19 }, + { name = "U2DTR", id = 20 }, + { name = "I2S1_MCLK", id = 21 }, + { name = "I2S0O_BCK", id = 22 }, + { name = "I2S0_MCLK", id = 23 }, + { name = "I2S0O_WS", id = 24 }, + { name = "I2S0O_SD", id = 25 }, + { name = "I2S0I_BCK", id = 26 }, + { name = "I2S0I_WS", id = 27 }, + { name = "I2S1O_BCK", id = 28 }, + { name = "I2S1O_WS", id = 29 }, + { name = "I2S1O_SD", id = 30 }, + { name = "I2S1I_BCK", id = 31 }, + { name = "I2S1I_WS", id = 32 }, + { name = "CORE1_GPIO7", id = 54 }, + { name = "USB_EXTPHY_OEN", id = 55 }, + { name = "USB_EXTPHY_VPO", id = 57 }, + { name = "USB_EXTPHY_VMO", id = 58 }, + { name = "SPI3_CLK", id = 66 }, + { name = "SPI3_Q", id = 67 }, + { name = "SPI3_D", id = 68 }, + { name = "SPI3_HD", id = 69 }, + { name = "SPI3_WP", id = 70 }, + { name = "SPI3_CS0", id = 71 }, + { name = "SPI3_CS1", id = 72 }, + { name = "LEDC_LS_SIG0", id = 73 }, + { name = "LEDC_LS_SIG1", id = 74 }, + { name = "LEDC_LS_SIG2", id = 75 }, + { name = "LEDC_LS_SIG3", id = 76 }, + { name = "LEDC_LS_SIG4", id = 77 }, + { name = "LEDC_LS_SIG5", id = 78 }, + { name = "LEDC_LS_SIG6", id = 79 }, + { name = "LEDC_LS_SIG7", id = 80 }, + { name = "RMT_SIG_0", id = 81 }, + { name = "RMT_SIG_1", id = 82 }, + { name = "RMT_SIG_2", id = 83 }, + { name = "RMT_SIG_3", id = 84 }, + { name = "I2CEXT0_SCL", id = 89 }, + { name = "I2CEXT0_SDA", id = 90 }, + { name = "I2CEXT1_SCL", id = 91 }, + { name = "I2CEXT1_SDA", id = 92 }, + { name = "GPIO_SD0", id = 93 }, + { name = "GPIO_SD1", id = 94 }, + { name = "GPIO_SD2", id = 95 }, + { name = "GPIO_SD3", id = 96 }, + { name = "GPIO_SD4", id = 97 }, + { name = "GPIO_SD5", id = 98 }, + { name = "GPIO_SD6", id = 99 }, + { name = "GPIO_SD7", id = 100 }, + { name = "FSPICLK", id = 101 }, + { name = "FSPIQ", id = 102 }, + { name = "FSPID", id = 103 }, + { name = "FSPIHD", id = 104 }, + { name = "FSPIWP", id = 105 }, + { name = "FSPIIO4", id = 106 }, + { name = "FSPIIO5", id = 107 }, + { name = "FSPIIO6", id = 108 }, + { name = "FSPIIO7", id = 109 }, + { name = "FSPICS0", id = 110 }, + { name = "FSPICS1", id = 111 }, + { name = "FSPICS2", id = 112 }, + { name = "FSPICS3", id = 113 }, + { name = "FSPICS4", id = 114 }, + { name = "FSPICS5", id = 115 }, + { name = "TWAI_TX", id = 116 }, + { name = "SUBSPICLK", id = 119 }, + { name = "SUBSPIQ", id = 120 }, + { name = "SUBSPID", id = 121 }, + { name = "SUBSPIHD", id = 122 }, + { name = "SUBSPIWP", id = 123 }, + { name = "SUBSPICS0", id = 124 }, + { name = "SUBSPICS1", id = 125 }, + { name = "FSPIDQS", id = 126 }, + { name = "SPI3_CS2", id = 127 }, + { name = "I2S0O_SD1", id = 128 }, + { name = "CORE1_GPIO0", id = 129 }, + { name = "CORE1_GPIO1", id = 130 }, + { name = "CORE1_GPIO2", id = 131 }, + { name = "LCD_CS", id = 132 }, + { name = "LCD_DATA_0", id = 133 }, + { name = "LCD_DATA_1", id = 134 }, + { name = "LCD_DATA_2", id = 135 }, + { name = "LCD_DATA_3", id = 136 }, + { name = "LCD_DATA_4", id = 137 }, + { name = "LCD_DATA_5", id = 138 }, + { name = "LCD_DATA_6", id = 139 }, + { name = "LCD_DATA_7", id = 140 }, + { name = "LCD_DATA_8", id = 141 }, + { name = "LCD_DATA_9", id = 142 }, + { name = "LCD_DATA_10", id = 143 }, + { name = "LCD_DATA_11", id = 144 }, + { name = "LCD_DATA_12", id = 145 }, + { name = "LCD_DATA_13", id = 146 }, + { name = "LCD_DATA_14", id = 147 }, + { name = "LCD_DATA_15", id = 148 }, + { name = "CAM_CLK", id = 149 }, + { name = "LCD_H_ENABLE", id = 150 }, + { name = "LCD_H_SYNC", id = 151 }, + { name = "LCD_V_SYNC", id = 152 }, + { name = "LCD_DC", id = 153 }, + { name = "LCD_PCLK", id = 154 }, + { name = "SUBSPID4", id = 155 }, + { name = "SUBSPID5", id = 156 }, + { name = "SUBSPID6", id = 157 }, + { name = "SUBSPID7", id = 158 }, + { name = "SUBSPIDQS", id = 159 }, + { name = "PWM0_0A", id = 160 }, + { name = "PWM0_0B", id = 161 }, + { name = "PWM0_1A", id = 162 }, + { name = "PWM0_1B", id = 163 }, + { name = "PWM0_2A", id = 164 }, + { name = "PWM0_2B", id = 165 }, + { name = "PWM1_0A", id = 166 }, + { name = "PWM1_0B", id = 167 }, + { name = "PWM1_1A", id = 168 }, + { name = "PWM1_1B", id = 169 }, + { name = "PWM1_2A", id = 170 }, + { name = "PWM1_2B", id = 171 }, + { name = "SDHOST_CCLK_OUT_1", id = 172 }, + { name = "SDHOST_CCLK_OUT_2", id = 173 }, + { name = "SDHOST_RST_N_1", id = 174 }, + { name = "SDHOST_RST_N_2", id = 175 }, + { name = "SDHOST_CCMD_OD_PULLUP_EN_N", id = 176 }, + { name = "SDIO_TOHOST_INT", id = 177 }, + { name = "SDHOST_CCMD_OUT_1", id = 178 }, + { name = "SDHOST_CCMD_OUT_2", id = 179 }, + { name = "SDHOST_CDATA_OUT_10", id = 180 }, + { name = "SDHOST_CDATA_OUT_11", id = 181 }, + { name = "SDHOST_CDATA_OUT_12", id = 182 }, + { name = "SDHOST_CDATA_OUT_13", id = 183 }, + { name = "SDHOST_CDATA_OUT_14", id = 184 }, + { name = "SDHOST_CDATA_OUT_15", id = 185 }, + { name = "SDHOST_CDATA_OUT_16", id = 186 }, + { name = "SDHOST_CDATA_OUT_17", id = 187 }, + { name = "SDHOST_CDATA_OUT_20", id = 213 }, + { name = "SDHOST_CDATA_OUT_21", id = 214 }, + { name = "SDHOST_CDATA_OUT_22", id = 215 }, + { name = "SDHOST_CDATA_OUT_23", id = 216 }, + { name = "SDHOST_CDATA_OUT_24", id = 217 }, + { name = "SDHOST_CDATA_OUT_25", id = 218 }, + { name = "SDHOST_CDATA_OUT_26", id = 219 }, + { name = "SDHOST_CDATA_OUT_27", id = 220 }, + + { name = "PRO_ALONEGPIO0", id = 221 }, + { name = "PRO_ALONEGPIO1", id = 222 }, + { name = "PRO_ALONEGPIO2", id = 223 }, + { name = "PRO_ALONEGPIO3", id = 224 }, + { name = "PRO_ALONEGPIO4", id = 225 }, + { name = "PRO_ALONEGPIO5", id = 226 }, + { name = "PRO_ALONEGPIO6", id = 227 }, + { name = "PRO_ALONEGPIO7", id = 228 }, + + { name = "USB_JTAG_TRST", id = 251 }, + { name = "CORE1_GPIO3", id = 252 }, + { name = "CORE1_GPIO4", id = 253 }, + { name = "CORE1_GPIO5", id = 254 }, + { name = "CORE1_GPIO6", id = 255 }, + + { name = "GPIO", id = 256 }, + + { name = "SPIIO4" }, + { name = "SPIIO5" }, + { name = "SPIIO6" }, + { name = "SPIIO7" }, + + { name = "CLK_OUT1" }, + { name = "CLK_OUT2" }, + { name = "CLK_OUT3" }, + + { name = "SPICLK_P_DIFF" }, + { name = "SPICLK_N_DIFF" }, + { name = "SUBSPICLK_P_DIFF" }, + { name = "SUBSPICLK_N_DIFF" }, + + { name = "MTDO" }, +] + +[device.dedicated_gpio] +support_status = "partial" +channels = [ + ["PRO_ALONEGPIO0", "PRO_ALONEGPIO1", "PRO_ALONEGPIO2", "PRO_ALONEGPIO3", "PRO_ALONEGPIO4", "PRO_ALONEGPIO5", "PRO_ALONEGPIO6", "PRO_ALONEGPIO7"], + ["CORE1_GPIO0", "CORE1_GPIO1", "CORE1_GPIO2", "CORE1_GPIO3", "CORE1_GPIO4", "CORE1_GPIO5", "CORE1_GPIO6", "CORE1_GPIO7" ] +] +needs_initialization = true + +[device.i2c_master] +support_status = "supported" +instances = [ + { name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" }, + { name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" }, +] +has_fsm_timeouts = true +has_hw_bus_clear = false +ll_intr_mask = 0x3ffff +fifo_size = 32 +has_bus_timeout_enable = true +max_bus_timeout = 0x1F +can_estimate_nack_reason = true +has_conf_update = true +has_arbitration_en = true +has_tx_fifo_watermark = true +bus_timeout_is_exponential = true + +[device.i2c_slave] +support_status = "not_supported" + +[device.interrupts] +support_status = "partial" +status_registers = 4 +software_interrupt_count = 4 +software_interrupt_delay = 0 +controller = "Xtensa" + +[device.rmt] +support_status = "partial" +ram_start = 0x60016800 +channel_ram_size = 48 +channels = ["Tx", "Tx", "Tx", "Tx", "Rx", "Rx", "Rx", "Rx"] +has_tx_immediate_stop = true +has_tx_loop_count = true +has_tx_loop_auto_stop = true +has_tx_carrier_data_only = true +has_tx_sync = true +has_rx_wrap = true +has_rx_demodulation = true +has_dma = true +clock_sources.supported = [ "None", "Apb", "RcFast", "Xtal" ] +clock_sources.default = "Apb" + +[device.rsa] +support_status = "partial" +size_increment = 32 +memory_size_bytes = 512 + +[device.sha] +support_status = "partial" +dma = true +algo = { sha1 = 0, sha224 = 1, sha256 = 2, sha384 = 3, sha512 = 4, sha512_224 = 5, sha512_256 = 6, sha512_t = 7 } + +[device.spi_master] +support_status = "supported" +supports_dma = true +has_octal = true +has_app_interrupts = true +has_dma_segmented_transfer = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD", "FSPIIO4", "FSPIIO5", "FSPIIO6", "FSPIIO7"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", sio = ["SPI3_D", "SPI3_Q", "SPI3_WP", "SPI3_HD"], cs = ["SPI3_CS0", "SPI3_CS1", "SPI3_CS2"] }, +] + +[device.spi_slave] +support_status = "partial" +supports_dma = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", mosi = "SPI3_D", miso = "SPI3_Q", cs = "SPI3_CS0" }, +] + +[device.timergroup] +support_status = "partial" +timg_has_timer1 = true +timg_has_divcnt_rst = false +instances = [{ name = "timg0" }, { name = "timg1" }] + +[device.uart] +support_status = "supported" +instances = [ + { name = "uart0", sys_instance = "Uart0", tx = "U0TXD", rx = "U0RXD", cts = "U0CTS", rts = "U0RTS" }, + { name = "uart1", sys_instance = "Uart1", tx = "U1TXD", rx = "U1RXD", cts = "U1CTS", rts = "U1RTS" }, + { name = "uart2", sys_instance = "Uart2", tx = "U2TXD", rx = "U2RXD", cts = "U2CTS", rts = "U2RTS" }, +] +ram_size = 128 + +[device.uhci] +support_status = "partial" + +[device.touch] +support_status = "not_supported" + +[device.ds] +support_status = "not_supported" + +[device.rng] +support_status = "partial" +trng_supported = true +apb_cycle_wait_num = 16 # TODO + +[device.sleep] +support_status = "partial" +light_sleep = true +deep_sleep = true + +# Other drivers which are partially supported but have no other configuration: + +## Crypto +[device.hmac] + +## Interfaces +[device.i2s] +[device.camera] +[device.rgb_display] +[device.ledc] +[device.mcpwm] +[device.pcnt] +[device.sd_host] +[device.twai] +[device.usb_otg] +[device.usb_serial_jtag] + +## Miscellaneous +[device.io_mux] +[device.psram] +[device.systimer] +[device.temp_sensor] +[device.ulp_fsm] +[device.ulp_riscv] +[device.lp_timer] + +## Radio +[device.wifi] +mac_version = 1 + +[device.bt] +support_status = "partial" +controller = "btdm" + +[device.phy] +combo_module = true +backed_up_digital_register_count = 21 diff --git a/esp-metadata/src/cfg.rs b/esp-metadata/src/cfg.rs new file mode 100644 index 00000000000..fb03ea34d16 --- /dev/null +++ b/esp-metadata/src/cfg.rs @@ -0,0 +1,763 @@ +pub(crate) mod aes; +pub(crate) mod ecc; +pub(crate) mod gpio; +pub(crate) mod i2c_master; +pub(crate) mod interrupt; +pub(crate) mod rmt; +pub(crate) mod rsa; +pub(crate) mod sha; +pub(crate) mod soc; +pub(crate) mod spi_master; +pub(crate) mod spi_slave; +pub(crate) mod timergroup; +pub(crate) mod uart; + +pub(crate) use aes::*; +pub(crate) use ecc::*; +pub(crate) use gpio::*; +pub(crate) use i2c_master::*; +pub(crate) use interrupt::*; +pub(crate) use rmt::*; +pub(crate) use sha::*; +pub(crate) use soc::*; +pub(crate) use spi_master::*; +pub(crate) use spi_slave::*; +pub(crate) use timergroup::*; +pub(crate) use uart::*; + +pub(crate) trait GenericProperty { + fn cfgs(&self) -> Option> { + None + } + + fn macros(&self) -> Option { + None + } + + fn property_macro_branches(&self) -> proc_macro2::TokenStream { + quote::quote! {} + } +} + +impl GenericProperty for Option { + fn cfgs(&self) -> Option> { + self.as_ref().and_then(|v| v.cfgs()) + } + + fn macros(&self) -> Option { + self.as_ref().and_then(|v| v.macros()) + } + + fn property_macro_branches(&self) -> proc_macro2::TokenStream { + self.as_ref() + .map(|v| v.property_macro_branches()) + .unwrap_or_default() + } +} + +/// Represents a value in the driver configuration. +pub(crate) enum Value { + Unset, + /// A numeric value. The generated macro will not include a type suffix + /// (i.e. will not be generated as `0u32`). + Number(u32), + /// A boolean value. If true, the value is included in the cfg symbols. + Boolean(bool), + /// A string. + String(String), + /// A list of numeric values. A for-each macro is generated for the list. + NumberList(Vec), + /// A list of strings. A separate symbol is generated for each string. + StringList(Vec), + /// A generic object. + Generic(Box), +} + +impl From> for Value { + fn from(value: Option) -> Self { + match value { + Some(v) => Value::Number(v), + None => Value::Unset, + } + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub(crate) enum SupportStatusLevel { + NotAvailable, + NotSupported, + #[default] // Just the common option to reduce visual noise of "declare only" drivers. + Partial, + Supported, +} + +impl SupportStatusLevel { + pub fn icon(self) -> &'static str { + match self { + SupportStatusLevel::NotAvailable => "", + SupportStatusLevel::NotSupported => "❌", + SupportStatusLevel::Partial => "⚒️", + SupportStatusLevel::Supported => "✔️", + } + } + + pub fn status(self) -> &'static str { + match self { + SupportStatusLevel::NotAvailable => "Not available", + SupportStatusLevel::NotSupported => "Not supported", + SupportStatusLevel::Partial => "Partial support", + SupportStatusLevel::Supported => "Supported", + } + } +} + +/// An empty configuration, used when a driver just wants to declare that +/// it supports a peripheral, but does not have any configuration options. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct EmptyInstanceConfig {} + +/// A peripheral instance for which a driver is implemented. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct PeriInstance { + /// The name of the instance + pub name: String, + #[serde(flatten)] + pub instance_config: I, +} + +/// A single cell in the peripheral support table. +#[derive(Default)] +pub(crate) struct SupportItem { + /// The human-readable name of the driver in the table (leftmost cell.) + pub name: &'static str, + /// The ID of the driver ([device.]) in the TOML, that this + /// item corresponds to. + pub config_group: &'static str, + /// If true, this driver is not shown in the peripheral support table. + pub hide_from_peri_table: bool, +} + +/// Define driver configuration structs, and a PeriConfig struct +/// that contains all of them. +macro_rules! driver_configs { + (@ignore $t:tt) => {}; + (@property (u32) $self:ident, $config:ident) => { Value::Number($self.$config) }; + (@property (bool) $self:ident, $config:ident) => { Value::Boolean($self.$config) }; + (@property (String) $self:ident, $config:ident) => { Value::String($self.$config.clone()) }; + (@property (Vec) $self:ident, $config:ident) => { Value::NumberList($self.$config.clone()) }; + (@property (Vec) $self:ident, $config:ident) => { Value::StringList($self.$config.clone()) }; + (@property (Option) $self:ident, $config:ident) => { Value::from($self.$config) }; + (@property ($($other:ty)*) $self:ident, $config:ident) => { Value::Generic(Box::new($self.$config.clone())) }; + (@is_optional Option<$t:ty>) => { true }; + (@is_optional $t:ty) => { false }; + + (@default $default:literal) => { $default }; + (@default $default:literal $opt:literal) => { $opt }; + + // Creates a single struct + (@one + $struct:ident $(<$instance_config:ident>)? ($group:ident) { + $( + $(#[$meta:meta])* $config:ident: $ty:tt $(<$generic:tt>)?, + )* + } + ) => { + #[derive(Debug, Clone, serde::Deserialize)] + pub(crate) struct $struct { + #[serde(default)] + pub support_status: SupportStatusLevel, + // The list of peripherals for which this driver is implemented. + // If empty, the driver supports a single instance only. + #[serde(default)] + pub instances: Vec)?>, + $( + $(#[$meta])* + pub $config: $ty $(<$generic>)? + ),* + } + + impl $struct { + fn properties(&self) -> impl Iterator { + [$( // for each property, generate a tuple + ( + /* name: */ concat!(stringify!($group), ".", stringify!($config)), + /* is_optional: */ driver_configs!(@is_optional $ty $(<$generic>)?), + /* value: */ driver_configs!(@property ($ty $(<$generic>)?) self, $config), + ), + )*].into_iter() + } + } + }; + + // Repeat pattern for multiple structs + ($( + $struct:ident $(<$instance_config:ident>)? { + // This name will be emitted as a cfg symbol, to activate a driver. + driver: $driver:ident, + + // Driver name, used in the generated documentation. + name: $name:literal, + + $(hide_from_peri_table: $hide:literal,)? + + // When set, the type must provide `fn computed_properties(&self) -> impl Iterator`. + // The iterator yields `(name_with_prefix, optional, value)`. + $(has_computed_properties: $computed:literal,)? + + properties: $tokens:tt + }, + )+) => { + // Implement the config driver and DriverConfig trait for each driver + $( + driver_configs!(@one $struct $(<$instance_config>)? ($driver) $tokens); + )+ + + // Generate a single PeriConfig struct that contains all the drivers. Each of the + // drivers is optional to support devices that may not have all peripherals. + #[derive(Default, Debug, Clone, serde::Deserialize)] + pub(crate) struct PeriConfig { + $( + // Each driver is an optional struct. + #[serde(default)] + pub(crate) $driver: Option<$struct>, + )+ + } + + impl PeriConfig { + pub fn drivers() -> &'static [SupportItem] { + &[ + $( + SupportItem { + name: $name, + config_group: stringify!($driver), + hide_from_peri_table: driver_configs!(@default false $($hide)?), + }, + )+ + ] + } + + /// Returns an iterator over all driver names, that are + /// available on the selected device. + pub fn driver_names(&self) -> impl Iterator { + [$( + self.$driver.as_ref().and_then(|d| { + match d.support_status { + SupportStatusLevel::NotAvailable | SupportStatusLevel::NotSupported => None, + _ => Some(stringify!($driver)), + } + }), + )*].into_iter().flatten() + } + + pub fn driver_instances(&self) -> impl Iterator { + // Collect into a vector. This compiles faster than chaining iterators. + let mut instances = vec![]; + $( + if let Some(driver) = &self.$driver { + instances.extend(driver.instances.iter().map(|i| { + format!("{}.{}", stringify!($driver), i.name) + })); + } + )* + instances.into_iter() + } + + /// Returns an iterator over all properties of all peripherals. + /// + /// (property name, optional?, value) + pub fn properties(&self) -> impl Iterator { + // Collect into a vector. This compiles faster than chaining iterators. + let mut properties = vec![]; + $( + if let Some(driver) = &self.$driver { + properties.extend(driver.properties()); + $( + driver_configs!(@ignore $computed); + properties.extend(driver.computed_properties()); + )? + } + )* + properties.into_iter() + } + + /// Returns the support status of a peripheral by its name. + pub fn support_status(&self, driver: &str) -> SupportStatusLevel { + let maybe_status = match driver { + $(stringify!($driver) => self.$driver.as_ref().map(|p| p.support_status),)* + _ => None, + }; + maybe_status.unwrap_or(SupportStatusLevel::NotAvailable) + } + } + }; +} + +// TODO: sort this similar to how the product portfolio is organized +driver_configs![ + AdcProperties { + driver: adc, + name: "ADC", + properties: {} + }, + AesProperties { + driver: aes, + name: "AES", + properties: { + key_length: AesKeyLength, + #[serde(default)] + dma: bool, + #[serde(default)] + dma_mode: Vec, + has_split_text_registers: bool, + endianness_configurable: bool, + } + }, + AssistDebugProperties { + driver: assist_debug, + name: "ASSIST_DEBUG", + properties: { + #[serde(default)] + has_sp_monitor: bool, + #[serde(default)] + has_region_monitor: bool, + } + }, + AvcProperties { + driver: avc, + name: "Analog Voltage Comparator", + properties: {} + }, + BitScramblerProperties { + driver: bit_scrambler, + name: "Bit Scrambler", + properties: {} + }, + BluetoothProperties { + driver: bt, + name: "Bluetooth", + properties: { + controller: String, + } + }, + CameraProperties { + driver: camera, + name: "Camera interface", // LCD_CAM, ESP32 I2S, S2 SPI + properties: {} + }, + DacProperties { + driver: dac, + name: "DAC", + properties: {} + }, + DedicatedGpioProperties { + driver: dedicated_gpio, + name: "Dedicated GPIO", + properties: { + #[serde(default)] + needs_initialization: bool, + #[serde(flatten)] + channel_properties: DedicatedGpioChannels, + } + }, + DmaProperties { + driver: dma, + name: "DMA", + properties: { + kind: String, + #[serde(default)] + supports_mem2mem: bool, + #[serde(default)] + separate_in_out_interrupts: bool, + #[serde(default)] + max_priority: Option, + #[serde(default)] + gdma_version: Option, + } + }, + DsProperties { + driver: ds, + name: "DS", + properties: {} + }, + EccProperties { + driver: ecc, + name: "ECC", + properties: { + #[serde(default)] + zero_extend_writes: bool, + #[serde(default)] + separate_jacobian_point_memory: bool, // Qx, Qy, Qz memory blocks + #[serde(default)] + has_memory_clock_gate: bool, // ECC_MULT_MEM_CLOCK_GATE_FORCE_ON + #[serde(default)] + supports_enhanced_security: bool, // ECC_MULT_SECURITY_MODE + #[serde(flatten)] + extras: EccDriverProperties, + } + }, + EthernetProperties { + driver: ethernet, + name: "Ethernet", + properties: {} + }, + EtmProperties { + driver: etm, + name: "ETM", + properties: {} + }, + GpioProperties { + driver: gpio, + name: "GPIO", + has_computed_properties: true, + properties: { + #[serde(default)] + has_bank_1: bool, + gpio_function: u32, + constant_0_input: u32, + constant_1_input: u32, + #[serde(default)] + remap_iomux_pin_registers: bool, + #[serde(default)] // currently 0 in all devices + func_in_sel_offset: u32, + + #[serde(flatten)] + pins_and_signals: GpioPinsAndSignals, + } + }, + HmacProperties { + driver: hmac, + name: "HMAC", + properties: {} + }, + I2cMasterProperties { + driver: i2c_master, + name: "I2C master", + properties: { + #[serde(default)] + has_fsm_timeouts: bool, + #[serde(default)] + has_hw_bus_clear: bool, + #[serde(default)] + has_bus_timeout_enable: bool, + #[serde(default)] + separate_filter_config_registers: bool, + #[serde(default)] + can_estimate_nack_reason: bool, + #[serde(default)] + has_conf_update: bool, + #[serde(default)] + has_reliable_fsm_reset: bool, + #[serde(default)] + has_arbitration_en: bool, + #[serde(default)] + has_tx_fifo_watermark: bool, + #[serde(default)] + bus_timeout_is_exponential: bool, + #[serde(default)] + i2c0_data_register_ahb_address: Option, + max_bus_timeout: u32, + ll_intr_mask: u32, + fifo_size: u32, + } + }, + I2cSlaveProperties { + driver: i2c_slave, + name: "I2C slave", + properties: {} + }, + I2sProperties { + driver: i2s, + name: "I2S", + properties: {} + }, + IeeeProperties { + driver: ieee802154, + name: "IEEE 802.15.4", + properties: {} + }, + InterruptProperties { + driver: interrupts, + name: "Interrupts", + properties: { + status_registers: u32, + controller: InterruptControllerProperties, + #[serde(flatten)] + software_interrupt_properties: SoftwareInterruptProperties, + } + }, + IoMuxProperties { + driver: io_mux, + name: "IOMUX", + properties: {} + }, + KeyManagerProperties { + driver: key_manager, + name: "Key Manager", + properties: {} + }, + LedcProperties { + driver: ledc, + name: "LEDC", + properties: {} + }, + LpI2cMasterProperties { + driver: lp_i2c_master, + name: "LP I2C master", + properties: { + fifo_size: u32, + } + }, + LpUartProperties { + driver: lp_uart, + name: "LP UART", + properties: { + ram_size: u32, + } + }, + McpwmProperties { + driver: mcpwm, + name: "MCPWM", + properties: {} + }, + ParlIoProperties { + driver: parl_io, + name: "PARL_IO", + properties: { + version: u32, + // TODO: model signal counts, RC CLK out capability, etc. + } + }, + PcntProperties { + driver: pcnt, + name: "PCNT", + properties: {} + }, + PhyProperties { + driver: phy, + name: "PHY", + properties: { + #[serde(default)] + combo_module: bool, + #[serde(default)] + backed_up_digital_register_count: Option, + } + }, + PsramProperties { + driver: psram, + name: "PSRAM", + properties: {} + }, + RgbProperties { + driver: rgb_display, + name: "RGB display", // LCD_CAM, ESP32 I2S, S2 SPI + properties: {} + }, + RmtProperties { + driver: rmt, + name: "RMT", + properties: { + ram_start: u32, + channel_ram_size: u32, + channels: RmtChannelConfig, + #[serde(default)] + has_tx_immediate_stop: bool, + #[serde(default)] + has_tx_loop_count: bool, + #[serde(default)] + has_tx_loop_auto_stop: bool, + #[serde(default)] + has_tx_carrier_data_only: bool, + #[serde(default)] + has_tx_sync: bool, + #[serde(default)] + has_rx_wrap: bool, + #[serde(default)] + has_rx_demodulation: bool, + #[serde(default)] + has_dma: bool, + #[serde(default)] + has_per_channel_clock: bool, + clock_sources: RmtClockSourcesConfig, + } + }, + RngProperties { + driver: rng, + name: "RNG", + properties: { + apb_cycle_wait_num: u32, + #[serde(default)] + trng_supported: bool, + } + }, + RsaProperties { + driver: rsa, + name: "RSA", + has_computed_properties: true, + properties: { + size_increment: u32, + memory_size_bytes: u32, + } + }, + LpTimer { + driver: lp_timer, + name: "RTC Timekeeping", + properties: {} + }, + SdHostProperties { + driver: sd_host, + name: "SDIO host", + properties: {} + }, + SdSlaveProperties { + driver: sd_slave, + name: "SDIO slave", + properties: {} + }, + ShaProperties { + driver: sha, + name: "SHA", + properties: { + #[serde(default)] + dma: bool, + #[serde(default)] + algo: ShaAlgoMap, + } + }, + SleepProperties { + driver: sleep, + name: "Light/deep sleep", + properties: { + #[serde(default)] + light_sleep: bool, + #[serde(default)] + deep_sleep: bool, + } + }, + SocProperties { + driver: soc, + name: "SOC", + hide_from_peri_table: true, + properties: { + #[serde(default)] + cpu_has_branch_predictor: bool, + #[serde(default)] + cpu_has_csr_pc: bool, + #[serde(default)] + multi_core_enabled: bool, + #[serde(default)] + cpu_csr_prv_mode: Option, + #[serde(default)] + rc_fast_clk_default: Option, + #[serde(default)] + clocks: DeviceClocks, + memory_map: MemoryMap, + } + }, + SpiMasterProperties { + driver: spi_master, + name: "SPI master", + properties: { + #[serde(default)] + supports_dma: bool, + #[serde(default)] + has_octal: bool, + #[serde(default)] + has_app_interrupts: bool, + #[serde(default)] + has_dma_segmented_transfer: bool, + /// The PCR has a clock pre-divider before the SPI peripheral. + #[serde(default)] + has_clk_pre_div: bool, + } + }, + SpiSlaveProperties { + driver: spi_slave, + name: "SPI slave", + properties: { + #[serde(default)] + supports_dma: bool, + } + }, + SysTimerProperties { + driver: systimer, + name: "SYSTIMER", + properties: {} + }, + TempProperties { + driver: temp_sensor, + name: "Temperature sensor", + properties: {} + }, + TimersProperties { + driver: timergroup, + name: "Timers", + properties: { + #[serde(default)] + timg_has_timer1: bool, + #[serde(default)] + timg_has_divcnt_rst: bool, + + #[serde(default)] + rc_fast_calibration: Option, + } + }, + TouchProperties { + driver: touch, + name: "Touch", + properties: {} + }, + TwaiProperties { + driver: twai, + name: "TWAI / CAN / CANFD", + properties: {} + }, + UartProperties { + driver: uart, + name: "UART", + properties: { + ram_size: u32, + #[serde(default)] + peripheral_controls_mem_clk: bool, + } + }, + UhciProperties { + driver: uhci, + name: "UHCI", + properties: { + #[serde(default)] + combined_uart_selector_field: bool, + } + }, + UlpFsmProperties { + driver: ulp_fsm, + name: "ULP (FSM)", + properties: {} + }, + UlpRiscvProperties { + driver: ulp_riscv, + name: "ULP (RISC-V)", + properties: {} + }, + UsbOtgProperties { + driver: usb_otg, + name: "USB OTG FS", + properties: {} + }, + UsbSerialJtagProperties { + driver: usb_serial_jtag, + name: "USB Serial/JTAG", + properties: {} + }, + WifiProperties { + driver: wifi, + name: "WIFI", + properties: { + #[serde(default)] + has_wifi6: bool, + mac_version: u32, + #[serde(default)] + has_5g: bool, + } + }, +]; diff --git a/esp-metadata/src/cfg/aes.rs b/esp-metadata/src/cfg/aes.rs new file mode 100644 index 00000000000..7f395dc75f4 --- /dev/null +++ b/esp-metadata/src/cfg/aes.rs @@ -0,0 +1,49 @@ +use quote::quote; + +use crate::{cfg::GenericProperty, generate_for_each_macro, number}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct AesKeyLengthOption { + /// The key length in bits. + pub bits: u32, + + /// The ID of the encryption mode. + pub encrypt_mode: u32, + + /// The ID of the decryption mode. + pub decrypt_mode: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct AesKeyLength { + /// The supported key length options. + options: Vec, +} + +impl GenericProperty for AesKeyLength { + fn macros(&self) -> Option { + let bits = self + .options + .iter() + .map(|opt| number(opt.bits)) + .collect::>(); + + let modes = self + .options + .iter() + .map(|opt| { + let bits = number(opt.bits); + let encrypt_mode = number(opt.encrypt_mode); + let decrypt_mode = number(opt.decrypt_mode); + quote! { + #bits, #encrypt_mode, #decrypt_mode + } + }) + .collect::>(); + + Some(generate_for_each_macro( + "aes_key_length", + &[("bits", &bits), ("modes", &modes)], + )) + } +} diff --git a/esp-metadata/src/cfg/ecc.rs b/esp-metadata/src/cfg/ecc.rs new file mode 100644 index 00000000000..1a2aafa1033 --- /dev/null +++ b/esp-metadata/src/cfg/ecc.rs @@ -0,0 +1,120 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct EccDriverProperties { + working_modes: Vec, + curves: Vec, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +struct WorkingModeEntry { + id: u32, + mode: WorkingMode, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +struct CurveEntry { + id: u32, + curve: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, strum::Display)] +#[serde(rename_all = "snake_case")] +enum WorkingMode { + AffinePointMultiplication, + AffinePointVerification, + AffinePointVerificationAndMultiplication, + AffinePointAddition, + JacobianPointMultiplication, + JacobianPointVerification, + AffinePointVerificationAndJacobianPointMultiplication, + FiniteFieldDivision, + ModularAddition, + ModularSubtraction, + ModularMultiplication, + ModularDivision, +} + +impl super::GenericProperty for EccDriverProperties { + fn cfgs(&self) -> Option> { + let mut cfgs = vec![]; + + let features = [ + (WorkingMode::FiniteFieldDivision, "finite_field_division"), + (WorkingMode::ModularAddition, "modular_arithmetic"), + (WorkingMode::ModularSubtraction, "modular_arithmetic"), + (WorkingMode::ModularMultiplication, "modular_arithmetic"), + (WorkingMode::ModularDivision, "modular_arithmetic"), + (WorkingMode::AffinePointAddition, "point_addition"), + ]; + + for feature in features { + if self.working_modes.iter().any(|mode| mode.mode == feature.0) { + let cfg = format!("ecc_has_{}", feature.1); + if !cfgs.contains(&cfg) { + cfgs.push(cfg); + } + } + } + + for curve in self.curves.iter() { + cfgs.push(format!("ecc_has_curve_p{}", curve.curve)); + } + + Some(cfgs) + } + + fn property_macro_branches(&self) -> proc_macro2::TokenStream { + let mem_block_size = self + .curves + .iter() + .map(|entry| entry.curve / 8) + .max() + .unwrap(); + + let mem_block_size = crate::number(mem_block_size); + + quote::quote! { + ("ecc.mem_block_size") => { + #mem_block_size + }; + } + } + + fn macros(&self) -> Option { + let working_mode_branches = self + .working_modes + .iter() + .map(|entry| { + let id = crate::number(entry.id); + let mode = quote::format_ident!("{}", entry.mode.to_string()); + + quote::quote! { + #id, #mode + } + }) + .collect::>(); + + let curve_branches = self + .curves + .iter() + .map(|entry| { + let name = quote::format_ident!("P{}", entry.curve); + let id = crate::number(entry.id); + let curve = crate::number(entry.curve); + quote::quote! { + #id, #name, #curve + } + }) + .collect::>(); + + let for_each_working_mode = + crate::generate_for_each_macro("ecc_working_mode", &[("all", &working_mode_branches)]); + let for_each_curve = + crate::generate_for_each_macro("ecc_curve", &[("all", &curve_branches)]); + Some(quote::quote! { + #for_each_working_mode + #for_each_curve + }) + } +} diff --git a/esp-metadata/src/cfg/gpio.rs b/esp-metadata/src/cfg/gpio.rs new file mode 100644 index 00000000000..ad21cef4396 --- /dev/null +++ b/esp-metadata/src/cfg/gpio.rs @@ -0,0 +1,735 @@ +//! This module contains configuration used in [device.gpio], as well as +//! functions that generate code for esp-hal. + +use std::str::FromStr; + +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +use crate::{ + cfg::{GenericProperty, Value}, + generate_for_each_macro, + number, +}; + +/// Additional properties (besides those defined in cfg.rs) for [device.gpio]. +/// These don't get turned into symbols, but are used to generate code. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct GpioPinsAndSignals { + /// The list of GPIO pins and their properties. + pub pins: Vec, + + /// The list of peripheral input signals. + pub input_signals: Vec, + + /// The list of peripheral output signals. + pub output_signals: Vec, +} + +impl GenericProperty for GpioPinsAndSignals {} + +/// Possible special cases that may affect pin availability or functionality. +/// +/// Some of these are explicitly encoded in the TOMLs, others are inferred from the pin alternate +/// function list. +#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub(crate) enum PinLimitation { + /// Strapping pin, signal level needs to be carefully set during boot. + Strapping, + + /// Pin is used to interface with SPI flash. + SpiFlash, + + /// Pin is used to interface with Octal SPI flash. + OctalFlash, + + /// Pin is used to interface with SPI PSRAM. + SpiPsram, + + /// Pin is used to interface with Octal SPI PSRAM. + OctalPsram, + + /// Pin is only available on ESP32-PICO-V3. + Esp32PicoV3, + + /// The pin has no output stage. + InputOnly, + + /// Default UART pins. + BootloaderUart, + + /// Debugger pins. + Jtag, + + /// USB Serial/JTAG debugger pins. + UsbJtag, +} + +impl std::fmt::Display for PinLimitation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PinLimitation::Strapping => write!( + f, + "This pin is a strapping pin, it determines how the chip boots." + ), + PinLimitation::SpiFlash => { + write!( + f, + "This pin may be reserved for interfacing with SPI flash." + ) + } + PinLimitation::OctalFlash => { + write!( + f, + "This pin may be reserved for interfacing with Octal SPI flash." + ) + } + PinLimitation::SpiPsram => { + write!( + f, + "This pin may be reserved for interfacing with SPI PSRAM." + ) + } + PinLimitation::OctalPsram => { + write!( + f, + "This pin may be reserved for interfacing with Octal SPI PSRAM." + ) + } + PinLimitation::Esp32PicoV3 => write!(f, "This pin is only available on ESP32-PICO-V3."), + PinLimitation::InputOnly => write!(f, "This pin can only be used as an input."), + PinLimitation::BootloaderUart => { + write!( + f, + "By default, this pin is used by the UART programming interface." + ) + } + PinLimitation::Jtag => { + write!( + f, + "These pins may be used to debug the chip using an external JTAG debugger." + ) + } + PinLimitation::UsbJtag => { + write!(f, "These pins may be used to debug the chip using USB.") + } + } + } +} + +/// Properties of a single GPIO pin. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct PinConfig { + /// The GPIO pin number. + pub pin: usize, + + /// Available IO MUX functions for this pin. + #[serde(default)] + pub functions: FunctionMap, + + /// Available analog functions for this pin. + #[serde(default)] + pub analog: AnalogMap, + + /// Available LP/RTC IO functions for this pin. + #[serde(default, alias = "rtc")] + pub lp: LowPowerMap, + + /// Lists cases where the GPIO needs special attention. + #[serde(default)] + pub limitations: Vec, +} + +/// Available alternate functions for a given GPIO pin. +/// +/// Alternate functions allow bypassing the GPIO matrix by selecting a different +/// path in the multiplexers controlled by MCU_SEL. +/// +/// Values of this struct correspond to rows in the IO MUX Pad List table. +/// +/// Used in [device.gpio.pins[X].functions]. The GPIO function is not +/// written here as that is common to all pins. The values are signal names +/// listed in [device.gpio.input_signals] or [device.gpio.output_signals]. +/// `None` means the pin does not provide the given alternate function. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct FunctionMap { + #[serde(rename = "0")] + af0: Option, + #[serde(rename = "1")] + af1: Option, + #[serde(rename = "2")] + af2: Option, + #[serde(rename = "3")] + af3: Option, + #[serde(rename = "4")] + af4: Option, + #[serde(rename = "5")] + af5: Option, +} + +impl FunctionMap { + const COUNT: usize = 6; + + /// Returns the signal associated with the nth alternate function. + /// + /// Note that not all alternate functions are defined. The number of the + /// GPIO function is available separately. Not all alternate function have + /// IO signals. + pub fn get(&self, af: usize) -> Option<&str> { + match af { + 0 => self.af0.as_deref(), + 1 => self.af1.as_deref(), + 2 => self.af2.as_deref(), + 3 => self.af3.as_deref(), + 4 => self.af4.as_deref(), + 5 => self.af5.as_deref(), + _ => None, + } + } +} + +/// Available analog functions for a given GPIO pin. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct AnalogMap { + #[serde(rename = "0")] + af0: Option, + #[serde(rename = "1")] + af1: Option, + #[serde(rename = "2")] + af2: Option, + #[serde(rename = "3")] + af3: Option, + #[serde(rename = "4")] + af4: Option, + #[serde(rename = "5")] + af5: Option, +} + +impl AnalogMap { + const COUNT: usize = 6; + + /// Returns the signal associated with the nth alternate function. + pub fn get(&self, af: usize) -> Option<&str> { + match af { + 0 => self.af0.as_deref(), + 1 => self.af1.as_deref(), + 2 => self.af2.as_deref(), + 3 => self.af3.as_deref(), + 4 => self.af4.as_deref(), + 5 => self.af5.as_deref(), + _ => None, + } + } +} + +/// Available RTC/LP functions for a given GPIO pin. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct LowPowerMap { + #[serde(rename = "0")] + af0: Option, + #[serde(rename = "1")] + af1: Option, + #[serde(rename = "2")] + af2: Option, + #[serde(rename = "3")] + af3: Option, + #[serde(rename = "4")] + af4: Option, + #[serde(rename = "5")] + af5: Option, +} + +impl LowPowerMap { + const COUNT: usize = 6; + + /// Returns the signal associated with the nth alternate function. + pub fn get(&self, af: usize) -> Option<&str> { + match af { + 0 => self.af0.as_deref(), + 1 => self.af1.as_deref(), + 2 => self.af2.as_deref(), + 3 => self.af3.as_deref(), + 4 => self.af4.as_deref(), + 5 => self.af5.as_deref(), + _ => None, + } + } +} + +/// An input or output peripheral signal. The names usually match the signal +/// name in the Peripheral Signal List table, without the `in` or `out` suffix. +/// If the `id` is `None`, the signal cannot be routed through the GPIO matrix. +/// +/// If the TRM's signal table says "no" to Direct Input/Output via IO MUX, the +/// signal does not have an Alternate Function and must be routed through the +/// GPIO matrix. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct IoMuxSignal { + /// The name of the signal. + pub name: String, + + /// The numeric ID of the signal, if the signal can be routed through the + /// GPIO matrix. + #[serde(default)] + pub id: Option, +} + +impl super::GpioProperties { + pub(super) fn computed_properties(&self) -> impl Iterator { + let input_max = self + .pins_and_signals + .input_signals + .iter() + .filter_map(|s| s.id) + .max() + .unwrap_or(0) as u32; + let output_max = self + .pins_and_signals + .output_signals + .iter() + .filter_map(|s| s.id) + .max() + .unwrap_or(0) as u32; + + [ + ("gpio.input_signal_max", false, Value::Number(input_max)), + ("gpio.output_signal_max", false, Value::Number(output_max)), + ] + .into_iter() + } +} + +pub(crate) fn generate_gpios(gpio: &super::GpioProperties) -> TokenStream { + let pin_numbers = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| number(pin.pin)) + .collect::>(); + + let pin_peris = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| format_ident!("GPIO{}", pin.pin)) + .collect::>(); + + let pin_attrs = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| { + // Input must come first + if pin.limitations.contains(&PinLimitation::InputOnly) { + vec![quote! { Input }, quote! {}] + } else { + vec![quote! { Input }, quote! { Output }] + } + }) + .collect::>(); + + let mut lp_functions = vec![]; + let mut expanded_lp_functions = vec![]; + let mut analog_functions = vec![]; + let mut expanded_analog_functions = vec![]; + + let pin_afs = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| { + let mut input_afs = vec![]; + let mut output_afs = vec![]; + + let pin_peri = format_ident!("GPIO{}", pin.pin); + + for af in 0..FunctionMap::COUNT { + let Some(signal) = pin.functions.get(af) else { + continue; + }; + + let af_variant = format_ident!("_{af}"); + let mut found = false; + + // Is the signal present among the input signals? + if let Some(signal) = gpio + .pins_and_signals + .input_signals + .iter() + .find(|s| s.name == signal) + { + let signal_tokens = TokenStream::from_str(&signal.name).unwrap(); + input_afs.push(quote! { #af_variant => #signal_tokens }); + found = true; + } + + // Is the signal present among the output signals? + if let Some(signal) = gpio + .pins_and_signals + .output_signals + .iter() + .find(|s| s.name == signal) + { + let signal_tokens = TokenStream::from_str(&signal.name).unwrap(); + output_afs.push(quote! { #af_variant => #signal_tokens }); + found = true; + } + + assert!( + found, + "Signal '{signal}' not found in input signals for GPIO pin {}", + pin.pin + ); + } + + fn create_matchers_for_signal( + branches: &mut Vec, + pin_peri: &Ident, + signal: &str, + ) { + // Split "NAMEnumber" format fragments into the NAME and the number. The function + // returns `None` if the input string is not in this format. The NAME part can be + // empty (i.e. this function can return `Some("", number)`). + fn split_signal_with_number(fragment: &str) -> Option<(&str, usize)> { + // Find the first character that is not a letter. + let Some(breakpoint) = fragment + .char_indices() + .filter_map(|(idx, c)| if c.is_alphabetic() { None } else { Some(idx) }) + .next() + else { + // fragment only contains letters + return None; + }; + + let number: usize = fragment[breakpoint..].parse().ok()?; + + Some((&fragment[..breakpoint], number)) + } + + let signal_name = TokenStream::from_str(signal).unwrap(); + + let full_signal = { + // The signal name, with numbers replaced with placeholders + let mut pattern = String::new(); + let mut numbers = vec![]; + + let placeholders = ['n', 'm']; + + let mut separator = ""; + for fragment in signal.split('_') { + if let Some((prefix, n)) = split_signal_with_number(fragment) { + let placeholder = placeholders[numbers.len()]; + numbers.push(number(n)); + pattern = format!("{pattern}{separator}{prefix}{placeholder}") + } else { + pattern = format!("{pattern}{separator}{fragment}"); + }; + + separator = "_"; + } + + if pattern == signal { + None + } else { + let pattern = format_ident!("{pattern}"); + + Some(quote! { + ( #signal_name, #pattern #(, #numbers)* ) + }) + } + }; + + if let Some(full_signal) = full_signal { + branches.push(quote! { + #full_signal, #pin_peri + }); + } + } + + for af in 0..AnalogMap::COUNT { + if let Some(signal) = pin.analog.get(af) { + let signal_name = TokenStream::from_str(signal).unwrap(); + analog_functions.push(quote! { #signal_name, #pin_peri }); + create_matchers_for_signal(&mut expanded_analog_functions, &pin_peri, signal); + } + } + + for af in 0..LowPowerMap::COUNT { + if let Some(signal) = pin.lp.get(af) { + let signal_name = TokenStream::from_str(signal).unwrap(); + lp_functions.push(quote! { #signal_name, #pin_peri }); + create_matchers_for_signal(&mut expanded_lp_functions, &pin_peri, signal); + } + } + + quote! { + ( #(#input_afs)* ) ( #(#output_afs)* ) + } + }) + .collect::>(); + + let io_mux_accessor = if gpio.remap_iomux_pin_registers { + let iomux_pin_regs = gpio.pins_and_signals.pins.iter().map(|pin| { + let pin = number(pin.pin); + let accessor = format_ident!("gpio{pin}"); + + quote! { #pin => iomux.#accessor(), } + }); + + quote! { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { + let iomux = crate::peripherals::IO_MUX::regs(); + match gpio_num { + #(#iomux_pin_regs)* + other => panic!("GPIO {} does not exist", other), + } + } + + } + } else { + quote! { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO { + crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize) + } + } + }; + + let mut branches = vec![]; + for (((n, p), af), attrs) in pin_numbers + .iter() + .zip(pin_peris.iter()) + .zip(pin_afs.iter()) + .zip(pin_attrs.iter()) + { + branches.push(quote! { + #n, #p #af (#([#attrs])*) + }) + } + + let for_each_gpio = generate_for_each_macro("gpio", &[("all", &branches)]); + let for_each_analog = generate_for_each_macro( + "analog_function", + &[ + ("all", &analog_functions), + ("all_expanded", &expanded_analog_functions), + ], + ); + let for_each_lp = generate_for_each_macro( + "lp_function", + &[ + ("all", &lp_functions), + ("all_expanded", &expanded_lp_functions), + ], + ); + let input_signals = render_signals("InputSignal", &gpio.pins_and_signals.input_signals); + let output_signals = render_signals("OutputSignal", &gpio.pins_and_signals.output_signals); + + quote! { + /// This macro can be used to generate code for each `GPIOn` instance. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has one option for its "Individual matcher" case: + /// + /// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => $digital_input_signal:ident)*) ($($digital_output_function:ident => $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))` + /// + /// Macro fragments: + /// + /// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0. + /// - `$gpio`: the name of the GPIO. + /// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for function 0 this is `_0`). + /// - `$digital_input_function`: the name of the digital function, as an identifier. + /// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for function 0 this is `_0`). + /// - `$digital_output_function`: the name of the digital function, as an identifier. + /// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. Bracketed so that they can also be matched as optional fragments. Order is always Input first. + /// + /// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))` + #for_each_gpio + + /// This macro can be used to generate code for each analog function of each GPIO. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has two options for its "Individual matcher" case: + /// + /// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers + /// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - expanded signal case, where you need the number(s) of a signal, or the general group to which the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like `(ADC2_CH3, ADCn_CHm, 2, 3)`. + /// + /// Macro fragments: + /// + /// - `$signal`: the name of the signal. + /// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this is `ADCn_CHm`. + /// - `$number`: the numbers extracted from `$signal`. + /// - `$gpio`: the name of the GPIO. + /// + /// Example data: + /// - `(ADC2_CH5, GPIO12)` + /// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)` + /// + /// The expanded syntax is only available when the signal has at least one numbered component. + #for_each_analog + + /// This macro can be used to generate code for each LP/RTC function of each GPIO. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has two options for its "Individual matcher" case: + /// + /// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers + /// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - expanded signal case, where you need the number(s) of a signal, or the general group to which the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`. + /// + /// Macro fragments: + /// + /// - `$signal`: the name of the signal. + /// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this is `ADCn_CHm`. + /// - `$number`: the numbers extracted from `$signal`. + /// - `$gpio`: the name of the GPIO. + /// + /// Example data: + /// - `(RTC_GPIO15, GPIO12)` + /// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)` + /// + /// The expanded syntax is only available when the signal has at least one numbered component. + #for_each_lp + + /// Defines the `InputSignal` and `OutputSignal` enums. + /// + /// This macro is intended to be called in esp-hal only. + #[macro_export] + #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] + macro_rules! define_io_mux_signals { + () => { + #input_signals + #output_signals + }; + } + + /// Defines and implements the `io_mux_reg` function. + /// + /// The generated function has the following signature: + /// + /// ```rust,ignore + /// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { + /// // ... + /// # unimplemented!() + /// } + /// ``` + /// + /// This macro is intended to be called in esp-hal only. + #[macro_export] + #[expect(clippy::crate_in_macro_def)] + #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] + macro_rules! define_io_mux_reg { + () => { + #io_mux_accessor + }; + } + } +} + +fn render_signals(enum_name: &str, signals: &[IoMuxSignal]) -> TokenStream { + if signals.is_empty() { + // If there are no signals, we don't need to generate an enum. + return quote! {}; + } + let mut variants = vec![]; + + for signal in signals { + // First, process only signals that have an ID. + let Some(id) = signal.id else { + continue; + }; + + let name = format_ident!("{}", signal.name); + let value = number(id); + variants.push(quote! { + #name = #value, + }); + } + + for signal in signals { + // Now process signals that do not have an ID. + if signal.id.is_some() { + continue; + }; + + let name = format_ident!("{}", signal.name); + variants.push(quote! { + #name, + }); + } + + let enum_name = format_ident!("{enum_name}"); + + quote! { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Debug, PartialEq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[doc(hidden)] + pub enum #enum_name { + #(#variants)* + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize)] +pub struct DedicatedGpioChannels { + // Cpu -> list of Signals + channels: Vec>, +} + +impl DedicatedGpioChannels { + fn channel_count(&self) -> usize { + assert!( + self.channels + .iter() + .all(|channel| channel.len() == self.channels[0].len()), + "All cores must have the same number of dedicated GPIO channels" + ); + self.channels[0].len() + } +} + +impl GenericProperty for DedicatedGpioChannels { + fn macros(&self) -> Option { + let channel_count = self.channel_count(); + let channel_branches = (0..channel_count).map(number).collect::>(); + let signal_branches = self + .channels + .iter() + .enumerate() + .flat_map(|(core, channels)| { + channels.iter().enumerate().map(move |(channel, signal)| { + let signal = format_ident!("{signal}"); + let core = number(core); + let channel = number(channel); + quote! { #core, #channel, #signal } + }) + }) + .collect::>(); + + Some(generate_for_each_macro( + "dedicated_gpio", + &[ + ("channels", &channel_branches), + ("signals", &signal_branches), + ], + )) + } + + fn property_macro_branches(&self) -> proc_macro2::TokenStream { + let channel_count = number(self.channel_count()); + quote::quote! { + ("dedicated_gpio.channel_count") => { + #channel_count + }; + ("dedicated_gpio.channel_count", str) => { + stringify!(#channel_count) + }; + } + } +} diff --git a/esp-metadata/src/cfg/i2c_master.rs b/esp-metadata/src/cfg/i2c_master.rs new file mode 100644 index 00000000000..793f8683831 --- /dev/null +++ b/esp-metadata/src/cfg/i2c_master.rs @@ -0,0 +1,67 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{cfg::I2cMasterProperties, generate_for_each_macro}; + +/// Instance configuration, used in [device.i2c_master.instances] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct I2cMasterInstanceConfig { + /// The name of the instance in the `esp_hal::system::Peripheral` enum + pub sys_instance: String, + + /// IOMUX signal name of the instance's SCL signal. + pub scl: String, + + /// IOMUX signal name of the instance's SDA signal. + pub sda: String, +} + +/// Generates `for_each_i2c_master!` which can be used to implement the I2C +/// master Instance trait for the relevant peripherals. The macro generates code +/// for each [device.i2c_master.instances[X]] instance. +pub(crate) fn generate_i2c_master_peripherals(i2c: &I2cMasterProperties) -> TokenStream { + let i2c_master_instance_cfgs = i2c + .instances + .iter() + .enumerate() + .map(|(index, instance)| { + let instance_config = &instance.instance_config; + + let id = crate::number(index); + + let instance = format_ident!("{}", instance.name.to_uppercase()); + + let sys = format_ident!("{}", instance_config.sys_instance); + let sda = format_ident!("{}", instance_config.sda); + let scl = format_ident!("{}", instance_config.scl); + + // The order and meaning of these tokens must match their use in the + // `for_each_i2c_master!` call. + quote! { + #id, #instance, #sys, #scl, #sda + } + }) + .collect::>(); + + let for_each = generate_for_each_macro("i2c_master", &[("all", &i2c_master_instance_cfgs)]); + + quote! { + /// This macro can be used to generate code for each peripheral instance of the I2C master driver. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has one option for its "Individual matcher" case: + /// + /// Syntax: `($id:literal, $instance:ident, $sys:ident, $scl:ident, $sda:ident)` + /// + /// Macro fragments: + /// - `$id`: the index of the I2C instance + /// - `$instance`: the name of the I2C instance + /// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. + /// - `$scl`, `$sda`: peripheral signal names. + /// + /// Example data: `(0, I2C0, I2cExt0, I2CEXT0_SCL, I2CEXT0_SDA)` + #for_each + } +} diff --git a/esp-metadata/src/cfg/interrupt.rs b/esp-metadata/src/cfg/interrupt.rs new file mode 100644 index 00000000000..c1f1ae07b03 --- /dev/null +++ b/esp-metadata/src/cfg/interrupt.rs @@ -0,0 +1,273 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{cfg::GenericProperty, generate_for_each_macro, number}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct SoftwareInterruptProperties { + #[serde(rename = "software_interrupt_count")] + count: u32, + #[serde(rename = "software_interrupt_delay")] + delay: u32, +} + +/// Generates `for_each_sw_interrupt!` which can be used to implement SoftwareInterruptControl, and +/// `sw_interrupt_delay` which repeats `nop` enough times to ensure the interrupt is fired before +/// returning. +impl GenericProperty for SoftwareInterruptProperties { + fn macros(&self) -> Option { + let nops = + std::iter::repeat(quote! { ::core::arch::asm!("nop"); }).take(self.delay as usize); + + let channels = (0..self.count) + .map(|i| { + let idx = number(i); + let interrupt = format_ident!("FROM_CPU_INTR{}", i); + let field = format_ident!("software_interrupt{}", i); + quote! { #idx, #interrupt, #field } + }) + .collect::>(); + + let for_each_sw_interrupt = generate_for_each_macro("sw_interrupt", &[("all", &channels)]); + + Some(quote! { + #for_each_sw_interrupt + + #[macro_export] + macro_rules! sw_interrupt_delay { + () => { + unsafe { + #(#nops)* + } + }; + } + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RiscvFlavour { + Basic, + Plic, + Clic, +} + +impl RiscvFlavour { + /// Interrupt lines reserved by hardware. + fn reserved_interrupts(&self) -> impl Iterator { + let reserved: &[_] = match self { + RiscvFlavour::Basic => &[ + 0, // Permanently disabled + 1, // Either disabled or reserved for Wi-Fi, unclear + ], + RiscvFlavour::Plic => { + // Some CLINT interrupts + &[0, 3, 4, 7] + } + RiscvFlavour::Clic => { + // All CLINT interrupts + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + } + }; + reserved.iter().copied() + } + + fn disabled_interrupt(&self) -> usize { + match *self { + RiscvFlavour::Plic => 31, // 0 is U-mode software interrupt + RiscvFlavour::Basic | RiscvFlavour::Clic => 0, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct RiscvControllerProperties { + flavour: RiscvFlavour, + /// Total number of interrupts supported by the controller. + interrupts: u32, + /// Priority levels above 0 + priority_levels: u32, +} + +impl RiscvControllerProperties { + /// Interrupt lines allocated for vectored interrupt handling. + fn vector_interrupts(&self) -> impl Iterator { + let vectors = match self.flavour { + RiscvFlavour::Basic | RiscvFlavour::Plic => { + // Vectoring uses high interrupt lines, as higher IDs are serviced later. + // Allocate the last interrupt lines, that are not reserved or disabled. + let mut interrupts = (0..self.interrupts as usize) + .rev() + .filter(|&intr| { + self.disabled_interrupt() != intr + && self.reserved_interrupts().all(|reserved| reserved != intr) + }) + .take(self.priority_levels as usize) + .collect::>(); + + // We want to return an ascending order + interrupts.reverse(); + + interrupts + } + RiscvFlavour::Clic => { + // After CLINT interrupts. Lower IDs are serviced later. + // Controller implements proper level/priority management. + (16..(16 + self.priority_levels as usize)).collect::>() + } + }; + + vectors.into_iter() + } + + fn disabled_interrupt(&self) -> usize { + self.flavour.disabled_interrupt() + } + + fn reserved_interrupts(&self) -> impl Iterator { + self.flavour.reserved_interrupts() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub enum InterruptControllerProperties { + Xtensa, + Riscv(RiscvControllerProperties), +} + +impl GenericProperty for InterruptControllerProperties { + fn macros(&self) -> Option { + let Self::Riscv(properties) = self else { + return None; + }; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Class { + Interrupt, + Reserved, + Vector, + Disabled, + } + + // interrupt number => class + let mut classes = (0..properties.interrupts) + .map(|_| Class::Interrupt) + .collect::>(); + + // Implementation assumes contiguous range of interrupts + assert!(is_contiguous(properties.vector_interrupts())); + + for intr in properties.vector_interrupts() { + assert_eq!(classes[intr], Class::Interrupt); + classes[intr] = Class::Vector; + } + for intr in properties.reserved_interrupts() { + assert_eq!(classes[intr], Class::Interrupt); + classes[intr] = Class::Reserved; + } + assert_ne!(classes[properties.disabled_interrupt()], Class::Vector); + classes[properties.disabled_interrupt()] = Class::Disabled; + + let mut all = vec![]; + let mut vector = vec![]; + let mut reserved = vec![]; + let mut direct_bindable = vec![]; + + for (i, class) in classes.iter().enumerate() { + let intr_number = number(i); + let idx_in_class = number(match class { + Class::Interrupt => direct_bindable.len(), + Class::Vector => vector.len(), + Class::Reserved => reserved.len(), + Class::Disabled => 0, + }); + let class_name = match class { + Class::Interrupt => format_ident!("direct_bindable"), + Class::Vector => format_ident!("vector"), + Class::Reserved => format_ident!("reserved"), + Class::Disabled => format_ident!("disabled"), + }; + + let tokens = quote! { [#class_name #idx_in_class] #intr_number }; + + all.push(tokens.clone()); + match class { + Class::Interrupt => direct_bindable.push(tokens), + Class::Vector => vector.push(tokens), + Class::Reserved => reserved.push(tokens), + Class::Disabled => {} + } + } + + let for_each_interrupt = generate_for_each_macro("interrupt", &[("all", &all)]); + let for_each_direct_bindable_interrupt = generate_for_each_macro( + "classified_interrupt", + &[ + ("direct_bindable", &direct_bindable), + ("vector", &vector), + ("reserved", &reserved), + ], + ); + + let all_priorities = (0..properties.priority_levels) + .map(|p| { + let variant = format_ident!("Priority{}", p + 1); + let numeric = number(p + 1); + let p = number(p); + + quote! { + #p, #numeric, #variant + } + }) + .collect::>(); + + let for_each_priority = + generate_for_each_macro("interrupt_priority", &[("all", &all_priorities)]); + + Some(quote! { + #for_each_interrupt + #for_each_direct_bindable_interrupt + #for_each_priority + }) + } + + fn cfgs(&self) -> Option> { + let controller = match self { + Self::Xtensa => "xtensa", + Self::Riscv(properties) => match properties.flavour { + RiscvFlavour::Basic => "riscv_basic", + RiscvFlavour::Plic => "plic", + RiscvFlavour::Clic => "clic", + }, + }; + + Some(vec![format!("interrupt_controller=\"{controller}\"")]) + } + + fn property_macro_branches(&self) -> proc_macro2::TokenStream { + match self { + Self::Xtensa => quote! {}, + Self::Riscv(properties) => { + let disabled = number(properties.disabled_interrupt()); + quote::quote! { + ("interrupts.disabled_interrupt") => { + #disabled + }; + } + } + } + } +} + +fn is_contiguous(mut iter: impl Iterator) -> bool { + if let Some(mut prev) = iter.next() { + for next in iter { + if next - prev != 1 { + return false; + } + prev = next; + } + } + true +} diff --git a/esp-metadata/src/cfg/rmt.rs b/esp-metadata/src/cfg/rmt.rs new file mode 100644 index 00000000000..521443808ce --- /dev/null +++ b/esp-metadata/src/cfg/rmt.rs @@ -0,0 +1,144 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{cfg::GenericProperty, generate_for_each_macro, number}; + +/// The capabilities of an RMT channel, used in [device.rmt.channels] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] +pub(crate) enum RmtChannelCapability { + Rx, + Tx, + RxTx, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub(crate) struct RmtChannelConfig( + /// The capability of each channel + Vec, +); + +/// Generates `for_each_rmt_channel!` which can be used to implement channel creators and the main +/// driver struct for the RMT peripheral. +/// +/// The macro generates code for each [device.rmt.channels[X]] entry. +impl GenericProperty for RmtChannelConfig { + fn macros(&self) -> Option { + let channel_cfgs = self + .0 + .iter() + .enumerate() + .map(|(num, _)| { + let num = number(num); + quote! { + #num + } + }) + .collect::>(); + + let make_channel_cfgs = |filter: fn(RmtChannelCapability) -> bool| { + self.0 + .iter() + .enumerate() + .filter(|&(_, &cap)| filter(cap)) + .enumerate() + .map(|(idx, (num, _))| { + let num = number(num); + let idx = number(idx); + quote! { + #num, #idx + } + }) + .collect::>() + }; + + let tx_channel_cfgs = make_channel_cfgs(|cap| { + matches!(cap, RmtChannelCapability::Tx | RmtChannelCapability::RxTx) + }); + let rx_channel_cfgs = make_channel_cfgs(|cap| { + matches!(cap, RmtChannelCapability::Rx | RmtChannelCapability::RxTx) + }); + + let for_each = generate_for_each_macro( + "rmt_channel", + &[ + ("all", &channel_cfgs), + ("tx", &tx_channel_cfgs), + ("rx", &rx_channel_cfgs), + ], + ); + + Some(quote! { + /// This macro can be used to generate code for each channel of the RMT peripheral. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has three options for its "Individual matcher" case: + /// + /// - `all`: `($num:literal)` + /// - `tx`: `($num:literal, $idx:literal)` + /// - `rx`: `($num:literal, $idx:literal)` + /// + /// Macro fragments: + /// + /// - `$num`: number of the channel, e.g. `0` + /// - `$idx`: index of the channel among channels of the same capability, e.g. `0` + /// + /// Example data: + /// + /// - `all`: `(0)` + /// - `tx`: `(1, 1)` + /// - `rx`: `(2, 0)` + #for_each + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub(crate) struct RmtClockSourcesConfig { + supported: Vec, + default: String, +} + +impl GenericProperty for RmtClockSourcesConfig { + fn cfgs(&self) -> Option> { + let mut cfgs = Vec::new(); + + for value in &self.supported { + cfgs.push(format!("rmt_supports_{}_clock", value.to_lowercase())); + } + + Some(cfgs) + } + + fn macros(&self) -> Option { + let clock_sources = self + .supported + .iter() + .enumerate() + .filter(|(_, name)| *name != "None") + .map(|(bits, name)| { + let src_name = format_ident!("{}", name); + let bits = number(bits); + quote! { + #src_name, #bits + } + }) + .collect::>(); + + let default = format_ident!("{}", self.default); + let default_clock_source = [quote!( #default )]; + + let branches: &[(&str, &[TokenStream])] = if self.supported.len() <= 2 { + &[ + ("all", &clock_sources), + ("default", &default_clock_source), + ("is_boolean", &[]), + ] + } else { + &[("all", &clock_sources), ("default", &default_clock_source)] + }; + + Some(generate_for_each_macro("rmt_clock_source", branches)) + } +} diff --git a/esp-metadata/src/cfg/rsa.rs b/esp-metadata/src/cfg/rsa.rs new file mode 100644 index 00000000000..34fc06ed35b --- /dev/null +++ b/esp-metadata/src/cfg/rsa.rs @@ -0,0 +1,30 @@ +use crate::cfg::Value; + +impl super::RsaProperties { + pub(super) fn computed_properties(&self) -> impl Iterator { + let increment = self.size_increment; + let memory_bits = self.memory_size_bytes * 8; + + [ + ( + "rsa.exponentiation", + false, + Value::NumberList( + (increment..=memory_bits) + .step_by(increment as usize) + .collect(), + ), + ), + ( + "rsa.multiplication", + false, + Value::NumberList( + (increment..=memory_bits / 2) + .step_by(increment as usize) + .collect(), + ), + ), + ] + .into_iter() + } +} diff --git a/esp-metadata/src/cfg/sha.rs b/esp-metadata/src/cfg/sha.rs new file mode 100644 index 00000000000..0394e5676f1 --- /dev/null +++ b/esp-metadata/src/cfg/sha.rs @@ -0,0 +1,165 @@ +use proc_macro2::Ident; +use quote::{ToTokens, format_ident, quote}; +use serde::{Deserialize, Serialize}; + +use crate::{generate_for_each_macro, number}; + +struct ShaAlgo { + name: &'static str, + ident: Ident, + digest_len: u32, + block_size: u32, + message_len_bytes: u32, + // These bits come from + insecure_against_collision: bool, + insecure_against_length_extension: bool, +} + +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ShaAlgoMap { + sha1: Option, + sha224: Option, + sha256: Option, + sha384: Option, + sha512: Option, + sha512_224: Option, + sha512_256: Option, + sha512_t: Option, +} + +impl super::GenericProperty for ShaAlgoMap { + fn macros(&self) -> Option { + let modes = [ + ("SHA-1", self.sha1), + ("SHA-224", self.sha224), + ("SHA-256", self.sha256), + ("SHA-384", self.sha384), + ("SHA-512", self.sha512), + ("SHA-512/224", self.sha512_224), + ("SHA-512/256", self.sha512_256), + ("SHA-512/t", self.sha512_t), + ]; + + let algos = modes + .into_iter() + .filter_map(|(name, mode)| mode.map(|m| (name, m))) + .filter_map(|(name, mode)| ShaAlgo::new(name).map(|name| (name, mode))) + .map(|(algo, mode)| { + let mode = number(mode); + quote! { #algo, #mode } + }) + .collect::>(); + + Some(generate_for_each_macro( + "sha_algorithm", + &[("algos", &algos)], + )) + } +} + +impl ShaAlgo { + fn new(algo: &str) -> Option { + let known = [ + Self { + name: "SHA-1", + ident: format_ident!("Sha1"), + digest_len: 20, + block_size: 64, + message_len_bytes: 8, + insecure_against_collision: true, + insecure_against_length_extension: true, + }, + Self { + name: "SHA-224", + ident: format_ident!("Sha224"), + digest_len: 28, + block_size: 64, + message_len_bytes: 8, + insecure_against_collision: false, + insecure_against_length_extension: true, + }, + Self { + name: "SHA-256", + ident: format_ident!("Sha256"), + digest_len: 32, + block_size: 64, + message_len_bytes: 8, + insecure_against_collision: false, + insecure_against_length_extension: true, + }, + Self { + name: "SHA-384", + ident: format_ident!("Sha384"), + digest_len: 48, + block_size: 128, + message_len_bytes: 16, + insecure_against_collision: false, + insecure_against_length_extension: false, + }, + Self { + name: "SHA-512", + ident: format_ident!("Sha512"), + digest_len: 64, + block_size: 128, + message_len_bytes: 16, + insecure_against_collision: false, + insecure_against_length_extension: true, + }, + Self { + name: "SHA-512/224", + ident: format_ident!("Sha512_224"), + digest_len: 28, + block_size: 128, + message_len_bytes: 16, + insecure_against_collision: false, + insecure_against_length_extension: false, + }, + Self { + name: "SHA-512/256", + ident: format_ident!("Sha512_256"), + digest_len: 32, + block_size: 128, + message_len_bytes: 16, + insecure_against_collision: false, + insecure_against_length_extension: false, + }, + ]; + + for a in known { + if a.name == algo { + return Some(a); + } + } + + // Special-case (ignore) general truncated algo until we figure out how to describe its + // `digest_words`. + if algo == "SHA-512/t" { + return None; + } + + panic!("Unknown SHA algorithm: {algo}") + } +} + +impl ToTokens for ShaAlgo { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ident = &self.ident; + let name = &self.name; + let digest_len = number(self.digest_len); + let block_size = number(self.block_size); + let message_len_bytes = number(self.message_len_bytes); + + let mut insecure = vec![]; + + if self.insecure_against_collision { + insecure.push("collision"); + } + if self.insecure_against_length_extension { + insecure.push("length extension"); + } + + tokens.extend( + quote! { #ident, #name (sizes: #block_size, #digest_len, #message_len_bytes) (insecure_against: #(#insecure),*) }, + ); + } +} diff --git a/esp-metadata/src/cfg/soc.rs b/esp-metadata/src/cfg/soc.rs new file mode 100644 index 00000000000..1e3e1678c77 --- /dev/null +++ b/esp-metadata/src/cfg/soc.rs @@ -0,0 +1,756 @@ +use std::{collections::HashMap, ops::Range, str::FromStr}; + +use anyhow::{Context, Result}; +use convert_case::{Boundary, Case, Casing, pattern}; +use indexmap::IndexMap; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use serde::{Deserialize, Serialize}; + +use crate::{ + cfg::{ + clock_tree::{ + ClockTreeItem, + ClockTreeNodeType, + DependencyGraph, + ManagementProperties, + PeripheralClockSource, + ValidationContext, + }, + soc::clock_tree::PeripheralClockTreeEntry, + }, + number_hex, +}; + +pub mod clock_tree; + +/// Memory region. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct MemoryRange { + pub name: String, + #[serde(flatten)] + pub range: Range, +} + +/// Memory regions. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct MemoryMap { + pub ranges: Vec, +} + +impl super::GenericProperty for MemoryMap { + fn macros(&self) -> Option { + let region_branches = self.ranges.iter().map(|region| { + let name = region.name.to_uppercase(); + let start = number_hex(region.range.start as usize); + let end = number_hex(region.range.end as usize); + let size = format!( + "{}", + region.range.end as usize - region.range.start as usize + ); + + quote! { + ( #name ) => { + #start .. #end + }; + ( size as str, #name ) => { + #size + }; + } + }); + + Some(quote! { + /// Macro to get the address range of the given memory region. + /// + /// This macro provides two syntax options for each memory region: + /// + /// - `memory_range!("region_name")` returns the address range as a range expression (`start..end`). + /// - `memory_range!(size as str, "region_name")` returns the size of the region as a string literal. + #[macro_export] + #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] + macro_rules! memory_range { + #(#region_branches)* + } + }) + } + + fn cfgs(&self) -> Option> { + Some( + self.ranges + .iter() + .map(|region| format!("has_{}_region", region.name.to_lowercase())) + .collect(), + ) + } +} + +/// Represents the clock sources and clock distribution tree in the SoC. +#[derive(Debug, Default, Clone, Deserialize)] +pub struct SystemClocks { + clock_tree: Vec, +} + +pub(crate) struct ProcessedClockData { + /// For each clock tree node, this map stores which of them are fixed, configurable, or + /// dependent on other nodes. + classified_clocks: IndexMap, + + /// The device's `system_clocks` data. + clock_tree: Vec>, + + /// Refcount/config type properties of the clock tree nodes. + management_properties: HashMap, + + /// System clock graph. + dependency_graph: DependencyGraph, +} + +impl ProcessedClockData { + /// Returns a node by its name (e.g. `XTAL_CLK`). + /// + /// As the clock tree is stored as a vector, this method performs a linear search. + fn node(&self, name: &str) -> &dyn ClockTreeNodeType { + self.clock_tree + .iter() + .find(|item| item.name_str() == name) + .map(|b| b.as_ref()) + .unwrap_or_else(|| panic!("Clock node {} not found", name)) + } + + fn properties(&self, node: &CTNT) -> &ManagementProperties + where + CTNT: ClockTreeNodeType + ?Sized, + { + self.management_properties + .get(node.name_str()) + .unwrap_or_else(|| panic!("Management properties for {} not found", node.name_str())) + } +} + +#[derive(Clone, Debug, PartialEq)] +enum ClockType { + /// The clock tree item is not configurable. + Fixed, + + /// The clock tree item is configurable. + Configurable, + + /// The clock tree item is configured by some other item. + Dependent(String), + + /// Peripheral clock source - may be Configurable, but shouldn't be exposed via the global clock + /// tree config. + Peripheral, +} + +impl SystemClocks { + fn generate_macro(&self, tree: &ProcessedClockData) -> Result { + let mut clock_tree_node_defs = vec![]; + let mut clock_tree_node_impls = vec![]; + let mut clock_tree_node_state_getter_doclines = vec![]; + let mut clock_tree_state_fields = vec![]; + let mut clock_tree_state_field_types = vec![]; + let mut clock_tree_refcount_fields = vec![]; + let mut configurables = vec![]; + let mut system_config_steps = HashMap::new(); + let mut provided_function_doclines = vec![]; + for (item, kind) in tree + .classified_clocks + .iter() + .map(|(item, kind)| (item, kind.clone())) + { + let clock_item = tree.node(item); + + // Generate code for all clock tree nodes + if let Some(config_type) = clock_item.config_type() { + let doclines = clock_item.config_docline().unwrap(); + let doclines = doclines.lines(); + + clock_tree_node_defs.push(quote! { + #(#[doc = #doclines])* + #config_type + }); + } + let node_state = tree.properties(clock_item); + if let Some(type_name) = node_state.type_name() { + clock_tree_state_fields.push(node_state.field_name()); + clock_tree_state_field_types.push(type_name); + clock_tree_node_state_getter_doclines.push(format!( + "Returns the current configuration of the {} clock tree node", + clock_item.name_str(), + )); + } + if let Some(refcount_field) = node_state.refcount_field_name() { + clock_tree_refcount_fields.push(refcount_field); + } + + let node_funcs = ClockTreeItem::node_functions(clock_item, tree); + clock_tree_node_impls.push(node_funcs.implement_functions()); + if !node_funcs.hal_functions.is_empty() { + let header = format!(" // {}", clock_item.name_str()); + provided_function_doclines.push(quote! { + #[doc = ""] + #[doc = #header] + #[doc = ""] + }); + for func in node_funcs.hal_functions.iter() { + if func.is_empty() { + continue; + } + let func = func.to_string(); + provided_function_doclines.push(quote! { + #[doc = #func] + #[doc = ""] // empty line between functions + }); + } + } + + if kind == ClockType::Configurable { + // Generate code for the global clock configuration + let config_type_name = clock_item.config_type_name(); + let config_apply_function_name = clock_item.config_apply_function_name(); + + let item = clock_item.name().to_case(Case::Snake); + + let name = format_ident!("{}", item); + + let docline = clock_item.config_documentation().map(|doc| { + // TODO: add explanation what happens if the field is left `None`. + let doc = doc.lines(); + quote! { #(#[doc = #doc])* } + }); + + configurables.push(quote! { + #docline + pub #name: Option<#config_type_name>, + }); + + system_config_steps.insert( + clock_item.name_str(), + quote! { + if let Some(config) = self.#name { + #config_apply_function_name(clocks, config); + } + }, + ); + } + } + + let system_config_steps = tree + .dependency_graph + .iter() + .map(|node| system_config_steps.get(&node)); + + Ok(quote! { + #[macro_export] + /// ESP-HAL must provide implementation for the following functions: + /// ```rust, no_run + #(#provided_function_doclines)* + /// ``` + macro_rules! define_clock_tree_types { + () => { + #(#clock_tree_node_defs)* + + /// Represents the device's clock tree. + pub struct ClockTree { + #(#clock_tree_state_fields: Option<#clock_tree_state_field_types>,)* + #(#clock_tree_refcount_fields: u32,)* + } + impl ClockTree { + /// Locks the clock tree for exclusive access. + pub fn with(f: impl FnOnce(&mut ClockTree) -> R) -> R { + CLOCK_TREE.with(f) + } + + #( + #[doc = #clock_tree_node_state_getter_doclines] + pub fn #clock_tree_state_fields(&self) -> Option<#clock_tree_state_field_types> { + self.#clock_tree_state_fields + } + )* + } + + static CLOCK_TREE: ::esp_sync::NonReentrantMutex = + ::esp_sync::NonReentrantMutex::new(ClockTree { + #(#clock_tree_state_fields: None,)* + #(#clock_tree_refcount_fields: 0,)* + }); + + #(#clock_tree_node_impls)* + + /// Clock tree configuration. + /// + /// The fields of this struct are optional, with the following caveats: + /// - If `XTAL_CLK` is not specified, the crystal frequency will be automatically detected + /// if possible. + /// - The CPU and its upstream clock nodes will be set to a default configuration. + /// - Other unspecified clock sources will not be useable by peripherals. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[instability::unstable] + pub struct ClockConfig { + #(#configurables)* + } + + impl ClockConfig { + fn apply(&self) { + ClockTree::with(|clocks| { + #(#system_config_steps)* + }); + } + } + + // static CLOCK_BITMAP: ::portable_atomic::AtomicU32 = ::portable_atomic::AtomicU32::new(0); + fn increment_reference_count(refcount: &mut u32) -> bool { + let first = *refcount == 0; + // CLOCK_BITMAP.fetch_or(!clock_id, Ordering::Relaxed); + *refcount = unwrap!(refcount.checked_add(1), "Reference count overflow"); + first + } + fn decrement_reference_count(refcount: &mut u32) -> bool { + *refcount = refcount.saturating_sub(1); + let last = *refcount == 0; + // CLOCK_BITMAP.fetch_and(!clock_id, Ordering::Relaxed); + last + } + }; + } + }) + } +} + +/// A named template. Can contain `{{placeholder}}` placeholders that will be substituted with +/// actual values. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct Template { + /// The name of the template. Other templates can substitute this template's value by using the + /// `{{name}}` placeholder. + pub name: String, + + /// The value of the template. Can contain `{{placeholder}}` placeholders that will be + /// substituted with actual values. + pub value: String, +} + +/// A named peripheral clock signal. These are extracted from the SoC's TRM. Each element generates +/// a `Peripheral::Variant`, and code branches to enable/disable the clock signal, as well as to +/// assert the reset signal of the peripheral. +/// +/// `template_params` is a map of substitutions, which will overwrite the defaults set in +/// `PeripheralClocks`. This way each peripheral clock signal can either simply use the defaults, +/// or override them with custom values in case they don't fit the scheme for some reason. +#[derive(Debug, Default, Clone, Deserialize)] +pub struct PeripheralClock { + /// The name of the peripheral clock signal. Usually specified as CamelCase. Also determines + /// the value of the `peripheral` template parameter, by converting the name to snake_case. + pub name: String, + + /// Custom template parameters. These will override the defaults set in `PeripheralClocks`. + #[serde(default)] + template_params: HashMap, + + /// When true, prevents resetting and disabling the peripheral on startup. + // TODO: we should do something better, as we keep too many things running. USB/UART depends on + // esp-println's output option and whether the USB JTAG is connected, TIMG0 is not necessary + // outside of clock calibrations when the device has Systimer. + #[serde(default)] + keep_enabled: bool, + + /// Peripheral clock source data. + /// + /// These can be new definitions, or they can reference another `peripheral_clocks` entry, in + /// which case the peripherals will use the same data types, and the same API will be + /// generated for them. + #[serde(default)] + #[serde(deserialize_with = "clock_tree::ref_or_def")] + clocks: PeripheralClockTreeEntry, +} +impl PeripheralClock { + fn clock_signals<'a>(&'a self, peripheral_clocks: &'a PeripheralClocks) -> &'a [ClockTreeItem] { + match &self.clocks { + PeripheralClockTreeEntry::Definition(items) => items.as_slice(), + PeripheralClockTreeEntry::Reference(template) => peripheral_clocks + .peripheral_clocks + .iter() + .filter_map(|c| { + if c.name.as_str() != template { + return None; + } + match &c.clocks { + PeripheralClockTreeEntry::Definition(items) => Some(items.as_slice()), + PeripheralClockTreeEntry::Reference(_) => { + panic!("Referenced peripheral must have clock node definitions"); + } + } + }) + .next() + .unwrap(), + } + } + + fn template_peripheral_name(&self) -> &str { + match &self.clocks { + PeripheralClockTreeEntry::Definition(_) => self.name.as_str(), + PeripheralClockTreeEntry::Reference(inherit_from) => inherit_from.as_str(), + } + } +} + +#[derive(Debug, Default, Clone, Deserialize)] +pub struct PeripheralClocks { + pub(crate) templates: Vec

    You might want to browse the esp-lp-hal documentation on the esp-rs website instead.

    The documentation here on docs.rs is built for a single chip only (ESP32-C6, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs change depending on the chip.

    +The method output_level is currently not available on ESP32-S3 due to +an LLVM bug. See https://github.com/espressif/llvm-project/issues/120 for details. +