diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2dbabf9 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +rustflags = [ + "--cfg", "portable_atomic_target_feature=\"zaamo\"", +] + +[build] +target = "riscv32imc-unknown-none-elf" diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index ddc52ad..3de2072 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -19,6 +19,10 @@ jobs: filters: | e310x: - 'e310x/**' + e310x-hal: + - 'e310x-hal/**' + hifive1: + - 'hifive1/**' - name: Check for CHANGELOG.md (e310x) if: steps.changes.outputs['e310x'] == 'true' @@ -27,3 +31,20 @@ jobs: changeLogPath: ./e310x/CHANGELOG.md skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the e310x/CHANGELOG.md file.' + + - name: Check for CHANGELOG.md (e310x-hal) + if: steps.changes.outputs['e310x-hal'] == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./e310x-hal/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the e310x-hal/CHANGELOG.md file.' + + - name: Check for CHANGELOG.md (hifive1) + if: steps.changes.outputs['hifive1'] == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./hifive1/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the hifive1/CHANGELOG.md file.' + \ No newline at end of file diff --git a/.github/workflows/e310x-hal.yaml b/.github/workflows/e310x-hal.yaml new file mode 100644 index 0000000..0ffea09 --- /dev/null +++ b/.github/workflows/e310x-hal.yaml @@ -0,0 +1,50 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Build check (e310x-hal) + +jobs: + # We check that the crate builds and links for all the toolchains and targets. + build-riscv: + strategy: + matrix: + # All generated code should be running on stable now, MRSV is 1.72.0 + toolchain: [ stable, nightly, 1.72.0 ] + include: + # Nightly is only for reference and allowed to fail + - toolchain: nightly + experimental: true + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v4 + - name: Update Rust toolchain + run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - name: Install Rust target + run: rustup target install riscv32imc-unknown-none-elf + - name: Build (no features) + run: cargo build --package e310x-hal + - name: Build (all features) + run: cargo build --package e310x-hal --all-features + + # On MacOS and Ubuntu, we at least make sure that the crate builds and links. + # On Windows, linking fails when the rt feature is enabled. + build-others: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Update Rust toolchain + run: rustup update stable && rustup default stable + - name: Rename .cargo/config to .cargo/config.bak to ignore it + run: mv .cargo/config.toml .cargo/config.bak + - name: Build (no features) + run: cargo test --package e310x-hal + - name: Build (all features) + run: cargo test --package e310x-hal --all-features + \ No newline at end of file diff --git a/.github/workflows/e310x.yaml b/.github/workflows/e310x.yaml index d7da24f..8247a9f 100644 --- a/.github/workflows/e310x.yaml +++ b/.github/workflows/e310x.yaml @@ -11,11 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.65.0 - toolchain: [ stable, nightly, 1.65.0 ] - target: - - riscv32imc-unknown-none-elf - - riscv32imac-unknown-none-elf # TODO e310x is not a purely IMAC core + # All generated code should be running on stable now, MRSV is 1.72.0 + toolchain: [ stable, nightly, 1.72.0 ] include: # Nightly is only for reference and allowed to fail - toolchain: nightly @@ -27,11 +24,11 @@ jobs: - name: Update Rust toolchain run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - name: Install Rust target - run: rustup target install ${{ matrix.target }} + run: rustup target install riscv32imc-unknown-none-elf - name: Build (no features) - run: cargo build --package e310x --target ${{ matrix.target }} + run: cargo build --package e310x - name: Build (all features) - run: cargo build --package e310x --target ${{ matrix.target }} --all-features + run: cargo build --package e310x --all-features # On MacOS and Ubuntu, we at least make sure that the crate builds and links. # On Windows, linking fails when the rt feature is enabled. @@ -44,6 +41,8 @@ jobs: - uses: actions/checkout@v4 - name: Update Rust toolchain run: rustup update stable && rustup default stable + - name: Rename .cargo/config to .cargo/config.bak to ignore it + run: mv .cargo/config.toml .cargo/config.bak - name: Build (no features) run: cargo test --package e310x - name: Build (all features) diff --git a/.github/workflows/hifive1.yaml b/.github/workflows/hifive1.yaml new file mode 100644 index 0000000..0ff7f13 --- /dev/null +++ b/.github/workflows/hifive1.yaml @@ -0,0 +1,52 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Build check (hifive1) + +jobs: + # We check that the crate builds and links for all the toolchains and targets. + build-riscv: + strategy: + matrix: + # All generated code should be running on stable now, MRSV is 1.72.0 + toolchain: [nightly, stable, 1.72.0] + board: [hifive1, hifive1-revb, redv, lofive, lofive-r1] + include: + # Nightly is only for reference and allowed to fail + - toolchain: nightly + experimental: true + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v4 + - name: Update Rust toolchain + run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - name: Install Rust target + run: rustup target install riscv32imc-unknown-none-elf + - name: Build (direct) + run: cargo build --package hifive1 --features board-${{ matrix.board }} + - name: Build (vectored) + run: cargo build --package hifive1 --features board-${{ matrix.board }},virq + + # On MacOS and Ubuntu, we at least make sure that the crate builds and links. + # On Windows, linking fails when the rt feature is enabled. + build-others: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest ] + board: [hifive1, hifive1-revb, redv, lofive, lofive-r1] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Update Rust toolchain + run: rustup update stable && rustup default stable + - name: Rename .cargo/config to .cargo/config.bak to ignore it + run: mv .cargo/config.toml .cargo/config.bak + - name: Build (direct) + run: cargo test --package hifive1 --features board-${{ matrix.board }} + - name: Build (vectored) + run: cargo test --package hifive1 --features board-${{ matrix.board }},virq + \ No newline at end of file diff --git a/.gitignore b/.gitignore index b354aec..9182e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Cargo.lock -target/ \ No newline at end of file +target/ +.vscode/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5dc69be..0000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: rust - -env: - - TARGET=x86_64-unknown-linux-gnu - - TARGET=riscv32imac-unknown-none-elf - -rust: - - nightly - - stable - -if: (branch = staging OR branch = trying OR branch = master) OR (type = pull_request AND branch = master) - - -install: - - ci/install.sh - -script: - - ci/script.sh - -cache: cargo -before_cache: - # Travis can't cache files that are not readable by "others" - - chmod -R a+r $HOME/.cargo - -branches: - only: - - master - - staging - - trying - -notifications: - email: - on_success: never diff --git a/Cargo.toml b/Cargo.toml index 0a4be38..2cdd788 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,6 @@ resolver = "2" members = [ "e310x", + "e310x-hal", + "hifive1", ] diff --git a/README.md b/README.md index d6ff951..4532724 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ This repository contains various crates useful for writing Rust programs on E310x microcontrollers: * [`e310x`]: Peripheral Access Crate (PAC) for E310x chips. +* [`e310x-hal`]: HAL for the E310x family of microcontrollers. This project is developed and maintained by the [RISC-V team][team]. diff --git a/e310x-hal/CHANGELOG.md b/e310x-hal/CHANGELOG.md new file mode 100644 index 0000000..27a341a --- /dev/null +++ b/e310x-hal/CHANGELOG.md @@ -0,0 +1,52 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Changed +- Use `portable-atomic` with `zaamo` feature to use native `amo*` operations. +- Official target is now `riscv32imc-unknown-none-elf`, as it does not fully support the A extension. +- Update `e310x` dependency and adapt code +- Bump MSRV to 1.72.0 to ensure a correct behavior of portable-atomic + +## [v0.10.0] - 2023-03-28 + +### Added +- Added Pulse Width Modulation interface implementing `embedded_hal::Pwm` +- Added `interrupt` module for vectored interrupt handlers. This module is only active if feature `virq` is selected. + +### Changed +- Refactored `e310x-hal::spi` module, splitting the abstraction into `SpiBus` and `SpiExclusiveDevice/SpiSharedDevice` to allow multiple devices on a single SPI bus to co-exist +- Update `e310x` dependency to version 0.11 +- Update `riscv` dependency to version 0.10 + +### Removed +- removed interrupt linking definitions, they are now provided by `e310x` via `svd2rust` + +## [v0.9.4] - 2022-07-10 + +### Changed + +- Fixed code still using old `riscv::interrupt::Nr` + +## [v0.9.3] - 2021-08-15 + +### Changed + +- Fixed `e310x-hal::delay::Delay` call typo to `delay_ms` + +## [v0.9.2] - 2021-07-17 + +### Changed + +- Fixed `e310x-hal::delay::Delay` timing typo with extra 0 + +## [v0.9.1] - 2021-07-15 + +### Added + +- Added implementation of `embedded_hal::blocking::delay::DelayUs` for `e310x-hal::delay::Delay` using `MTIME` diff --git a/e310x-hal/Cargo.toml b/e310x-hal/Cargo.toml new file mode 100644 index 0000000..57eaf03 --- /dev/null +++ b/e310x-hal/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "e310x-hal" +version = "0.11.0" +authors = ["David Craven "] +repository = "https://github.com/riscv-rust/e310x" +categories = ["embedded", "hardware-support", "no-std"] +description = "HAL for the E310x family of microcontrollers." +keywords = ["riscv", "e310", "hal"] +license = "ISC" +edition = "2021" +rust-version = "1.72" + +[dependencies] +embedded-hal = { version = "0.2.6", features = ["unproven"] } +nb = "1.0.0" +riscv = { version = "0.10.1", features = ["critical-section-single-hart"] } +e310x = { path = "../e310x", version = "0.11.0", features = ["rt", "critical-section"] } +portable-atomic = { version = "1.9", default-features = false} + +[features] +g002 = ["e310x/g002"] +virq = [] + +[package.metadata.docs.rs] +features = ["g002", "virq"] diff --git a/e310x-hal/README.md b/e310x-hal/README.md new file mode 100644 index 0000000..a3f65bb --- /dev/null +++ b/e310x-hal/README.md @@ -0,0 +1,41 @@ +[![crates.io](https://img.shields.io/crates/d/e310x-hal.svg)](https://crates.io/crates/e310x-hal) +[![crates.io](https://img.shields.io/crates/v/e310x-hal.svg)](https://crates.io/crates/e310x-hal) +[![Build Status](https://travis-ci.org/riscv-rust/e310x-hal.svg?branch=master)](https://travis-ci.org/riscv-rust/e310x-hal) + +# `e310x-hal` + +> HAL for the E310x family of microcontrollers. + +This project is developed and maintained by the [RISC-V team][team]. + +## [Documentation](https://docs.rs/crate/e310x-hal) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.72.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2018-2019 [RISC-V team][team] + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-risc-v-team diff --git a/e310x-hal/src/clock.rs b/e310x-hal/src/clock.rs new file mode 100644 index 0000000..29c359d --- /dev/null +++ b/e310x-hal/src/clock.rs @@ -0,0 +1,419 @@ +//! Clock configuration +use crate::core::clint::MTIME; +use crate::time::Hertz; +use e310x::{Aonclk as AONCLK, Prci as PRCI}; +use riscv::interrupt; +use riscv::register::mcycle; + +const PLLREF_MIN: u32 = 6_000_000; +const PLLREF_MAX: u32 = 48_000_000; +const REFR_MIN: u32 = 6_000_000; +const REFR_MAX: u32 = 12_000_000; +const VCO_MIN: u32 = 384_000_000; +const VCO_MAX: u32 = 768_000_000; +const PLLOUT_MIN: u32 = 48_000_000; +const PLLOUT_MAX: u32 = 384_000_000; +const DIVOUT_MIN: u32 = 375_000; +const DIVOUT_MAX: u32 = 384_000_000; + +/// PrciExt trait extends `PRCI` peripheral. +pub trait PrciExt { + /// Constrains the `PRCI` peripheral so it plays nicely with the other + /// abstractions. + fn constrain(self) -> CoreClk; +} + +/// AonExt trait extends `AONCLK` peripheral. +pub trait AonExt { + /// Constrains the `AON` peripheral so it plays nicely with the other + /// abstractions. + fn constrain(self) -> AonClk; +} + +impl PrciExt for PRCI { + fn constrain(self) -> CoreClk { + CoreClk { + hfxosc: None, + coreclk: Hertz(13_800_000), // Default after reset + } + } +} + +impl AonExt for AONCLK { + fn constrain(self) -> AonClk { + AonClk { lfaltclk: None } + } +} + +/// Constrainted `PRCI` peripheral +pub struct CoreClk { + hfxosc: Option, + coreclk: Hertz, +} + +impl CoreClk { + /// Uses `HFXOSC` (external oscillator) instead of `HFROSC` (internal ring oscillator) as the clock source. + pub fn use_external>(mut self, freq: F) -> Self { + let hz: Hertz = freq.into(); + assert!(hz.0 < 20_000_000); + + self.hfxosc = Some(hz); + self + } + + /// Sets the desired frequency for the `coreclk` clock + pub fn coreclk>(mut self, freq: F) -> Self { + self.coreclk = freq.into(); + self + } + + /// Freezes high-frequency clock configuration, making it effective + pub(crate) fn freeze(self) -> Hertz { + // Assume `psdclkbypass_n` is not used + + // Temporarily switch to the internal oscillator + let prci = unsafe { PRCI::steal() }; + let hfrosc_freq = self.configure_hfrosc(); + // Switch to HFROSC, bypass PLL + prci.pllcfg() + .modify(|_, w| w.sel().bit(false).bypass().bit(true)); + + if let Some(freq) = self.hfxosc { + self.configure_with_external(freq) + } else { + self.configure_with_internal(hfrosc_freq) + } + } + + /// Configures clock generation system with external oscillator + fn configure_with_external(self, source_freq: Hertz) -> Hertz { + let prci = unsafe { PRCI::steal() }; + + // Enable HFXOSC + prci.hfxosccfg().write(|w| w.enable().bit(true)); + + // Wait for HFXOSC to stabilize + while !prci.hfxosccfg().read().ready().bit_is_set() {} + + // Select HFXOSC as pllref + prci.pllcfg().modify(|_, w| w.refsel().bit(true)); + + let freq; + if source_freq.0 == self.coreclk.0 { + // Use external oscillator with bypassed PLL + freq = source_freq; + + // Bypass PLL + prci.pllcfg().modify(|_, w| w.bypass().bit(true)); + + // Bypass divider + prci.plloutdiv().write(|w| w.divby1().bit(true)); + } else { + // Use external oscillator with PLL + + // Configure PLL and divider + freq = self.configure_pll(source_freq, self.coreclk); + } + + // Switch to PLL + prci.pllcfg().modify(|_, w| w.sel().bit(true)); + + // Disable HFROSC to save power + prci.hfrosccfg().write(|w| w.enable().bit(false)); + + freq + } + + /// Configures clock generation system with internal oscillator + fn configure_with_internal(self, hfrosc_freq: Hertz) -> Hertz { + let prci = unsafe { PRCI::steal() }; + + let freq; + if hfrosc_freq.0 == self.coreclk.0 { + // Use internal oscillator with bypassed PLL + freq = hfrosc_freq; + + // Switch to HFROSC, bypass PLL to save power + prci.pllcfg() + .modify(|_, w| w.sel().bit(false).bypass().bit(true)); + + // + prci.pllcfg().modify(|_, w| w.bypass().bit(true)); + } else { + // Use internal oscillator with PLL + + // Configure PLL and divider + freq = self.configure_pll(hfrosc_freq, self.coreclk); + + // Switch to PLL + prci.pllcfg().modify(|_, w| w.sel().bit(true)); + } + + // Disable HFXOSC to save power + prci.hfxosccfg().write(|w| w.enable().bit(false)); + + freq + } + + /// Configures internal high-frequency oscillator (`HFROSC`) + fn configure_hfrosc(&self) -> Hertz { + let prci = unsafe { PRCI::steal() }; + + // TODO: use trim value from OTP + + // Configure HFROSC to 13.8 MHz + prci.hfrosccfg() + .write(|w| unsafe { w.div().bits(4).trim().bits(16).enable().bit(true) }); + + // Wait for HFROSC to stabilize + while !prci.hfrosccfg().read().ready().bit_is_set() {} + + Hertz(13_800_000) + } + + /// Configures PLL and PLL Output Divider + /// The resulting frequency may differ by 0-2% from the requested + fn configure_pll(&self, pllref_freq: Hertz, divout_freq: Hertz) -> Hertz { + let pllref_freq = pllref_freq.0; + assert!(PLLREF_MIN <= pllref_freq && pllref_freq <= PLLREF_MAX); + + let divout_freq = divout_freq.0; + assert!(DIVOUT_MIN <= divout_freq && divout_freq <= DIVOUT_MAX); + + // Calculate PLL Output Divider settings + let divider_div; + let divider_bypass; + + let d = PLLOUT_MAX / divout_freq; + if d > 1 { + divider_bypass = false; + + if d > 128 { + divider_div = (128 / 2) - 1; + } else { + divider_div = (d / 2) - 1; + } + } else { + divider_div = 0; + divider_bypass = true; + } + + // Calculate pllout frequency + let d = if divider_bypass { + 1 + } else { + 2 * (divider_div + 1) + }; + let pllout_freq = divout_freq * d; + assert!(PLLOUT_MIN <= pllout_freq && pllout_freq <= PLLOUT_MAX); + + // Calculate PLL R ratio + let r = match pllref_freq { + 24_000_000..=48_000_000 => 4, + 18_000_000..=23_999_999 => 3, + 12_000_000..=17_999_999 => 2, + 6_000_000..=11_999_999 => 1, + _ => unreachable!(), + }; + + // Calculate refr frequency + let refr_freq = pllref_freq / r; + assert!(REFR_MIN <= refr_freq && refr_freq <= REFR_MAX); + + // Calculate PLL Q ratio + let q = match pllout_freq { + 192_000_000..=384_000_000 => 2, + 96_000_000..=191_999_999 => 4, + 48_000_000..=95_999_999 => 8, + _ => unreachable!(), + }; + + // Calculate the desired vco frequency + let target_vco_freq = pllout_freq * q; + assert!(VCO_MIN <= target_vco_freq && target_vco_freq <= VCO_MAX); + + // Calculate PLL F ratio + let f = target_vco_freq / refr_freq; + assert!(f <= 128); + + // Choose the best F ratio + let f_lo = (f / 2) * 2; // F must be a multiple of 2 + let vco_lo = refr_freq * f_lo; + let f_hi = f_lo + 2; + let vco_hi = refr_freq * f_hi; + let (f, vco_freq) = if (f_hi <= 128 && vco_hi <= VCO_MAX) + && (target_vco_freq as i32 - vco_hi as i32).abs() + < (target_vco_freq as i32 - vco_lo as i32).abs() + { + (f_hi, vco_hi) + } else { + (f_lo, vco_lo) + }; + assert!(VCO_MIN <= vco_freq && vco_freq <= VCO_MAX); + + // Calculate actual pllout frequency + let pllout_freq = vco_freq / q; + assert!(PLLOUT_MIN <= pllout_freq && pllout_freq <= PLLOUT_MAX); + + // Calculate actual divout frequency + let divout_freq = pllout_freq / d; + assert!(DIVOUT_MIN <= divout_freq && divout_freq <= DIVOUT_MAX); + + // Calculate bit-values + let r: u8 = (r - 1) as u8; + let f: u8 = (f / 2 - 1) as u8; + let q: u8 = match q { + 2 => 0b01, + 4 => 0b10, + 8 => 0b11, + _ => unreachable!(), + }; + + // Configure PLL + let prci = unsafe { PRCI::steal() }; + prci.pllcfg().modify(|_, w| unsafe { + w.pllr() + .bits(r) + .pllf() + .bits(f) + .pllq() + .bits(q) + .bypass() + .bit(false) + }); + + // Configure PLL Output Divider + prci.plloutdiv() + .write(|w| unsafe { w.div().bits(divider_div as u8).divby1().bit(divider_bypass) }); + + // Wait for PLL Lock + // Note that the Lock signal can be glitchy. + // Need to wait 100 us + // RTC is running at 32kHz. + // So wait 4 ticks of RTC. + let mtime = MTIME; + let time = mtime.mtime() + 4; + while mtime.mtime() < time {} + // Now it is safe to check for PLL Lock + while !prci.pllcfg().read().lock().bit_is_set() {} + + Hertz(divout_freq) + } +} + +/// Constrained `AONCLK` peripheral +pub struct AonClk { + lfaltclk: Option, +} + +impl AonClk { + /// Uses `LFALTCLK` (external low-frequency clock) instead of `LFROSC` (internal ring oscillator) as the clock source. + pub fn use_external>(mut self, freq: F) -> Self { + let hz: Hertz = freq.into(); + assert!(hz.0 < 500_000); + + self.lfaltclk = Some(hz); + self + } + + /// Freezes low-frequency clock configuration, making it effective + pub(crate) fn freeze(self) -> Hertz { + let aonclk = unsafe { AONCLK::steal() }; + + if let Some(freq) = self.lfaltclk { + // Use external oscillator. + + // Disable unused LFROSC to save power. + aonclk.lfrosccfg().write(|w| w.enable().bit(false)); + + freq + } else { + // Use internal oscillator. + + let trim = 16; + let div = 4; // LFROSC/5 + + // Configure LFROSC + aonclk.lfrosccfg().write(|w| unsafe { + w.trim().bits(trim); + w.div().bits(div); + w.enable().bit(true) + }); + + // Wait for LFROSC to stabilize + while !aonclk.lfrosccfg().read().ready().bit_is_set() {} + + Hertz(32_768) // It's not so accurate: ≈30 kHz according to the datasheet + } + } +} + +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no +/// longer be changed. +#[derive(Clone, Copy)] +pub struct Clocks { + coreclk: Hertz, + lfclk: Hertz, +} + +impl Clocks { + /// Freezes the coreclk and aonclk frequencies. + pub fn freeze(coreclk: CoreClk, aonclk: AonClk) -> Self { + let coreclk = coreclk.freeze(); + let lfclk = aonclk.freeze(); + Clocks { coreclk, lfclk } + } + + /// Returns the frozen coreclk frequency + pub fn coreclk(&self) -> Hertz { + self.coreclk + } + + /// Returns the frozen tlclk frequency + pub fn tlclk(&self) -> Hertz { + // For the FE310-G000, the TileLink bus clock (tlclk) is fixed to be + // the same as the processor core clock (coreclk) + self.coreclk + } + + /// Returns the frozen lfclk frequency + pub fn lfclk(&self) -> Hertz { + self.lfclk + } + + /// Measure the coreclk frequency by counting the number of aonclk ticks. + fn _measure_coreclk(&self, min_ticks: u64) -> Hertz { + let mtime = MTIME; + interrupt::free(|| { + // Don't start measuring until we see an mtime tick + while mtime.mtime() == mtime.mtime() {} + + let start_cycle = mcycle::read64(); + let start_time = mtime.mtime(); + + // Wait for min_ticks to pass + while start_time + min_ticks > mtime.mtime() {} + + let end_cycle = mcycle::read64(); + let end_time = mtime.mtime(); + + let delta_cycle: u64 = end_cycle - start_cycle; + let delta_time: u64 = end_time - start_time; + + let res = (delta_cycle / delta_time) * 32768 + + ((delta_cycle % delta_time) * 32768) / delta_time; + // u32 can represent 4GHz way above the expected measurement value + Hertz(res as u32) + }) + } + + /// Measure the coreclk frequency by counting the number of aonclk ticks. + pub fn measure_coreclk(&self) -> Hertz { + // warm up I$ + self._measure_coreclk(1); + // measure for real + self._measure_coreclk(10) + } +} diff --git a/e310x-hal/src/core/clint.rs b/e310x-hal/src/core/clint.rs new file mode 100644 index 0000000..82912e6 --- /dev/null +++ b/e310x-hal/src/core/clint.rs @@ -0,0 +1,117 @@ +//! Core-Local Interruptor + +use e310x::Clint as CLINT; + +macro_rules! read64 { + ($hi:expr, $lo:expr) => { + loop { + let hi = $hi; + let lo = $lo; + if hi == $hi { + return ((hi as u64) << 32) | lo as u64; + } + } + }; +} + +/// Opaque msip register +pub struct MSIP { + _0: (), +} + +impl MSIP { + /// Set msip register value + pub fn set_value(&mut self, value: bool) { + unsafe { + CLINT::steal() + .msip() + .write(|w| if value { w.bits(1) } else { w.bits(0) }) + } + } +} + +/// Opaque mtime register +pub struct MTIME; + +impl MTIME { + /// Read mtime register. + #[inline] + pub fn mtime_lo(&self) -> u32 { + unsafe { CLINT::steal() }.mtime().read().bits() + } + + /// Read mtimeh register. + #[inline] + pub fn mtime_hi(&self) -> u32 { + unsafe { CLINT::steal() }.mtimeh().read().bits() + } + + /// Read mtime and mtimeh registers. + pub fn mtime(&self) -> u64 { + read64!(self.mtime_hi(), self.mtime_lo()) + } +} + +/// Opaque mtimecmp register +pub struct MTIMECMP { + _0: (), +} + +impl MTIMECMP { + /// Read mtimecmp register. + #[inline] + pub fn mtimecmp_lo(&self) -> u32 { + unsafe { CLINT::steal() }.mtimecmp().read().bits() + } + + /// Read mtimecmph register. + #[inline] + pub fn mtimecmp_hi(&self) -> u32 { + unsafe { CLINT::steal() }.mtimecmph().read().bits() + } + + /// Read mtimecmp and mtimecmph registers. + pub fn mtimecmp(&self) -> u64 { + read64!(self.mtimecmp_hi(), self.mtimecmp_lo()) + } + + /// Write mtimecmp register + #[inline] + pub fn set_mtimecmp_lo(&mut self, value: u32) { + unsafe { CLINT::steal().mtimecmp().write(|w| w.bits(value)) }; + } + + /// Write mtimecmph register + #[inline] + pub fn set_mtimecmp_hi(&mut self, value: u32) { + unsafe { CLINT::steal().mtimecmph().write(|w| w.bits(value)) }; + } + + /// Write mtimecmp and mtimecmph registers. + pub fn set_mtimecmp(&mut self, value: u64) { + // Volume II: RISC-V Privileged Architectures V1.10 p.31, figure 3.15 + self.set_mtimecmp_lo(0xffff_ffff); // No smaller than old value + self.set_mtimecmp_hi((value >> 32) as u32); // New value + self.set_mtimecmp_lo(value as u32); // New value + } +} + +/// Core-Local Interruptor abstraction +pub struct Clint { + /// Opaque msip register + pub msip: MSIP, + /// Opaque mtimecmp register + pub mtimecmp: MTIMECMP, + /// Opaque mtime register + pub mtime: MTIME, +} + +impl From for Clint { + fn from(_: CLINT) -> Self { + Clint { + msip: MSIP { _0: () }, + mtimecmp: MTIMECMP { _0: () }, + mtime: MTIME, + } + } +} diff --git a/e310x-hal/src/core/counters.rs b/e310x-hal/src/core/counters.rs new file mode 100644 index 0000000..84e25b2 --- /dev/null +++ b/e310x-hal/src/core/counters.rs @@ -0,0 +1,71 @@ +//! Performance counters + +use riscv::register::{mcycle, mhpmcounter3, mhpmcounter4, minstret}; + +/// Opaque mcycle register +pub struct MCYCLE; + +impl MCYCLE { + /// Read mcycle and mcycleh registers. + #[inline] + pub fn value(&self) -> u64 { + mcycle::read64() + } +} + +/// Opaque minstret register. +pub struct MINSTRET; + +impl MINSTRET { + /// Read minstret and minstreth registers. + #[inline] + pub fn value(&self) -> u64 { + minstret::read64() + } +} + +/// Opaque mhpmcounter3 register. +pub struct MHPMCOUNTER3; + +impl MHPMCOUNTER3 { + /// Read mhpmcounter3 and mhpmcounter3h registers. + #[inline] + pub fn value(&self) -> u64 { + mhpmcounter3::read64() + } +} + +/// Opaque mhpmcounter4 register. +pub struct MHPMCOUNTER4; + +impl MHPMCOUNTER4 { + /// Read mhpmcounter4 and mhpmcounter4h registers. + #[inline] + pub fn value(&self) -> u64 { + mhpmcounter4::read64() + } +} + +/// Performance counters +pub struct PerformanceCounters { + /// 64-bit mcycle counter + pub mcycle: MCYCLE, + /// 64-bit minstret counter + pub minstret: MINSTRET, + /// 40-bit mhpmcounter3 counter + pub mhpmcounter3: MHPMCOUNTER3, + /// 40-bit mhpmcounter4 counter + pub mhpmcounter4: MHPMCOUNTER4, + // TODO: mhpmevent3, mhpmevent4 +} + +impl PerformanceCounters { + pub(crate) fn new() -> Self { + Self { + mcycle: MCYCLE, + minstret: MINSTRET, + mhpmcounter3: MHPMCOUNTER3, + mhpmcounter4: MHPMCOUNTER4, + } + } +} diff --git a/e310x-hal/src/core/mod.rs b/e310x-hal/src/core/mod.rs new file mode 100644 index 0000000..43d1483 --- /dev/null +++ b/e310x-hal/src/core/mod.rs @@ -0,0 +1,33 @@ +//! E31 core peripherals + +pub mod clint; +pub mod counters; +pub mod plic; + +/// Core peripherals +pub struct CorePeripherals { + /// Core-Local Interruptor + pub clint: clint::Clint, + + /// Platform-Level Interrupt Controller + pub plic: plic::Plic, + + /// Performance counters + pub counters: counters::PerformanceCounters, +} + +impl CorePeripherals { + pub(crate) fn new(clint: e310x::Clint, plic: e310x::Plic) -> Self { + Self { + clint: clint.into(), + plic: plic.into(), + counters: counters::PerformanceCounters::new(), + } + } + + /// Steal the peripherals + pub unsafe fn steal() -> Self { + let p = e310x::Peripherals::steal(); + Self::new(p.clint, p.plic) + } +} diff --git a/e310x-hal/src/core/plic.rs b/e310x-hal/src/core/plic.rs new file mode 100644 index 0000000..3da4167 --- /dev/null +++ b/e310x-hal/src/core/plic.rs @@ -0,0 +1,252 @@ +//! Platform-Level Interrupt Controller +use core::marker::PhantomData; +use e310x::Interrupt; +use e310x::Plic as PLIC; +use riscv::register::{mie, mip}; + +/// Priority of a plic::Interrupt. +#[derive(Clone, Copy)] +pub enum Priority { + /// Priority 0: Never interrupt + P0, + /// Priority 1: Lowest active priority + P1, + /// Priority 2 + P2, + /// Priority 3 + P3, + /// Priority 4 + P4, + /// Priority 5 + P5, + /// Priority 6 + P6, + /// Priority 7: Highest priority + P7, +} + +impl Priority { + /// Takes a read interrupt priority or plic threshold + /// register value and returns a plic::Priority enum. + fn from(prio: u32) -> Option { + match prio { + 0 => Some(Priority::P0), + 1 => Some(Priority::P1), + 2 => Some(Priority::P2), + 3 => Some(Priority::P3), + 4 => Some(Priority::P4), + 5 => Some(Priority::P5), + 6 => Some(Priority::P6), + 7 => Some(Priority::P7), + _ => None, + } + } +} + +impl Into for Priority { + /// Returns the numeric priority for writing to a + /// interrupt priority or the plic threshold register. + fn into(self) -> u32 { + match self { + Priority::P0 => 0, + Priority::P1 => 1, + Priority::P2 => 2, + Priority::P3 => 3, + Priority::P4 => 4, + Priority::P5 => 5, + Priority::P6 => 6, + Priority::P7 => 7, + } + } +} + +/// Watchdog interrupt (type state) +pub struct IrqWatchdog; +/// Realtime clock interrupt (type state) +pub struct IrqRtc; +/// Uart0 interrupt (type state) +pub struct IrqUart0; + +/// Parts of `PLIC` peripheral for fine grained permissions. +pub struct Plic { + /// Opaque mext register + pub mext: MEXT, + /// Opaque threshold register + pub threshold: THRESHOLD, + /// Opaque claim register + pub claim: CLAIM, + /// Opaque watchdog register + pub wdog: INTERRUPT, + /// Opaque rtc register + pub rtc: INTERRUPT, + /// Opaque uart0 register + pub uart0: INTERRUPT, +} + +impl From for Plic { + fn from(_: PLIC) -> Self { + Plic { + mext: MEXT { _0: () }, + threshold: THRESHOLD { _0: () }, + claim: CLAIM { _0: () }, + wdog: INTERRUPT { + offset: 0, + mask: 1 << (Interrupt::WATCHDOG as u8), + priority_offset: Interrupt::WATCHDOG as usize, + _marker: PhantomData, + }, + rtc: INTERRUPT { + offset: 0, + mask: 1 << (Interrupt::RTC as u8), + priority_offset: Interrupt::RTC as usize, + _marker: PhantomData, + }, + uart0: INTERRUPT { + offset: 0, + mask: 1 << (Interrupt::UART0 as u8), + priority_offset: Interrupt::UART0 as usize, + _marker: PhantomData, + }, + } + } +} + +/// Opaque MEXT register. +pub struct MEXT { + _0: (), +} + +impl MEXT { + /// Enable MachineExternal interrupt. + #[inline] + pub fn enable(&mut self) { + unsafe { mie::set_mext() }; + } + + /// Disable MachineExternal interrupt. + #[inline] + pub fn disable(&mut self) { + unsafe { mie::clear_mext() }; + } + + /// Returns true when MachineExternal interrupt is pending. + #[inline] + pub fn is_pending(&self) -> bool { + mip::read().mext() + } +} + +/// Opaque THRESHOLD register. +pub struct THRESHOLD { + _0: (), +} + +impl THRESHOLD { + /// Returns the current active priority threshold. + pub fn get(&self) -> Priority { + // NOTE: Atomic read with no side effects. + let threshold = unsafe { PLIC::steal() }.threshold().read(); + Priority::from(threshold.bits()).unwrap() + } + + /// Sets the current active priority threshold. This + /// deactivates all interrupts with a lower priority. + pub fn set(&mut self, priority: Priority) { + // NOTE: Atomic write with no side effects. + unsafe { PLIC::steal().threshold().write(|w| w.bits(priority.into())) }; + } +} + +/// Opaque CLAIM register. +pub struct CLAIM { + _0: (), +} + +impl CLAIM { + /// Claims the interrupt with the highest priority. + pub fn claim(&mut self) -> Option { + // NOTE: Atomic read with side effects. + let intr = unsafe { PLIC::steal() }.claim().read().bits(); + + // If no interrupt is pending return None + if intr == 0 { + None + } else { + Some(Interrupt::try_from(intr as u8).unwrap()) + } + } + + /// Notifies the PLIC that a claimed interrupt is complete. + pub fn complete(&mut self, intr: Interrupt) { + // NOTE: Atomic write with side effects. + unsafe { PLIC::steal().claim().write(|w| w.bits(intr as u32)) }; + } +} + +/// Fine grained interrupt handling. +pub struct INTERRUPT { + /// Offset in to enable and pending plic registers + offset: usize, + /// Bitmask for enable and pending plic registers + mask: u32, + /// Offset in to priority plic registers + priority_offset: usize, + _marker: PhantomData, +} + +impl INTERRUPT { + /// Enable IRQ interrupt. + #[inline] + pub fn enable(&mut self) { + // NOTE: should use atomic operations + unsafe { + PLIC::steal() + .enable(self.offset) + .modify(|r, w| w.bits(r.bits() | self.mask)) + }; + } + + /// Disable IRQ interrupt. + #[inline] + pub fn disable(&mut self) { + // NOTE: should use atomic operations + unsafe { + PLIC::steal() + .enable(self.offset) + .modify(|r, w| w.bits(r.bits() & !self.mask)) + }; + } + + /// Returns true when IRQ interrupt is pending. + pub fn is_pending(&self) -> bool { + // NOTE: Atomic write without side effects. + let pending = unsafe { PLIC::steal() }.pending(self.offset).read(); + pending.bits() & self.mask == self.mask + } + + /// Returns true when WDOG interrupt is enabled. + pub fn is_enabled(&self) -> bool { + // NOTE: Atomic write without side effects. + let enabled = unsafe { PLIC::steal() }.enable(self.offset).read(); + enabled.bits() & self.mask == self.mask + } + + /// Returns the priority of the IRQ interrupt. + pub fn priority(&self) -> Priority { + // NOTE: Atomic read without side effects. + let priority = unsafe { PLIC::steal() } + .priority(self.priority_offset) + .read(); + Priority::from(priority.bits()).unwrap() + } + + /// Sets the priority of the IRQ interrupt. + pub fn set_priority(&mut self, priority: Priority) { + // NOTE: Atomic write without side effects. + unsafe { + PLIC::steal() + .priority(self.priority_offset) + .write(|w| w.bits(priority as u32)) + }; + } +} diff --git a/e310x-hal/src/delay.rs b/e310x-hal/src/delay.rs new file mode 100644 index 0000000..4778bf1 --- /dev/null +++ b/e310x-hal/src/delay.rs @@ -0,0 +1,153 @@ +//! # Delays + +use crate::clock::Clocks; +use crate::core::clint::{MTIME, MTIMECMP}; +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use riscv::register::{mie, mip}; + +/// Machine timer (mtime) as a busyloop delay provider +pub struct Delay; + +const TICKS_PER_SECOND: u64 = 32768; + +impl Delay { + /// Constructs a delay provider based on the machine timer (mtime) + pub fn new() -> Self { + Delay + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + let ticks = (us as u64) * TICKS_PER_SECOND / 1_000_000; + + let mtime = MTIME; + let t = mtime.mtime() + ticks; + while mtime.mtime() < t {} + } +} + +// This is a workaround to allow `delay_us(42)` construction without specifying a type. +impl DelayUs for Delay { + #[inline(always)] + fn delay_us(&mut self, us: i32) { + assert!(us >= 0); + self.delay_us(us as u32); + } +} + +impl DelayUs for Delay { + #[inline(always)] + fn delay_us(&mut self, us: u16) { + self.delay_us(u32::from(us)); + } +} + +impl DelayUs for Delay { + #[inline(always)] + fn delay_us(&mut self, us: u8) { + self.delay_us(u32::from(us)); + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + self.delay_us(ms * 1000); + } +} + +// This is a workaround to allow `delay_ms(42)` construction without specifying a type. +impl DelayMs for Delay { + #[inline(always)] + fn delay_ms(&mut self, ms: i32) { + assert!(ms >= 0); + self.delay_ms(ms as u32); + } +} + +impl DelayMs for Delay { + #[inline(always)] + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32::from(ms)); + } +} + +impl DelayMs for Delay { + #[inline(always)] + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32::from(ms)); + } +} + +/// Machine timer (mtime) as a sleep delay provider using mtimecmp +pub struct Sleep { + clock_freq: u32, + mtimecmp: MTIMECMP, +} + +impl Sleep { + /// Constructs a delay provider using mtimecmp register to sleep + pub fn new(mtimecmp: MTIMECMP, clocks: Clocks) -> Self { + Sleep { + clock_freq: clocks.lfclk().0, + mtimecmp, + } + } +} + +impl DelayMs for Sleep { + fn delay_ms(&mut self, ms: u32) { + let ticks = (ms as u64) * (self.clock_freq as u64) / 1000; + let t = MTIME.mtime() + ticks; + + self.mtimecmp.set_mtimecmp(t); + + // Enable timer interrupt + unsafe { + mie::set_mtimer(); + } + + // Wait For Interrupt will put CPU to sleep until an interrupt hits + // in our case when internal timer mtime value >= mtimecmp value + // after which empty handler gets called and we go into the + // next iteration of this loop + loop { + unsafe { + riscv::asm::wfi(); + } + + // check if we got the right interrupt cause, otherwise just loop back to wfi + if mip::read().mtimer() { + break; + } + } + + // Clear timer interrupt + unsafe { + mie::clear_mtimer(); + } + } +} + +// This is a workaround to allow `delay_ms(42)` construction without specifying a type. +impl DelayMs for Sleep { + #[inline(always)] + fn delay_ms(&mut self, ms: i32) { + assert!(ms >= 0); + self.delay_ms(ms as u32); + } +} + +impl DelayMs for Sleep { + #[inline(always)] + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32::from(ms)); + } +} + +impl DelayMs for Sleep { + #[inline(always)] + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32::from(ms)); + } +} diff --git a/e310x-hal/src/device.rs b/e310x-hal/src/device.rs new file mode 100644 index 0000000..f1a2f13 --- /dev/null +++ b/e310x-hal/src/device.rs @@ -0,0 +1,180 @@ +//! Device resources available in FE310-G000 and FE310-G002 chip packages + +use crate::core::CorePeripherals; +use crate::gpio::{gpio0::*, GpioExt, Unknown}; +use e310x::{ + Aonclk, Backup, Gpio0, Otp, Peripherals, Pmu, Prci, Pwm0, Pwm1, Pwm2, Qspi0, Qspi1, Rtc, Uart0, + Wdog, +}; +#[cfg(feature = "g002")] +use e310x::{I2c0, Uart1}; + +/// Device peripherals available in a 48QFN package, except GPIO0 +#[allow(non_snake_case)] +pub struct DevicePeripherals { + /// WDOG peripheral + pub WDOG: Wdog, + /// RTC peripheral + pub RTC: Rtc, + /// AONCLK peripheral + pub AONCLK: Aonclk, + /// BACKUP peripheral + pub BACKUP: Backup, + /// PMU peripheral + pub PMU: Pmu, + /// PRCI peripheral + pub PRCI: Prci, + /// OTP peripheral + pub OTP: Otp, + + /// UART0 peripheral + pub UART0: Uart0, + #[cfg(feature = "g002")] + /// UART1 peripheral (FE310-G002 only) + pub UART1: Uart1, + + /// QSPI0 peripheral + pub QSPI0: Qspi0, + /// QSPI1 peripheral + pub QSPI1: Qspi1, + + #[cfg(feature = "g002")] + /// I2C0 peripheral (FE310-G002 only) + pub I2C0: I2c0, + + /// PWM0 peripheral + pub PWM0: Pwm0, + /// PWM1 peripheral + pub PWM1: Pwm1, + /// PWM2 peripheral + pub PWM2: Pwm2, +} + +/// Device GPIO pins available in a 48QFN package +pub struct DeviceGpioPins { + /// GPIO 0, package pin 25 + pub pin0: Pin0, + /// GPIO 1, package pin 26 + pub pin1: Pin1, + /// GPIO 2, package pin 27 + pub pin2: Pin2, + /// GPIO 3, package pin 28 + pub pin3: Pin3, + /// GPIO 4, package pin 29 + pub pin4: Pin4, + /// GPIO 5, package pin 31 + pub pin5: Pin5, + /// GPIO 9, package pin 33 + pub pin9: Pin9, + /// GPIO 10, package pin 34 + pub pin10: Pin10, + /// GPIO 11, package pin 35 + pub pin11: Pin11, + /// GPIO 12, package pin 36 + pub pin12: Pin12, + /// GPIO 13, package pin 37 + pub pin13: Pin13, + /// GPIO 16, package pin 38 + pub pin16: Pin16, + /// GPIO 17, package pin 39 + pub pin17: Pin17, + /// GPIO 18, package pin 40 + pub pin18: Pin18, + /// GPIO 19, package pin 41 + pub pin19: Pin19, + /// GPIO 20, package pin 42 + pub pin20: Pin20, + /// GPIO 21, package pin 43 + pub pin21: Pin21, + /// GPIO 22, package pin 44 + pub pin22: Pin22, + /// GPIO 23, package pin 45 + pub pin23: Pin23, +} + +impl From for DeviceGpioPins { + fn from(gpio: Gpio0) -> Self { + let parts = gpio.split(); + + DeviceGpioPins { + pin0: parts.pin0, + pin1: parts.pin1, + pin2: parts.pin2, + pin3: parts.pin3, + pin4: parts.pin4, + pin5: parts.pin5, + pin9: parts.pin9, + pin10: parts.pin10, + pin11: parts.pin11, + pin12: parts.pin12, + pin13: parts.pin13, + pin16: parts.pin16, + pin17: parts.pin17, + pin18: parts.pin18, + pin19: parts.pin19, + pin20: parts.pin20, + pin21: parts.pin21, + pin22: parts.pin22, + pin23: parts.pin23, + } + } +} + +/// Device resources available in a 48QFN package +pub struct DeviceResources { + /// Core peripherals + pub core_peripherals: CorePeripherals, + + /// Device peripherals + pub peripherals: DevicePeripherals, + + /// Device GPIO pins + pub pins: DeviceGpioPins, +} + +impl From for DeviceResources { + fn from(p: Peripherals) -> Self { + let peripherals = DevicePeripherals { + WDOG: p.wdog, + RTC: p.rtc, + AONCLK: p.aonclk, + BACKUP: p.backup, + PMU: p.pmu, + PRCI: p.prci, + OTP: p.otp, + + UART0: p.uart0, + #[cfg(feature = "g002")] + UART1: p.uart1, + + QSPI0: p.qspi0, + QSPI1: p.qspi1, + + #[cfg(feature = "g002")] + I2C0: p.i2c0, + + PWM0: p.pwm0, + PWM1: p.pwm1, + PWM2: p.pwm2, + }; + + DeviceResources { + core_peripherals: CorePeripherals::new(p.clint, p.plic), + peripherals, + pins: p.gpio0.into(), + } + } +} + +impl DeviceResources { + /// Returns all the device resources *once* + #[inline] + pub fn take() -> Option { + e310x::Peripherals::take().map(DeviceResources::from) + } + + /// Unchecked version of `DeviceResources::take` + pub unsafe fn steal() -> Self { + e310x::Peripherals::steal().into() + } +} diff --git a/e310x-hal/src/gpio.rs b/e310x-hal/src/gpio.rs new file mode 100644 index 0000000..14604b7 --- /dev/null +++ b/e310x-hal/src/gpio.rs @@ -0,0 +1,366 @@ +//! General Purpose I/O + +use core::marker::PhantomData; + +use portable_atomic::{AtomicU32, Ordering}; + +/// GpioExt trait extends the GPIO0 peripheral. +pub trait GpioExt { + /// The parts to split the GPIO into. + type Parts; + + /// Splits the GPIO block into independent pins and registers. + fn split(self) -> Self::Parts; +} + +/// Unknown mode (type state) +pub struct Unknown; + +/// Input mode (type state) +pub struct Input { + _mode: PhantomData, +} + +/// Floating input (type state) +pub struct Floating; +/// Pulled up input (type state) +pub struct PullUp; + +/// Output mode (type state) +pub struct Output { + _mode: PhantomData, +} + +/// Regular output mode (type state) +pub struct Regular { + _mode: PhantomData, +} + +/// High current mode (type state) +pub struct Drive { + _mode: PhantomData, +} + +/// Alternate function 0 (type state) +pub struct IOF0 { + _mode: PhantomData, +} + +/// Alternate function 1 (type state) +pub struct IOF1 { + _mode: PhantomData, +} + +/// Non-inverted output mode (type state) +pub struct NoInvert; + +/// Invert output mode (type state) +pub struct Invert; + +trait PinIndex { + const INDEX: usize; +} + +#[inline(always)] +fn atomic_set_bit(r: &AtomicU32, index: usize, bit: bool) { + let mask = 1 << (index & 31); + match bit { + true => r.fetch_or(mask, Ordering::SeqCst), + false => r.fetch_and(!mask, Ordering::SeqCst), + }; +} + +trait PeripheralAccess { + fn peripheral() -> e310x::Gpio0; + + fn input_value(index: usize) -> bool { + let p = Self::peripheral(); + (p.input_val().read().bits() >> (index & 31) & 1) != 0 + } + + fn set_input_en(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.input_en()) }; + atomic_set_bit(r, index, bit); + } + + fn set_output_en(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.output_en()) }; + atomic_set_bit(r, index, bit); + } + + fn set_output_value(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.output_val()) }; + atomic_set_bit(r, index, bit); + } + + fn toggle_pin(index: usize) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.output_val()) }; + let mask = 1 << (index & 31); + r.fetch_xor(mask, Ordering::SeqCst); + } + + fn set_pullup(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.pullup()) }; + atomic_set_bit(r, index, bit); + } + + fn set_drive(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.drive()) }; + atomic_set_bit(r, index, bit); + } + + fn set_out_xor(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.out_xor()) }; + atomic_set_bit(r, index, bit); + } + + fn set_iof_en(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.iof_en()) }; + atomic_set_bit(r, index, bit); + } + + fn set_iof_sel(index: usize, bit: bool) { + let p = Self::peripheral(); + let r: &AtomicU32 = unsafe { core::mem::transmute(&p.iof_sel()) }; + atomic_set_bit(r, index, bit); + } +} + +macro_rules! gpio { + ($GPIOX:ident, $gpiox:ident, [ + $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty),)+ + ]) => { + /// GPIO + pub mod $gpiox { + use core::marker::PhantomData; + use core::convert::Infallible; + + use embedded_hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin, + ToggleableOutputPin}; + use e310x::$GPIOX; + use super::{Unknown, IOF0, IOF1, Drive, Floating, GpioExt, Input, Invert, + NoInvert, Output, PullUp, Regular, PinIndex, PeripheralAccess}; + + /// GPIO parts for fine grained permission control. + pub struct Parts { + $( + /// Pin + pub $pxi: $PXi<$MODE>, + )+ + } + + impl PeripheralAccess for $GPIOX { + #[inline(always)] + fn peripheral() -> e310x::Gpio0 { + unsafe { $GPIOX::steal() } + } + } + + impl GpioExt for $GPIOX { + type Parts = Parts; + + fn split(self) -> Parts { + Parts { + $( + $pxi: $PXi { _mode: PhantomData }, + )+ + } + } + } + + $( + /// Pin + pub struct $PXi { + _mode: PhantomData, + } + + impl PinIndex for $PXi { + const INDEX: usize = $i; + } + + impl $PXi { + /// Configures the pin to serve as alternate function 0 (AF0) + pub fn into_iof0(self) -> $PXi> { + $GPIOX::set_out_xor(Self::INDEX, false); + $GPIOX::set_iof_sel(Self::INDEX, false); + $GPIOX::set_iof_en(Self::INDEX, true); + $PXi { _mode: PhantomData } + } + + + /// Configures the pin to serve as alternate function 1 (AF1) + pub fn into_iof1(self) -> $PXi> { + $GPIOX::set_out_xor(Self::INDEX, false); + $GPIOX::set_iof_sel(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, true); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to serve as inverted alternate function 0 (AF0) + pub fn into_inverted_iof0(self) -> $PXi> { + $GPIOX::set_out_xor(Self::INDEX, true); + $GPIOX::set_iof_sel(Self::INDEX, false); + $GPIOX::set_iof_en(Self::INDEX, true); + $PXi { _mode: PhantomData } + } + + + /// Configures the pin to serve as inverted alternate function 1 (AF1) + pub fn into_inverted_iof1(self) -> $PXi> { + $GPIOX::set_out_xor(Self::INDEX, true); + $GPIOX::set_iof_sel(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, true); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to serve as a floating input pin + pub fn into_floating_input(self) -> $PXi> { + $GPIOX::set_pullup(Self::INDEX, false); + $GPIOX::set_input_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as a pulled down input pin + pub fn into_pull_up_input(self) -> $PXi> { + $GPIOX::set_pullup(Self::INDEX, true); + $GPIOX::set_input_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as an output pin + pub fn into_output(self) -> $PXi>> { + $GPIOX::set_drive(Self::INDEX, false); + $GPIOX::set_out_xor(Self::INDEX, false); + $GPIOX::set_output_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as an inverted output pin + pub fn into_inverted_output(self) -> $PXi>> { + $GPIOX::set_drive(Self::INDEX, false); + $GPIOX::set_out_xor(Self::INDEX, true); + $GPIOX::set_output_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + + /// Configure the pin to operate as an output pin with high + /// current drive + pub fn into_output_drive(self) -> $PXi>> { + $GPIOX::set_drive(Self::INDEX, true); + $GPIOX::set_out_xor(Self::INDEX, false); + $GPIOX::set_output_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + + /// Configure the pin to operate as an inverted output pin with + /// high current drive + pub fn into_inverted_output_drive(self) -> $PXi>> { + $GPIOX::set_drive(Self::INDEX, true); + $GPIOX::set_out_xor(Self::INDEX, true); + $GPIOX::set_output_en(Self::INDEX, true); + $GPIOX::set_iof_en(Self::INDEX, false); + $PXi { _mode: PhantomData } + } + } + + impl InputPin for $PXi> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok($GPIOX::input_value(Self::INDEX)) + + } + + fn is_low(&self) -> Result { + Ok(!self.is_high()?) + } + } + + impl StatefulOutputPin for $PXi> { + fn is_set_high(&self) -> Result { + Ok($GPIOX::input_value(Self::INDEX)) + } + + fn is_set_low(&self) -> Result { + Ok(!self.is_set_high()?) + } + } + + impl OutputPin for $PXi> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Infallible> { + $GPIOX::set_output_value(Self::INDEX, true); + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Infallible> { + $GPIOX::set_output_value(Self::INDEX, false); + Ok(()) + } + } + + impl ToggleableOutputPin for $PXi> { + type Error = Infallible; + + /// Toggles the pin state. + fn toggle(&mut self) -> Result<(), Infallible> { + $GPIOX::toggle_pin(Self::INDEX); + Ok(()) + } + } + )+ + } + } +} + +// By default, all GPIOs are in the Unknown state for two reasons: +// * bootloader may reconfigure some GPIOs +// * we do not enforce any specific state in `split()` +gpio!(Gpio0, gpio0, [ + Pin0: (pin0, 0, Unknown), + Pin1: (pin1, 1, Unknown), + Pin2: (pin2, 2, Unknown), + Pin3: (pin3, 3, Unknown), + Pin4: (pin4, 4, Unknown), + Pin5: (pin5, 5, Unknown), + Pin6: (pin6, 6, Unknown), + Pin7: (pin7, 7, Unknown), + Pin8: (pin8, 8, Unknown), + Pin9: (pin9, 9, Unknown), + Pin10: (pin10, 10, Unknown), + Pin11: (pin11, 11, Unknown), + Pin12: (pin12, 12, Unknown), + Pin13: (pin13, 13, Unknown), + Pin14: (pin14, 14, Unknown), + Pin15: (pin15, 15, Unknown), + Pin16: (pin16, 16, Unknown), + Pin17: (pin17, 17, Unknown), + Pin18: (pin18, 18, Unknown), + Pin19: (pin19, 19, Unknown), + Pin20: (pin20, 20, Unknown), + Pin21: (pin21, 21, Unknown), + Pin22: (pin22, 22, Unknown), + Pin23: (pin23, 23, Unknown), + Pin24: (pin24, 24, Unknown), + Pin25: (pin25, 25, Unknown), + Pin26: (pin26, 26, Unknown), + Pin27: (pin27, 27, Unknown), + Pin28: (pin28, 28, Unknown), + Pin29: (pin29, 29, Unknown), + Pin30: (pin30, 30, Unknown), + Pin31: (pin31, 31, Unknown), +]); diff --git a/e310x-hal/src/i2c.rs b/e310x-hal/src/i2c.rs new file mode 100644 index 0000000..c0aac2d --- /dev/null +++ b/e310x-hal/src/i2c.rs @@ -0,0 +1,311 @@ +//! I2C Master Interface +//! +//! The SiFive Inter-Integrated Circuit (I2C) Master Interface +//! is based on OpenCores® I2C Master Core. +//! +//! You can use the `I2c` interface with these I2C instances +//! +//! # I2C0 +//! - SDA: Pin 12 IOF0 +//! - SCL: Pin 13 IOF0 +//! - Interrupt::I2C0 + +use crate::clock::Clocks; +use crate::gpio::{gpio0, IOF0}; +use crate::time::Bps; +use core::mem; +use core::ops::Deref; +use e310x::{i2c0, I2c0}; +use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; + +/// SDA pin - DO NOT IMPLEMENT THIS TRAIT +pub unsafe trait SdaPin {} +/// SCL pin - DO NOT IMPLEMENT THIS TRAIT +pub unsafe trait SclPin {} + +unsafe impl SdaPin for gpio0::Pin12> {} +unsafe impl SclPin for gpio0::Pin13> {} + +/// I2C error +#[derive(Debug, Eq, PartialEq)] +pub enum Error { + /// Invalid peripheral state + InvalidState, + + /// Arbitration lost + ArbitrationLost, + + /// No ACK received + NoAck, +} + +/// Transmission speed +pub enum Speed { + /// 100Kbps + Normal, + + /// 400Kbps + Fast, + + /// Custom speed + Custom(Bps), +} + +/// I2C abstraction +pub struct I2c { + i2c: I2C, + pins: PINS, +} + +impl I2c { + /// Configures an I2C peripheral + pub fn new(i2c: I2c0, sda: SDA, scl: SCL, speed: Speed, clocks: Clocks) -> Self + where + SDA: SdaPin, + SCL: SclPin, + { + // Calculate prescaler value + let desired_speed = match speed { + Speed::Normal => 100_000, + Speed::Fast => 400_000, + Speed::Custom(bps) => bps.0, + }; + let clock = clocks.tlclk().0; + assert!(desired_speed * 5 <= clock); + let prescaler = clock / (5 * desired_speed) - 1; + assert!(prescaler < (1 << 16)); + + // Turn off i2c + i2c.ctr().write(|w| w.en().clear_bit().ien().clear_bit()); + + // Set prescaler + let prescaler_lo = (prescaler & 0xff) as u8; + let prescaler_hi = ((prescaler >> 8) & 0xff) as u8; + i2c.prer_lo() + .write(|w| unsafe { w.value().bits(prescaler_lo) }); + i2c.prer_hi() + .write(|w| unsafe { w.value().bits(prescaler_hi) }); + + // Turn on i2c + i2c.ctr().write(|w| w.en().set_bit()); + + Self { + i2c, + pins: (sda, scl), + } + } +} + +impl I2c { + /// Releases the I2C peripheral and associated pins + pub fn free(self) -> (I2C, PINS) { + (self.i2c, self.pins) + } +} + +impl, PINS> I2c { + fn reset(&self) { + // ACK pending interrupt event, clear commands + self.write_cr(|w| w.iack().set_bit()); + } + + fn write_cr(&self, f: F) + where + F: FnOnce(&mut i2c0::cr::W) -> &mut i2c0::cr::W, + { + self.i2c.cr().write(|w| unsafe { + let mut value: u32 = 0; + f(mem::transmute(&mut value)); + w.bits(value) + }); + } + + fn read_sr(&self) -> i2c0::sr::R { + unsafe { mem::transmute(self.i2c.sr().read()) } + } + + fn write_byte(&self, byte: u8) { + self.i2c.txr_rxr().write(|w| unsafe { w.data().bits(byte) }); + } + + fn read_byte(&self) -> u8 { + self.i2c.txr_rxr().read().data().bits() + } + + fn wait_for_interrupt(&self) -> Result<(), Error> { + loop { + let sr = self.read_sr(); + + if sr.al().bit_is_set() { + // Set STOP + self.write_cr(|w| w.sto().set_bit()); + self.wait_for_complete(); + + return Err(Error::ArbitrationLost); + } + + if sr.if_().bit_is_set() { + // ACK the interrupt + self.write_cr(|w| w.iack().set_bit()); + + return Ok(()); + } + } + } + + fn wait_for_read(&self) -> Result<(), Error> { + self.wait_for_interrupt() + } + + fn wait_for_write(&self) -> Result<(), Error> { + self.wait_for_interrupt()?; + + if self.read_sr().rx_ack().bit_is_set() { + // Set STOP + self.write_cr(|w| w.sto().set_bit()); + self.wait_for_complete(); + + return Err(Error::NoAck); + } + + Ok(()) + } + + fn wait_for_complete(&self) { + while self.read_sr().busy().bit_is_set() {} + } +} + +const FLAG_READ: u8 = 1; +const FLAG_WRITE: u8 = 0; + +impl, PINS> Read for I2c { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.reset(); + + if self.read_sr().busy().bit_is_set() { + return Err(Error::InvalidState); + } + + // Write address + R + self.write_byte((address << 1) + FLAG_READ); + + // Generate start condition and write command + self.write_cr(|w| w.sta().set_bit().wr().set_bit()); + self.wait_for_write()?; + + // Read bytes + let buffer_len = buffer.len(); + for (i, byte) in buffer.iter_mut().enumerate() { + if i != buffer_len - 1 { + // R + ACK + self.write_cr(|w| w.rd().set_bit().ack().clear_bit()); + } else { + // R + NACK + STOP + self.write_cr(|w| w.rd().set_bit().ack().set_bit().sto().set_bit()); + } + self.wait_for_read()?; + + *byte = self.read_byte(); + } + Ok(()) + } +} + +impl, PINS> Write for I2c { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.reset(); + + if self.read_sr().busy().bit_is_set() { + return Err(Error::InvalidState); + } + + // Write address + W + self.write_byte((address << 1) + FLAG_WRITE); + + // Generate start condition and write command + self.write_cr(|w| w.sta().set_bit().wr().set_bit()); + self.wait_for_write()?; + + // Write bytes + for (i, byte) in bytes.iter().enumerate() { + self.write_byte(*byte); + + if i != bytes.len() - 1 { + self.write_cr(|w| w.wr().set_bit()); + } else { + self.write_cr(|w| w.wr().set_bit().sto().set_bit()); + } + self.wait_for_write()?; + } + Ok(()) + } +} + +impl, PINS> WriteRead for I2c { + type Error = Error; + + fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.reset(); + + if self.read_sr().busy().bit_is_set() { + return Err(Error::InvalidState); + } + + if !bytes.is_empty() && buffer.is_empty() { + self.write(address, bytes) + } else if !buffer.is_empty() && bytes.is_empty() { + self.read(address, buffer) + } else if bytes.is_empty() && buffer.is_empty() { + Ok(()) + } else { + // Write address + W + self.write_byte((address << 1) + FLAG_WRITE); + + // Generate start condition and write command + self.write_cr(|w| w.sta().set_bit().wr().set_bit()); + self.wait_for_write()?; + + // Write bytes + for byte in bytes { + self.write_byte(*byte); + + self.write_cr(|w| w.wr().set_bit()); + self.wait_for_write()?; + } + + // Write address + R + self.write_byte((address << 1) + FLAG_READ); + + // Generate repeated start condition and write command + self.write_cr(|w| w.sta().set_bit().wr().set_bit()); + self.wait_for_write()?; + + // Read bytes + let buffer_len = buffer.len(); + for (i, byte) in buffer.iter_mut().enumerate() { + if i != buffer_len - 1 { + // W + ACK + self.write_cr(|w| w.rd().set_bit().ack().clear_bit()); + } else { + // W + NACK + STOP + self.write_cr(|w| w.rd().set_bit().ack().set_bit().sto().set_bit()); + } + self.wait_for_read()?; + + *byte = self.read_byte(); + } + + Ok(()) + } + } +} diff --git a/e310x-hal/src/interrupt.rs b/e310x-hal/src/interrupt.rs new file mode 100644 index 0000000..8599835 --- /dev/null +++ b/e310x-hal/src/interrupt.rs @@ -0,0 +1,203 @@ +//! Vectored machine external interrupt handler. +//! +//! # Notes +//! +//! - You must activate the `virq` feature to use this module. +//! +//! - The vectored handler automatically claims the PLIC interrupt source as complete. +//! Thus, users do not have to worry about this step. +//! +//! # Features +//! +//! This module provides: +//! +//! - A vectored implementation for handling each machine external interrupt source independently. +//! +//! - A linker script that provides weak symbols for all the interrupt sources of an E310X microcontroller. +//! This file must be supplied using rustflag when compiling. +//! +//! # Implementation details +//! +//! You can define a custom handler for each interrupt source (see [`e310x::interrupt::Interrupt`]). +//! For instance, if you want to define a custom handler for interrupts triggered by +//! the [`e310x::interrupt::Interrupt::GPIO0`] source, you must define the `GPIO0` function: +//! +//! ```ignore +//! #[no_mangle] +//! #[allow(non_snake_case)] +//! fn GPIO0() { +//! // define the behavior of your custom handler +//! } +//! ``` +//! +//! Note that the function must be marked as `no_mangle`. +//! You can also use the [`e310x::interrupt!`] macro. +//! +//! If a source without custom handler triggers an interruption, it executes the +//! `OtherMachineExternal` handler. This handler function is shared among all the +//! undefined interrupt sources. You can define this handler as follows: +//! +//! ```ignore,no_run +//! #[no_mangle] +//! #[allow(non_snake_case)] +//! fn OtherMachineExternal() { +//! // define the behavior of this handler +//! } +//! ``` +//! +//! By default, `OtherMachineExternal` executes the [`DefaultMachineExternal`] handler. +//! This handler is just an infinite loop. + +use crate::core::CorePeripherals; +pub use e310x::interrupt::*; + +extern "C" { + fn WATCHDOG(); + fn RTC(); + fn UART0(); + fn UART1(); + fn QSPI0(); + fn QSPI1(); + fn QSPI2(); + fn GPIO0(); + fn GPIO1(); + fn GPIO2(); + fn GPIO3(); + fn GPIO4(); + fn GPIO5(); + fn GPIO6(); + fn GPIO7(); + fn GPIO8(); + fn GPIO9(); + fn GPIO10(); + fn GPIO11(); + fn GPIO12(); + fn GPIO13(); + fn GPIO14(); + fn GPIO15(); + fn GPIO16(); + fn GPIO17(); + fn GPIO18(); + fn GPIO19(); + fn GPIO20(); + fn GPIO21(); + fn GPIO22(); + fn GPIO23(); + fn GPIO24(); + fn GPIO25(); + fn GPIO26(); + fn GPIO27(); + fn GPIO28(); + fn GPIO29(); + fn GPIO30(); + fn GPIO31(); + fn PWM0CMP0(); + fn PWM0CMP1(); + fn PWM0CMP2(); + fn PWM0CMP3(); + fn PWM1CMP0(); + fn PWM1CMP1(); + fn PWM1CMP2(); + fn PWM1CMP3(); + fn PWM2CMP0(); + fn PWM2CMP1(); + fn PWM2CMP2(); + fn PWM2CMP3(); + #[cfg(feature = "g002")] + fn I2C0(); +} + +#[no_mangle] +#[allow(non_snake_case)] +/// Default machine external interrupt handler. It is an infinite loop. +pub fn DefaultMachineExternal() { + loop { + // Prevent this from turning into a UDF instruction + // see rust-lang/rust#28728 for details + continue; + } +} + +#[cfg(not(feature = "g002"))] +const N_INTERRUPTS: usize = 51; +#[cfg(feature = "g002")] +const N_INTERRUPTS: usize = 52; + +/// Array of machine external interrupt handlers. +static HANDLERS: [unsafe extern "C" fn(); N_INTERRUPTS] = [ + WATCHDOG, + RTC, + UART0, + UART1, + QSPI0, + QSPI1, + QSPI2, + 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, + GPIO31, + PWM0CMP0, + PWM0CMP1, + PWM0CMP2, + PWM0CMP3, + PWM1CMP0, + PWM1CMP1, + PWM1CMP2, + PWM1CMP3, + PWM2CMP0, + PWM2CMP1, + PWM2CMP2, + PWM2CMP3, + #[cfg(feature = "g002")] + I2C0, +]; + +/// Handler for vectored machine external interrupts (see the [`riscv-rt`] crate). +#[no_mangle] +#[allow(non_snake_case)] +unsafe fn MachineExternal() { + // Steal the PLIC peripheral and claim the interrupt + let mut plic = CorePeripherals::steal().plic; + let interrupt = plic.claim.claim().unwrap(); + let interrupt_n = interrupt as usize; + // Match the appropriate machine external interrupt + if interrupt_n == 0 { + // Interrupt number 0 is defined as no interrupt + } else if interrupt_n <= HANDLERS.len() { + // Execute corresponding interrupt handler + HANDLERS[interrupt_n - 1](); + } else { + // Any other interrupt number is not allowed + DefaultMachineExternal(); + } + // Claim PLIC interrupt source as complete by this handler + plic.claim.complete(interrupt); +} diff --git a/e310x-hal/src/lib.rs b/e310x-hal/src/lib.rs new file mode 100644 index 0000000..40bf01a --- /dev/null +++ b/e310x-hal/src/lib.rs @@ -0,0 +1,67 @@ +//! HAL for the E310x family of microcontrollers +//! +//! This is an implementation of the [`embedded-hal`] traits for the E310x +//! family of microcontrollers. +//! +//! # Building an application for the E310x chips +//! +//! The E310x chips implement the [Zaamo](https://github.com/riscv/riscv-zaamo-zalrsc/blob/main/zaamo-zalrsc.adoc) +//! extension for atomic instructions. This means that it *partially* supports the `A` +//! extension for atomic. Specifically, it supports the `amo*` instructions, but not the +//! `lr*` and `sc*` instructions. +//! +//! It is discouraged to use the `riscv32imac-unknown-none-elf` target for E310x chips, as it +//! will potentially generate code that uses the `lr*` and `sc*` instructions, which are not +//! supported by the E310x chips. Thus, it is recommended to use `riscv32imc-unknown-none-elf`. +//! +//! # Working with atomic operations +//! +//! If you are using the `riscv32imc-unknown-none-elf` target, you will notice that +//! `core::sync::atomic` is not available. To work around this, you can use the +//! [`portable-atomic`](https://docs.rs/portable-atomic/1.8.0/portable_atomic/) crate. +//! This crate allows us to use native `amo*` instructions on the E310x chips without requiring +//! the `A` extension. Furthermore, you can emulate the `lr*` and `sc*` instructions if needed. +//! +//! Thus, the recommended way to work with E310x chips is: +//! +//! 1. Compile your code against the `riscv32imc-unknown-none-elf` target. +//! 2. Add the following configuration to your `.cargo/config.toml`: +//! +//! ```toml +//! [target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +//! rustflags = [ +//! "--cfg", "portable_atomic_target_feature=\"zaamo\"", +//! ] +//! +//! [build] +//! target = "riscv32imc-unknown-none-elf" +//! ``` +//! +//! This will ensure that the `portable-atomic` crate is correctly configured to work with the E310x chips. + +#![deny(missing_docs)] +#![no_std] + +pub use e310x; + +pub mod clock; +pub mod core; +pub mod delay; +pub mod device; +pub mod gpio; +pub mod pmu; +pub mod prelude; +pub mod pwm; +pub mod rtc; +pub mod serial; +pub mod spi; +pub mod stdout; +pub mod time; +pub mod wdog; + +#[cfg(feature = "g002")] +pub mod i2c; +#[cfg(feature = "virq")] +pub mod interrupt; + +pub use device::DeviceResources; diff --git a/e310x-hal/src/pmu.rs b/e310x-hal/src/pmu.rs new file mode 100644 index 0000000..21bb422 --- /dev/null +++ b/e310x-hal/src/pmu.rs @@ -0,0 +1,316 @@ +//! PMU Extension +#![allow(missing_docs)] +use e310x::{Backup, Pmu, Rtc}; + +/// Backup register size in bytes +const BACKUP_REGISTER_BYTES: usize = 4; + +const BACKUP_LEN: usize = 16; + +/// value required written to pmukey register before writing to other PMU registers +pub const PMU_KEY_VAL: u32 = 0x51F15E; + +// HiFive1 (Rev A) programs +#[cfg(not(feature = "g002"))] +const DEFAULT_SLEEP_PROGRAM: [u32; 8] = [ + 0x0F0, // assert corerst + 0x1F0, // assert hfclkrst + 0x1D0, // deassert pmu_out_1 + 0x1C0, // deassert pmu_out_0 + 0x1C0, // repeats + 0x1C0, 0x1C0, 0x1C0, +]; + +#[cfg(not(feature = "g002"))] +const DEFAULT_WAKE_PROGRAM: [u32; 8] = [ + 0x1F0, // assert all resets and enable all power supplies + 0x0F8, // idle 2^8 cycles, then deassert hfclkrst + 0x030, // deassert corerst and padrst + 0x030, // repeats + 0x030, 0x030, 0x030, 0x030, +]; + +// HiFive1 Rev B programs +#[cfg(feature = "g002")] +const DEFAULT_SLEEP_PROGRAM: [u32; 8] = [ + 0x2F0, // assert corerst + 0x3F0, // assert hfclkrst + 0x3D0, // deassert pmu_out_1 + 0x3C0, // deassert pmu_out_0 + 0x3C0, // repeats + 0x3C0, 0x3C0, 0x3C0, +]; + +#[cfg(feature = "g002")] +const DEFAULT_WAKE_PROGRAM: [u32; 8] = [ + 0x3F0, // assert all resets and enable all power supplies + 0x2F8, // idle 2^8 cycles, then deassert hfclkrst + 0x030, // deassert corerst and padrst + 0x030, // repeats + 0x030, 0x030, 0x030, 0x030, +]; + +/// +/// Enumeration of device reset causes +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResetCause { + /// Reset due to power on + PowerOn, + /// Reset due to external input (button) + External, + /// Reset due to watchdog + WatchDog, +} + +/// +/// Enumeration of device wakeup causes +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WakeupCause { + /// Wake up due to reset (see ResetCause) + Reset(ResetCause), + /// Wake up due to RTC clock + RTC, + /// Wake up due to digital input (button) + Digital, +} + +/// +/// Errors for user data backup procedures +/// +#[derive(Debug)] +pub enum BackupError { + /// Emitted when user data is larger than backup registers capacity + DataTooLarge, + /// Emitted when user data size is not divisible by 4 bytes + DataSizeInvalid, +} + +/// +/// Wakeup/Reset cause errors +/// +#[derive(Debug)] +pub enum CauseError { + /// Emitted if an unknown wakeup or reset cause is encountered + InvalidCause, +} + +pub trait PMUExt { + /// + /// Resets SLEEP and WAKE programs on the PMU to defaults + /// + fn load_default_programs(&self); + + /// + /// Puts device to sleep for N seconds, allowing wake-up button to wake it up as well + /// + /// # Arguments + /// + /// *sleep_time* - the amount of time to sleep for in seconds + /// + /// # Notes + /// + /// - enables RTC to be always on + /// - sets RTC scale to 1/s + /// + fn sleep(self, sleep_time: u32); + + /// + /// Returns an enumified version of the Wakeup and Reset causes from the pmucause register + /// + /// # Returns + /// + /// * `Result` - the cause enum is returned on success + /// + /// # Errors + /// + /// * `CauseError::InvalidCause` - returned if an unknown wakeup or reset cause is encountered + /// + fn wakeup_cause(&self) -> Result; + + /// + /// Stores user data `UD` to backup registers. + /// + /// # *WARNING* + /// + /// `user_data` value must not contain un-serializable types such as pointers or references. + /// + /// `user_data` size must be divisible by 4 bytes. + /// + /// **The data is only guaranteed to be consistent when program is compiled with the same version of the compiler on store/restore.** + /// + /// `#[repr(align(4))]` can be used to enforce a minimum alignment of 4 bytes for `user_data` + /// + /// # Arguments + /// + /// * `user_data` - reference to the user data to store. `user_data` size must by divisible by 4 bytes + /// + /// # Returns + /// + /// * `Result<(), BackupError>` - `()` is returned on success + /// + /// # Errors + /// + /// * `BackupError::DataTooLarge` - returned if `user_data` cannot fit into backup registers + /// * `BackupError::DataSizeInvalid` - returned if `user_data` size is not divisible by 4 bytes + /// + unsafe fn store_backup(&self, user_data: &UD) -> Result<(), BackupError>; + + /// + /// Restores user data `UD` from backup registers. + /// + /// # *WARNING* + /// + /// `user_data` value must not contain un-serializable types such as pointers or references. + /// + /// `user_data` size must be divisible by 4 bytes. + /// + /// **The data is only guaranteed to be consistent when program is compiled with the same version of the compiler on store/restore.** + /// + /// `#[repr(align(4))]` can be used to enforce a minimum alignment of 4 bytes for `user_data` + /// + /// # Arguments + /// + /// * `user_data` - the user data to restore to. `user_data` size must by divisible by 4 bytes + /// + /// # Returns + /// + /// * `Result<(), BackupError>` - `()` is returned on success + /// + /// # Errors + /// + /// * `BackupError::DataTooLarge` - returned if `user_data` cannot fit into backup registers + /// * `BackupError::DataSizeInvalid` - returned if `user_data` size is not divisible by 4 bytes /// + /// + unsafe fn restore_backup(&self, user_data: &mut UD) -> Result<(), BackupError>; + + /// + /// Clears all backup registers by setting each to zero + /// + fn clear_backup(&self); +} + +impl PMUExt for Pmu { + fn load_default_programs(&self) { + unsafe { + for i in 0..8 { + self.pmukey().write(|w| w.bits(PMU_KEY_VAL)); + self.pmusleeppm(i) + .write(|w| w.bits(DEFAULT_SLEEP_PROGRAM[i])); + + self.pmukey().write(|w| w.bits(PMU_KEY_VAL)); + self.pmuwakepm(i).write(|w| w.bits(DEFAULT_WAKE_PROGRAM[i])); + } + } + } + + fn sleep(self, sleep_time: u32) { + unsafe { + let rtc = Rtc::steal(); + + // set interrupt source to RTC enabled, each pmu register needs key set before write + self.pmukey().write(|w| w.bits(PMU_KEY_VAL)); + self.pmuie() + .write(|w| w.rtc().set_bit().dwakeup().set_bit()); + // set RTC clock scale to once per second for easy calculation + rtc.rtccfg() + .write(|w| w.enalways().set_bit().scale().bits(15)); + // get current RTC clock value scaled + let rtc_now = rtc.rtcs().read().bits(); + // set RTC clock comparator + rtc.rtccmp().write(|w| w.bits(rtc_now + sleep_time)); + // go to sleep for sleep_time seconds, need to set pmukey here as well + self.pmukey().write(|w| w.bits(PMU_KEY_VAL)); + self.pmusleep().write(|w| w.sleep().set_bit()); + } + } + + fn wakeup_cause(&self) -> Result { + let pmu_cause = self.pmucause().read(); + let wakeup_cause = pmu_cause.wakeupcause(); + if wakeup_cause.is_rtc() { + return Ok(WakeupCause::RTC); + } else if wakeup_cause.is_digital() { + return Ok(WakeupCause::Digital); + } else if wakeup_cause.is_reset() { + let reset_cause = pmu_cause.resetcause(); + + if reset_cause.is_power_on() { + return Ok(WakeupCause::Reset(ResetCause::PowerOn)); + } else if reset_cause.is_external() { + return Ok(WakeupCause::Reset(ResetCause::External)); + } else if reset_cause.is_watchdog() { + return Ok(WakeupCause::Reset(ResetCause::WatchDog)); + } + } + + Err(CauseError::InvalidCause) + } + + unsafe fn store_backup(&self, user_data: &UD) -> Result<(), BackupError> + where + UD: Sized, + { + let backup = Backup::steal(); + let ud_size = core::mem::size_of::(); + + if ud_size > BACKUP_LEN * BACKUP_REGISTER_BYTES { + return Err(BackupError::DataTooLarge); + } + + if ud_size % BACKUP_REGISTER_BYTES != 0 { + return Err(BackupError::DataSizeInvalid); + } + + let reg_count = ud_size / BACKUP_REGISTER_BYTES; + + let ptr = user_data as *const _; + let ptr_u32 = ptr as *const u32; + let sliced = core::slice::from_raw_parts(ptr_u32, reg_count); + + for i in 0..sliced.len() { + backup.backup(i).write(|w| w.bits(sliced[i])); + } + + Ok(()) + } + + unsafe fn restore_backup(&self, user_data: &mut UD) -> Result<(), BackupError> + where + UD: Sized, + { + let backup = Backup::steal(); + let ud_size = core::mem::size_of::(); + + if ud_size > BACKUP_LEN * BACKUP_REGISTER_BYTES { + return Err(BackupError::DataTooLarge); + } + + if ud_size % BACKUP_REGISTER_BYTES != 0 { + return Err(BackupError::DataSizeInvalid); + } + + let reg_count = ud_size / BACKUP_REGISTER_BYTES; + + let ptr = user_data as *const _; + let ptr_u32 = ptr as *mut u32; + let sliced = core::slice::from_raw_parts_mut(ptr_u32, reg_count); + + for i in 0..sliced.len() { + sliced[i] = backup.backup(i).read().bits(); + } + + Ok(()) + } + + fn clear_backup(&self) { + unsafe { + let backup = Backup::steal(); + + for backup_r in backup.backup_iter() { + backup_r.write(|w| w.bits(0u32)); + } + } + } +} diff --git a/e310x-hal/src/prelude.rs b/e310x-hal/src/prelude.rs new file mode 100644 index 0000000..c6ccd12 --- /dev/null +++ b/e310x-hal/src/prelude.rs @@ -0,0 +1,15 @@ +//! Prelude + +pub use crate::clock::AonExt as _e310x_hal_clock_AonExt; +pub use crate::clock::PrciExt as _e310x_hal_clock_PrciExt; +pub use crate::gpio::GpioExt as _e310x_hal_gpio_GpioExt; +pub use crate::rtc::RtcExt as _e310x_hal_rtc_RtcExt; +pub use crate::stdout::Write as _e310x_hal_stdout_Write; +pub use crate::time::U32Ext as _e310x_hal_time_U32Ext; +pub use crate::wdog::WdogExt as _e310x_hal_wdog_WdogExt; +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, +}; +pub use embedded_hal::prelude::*; diff --git a/e310x-hal/src/pwm.rs b/e310x-hal/src/pwm.rs new file mode 100644 index 0000000..e536c4f --- /dev/null +++ b/e310x-hal/src/pwm.rs @@ -0,0 +1,235 @@ +//! # Pulse Width Modulation Interface +//! +//! You can use the `PWM` with these [Pwm] instances +//! +//! # PWM0 - 8 bit period and duty +//! - Channel 1: Pin 9 IOF1 +//! - Channel 2: Pin 10 IOF1 +//! - Channel 3: Pin 11 IOF1 +//! +//! # PWM1 - 16 bit period and duty +//! - Channel 1: Pin 3 IOF1 +//! - Channel 2: Pin 5 IOF1 +//! - Channel 3: Pin 6 IOF1 +//! +//! # PWM2 - 16 bit period and duty +//! - Channel 1: Pin 17 IOF1 +//! - Channel 2: Pin 18 IOF1 +//! - Channel 3: Pin 19 IOF1 + +use core::marker::PhantomData; +use core::ops::Deref; + +use e310x::{pwm0, Pwm0, Pwm1, Pwm2}; + +/// PWM comparator index +#[derive(Copy, Clone)] +pub enum CmpIndex { + /// PWM comparator 1 + Cmp1, + /// PWM comparator 1 + Cmp2, + /// PWM comparator 1 + Cmp3, +} + +/// PWM pin - DO NOT IMPLEMENT THIS TRAIT +pub trait Pin { + #[doc(hidden)] + const CMP_INDEX: CmpIndex; +} + +mod pwm0_impl { + use super::{CmpIndex, Pin, Pwm0}; + use crate::gpio::{gpio0, NoInvert, IOF1}; + + impl Pin for gpio0::Pin1> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp1; + } + + impl Pin for gpio0::Pin2> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp2; + } + + impl Pin for gpio0::Pin3> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp3; + } +} + +mod pwm1_impl { + use super::{CmpIndex, Pin, Pwm1}; + use crate::gpio::{gpio0, NoInvert, IOF1}; + + impl Pin for gpio0::Pin19> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp1; + } + + impl Pin for gpio0::Pin21> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp2; + } + + impl Pin for gpio0::Pin22> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp3; + } +} + +mod pwm2_impl { + use super::{CmpIndex, Pin, Pwm2}; + use crate::gpio::{gpio0, NoInvert, IOF1}; + + impl Pin for gpio0::Pin11> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp1; + } + + impl Pin for gpio0::Pin12> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp2; + } + + impl Pin for gpio0::Pin13> { + const CMP_INDEX: CmpIndex = CmpIndex::Cmp3; + } +} + +/// PWM channel +pub struct Channel { + _pwm: PhantomData, + cmp_index: CmpIndex, +} + +impl Channel { + /// Constructs a PWM channel from a PWM pin for use with [Pwm] + pub fn from(_: PIN) -> Channel + where + PIN: Pin, + { + Channel { + _pwm: PhantomData, + cmp_index: PIN::CMP_INDEX, + } + } +} + +impl Clone for Channel { + fn clone(&self) -> Self { + Self { + _pwm: self._pwm.clone(), + cmp_index: self.cmp_index.clone(), + } + } +} + +impl Copy for Channel {} + +#[doc(hidden)] +pub trait PwmX: Deref { + type CmpWidth: Ord; + fn bits_from_cmp_width(other: Self::CmpWidth) -> u32; + fn bits_into_cmp_width(other: u32) -> Self::CmpWidth; +} + +macro_rules! pwmx_impl { + ($PWM:ident,$CMP_WIDTH:ident) => { + impl PwmX for $PWM { + type CmpWidth = $CMP_WIDTH; + fn bits_from_cmp_width(other: Self::CmpWidth) -> u32 { + other as u32 + } + fn bits_into_cmp_width(other: u32) -> Self::CmpWidth { + other as Self::CmpWidth + } + } + }; +} + +pwmx_impl!(Pwm0, u8); +pwmx_impl!(Pwm1, u16); +pwmx_impl!(Pwm2, u16); + +/// PWM abstraction +/// +/// # Notes +/// +/// [PWM0] has a max period of 255, as it only has an 8 bit comparison register, +/// the rest of them have a max value of 2^16 as they have 16 bit registers. +pub struct Pwm { + pwm: PWM, +} + +impl Pwm { + /// Configures a PWM device + pub fn new(pwm: PWM) -> Self { + pwm.cfg().reset(); + pwm.cfg().write(|w| { + w.zerocmp() + .set_bit() + .enalways() + .set_bit() + .deglitch() + .set_bit() + }); + pwm.cmp0().reset(); + pwm.cmp1().reset(); + pwm.cmp2().reset(); + pwm.cmp3().reset(); + Self { pwm } + } +} + +impl embedded_hal::Pwm for Pwm { + type Channel = Channel; + + type Time = PWM::CmpWidth; + + type Duty = PWM::CmpWidth; + + fn enable(&mut self, channel: Self::Channel) { + match channel.cmp_index { + CmpIndex::Cmp1 => self.pwm.cmp1().write(|w| unsafe { w.bits(u32::MAX) }), + CmpIndex::Cmp2 => self.pwm.cmp2().write(|w| unsafe { w.bits(u32::MAX) }), + CmpIndex::Cmp3 => self.pwm.cmp3().write(|w| unsafe { w.bits(u32::MAX) }), + } + } + + fn disable(&mut self, channel: Self::Channel) { + match channel.cmp_index { + CmpIndex::Cmp1 => self.pwm.cmp1().reset(), + CmpIndex::Cmp2 => self.pwm.cmp2().reset(), + CmpIndex::Cmp3 => self.pwm.cmp3().reset(), + } + } + + fn get_period(&self) -> Self::Time { + PWM::bits_into_cmp_width(self.pwm.cmp0().read().bits()) + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + let duty = match channel.cmp_index { + CmpIndex::Cmp1 => self.pwm.cmp1().read().bits(), + CmpIndex::Cmp2 => self.pwm.cmp2().read().bits(), + CmpIndex::Cmp3 => self.pwm.cmp3().read().bits(), + }; + PWM::bits_into_cmp_width(duty) + } + + fn get_max_duty(&self) -> Self::Duty { + self.get_period() + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + let duty = PWM::bits_from_cmp_width(duty.min(self.get_max_duty())); + match channel.cmp_index { + CmpIndex::Cmp1 => self.pwm.cmp1().write(|w| unsafe { w.bits(duty) }), + CmpIndex::Cmp2 => self.pwm.cmp2().write(|w| unsafe { w.bits(duty) }), + CmpIndex::Cmp3 => self.pwm.cmp3().write(|w| unsafe { w.bits(duty) }), + } + } + + fn set_period

(&mut self, period: P) + where + P: Into, + { + let period = PWM::bits_from_cmp_width(period.into()); + self.pwm.count().reset(); + self.pwm.cmp0().write(|w| unsafe { w.bits(period) }); + } +} diff --git a/e310x-hal/src/rtc.rs b/e310x-hal/src/rtc.rs new file mode 100644 index 0000000..8cb0274 --- /dev/null +++ b/e310x-hal/src/rtc.rs @@ -0,0 +1,94 @@ +//! RTC +#![allow(missing_docs)] + +use e310x::Rtc as RTC; + +pub trait RtcExt { + fn constrain(self) -> Rtc; +} + +impl RtcExt for RTC { + fn constrain(self) -> Rtc { + Rtc { _0: () } + } +} + +pub struct Rtc { + _0: (), +} + +impl Rtc { + #[inline] + pub fn is_pending(&self) -> bool { + unsafe { RTC::steal() }.rtccfg().read().cmpip().bit() + } + + #[inline] + pub fn set_scale(&mut self, scale: u8) { + unsafe { RTC::steal().rtccfg().modify(|_, w| w.scale().bits(scale)) }; + } + + #[inline] + pub fn enable(&mut self) { + unsafe { RTC::steal() } + .rtccfg() + .modify(|_, w| w.enalways().bit(true)); + } + + #[inline] + pub fn disable(&mut self) { + unsafe { RTC::steal() } + .rtccfg() + .modify(|_, w| w.enalways().bit(false)); + } + + #[inline] + pub fn is_enabled(&self) -> bool { + unsafe { RTC::steal() }.rtccfg().read().enalways().bit() + } + + #[inline] + pub fn rtc_lo(&self) -> u32 { + unsafe { RTC::steal() }.rtclo().read().bits() + } + + #[inline] + pub fn rtc_hi(&self) -> u32 { + unsafe { RTC::steal() }.rtchi().read().bits() + } + + pub fn rtc(&self) -> u64 { + loop { + let hi = self.rtc_hi(); + let lo = self.rtc_lo(); + if hi == self.rtc_hi() { + return ((hi as u64) << 32) | lo as u64; + } + } + } + + #[inline] + pub fn set_rtc_lo(&mut self, value: u32) { + unsafe { RTC::steal().rtclo().write(|w| w.bits(value)) }; + } + + #[inline] + pub fn set_rtc_hi(&mut self, value: u16) { + unsafe { RTC::steal().rtchi().write(|w| w.value().bits(value)) }; + } + + pub fn set_rtc(&mut self, value: u64) { + self.set_rtc_hi((value >> 32) as u16); + self.set_rtc_lo(value as u32); + } + + #[inline] + pub fn rtccmp(&self) -> u32 { + unsafe { RTC::steal() }.rtccmp().read().bits() + } + + #[inline] + pub fn set_rtccmp(&mut self, value: u32) { + unsafe { RTC::steal().rtccmp().write(|w| w.bits(value)) }; + } +} diff --git a/e310x-hal/src/serial.rs b/e310x-hal/src/serial.rs new file mode 100644 index 0000000..6a284ba --- /dev/null +++ b/e310x-hal/src/serial.rs @@ -0,0 +1,183 @@ +//! Serial interface +//! +//! You can use the `Serial` interface with these UART instances +//! +//! # UART0 +//! - TX: Pin 17 IOF0 +//! - RX: Pin 16 IOF0 +//! - Interrupt::UART0 +//! +//! # UART1 +//! *Warning:* UART1 pins are not connected to package in FE310-G000 +//! - TX: Pin 18 IOF0 +//! - RX: Pin 23 IOF0 +//! - Interrupt::UART1 + +use core::convert::Infallible; +use core::ops::Deref; + +use embedded_hal::serial; +use nb; + +use crate::clock::Clocks; +use crate::gpio::{gpio0, IOF0}; +use crate::time::Bps; +use core::mem; +#[allow(unused_imports)] +use e310x::{uart0, Uart0, Uart1}; + +// FIXME these should be "closed" traits +/// TX pin - DO NOT IMPLEMENT THIS TRAIT +pub unsafe trait TxPin {} + +/// RX pin - DO NOT IMPLEMENT THIS TRAIT +pub unsafe trait RxPin {} + +unsafe impl TxPin for gpio0::Pin17> {} +unsafe impl RxPin for gpio0::Pin16> {} + +#[cfg(feature = "g002")] +mod g002_ims { + use super::{gpio0, RxPin, TxPin, Uart1, IOF0}; + unsafe impl TxPin for gpio0::Pin18> {} + unsafe impl RxPin for gpio0::Pin23> {} +} + +#[doc(hidden)] +pub trait UartX: Deref {} +impl UartX for Uart0 {} +impl UartX for Uart1 {} + +/// Serial abstraction +pub struct Serial { + uart: UART, + pins: PINS, +} + +/// Serial receiver +pub struct Rx { + uart: UART, +} + +/// Serial transmitter +pub struct Tx { + uart: UART, +} + +impl Serial { + /// Configures a UART peripheral to provide serial communication + pub fn new(uart: UART, pins: (TX, RX), baud_rate: Bps, clocks: Clocks) -> Self + where + TX: TxPin, + RX: RxPin, + { + let div = clocks.tlclk().0 / baud_rate.0 - 1; + unsafe { + uart.ie().write(|w| w.txwm().bit(false).rxwm().bit(false)); + uart.div().write(|w| w.bits(div)); + uart.txctrl() + .write(|w| w.counter().bits(1).enable().bit(true)); + uart.rxctrl().write(|w| w.enable().bit(true)); + } + + Serial { uart, pins } + } + + /// Starts listening for an interrupt event + pub fn listen(self) -> Self { + self.uart + .ie() + .write(|w| w.txwm().bit(false).rxwm().bit(true)); + self + } + + /// Stops listening for an interrupt event + pub fn unlisten(self) -> Self { + self.uart + .ie() + .write(|w| w.txwm().bit(false).rxwm().bit(false)); + self + } + + /// Splits the `Serial` abstraction into a transmitter and a + /// receiver half + pub fn split(self) -> (Tx, Rx) { + ( + Tx { + uart: unsafe { mem::zeroed() }, + }, + Rx { uart: self.uart }, + ) + } + + /// Releases the UART peripheral and associated pins + pub fn free(self) -> (UART, (TX, RX)) { + (self.uart, self.pins) + } +} + +impl serial::Read for Rx { + type Error = Infallible; + + fn read(&mut self) -> nb::Result { + let rxdata = self.uart.rxdata().read(); + + if rxdata.empty().bit_is_set() { + Err(::nb::Error::WouldBlock) + } else { + Ok(rxdata.data().bits() as u8) + } + } +} + +impl serial::Write for Tx { + type Error = Infallible; + + fn write(&mut self, byte: u8) -> nb::Result<(), Infallible> { + let txdata = self.uart.txdata().read(); + + if txdata.full().bit_is_set() { + Err(::nb::Error::WouldBlock) + } else { + unsafe { + self.uart.txdata().write(|w| w.data().bits(byte)); + } + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Infallible> { + if self.uart.ip().read().txwm().bit_is_set() { + // FIFO count is below the receive watermark (1) + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +// Backward compatibility +impl Serial { + /// Configures a UART peripheral to provide serial communication + #[deprecated(note = "Please use Serial::new function instead")] + pub fn uart0(uart: Uart0, pins: (TX, RX), baud_rate: Bps, clocks: Clocks) -> Self + where + TX: TxPin, + RX: RxPin, + { + Self::new(uart, pins, baud_rate, clocks) + } +} + +#[cfg(feature = "g002")] +impl Serial { + /// Configures a UART peripheral to provide serial communication + #[deprecated(note = "Please use Serial::new function instead")] + pub fn uart1(uart: Uart1, pins: (TX, RX), baud_rate: Bps, clocks: Clocks) -> Self + where + TX: TxPin, + RX: RxPin, + { + Self::new(uart, pins, baud_rate, clocks) + } +} diff --git a/e310x-hal/src/spi.rs b/e310x-hal/src/spi.rs new file mode 100644 index 0000000..e5fe5c4 --- /dev/null +++ b/e310x-hal/src/spi.rs @@ -0,0 +1,66 @@ +//! # Serial Peripheral Interface +//! +//! You can use the `Spi` interface with these SPI instances +//! +//! # QSPI0 +//! - Interrupt::QSPI0 +//! +//! # QSPI1 +//! - MOSI: Pin 3 IOF0 +//! - MISO: Pin 4 IOF0 +//! - SCK: Pin 5 IOF0 +//! - CS0: Pin 2 IOF0 +//! - CS1: Pin 8 IOF0 (not connected to package in FE310) +//! - CS2: Pin 9 IOF0 +//! - CS3: Pin 10 IOF0 +//! - Interrupt::QSPI1 +//! +//! # QSPI2 +//! *Warning:* QSPI2 pins are not connected to package in FE310 +//! - MOSI: Pin 27 IOF0 +//! - MISO: Pin 28 IOF0 +//! - SCK: Pin 29 IOF0 +//! - CS: Pin 26 IOF0 +//! - Interrupt::QSPI2 +//! +//! # Exclusive Bus usage example +//!```ignore +//! let pins = (mosi, miso, sck, cs0); +//! let spi_bus = SpiBus::new(p.QSPI1, pins); +//! +//! let spi_config = SpiConfig::new(MODE_0, 100.khz().into(), &clocks); +//! let mut dev = spi_bus.new_device(&spi_config); +//! +//! dev.write(&[1, 2, 3]).unwrap(); +//!``` +//! +//! # Shared Bus usage example +//!```ignore +//! let pins = (mosi, miso, sck); +//! let spi_bus = SpiBus::shared(p.QSPI1, pins); +//! +//! let spi_config1 = SpiConfig::new(MODE_0, 100.khz().into(), &clocks); +//! let mut dev1 = spi_bus.new_device(cs0, &spi_config1); +//! +//! let spi_config2 = SpiConfig::new(MODE_3, 2.mhz().into(), &clocks); +//! let mut dev2 = spi_bus.new_device(cs1, &spi_config2); +//! +//! dev1.write(&[1, 2, 3]).unwrap(); +//! dev2.write(&[4, 5]).unwrap(); +//!``` + +mod bus; // contains the SPI Bus abstraction +mod config; +mod exclusive_device; // contains the exclusive SPI device abstraction +mod shared_bus; // shared bus newtype +mod shared_device; // contains the shared SPI device abstraction +mod traits; // contains SPI device abstraction + +pub use bus::*; +pub use config::*; +pub use exclusive_device::*; +pub use shared_bus::*; +pub use shared_device::*; +pub use traits::*; + +pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; diff --git a/e310x-hal/src/spi/bus.rs b/e310x-hal/src/spi/bus.rs new file mode 100644 index 0000000..4be10be --- /dev/null +++ b/e310x-hal/src/spi/bus.rs @@ -0,0 +1,252 @@ +use core::convert::Infallible; +use embedded_hal::blocking::spi::Operation; +pub use embedded_hal::blocking::spi::{Transfer, Write, WriteIter}; +pub use embedded_hal::spi::{FullDuplex, Phase, Polarity}; + +use nb; + +use super::{Pins, PinsNoCS, SharedBus, SpiConfig, SpiExclusiveDevice, SpiX}; + +/// SPI bus abstraction +pub struct SpiBus { + pub(crate) spi: SPI, + pub(crate) pins: PINS, +} + +impl SpiBus +where + SPI: SpiX, +{ + /// Construct the [SpiBus] for use with [SpiSharedDevice](super::SpiSharedDevice) or [SpiExclusiveDevice] + pub fn new(spi: SPI, pins: PINS) -> Self + where + PINS: Pins, + { + Self { spi, pins } + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SPI, PINS) { + (self.spi, self.pins) + } + + /// Configure the [SpiBus] with given [SpiConfig] + pub(crate) fn configure(&mut self, config: &SpiConfig, cs_index: Option) + where + PINS: Pins, + { + self.spi + .sckdiv() + .write(|w| unsafe { w.div().bits(config.clock_divisor as u16) }); + + if let Some(index) = cs_index { + self.spi.csid().write(|w| unsafe { w.bits(index) }); + } + self.spi + .csmode() + .write(|w| w.mode().variant(config.cs_mode)); + + // Set CS pin polarity to high + self.spi.csdef().reset(); + + // Set SPI mode + let phase = config.mode.phase == Phase::CaptureOnSecondTransition; + let polarity = config.mode.polarity == Polarity::IdleHigh; + self.spi + .sckmode() + .write(|w| w.pha().bit(phase).pol().bit(polarity)); + + self.spi.fmt().write(|w| unsafe { + w.proto().single(); + w.endian().big(); // Transmit most-significant bit (MSB) first + w.dir().rx(); + w.len().bits(8) + }); + + // Set watermark levels + self.spi + .txmark() + .write(|w| unsafe { w.txmark().bits(config.txmark) }); + self.spi + .rxmark() + .write(|w| unsafe { w.rxmark().bits(config.rxmark) }); + + // set delays + self.spi.delay0().write(|w| unsafe { + w.cssck().bits(config.delays.cssck); // delay between assert and clock + w.sckcs().bits(config.delays.sckcs) // delay between clock and de-assert + }); + self.spi.delay1().write(|w| unsafe { + w.intercs().bits(config.delays.intercs); // delay between CS re-assets + w.interxfr().bits(config.delays.interxfr) // intra-frame delay without CS re-asserts + }); + + self.end_frame(); // ensure CS is de-asserted before we begin + } + + fn wait_for_rxfifo(&self) { + // Ensure that RX FIFO is empty + while self.spi.rxdata().read().empty().bit_is_clear() {} + } + + /// Starts frame by flagging CS assert, unless CSMODE = OFF + pub(crate) fn start_frame(&mut self) { + if !self.spi.csmode().read().mode().is_off() { + self.spi.csmode().write(|w| w.mode().hold()); + } + } + + /// Finishes frame flagging CS deassert, unless CSMODE = OFF + pub(crate) fn end_frame(&mut self) { + if !self.spi.csmode().read().mode().is_off() { + self.spi.csmode().write(|w| w.mode().auto()); + } + } + + // ex-traits now only accessible via devices + + pub(crate) fn read(&mut self) -> nb::Result { + let rxdata = self.spi.rxdata().read(); + + if rxdata.empty().bit_is_set() { + Err(nb::Error::WouldBlock) + } else { + Ok(rxdata.data().bits()) + } + } + + pub(crate) fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + let txdata = self.spi.txdata().read(); + + if txdata.full().bit_is_set() { + Err(nb::Error::WouldBlock) + } else { + self.spi.txdata().write(|w| unsafe { w.data().bits(byte) }); + Ok(()) + } + } + + pub(crate) fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Infallible> { + let mut iwrite = 0; + let mut iread = 0; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while iwrite < words.len() || iread < words.len() { + if iwrite < words.len() && self.spi.txdata().read().full().bit_is_clear() { + let byte = unsafe { words.get_unchecked(iwrite) }; + iwrite += 1; + self.spi.txdata().write(|w| unsafe { w.data().bits(*byte) }); + } + + if iread < iwrite { + let data = self.spi.rxdata().read(); + if data.empty().bit_is_clear() { + unsafe { *words.get_unchecked_mut(iread) = data.data().bits() }; + iread += 1; + } + } + } + + Ok(words) + } + + pub(crate) fn write(&mut self, words: &[u8]) -> Result<(), Infallible> { + let mut iwrite = 0; + let mut iread = 0; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while iwrite < words.len() || iread < words.len() { + if iwrite < words.len() && self.spi.txdata().read().full().bit_is_clear() { + let byte = unsafe { words.get_unchecked(iwrite) }; + iwrite += 1; + self.spi.txdata().write(|w| unsafe { w.data().bits(*byte) }); + } + + if iread < iwrite { + // Read and discard byte, if any + if self.spi.rxdata().read().empty().bit_is_clear() { + iread += 1; + } + } + } + + Ok(()) + } + + pub(crate) fn write_iter(&mut self, words: WI) -> Result<(), Infallible> + where + WI: IntoIterator, + { + let mut iter = words.into_iter(); + + let mut read_count = 0; + let mut has_data = true; + + // Ensure that RX FIFO is empty + self.wait_for_rxfifo(); + + while has_data || read_count > 0 { + if has_data && self.spi.txdata().read().full().bit_is_clear() { + if let Some(byte) = iter.next() { + self.spi.txdata().write(|w| unsafe { w.data().bits(byte) }); + read_count += 1; + } else { + has_data = false; + } + } + + if read_count > 0 { + // Read and discard byte, if any + if self.spi.rxdata().read().empty().bit_is_clear() { + read_count -= 1; + } + } + } + + Ok(()) + } + + pub(crate) fn exec<'op>( + &mut self, + operations: &mut [Operation<'op, u8>], + ) -> Result<(), Infallible> { + for op in operations { + match op { + Operation::Transfer(words) => { + self.transfer(words)?; + } + Operation::Write(words) => { + self.write(words)?; + } + } + } + + Ok(()) + } +} + +impl SpiBus +where + SPI: SpiX, + PINS: Pins, +{ + /// Create a new [SpiExclusiveDevice] for exclusive use on this bus + pub fn new_device(self, config: &SpiConfig) -> SpiExclusiveDevice { + SpiExclusiveDevice::new(self, config) + } +} + +impl SpiBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + /// Create a [SharedBus] for use with multiple devices. + pub fn shared(spi: SPI, pins: PINS) -> SharedBus { + SharedBus::new(Self::new(spi, pins)) + } +} diff --git a/e310x-hal/src/spi/config.rs b/e310x-hal/src/spi/config.rs new file mode 100644 index 0000000..61a9e8d --- /dev/null +++ b/e310x-hal/src/spi/config.rs @@ -0,0 +1,69 @@ +use e310x::qspi0::csmode::Mode as MODE_A; +use embedded_hal::spi::Mode; + +use crate::{clock::Clocks, time::Hertz}; + +/// SPI Bus configuration + +#[derive(Clone)] +/// SPI Bus configuration +pub struct SpiConfig { + /// SPI Mode + pub mode: Mode, + /// Clock Divisor calculated from frozen core clock frequency and SPI frequency + pub(crate) clock_divisor: u32, + /// CS Mode + pub cs_mode: MODE_A, + /// Watermark level for transmits + pub txmark: u8, + /// Watermark level for received + pub rxmark: u8, + /// Configuration values for CS and SCK related delays + pub delays: SpiDelayConfig, +} + +#[derive(Clone)] +/// Configuration values for CS and SCK related delays +pub struct SpiDelayConfig { + /// delay between assert and clock in clock ticks + pub cssck: u8, + /// delay between clock and de-assert in clock ticks + pub sckcs: u8, + /// delay between CS re-assets in clock ticks + pub intercs: u8, + /// delay between frames when not re-asserting CS in clock ticks + pub interxfr: u8, +} + +impl SpiConfig { + /// Create new default configuration with given [Mode] and frequency using core [Clocks] + pub fn new(mode: Mode, freq: Hertz, clocks: &Clocks) -> Self { + let clock_divisor = clocks.tlclk().0 / (2 * freq.0) - 1; + assert!(clock_divisor <= 0xfff); + + Self { + mode, + clock_divisor, + cs_mode: MODE_A::Hold, + txmark: 1, + rxmark: 0, + delays: SpiDelayConfig::default(), + } + } + + /// Calculated clock divisor + pub fn clock_divisor(&self) -> u32 { + self.clock_divisor + } +} + +impl Default for SpiDelayConfig { + fn default() -> Self { + Self { + cssck: 1, // 1 cycle delay between CS assert and first clock + sckcs: 1, // 1 cycle delay between last clock and CS de-assert + intercs: 1, // 1 cycle delay between CS re-asserts + interxfr: 0, // no delay intra-frame when not CS re-asserting + } + } +} diff --git a/e310x-hal/src/spi/exclusive_device.rs b/e310x-hal/src/spi/exclusive_device.rs new file mode 100644 index 0000000..d9e808e --- /dev/null +++ b/e310x-hal/src/spi/exclusive_device.rs @@ -0,0 +1,120 @@ +use core::convert::Infallible; + +use embedded_hal::{ + blocking::spi::{Operation, Transactional, Transfer, Write, WriteIter}, + spi::FullDuplex, +}; + +use crate::spi::SpiConfig; + +use super::{Pins, SpiBus, SpiX}; + +/// SPI exclusive device abstraction +pub struct SpiExclusiveDevice { + bus: SpiBus, +} + +impl SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + /// Create [SpiExclusiveDevice] using the existing [SpiBus](super::SpiBus) + /// with the given [SpiConfig] + pub fn new(mut bus: SpiBus, config: &SpiConfig) -> Self + where + PINS: Pins, + { + bus.configure(config, PINS::CS_INDEX); + + Self { bus } + } + + /// Releases the Bus back deconstructing it + pub fn release(self) -> (SPI, PINS) { + self.bus.release() + } +} + +impl FullDuplex for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn read(&mut self) -> nb::Result { + self.bus.read() + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + self.bus.send(byte) + } +} + +impl Transfer for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.bus.start_frame(); + let result = self.bus.transfer(words); + self.bus.end_frame(); + + result + } +} + +impl Write for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.bus.start_frame(); + let result = self.bus.write(words); + self.bus.end_frame(); + + result + } +} + +impl WriteIter for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> + where + WI: IntoIterator, + { + self.bus.start_frame(); + let result = self.bus.write_iter(words); + self.bus.end_frame(); + + result + } +} + +impl Transactional for SpiExclusiveDevice +where + SPI: SpiX, + PINS: Pins, +{ + type Error = Infallible; + + fn exec<'op>(&mut self, operations: &mut [Operation<'op, u8>]) -> Result<(), Infallible> { + self.bus.start_frame(); + let result = self.bus.exec(operations); + self.bus.end_frame(); + + result + } +} diff --git a/e310x-hal/src/spi/shared_bus.rs b/e310x-hal/src/spi/shared_bus.rs new file mode 100644 index 0000000..6c75ebd --- /dev/null +++ b/e310x-hal/src/spi/shared_bus.rs @@ -0,0 +1,68 @@ +use core::cell::RefCell; +use core::ops::Deref; +use riscv::interrupt; + +use super::{PinCS, PinsNoCS, SpiBus, SpiConfig, SpiSharedDevice, SpiX}; + +/// Newtype for RefCell locked behind a Mutex. +/// Used to hold the [SpiBus] instance so it can be used for multiple [SpiSharedDevice] instances. +pub struct SharedBus(RefCell>); + +impl SharedBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + pub(crate) fn new(bus: SpiBus) -> Self { + Self(RefCell::new(bus)) + } + + /// Create a new shared device on this SPI bus. + pub fn new_device<'bus, CS>( + &'bus self, + cs: CS, + config: &SpiConfig, + ) -> SpiSharedDevice<'bus, SPI, PINS, CS> + where + CS: PinCS, + { + SpiSharedDevice::new(self, cs, config) + } +} + +impl SharedBus +where + SPI: SpiX, + PINS: PinsNoCS, +{ + /// Set HOLD CS mode to per-frame operation, unless CSMODE is set to OFF + pub fn start_frame(&mut self) { + interrupt::free(|| { + let mut bus = self.0.borrow_mut(); + bus.start_frame(); + }); + } + + /// Finishes transfer by deasserting CS (only for hardware-controlled CS) + pub fn end_frame(&mut self) { + interrupt::free(|| { + let mut bus = self.0.borrow_mut(); + bus.end_frame(); + }); + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SPI, PINS) { + let bus = self.0.into_inner(); + + (bus.spi, bus.pins) + } +} + +impl Deref for SharedBus { + type Target = RefCell>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/e310x-hal/src/spi/shared_device.rs b/e310x-hal/src/spi/shared_device.rs new file mode 100644 index 0000000..cbedea9 --- /dev/null +++ b/e310x-hal/src/spi/shared_device.rs @@ -0,0 +1,165 @@ +use core::convert::Infallible; + +use embedded_hal::{ + blocking::spi::{Operation, Transactional, Transfer, Write, WriteIter}, + spi::FullDuplex, +}; +use riscv::interrupt; + +use super::{PinCS, Pins, PinsNoCS, SharedBus, SpiConfig, SpiX}; + +/// SPI shared device abstraction +pub struct SpiSharedDevice<'bus, SPI, PINS, CS> { + bus: &'bus SharedBus, + cs: CS, + config: SpiConfig, +} + +impl<'bus, SPI, PINS, CS> SpiSharedDevice<'bus, SPI, PINS, CS> +where + SPI: SpiX, + PINS: PinsNoCS, + CS: PinCS, +{ + /// Create shared [SpiSharedDevice] using the existing [SharedBus] + /// and given [SpiConfig]. The config gets cloned. + pub fn new(bus: &'bus SharedBus, cs: CS, config: &SpiConfig) -> Self + where + PINS: PinsNoCS, + { + Self { + bus, + cs, + config: config.clone(), + } + } + + /// Releases the CS pin back + pub fn release(self) -> CS { + self.cs + } +} + +impl FullDuplex for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn read(&mut self) -> nb::Result { + interrupt::free(|| { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.read() + }) + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Infallible> { + interrupt::free(|| { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.send(byte) + }) + } +} + +impl Transfer for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + interrupt::free(move || { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.transfer(words); + bus.end_frame(); + + result + }) + } +} + +impl Write for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + interrupt::free(|| { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.write(words); + bus.end_frame(); + + result + }) + } +} + +impl WriteIter for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> + where + WI: IntoIterator, + { + interrupt::free(|| { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.write_iter(words); + bus.end_frame(); + + result + }) + } +} + +impl Transactional for SpiSharedDevice<'_, SPI, PINS, CS> +where + SPI: SpiX, + PINS: Pins, + CS: PinCS, +{ + type Error = Infallible; + + fn exec<'op>(&mut self, operations: &mut [Operation<'op, u8>]) -> Result<(), Infallible> { + interrupt::free(|| { + let mut bus = self.bus.borrow_mut(); + + bus.configure(&self.config, Some(CS::CS_INDEX)); + + bus.start_frame(); + let result = bus.exec(operations); + bus.end_frame(); + + result + }) + } +} diff --git a/e310x-hal/src/spi/traits.rs b/e310x-hal/src/spi/traits.rs new file mode 100644 index 0000000..a3b3dfc --- /dev/null +++ b/e310x-hal/src/spi/traits.rs @@ -0,0 +1,206 @@ +/// Helper traits for SPI pins +use core::ops::Deref; +use e310x::{qspi0, Qspi0, Qspi1, Qspi2}; + +#[doc(hidden)] +pub trait SpiX: Deref + private::Sealed {} +impl SpiX for Qspi0 {} +impl SpiX for Qspi1 {} +impl SpiX for Qspi2 {} + +/// SPI pins - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for pin tuples (), (MOSI, MISO, SCK) and (MOSI, MISO, SCK, CS) +/// and combinations without MOSI/MISO +pub trait Pins: private::Sealed { + #[doc(hidden)] + const CS_INDEX: Option; +} + +/// SPI pins without CS - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for pin tuples (), (MOSI, MISO, SCK) only without CS pin +/// and combinations without MOSI/MISO +pub trait PinsNoCS: Pins {} + +/// SPI Chip Select pin - DO NOT IMPLEMENT THIS TRAIT +/// +/// This trait is implemented for chip select pins only +pub trait PinCS: private::Sealed { + #[doc(hidden)] + const CS_INDEX: u32; +} + +/* SPI0 pins */ +impl Pins for () { + const CS_INDEX: Option = Some(0); +} + +/* SPI1 pins */ +mod spi1_impl { + use super::{PinCS, Pins, PinsNoCS, Qspi1}; + use crate::gpio::gpio0; + use crate::gpio::{NoInvert, IOF0}; + + type MOSI = gpio0::Pin3>; + type MISO = gpio0::Pin4>; + type SCK = gpio0::Pin5>; + type CS0 = gpio0::Pin2>; + type CS1 = gpio0::Pin8>; + type CS2 = gpio0::Pin9>; + type CS3 = gpio0::Pin10>; + + // ensure only the correct CS pins can be used to make SpiSharedDevice instances + impl PinCS for CS0 { + const CS_INDEX: u32 = 0; + } + impl PinCS for CS1 { + const CS_INDEX: u32 = 1; + } + impl PinCS for CS2 { + const CS_INDEX: u32 = 2; + } + impl PinCS for CS3 { + const CS_INDEX: u32 = 3; + } + + impl PinsNoCS for (MOSI, MISO, SCK) {} + impl PinsNoCS for (MOSI, (), SCK) {} + impl PinsNoCS for ((), MISO, SCK) {} + + impl Pins for (MOSI, MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, (), SCK) { + const CS_INDEX: Option = None; + } + impl Pins for ((), MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, (), SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for ((), MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, MISO, SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for (MOSI, (), SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for ((), MISO, SCK, CS1) { + const CS_INDEX: Option = Some(1); + } + impl Pins for (MOSI, MISO, SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for (MOSI, (), SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for ((), MISO, SCK, CS2) { + const CS_INDEX: Option = Some(2); + } + impl Pins for (MOSI, MISO, SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + impl Pins for (MOSI, (), SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + impl Pins for ((), MISO, SCK, CS3) { + const CS_INDEX: Option = Some(3); + } + + // seal the "private" traits + mod spi1_private { + use super::super::private::Sealed; + use super::*; + + impl Sealed for CS0 {} + impl Sealed for CS1 {} + impl Sealed for CS2 {} + impl Sealed for CS3 {} + impl Sealed for (MOSI, MISO, SCK) {} + impl Sealed for (MOSI, (), SCK) {} + impl Sealed for ((), MISO, SCK) {} + impl Sealed for (MOSI, MISO, SCK, CS0) {} + impl Sealed for (MOSI, (), SCK, CS0) {} + impl Sealed for ((), MISO, SCK, CS0) {} + impl Sealed for (MOSI, MISO, SCK, CS1) {} + impl Sealed for (MOSI, (), SCK, CS1) {} + impl Sealed for ((), MISO, SCK, CS1) {} + impl Sealed for (MOSI, MISO, SCK, CS2) {} + impl Sealed for (MOSI, (), SCK, CS2) {} + impl Sealed for ((), MISO, SCK, CS2) {} + impl Sealed for (MOSI, MISO, SCK, CS3) {} + impl Sealed for (MOSI, (), SCK, CS3) {} + impl Sealed for ((), MISO, SCK, CS3) {} + } +} + +/* SPI2 pins */ +mod spi2_impl { + use super::{PinCS, Pins, PinsNoCS, Qspi2}; + use crate::gpio::gpio0; + use crate::gpio::{NoInvert, IOF0}; + + type MOSI = gpio0::Pin27>; + type MISO = gpio0::Pin28>; + type SCK = gpio0::Pin29>; + type CS0 = gpio0::Pin26>; + + impl PinCS for CS0 { + const CS_INDEX: u32 = 0; + } + + impl PinsNoCS for (MOSI, MISO, SCK) {} + impl PinsNoCS for (MOSI, (), SCK) {} + impl PinsNoCS for ((), MISO, SCK) {} + + impl Pins for (MOSI, MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, (), SCK) { + const CS_INDEX: Option = None; + } + impl Pins for ((), MISO, SCK) { + const CS_INDEX: Option = None; + } + impl Pins for (MOSI, MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for (MOSI, (), SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + impl Pins for ((), MISO, SCK, CS0) { + const CS_INDEX: Option = Some(0); + } + + // seal the "private" traits + mod spi2_private { + use super::super::private::Sealed; + use super::*; + + impl Sealed for CS0 {} + impl Sealed for (MOSI, MISO, SCK) {} + impl Sealed for (MOSI, (), SCK) {} + impl Sealed for ((), MISO, SCK) {} + impl Sealed for (MOSI, MISO, SCK, CS0) {} + impl Sealed for (MOSI, (), SCK, CS0) {} + impl Sealed for ((), MISO, SCK, CS0) {} + } +} + +// seal the "private" traits +mod private { + pub trait Sealed {} + + impl Sealed for () {} + + impl Sealed for super::Qspi0 {} + impl Sealed for super::Qspi1 {} + impl Sealed for super::Qspi2 {} +} diff --git a/e310x-hal/src/stdout.rs b/e310x-hal/src/stdout.rs new file mode 100644 index 0000000..004bcc8 --- /dev/null +++ b/e310x-hal/src/stdout.rs @@ -0,0 +1,33 @@ +//! Stdout +pub use core::fmt::Write; +use nb::block; + +/// Stdout implements the core::fmt::Write trait for hal::serial::Write +/// implementations. +pub struct Stdout<'p, T>(pub &'p mut T) +where + T: 'p; + +impl<'p, T> Write for Stdout<'p, T> +where + T: embedded_hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + for byte in s.as_bytes() { + if *byte == b'\n' { + let res = block!(self.0.write(b'\r')); + + if res.is_err() { + return Err(::core::fmt::Error); + } + } + + let res = block!(self.0.write(*byte)); + + if res.is_err() { + return Err(::core::fmt::Error); + } + } + Ok(()) + } +} diff --git a/e310x-hal/src/time.rs b/e310x-hal/src/time.rs new file mode 100644 index 0000000..c21a86f --- /dev/null +++ b/e310x-hal/src/time.rs @@ -0,0 +1,68 @@ +//! Time units + +/// Bits per second +#[derive(Clone, Copy)] +pub struct Bps(pub u32); + +/// Hertz +#[derive(Clone, Copy)] +pub struct Hertz(pub u32); + +/// KiloHertz +#[derive(Clone, Copy)] +pub struct KiloHertz(pub u32); + +/// MegaHertz +#[derive(Clone, Copy)] +pub struct MegaHertz(pub u32); + +/// Extension trait that adds convenience methods to the `u32` type +pub trait U32Ext { + /// Wrap in `Bps` + fn bps(self) -> Bps; + + /// Wrap in `Hertz` + fn hz(self) -> Hertz; + + /// Wrap in `KiloHertz` + fn khz(self) -> KiloHertz; + + /// Wrap in `MegaHertz` + fn mhz(self) -> MegaHertz; +} + +impl U32Ext for u32 { + fn bps(self) -> Bps { + Bps(self) + } + + fn hz(self) -> Hertz { + Hertz(self) + } + + fn khz(self) -> KiloHertz { + KiloHertz(self) + } + + fn mhz(self) -> MegaHertz { + MegaHertz(self) + } +} + +impl Into for KiloHertz { + fn into(self) -> Hertz { + Hertz(self.0 * 1_000) + } +} + +impl Into for MegaHertz { + fn into(self) -> Hertz { + Hertz(self.0 * 1_000_000) + } +} + +impl Into for MegaHertz { + fn into(self) -> KiloHertz { + KiloHertz(self.0 * 1_000) + } +} diff --git a/e310x-hal/src/wdog.rs b/e310x-hal/src/wdog.rs new file mode 100644 index 0000000..6599b55 --- /dev/null +++ b/e310x-hal/src/wdog.rs @@ -0,0 +1,104 @@ +//! Watchdog +#![allow(missing_docs)] +use e310x::Wdog as WDOG; + +pub trait WdogExt { + fn configure(self) -> WdogCfg; +} + +impl WdogExt for WDOG { + fn configure(self) -> WdogCfg { + WdogCfg { + _0: (), + enable: false, + awake: false, + reset: false, + zero_cmp: false, + scale: 0, + } + } +} + +pub struct WdogCfg { + _0: (), + enable: bool, + awake: bool, + reset: bool, + zero_cmp: bool, + scale: u8, +} + +impl WdogCfg { + pub fn enable(mut self) -> Self { + self.enable = true; + self + } + + pub fn enable_awake(mut self) -> Self { + self.awake = true; + self + } + + pub fn enable_reset(mut self) -> Self { + self.reset = true; + self + } + + pub fn enable_zero_cmp(mut self) -> Self { + self.zero_cmp = true; + self + } + + pub fn scale(mut self, scale: u8) -> Self { + self.scale = scale; + self + } + + pub fn freeze(self) -> Wdog { + unsafe { + let wdog = WDOG::steal(); + wdog.wdogkey().write(|w| w.bits(0x51F15E)); + wdog.wdogcfg().write(|w| { + w.scale() + .bits(self.scale) + .rsten() + .bit(self.reset) + .zerocmp() + .bit(self.zero_cmp) + .enalways() + .bit(self.enable) + .encoreawake() + .bit(self.awake) + }); + } + Wdog { _0: () } + } +} + +pub struct Wdog { + _0: (), +} + +impl Wdog { + #[inline] + fn unlock(&mut self) { + unsafe { WDOG::steal().wdogkey().write(|w| w.bits(0x51F15E)) }; + } + + pub fn is_pending(&self) -> bool { + unsafe { WDOG::steal() }.wdogcfg().read().cmpip().bit() + } + + pub fn feed(&mut self) { + self.unlock(); + unsafe { WDOG::steal().wdogfeed().write(|w| w.bits(0xD09F00D)) }; + } + + pub fn cmp(&self) -> u16 { + unsafe { WDOG::steal() }.wdogcmp().read().value().bits() + } + + pub fn set_cmp(&mut self, value: u16) { + unsafe { WDOG::steal().wdogcmp().write(|w| w.value().bits(value)) }; + } +} diff --git a/e310x/CHANGELOG.md b/e310x/CHANGELOG.md index 4548bd1..cd31b6c 100644 --- a/e310x/CHANGELOG.md +++ b/e310x/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Changed -- Bump MSRV to 1.65.0 +- Bump MSRV to 1.72.0 to ensure a correct behavior of portable-atomic - Regenerate code with `svd2rust` v0.33.4 ## [v0.11.0] diff --git a/e310x/Cargo.toml b/e310x/Cargo.toml index 6a97ead..b73b28f 100644 --- a/e310x/Cargo.toml +++ b/e310x/Cargo.toml @@ -7,7 +7,7 @@ categories = ["embedded", "hardware-support", "no-std"] description = "With svd2rust generated peripherals for Freedom E310 MCU's." keywords = ["riscv", "register", "peripheral"] license = "ISC" -rust-version = "1.59" +rust-version = "1.72" edition = "2021" [dependencies] diff --git a/e310x/README.md b/e310x/README.md index e8a2a96..0411d7f 100644 --- a/e310x/README.md +++ b/e310x/README.md @@ -11,7 +11,7 @@ This project is developed and maintained by the [RISC-V team][team]. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.65.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.72.0 and up. It *might* compile with older versions but that may change in any new patch release. ## Requirements diff --git a/hifive1/CHANGELOG.md b/hifive1/CHANGELOG.md new file mode 100644 index 0000000..cc719d5 --- /dev/null +++ b/hifive1/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] +- Bump MSRV to 1.72 +- Adapt to new Cargo workspace +- Use inline assembly instead of binary blobs for flash + +## [v0.12.0] - 2023-03-28 +- Update e310x-hal to v0.11 with new svd2rust generated code + +## [v0.11.0] - 2023-03-03 + +### Changed +- Updated riscv dependency to v0.10 with interrupt/critical section changes + +## [v0.10.0] - 2021-07-15 + +### Added + +- Added [SparkFun Red-V RedBoard](https://www.sparkfun.com/products/15594)` support diff --git a/hifive1/Cargo.toml b/hifive1/Cargo.toml new file mode 100644 index 0000000..efa0546 --- /dev/null +++ b/hifive1/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "hifive1" +version = "0.13.0" +repository = "https://github.com/riscv-rust/hifive1" +authors = ["David Craven "] +categories = ["embedded", "hardware-support", "no-std"] +description = "Board support crate for HiFive1 and LoFive boards" +keywords = ["riscv", "register", "peripheral"] +license = "ISC" +edition = "2021" +rust-version = "1.72" + +[dependencies] +e310x-hal = { path = "../e310x-hal", version = "0.11.0" } +embedded-hal = "0.2.7" +riscv = "0.10.1" +nb = "1.0.0" + +[features] +board-hifive1 = [] +board-hifive1-revb = ["e310x-hal/g002"] +board-redv = ["e310x-hal/g002"] +board-lofive = [] +board-lofive-r1 = ["e310x-hal/g002"] +virq = ["e310x-hal/virq"] + +[package.metadata.docs.rs] +features = ['board-hifive1-revb'] diff --git a/hifive1/README.md b/hifive1/README.md new file mode 100644 index 0000000..a6454d3 --- /dev/null +++ b/hifive1/README.md @@ -0,0 +1,47 @@ +[![crates.io](https://img.shields.io/crates/d/hifive1.svg)](https://crates.io/crates/hifive1) +[![crates.io](https://img.shields.io/crates/v/hifive1.svg)](https://crates.io/crates/hifive1) +[![Build Status](https://travis-ci.org/riscv-rust/hifive1.svg?branch=master)](https://travis-ci.org/riscv-rust/hifive1) + +# `hifive1` + +> Board support crate for HiFive1 and LoFive boards + +## Supported Boards + +* [SiFive Hifive1](https://www.sifive.com/boards/hifive1) - use feature `board-hifive1` +* [SiFive Hifive1 RevB](https://www.sifive.com/boards/hifive1-rev-b) - use feature `board-hifive1-revb` +* [SparkFun Red-V RedBoard](https://www.sparkfun.com/products/15594) - use feature `board-redv` +* [lofive1](https://github.com/mwelling/lofive) - use feature `board-lofive` +* [lofive1-r1](https://github.com/mwelling/lofive) - use feature `board-lofive-r1` + +## [Documentation](https://docs.rs/crate/hifive1) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.72.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2018-2023 [RISC-V team][team] + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-risc-v-team diff --git a/hifive1/build.rs b/hifive1/build.rs new file mode 100644 index 0000000..c16de98 --- /dev/null +++ b/hifive1/build.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + // Put the memory definitions somewhere the linker can find it + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_dir.display()); + + let boards: Vec<_> = env::vars() + .filter_map(|(key, _value)| { + if key.starts_with("CARGO_FEATURE_BOARD") { + Some(key[20..].to_ascii_lowercase()) // Strip 'CARGO_FEATURE_BOARD_' + } else { + None + } + }) + .collect(); + + if boards.is_empty() { + panic!("No board features selected"); + } + if boards.len() > 1 { + panic!("More than one board feature selected: {:?}", boards); + } + + let board = boards.first().unwrap(); + + match board.as_str() { + "hifive1" => { + fs::copy("memory-hifive1.x", out_dir.join("hifive1-memory.x")).unwrap(); + println!("cargo:rerun-if-changed=memory-hifive1.x"); + } + "hifive1_revb" | "redv" => { + fs::copy("memory-hifive1-revb.x", out_dir.join("hifive1-memory.x")).unwrap(); + println!("cargo:rerun-if-changed=memory-hifive1-revb.x"); + } + "lofive" | "lofive_r1" => { + fs::copy("memory-lofive-r1.x", out_dir.join("hifive1-memory.x")).unwrap(); + println!("cargo:rerun-if-changed=memory-lofive-r1.x"); + } + + other => panic!("Unknown board: {}", other), + } + + fs::copy("hifive1-link.x", out_dir.join("hifive1-link.x")).unwrap(); + println!("cargo:rerun-if-changed=hifive1-link.x"); +} diff --git a/hifive1/hifive1-link.x b/hifive1/hifive1-link.x new file mode 100644 index 0000000..c472d9b --- /dev/null +++ b/hifive1/hifive1-link.x @@ -0,0 +1,2 @@ +INCLUDE hifive1-memory.x +INCLUDE link.x diff --git a/hifive1/memory-hifive1-revb.x b/hifive1/memory-hifive1-revb.x new file mode 100644 index 0000000..4239af3 --- /dev/null +++ b/hifive1/memory-hifive1-revb.x @@ -0,0 +1,15 @@ +INCLUDE device.x +MEMORY +{ + FLASH : ORIGIN = 0x20000000, LENGTH = 4M +} + +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +/* Skip first 64k allocated for bootloader */ +_stext = 0x20010000; diff --git a/hifive1/memory-hifive1.x b/hifive1/memory-hifive1.x new file mode 100644 index 0000000..e5dc86e --- /dev/null +++ b/hifive1/memory-hifive1.x @@ -0,0 +1,15 @@ +INCLUDE device.x +MEMORY +{ + FLASH : ORIGIN = 0x20000000, LENGTH = 16M +} + +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +/* Skip first 4M allocated for bootloader */ +_stext = 0x20400000; diff --git a/hifive1/memory-lofive-r1.x b/hifive1/memory-lofive-r1.x new file mode 100644 index 0000000..e5dc86e --- /dev/null +++ b/hifive1/memory-lofive-r1.x @@ -0,0 +1,15 @@ +INCLUDE device.x +MEMORY +{ + FLASH : ORIGIN = 0x20000000, LENGTH = 16M +} + +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +/* Skip first 4M allocated for bootloader */ +_stext = 0x20400000; diff --git a/hifive1/src/clock.rs b/hifive1/src/clock.rs new file mode 100644 index 0000000..d91867a --- /dev/null +++ b/hifive1/src/clock.rs @@ -0,0 +1,44 @@ +//! Board-specific clock configuration + +use e310x_hal::{ + clock::{AonExt, Clocks, PrciExt}, + e310x::{Aonclk, Prci}, + time::Hertz, +}; + +#[cfg(any( + feature = "board-hifive1", + feature = "board-hifive1-revb", + feature = "board-redv" +))] +/// Configures clock generation system. +/// +/// For HiFive1 and HiFive1 Rev B boards external oscillators are enabled for +/// both high-frequency and low-frequency clocks. +pub fn configure(prci: Prci, aonclk: Aonclk, target_coreclk: Hertz) -> Clocks { + let coreclk = prci.constrain(); + let coreclk = coreclk + .use_external(Hertz(16_000_000)) + .coreclk(target_coreclk); + + let aonclk = aonclk.constrain(); + let aonclk = aonclk.use_external(Hertz(32_768)); + + Clocks::freeze(coreclk, aonclk) +} + +#[cfg(any(feature = "board-lofive", feature = "board-lofive-r1"))] +/// Configures clock generation system. +/// +/// For the LoFive and LoFive R1 boards, external oscillator is enabled for +/// high-frequency clock. For low-frequency clock internal oscillator is used. +pub fn configure(prci: Prci, aonclk: Aonclk, target_coreclk: Hertz) -> Clocks { + let coreclk = prci.constrain(); + let coreclk = coreclk + .use_external(Hertz(16_000_000)) + .coreclk(target_coreclk); + + let aonclk = aonclk.constrain(); + + Clocks::freeze(coreclk, aonclk) +} diff --git a/hifive1/src/flash.rs b/hifive1/src/flash.rs new file mode 100644 index 0000000..71c2992 --- /dev/null +++ b/hifive1/src/flash.rs @@ -0,0 +1,71 @@ +//! On-board SPI Flash + +use e310x_hal::clock::Clocks; +use e310x_hal::e310x::Qspi0; + +#[cfg(target_arch = "riscv32")] +core::arch::global_asm!( + r#" + .cfi_sections .debug_frame + + .section .data._setup_is25lp + .global _setup_is25lp + .cfi_startproc + _setup_is25lp: + li a1, 0x10014000 // QSPI0 base address + + // Disable mapped region + sw zero,96(a1) // fctrl.en = 0 + + // Construct ffmt value for 4 dummy cycles + li a2, 0x00BB1447 + + beqz a0, 2f + + // We need to set 8 dummy cycles instead of 4. + // Issue a "Set Read Parameters" command. + + li a0,2 + sw a0,24(a1) // csmode = HOLD + li a0,0xC0 + sw a0,72(a1) // txdata = 0xC0 + li a0,0xF0 + sw a0,72(a1) // txdata = 0xF0 + sw zero,24(a1) // csmode = AUTO + + // Discard two response bytes + 1: lw a0,76(a1) + bltz a0,1b + 1: lw a0,76(a1) + bltz a0,1b + + addi a2,a2,0x40 // ffmt: 4 -> 8 dummy cycles + 2: + sw a2,100(a1) // Write ffmt + + // Enable mapped region + li a0, 1 + sw a0,96(a1) // fctrl.en = 1 + ret + + .cfi_endproc + .size _setup_is25lp, . - _setup_is25lp + "# +); + +/// Configure SPI Flash interface to maximum supported speed +#[inline(always)] +pub fn configure_spi_flash(qspi: &Qspi0, clocks: &Clocks) { + unsafe { + extern "C" { + fn _setup_is25lp(dummy8: bool); + } + + if clocks.coreclk().0 <= 208_000_000 { + _setup_is25lp(false) + } else { + _setup_is25lp(true) + } + } + qspi.sckdiv().modify(|_, w| unsafe { w.div().bits(0) }); +} diff --git a/hifive1/src/gpio.rs b/hifive1/src/gpio.rs new file mode 100644 index 0000000..abb2d74 --- /dev/null +++ b/hifive1/src/gpio.rs @@ -0,0 +1,281 @@ +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +/// +/// Returns single pin for given gpio object mapped accordingly +/// +/// # Mappings +/// +/// - `spi0_` — SPI pins where `` is one of (`sck`, `mosi`, `miso`, `ss0`, `ss2`, `ss3`) +/// - `i2c0_` — I2C pins where `` is one of (`sda`, `scl`) +/// - `uart0_` — UART pins where `` is one of (`tx`, `rx`) +/// - `dig#` — Digital/physical pins on the board where `#` is from range 0..19 +/// - `led_` - Internal LED light pins where `` is one of (`red`, `green`, `blue`) +/// +/// # Example +/// +/// ```ignore +/// let mosi = hifive1::pin!(gpio, spi0_mosi); // gpio.pin3 +/// ``` +/// +#[macro_export] +macro_rules! pin { + // empty + ($gpio:ident, none) => { + () + }; + // spi + ($gpio:ident, spi0_sck) => { + $gpio.pin5 + }; + ($gpio:ident, spi0_mosi) => { + $gpio.pin3 + }; + ($gpio:ident, spi0_miso) => { + $gpio.pin4 + }; + ($gpio:ident, spi0_ss0) => { + $gpio.pin2 + }; + // spi_ss1 is not documented + ($gpio:ident, spi0_ss2) => { + $gpio.pin9 + }; + ($gpio:ident, spi0_ss3) => { + $gpio.pin10 + }; + // i2c + ($gpio:ident, i2c0_sda) => { + $gpio.pin12 + }; + ($gpio:ident, i2c0_scl) => { + $gpio.pin13 + }; + // serial + ($gpio:ident, uart0_tx) => { + $gpio.pin17 + }; + ($gpio:ident, uart0_rx) => { + $gpio.pin16 + }; + // digital/physical + ($gpio:ident, dig0) => { + $gpio.pin16 + }; + ($gpio:ident, dig1) => { + $gpio.pin17 + }; + ($gpio:ident, dig2) => { + $gpio.pin18 + }; + ($gpio:ident, dig3) => { + $gpio.pin19 + }; + ($gpio:ident, dig4) => { + $gpio.pin20 + }; + ($gpio:ident, dig5) => { + $gpio.pin21 + }; + ($gpio:ident, dig6) => { + $gpio.pin22 + }; + ($gpio:ident, dig7) => { + $gpio.pin23 + }; + ($gpio:ident, dig8) => { + $gpio.pin0 + }; + ($gpio:ident, dig9) => { + $gpio.pin1 + }; + ($gpio:ident, dig10) => { + $gpio.pin2 + }; + ($gpio:ident, dig11) => { + $gpio.pin3 + }; + ($gpio:ident, dig12) => { + $gpio.pin4 + }; + ($gpio:ident, dig13) => { + $gpio.pin5 + }; + ($gpio:ident, dig14) => { + $gpio.pin8 + }; // tested + ($gpio:ident, dig15) => { + $gpio.pin9 + }; + ($gpio:ident, dig16) => { + $gpio.pin10 + }; + ($gpio:ident, dig17) => { + $gpio.pin11 + }; + ($gpio:ident, dig18) => { + $gpio.pin12 + }; + ($gpio:ident, dig19) => { + $gpio.pin13 + }; + // onboard LEDs + ($gpio:ident, led_red) => { + $gpio.pin22 + }; + ($gpio:ident, led_green) => { + $gpio.pin19 + }; + ($gpio:ident, led_blue) => { + $gpio.pin21 + }; +} + +#[cfg(feature = "board-redv")] +/// +/// Returns single pin for given gpio object mapped accordingly +/// +/// # Mappings +/// +/// - `spi0_` — SPI pins where `` is one of (`sck`, `mosi`, `miso`, `ss0`, `ss2`, `ss3`) +/// - `i2c0_` — I2C pins where `` is one of (`sda`, `scl`) +/// - `uart0_` — UART pins where `` is one of (`tx`, `rx`) +/// - `dig#` — Digital/physical pins on the board where `#` is from range 0..19 +/// - `led_` - Internal LED light pins where `` is one of (`red`, `green`, `blue`) +/// +/// # Example +/// +/// ```ignore +/// let mosi = hifive1::pin!(gpio, spi0_mosi); // gpio.pin3 +/// ``` +/// +#[macro_export] +macro_rules! pin { + // empty + ($gpio:ident, none) => { + () + }; + // spi + ($gpio:ident, spi0_sck) => { + $gpio.pin5 + }; + ($gpio:ident, spi0_mosi) => { + $gpio.pin3 + }; + ($gpio:ident, spi0_miso) => { + $gpio.pin4 + }; + ($gpio:ident, spi0_ss0) => { + $gpio.pin2 + }; + // spi_ss1 is not documented + ($gpio:ident, spi0_ss2) => { + $gpio.pin9 + }; + ($gpio:ident, spi0_ss3) => { + $gpio.pin10 + }; + // i2c + ($gpio:ident, i2c0_sda) => { + $gpio.pin12 + }; + ($gpio:ident, i2c0_scl) => { + $gpio.pin13 + }; + // serial + ($gpio:ident, uart0_tx) => { + $gpio.pin17 + }; + ($gpio:ident, uart0_rx) => { + $gpio.pin16 + }; + // digital/physical + ($gpio:ident, dig0) => { + $gpio.pin16 + }; + ($gpio:ident, dig1) => { + $gpio.pin17 + }; + ($gpio:ident, dig2) => { + $gpio.pin18 + }; + ($gpio:ident, dig3) => { + $gpio.pin19 + }; + ($gpio:ident, dig4) => { + $gpio.pin20 + }; + ($gpio:ident, dig5) => { + $gpio.pin21 + }; + ($gpio:ident, dig6) => { + $gpio.pin22 + }; + ($gpio:ident, dig7) => { + $gpio.pin23 + }; + ($gpio:ident, dig8) => { + $gpio.pin0 + }; + ($gpio:ident, dig9) => { + $gpio.pin1 + }; + ($gpio:ident, dig10) => { + $gpio.pin2 + }; + ($gpio:ident, dig11) => { + $gpio.pin3 + }; + ($gpio:ident, dig12) => { + $gpio.pin4 + }; + ($gpio:ident, dig13) => { + $gpio.pin5 + }; + ($gpio:ident, dig14) => { + $gpio.pin8 + }; // tested + ($gpio:ident, dig15) => { + $gpio.pin9 + }; + ($gpio:ident, dig16) => { + $gpio.pin10 + }; + ($gpio:ident, dig17) => { + $gpio.pin11 + }; + ($gpio:ident, dig18) => { + $gpio.pin12 + }; + ($gpio:ident, dig19) => { + $gpio.pin13 + }; + // onboard LEDs + ($gpio:ident, led_blue) => { + $gpio.pin5 + }; +} + +/// +/// Returns tuple of pins for given gpio object mapped accordingly +/// +/// # Mappings +/// +/// - `none` — Returns `()` for empty pin if needed in tuple +/// - `spi0_` — SPI pins where `` is one of (`sck`, `mosi`, `miso`, `ss0`, `ss2`, `ss3`) +/// - `i2c0_` — I2C pins where `` is one of (`sda`, `scl`) +/// - `uart0_` — UART pins where `` is one of (`tx`, `rx`) +/// - `dig#` — Digital/physical pins on the board where `#` is from range 0..19 +/// - `led_` - Internal LED light pins `` is one of (`red`, `green`, `blue`) +/// +/// # Example +/// +/// ```ignore +/// let (mosi, miso, sck, cs) = hifive1::pins!(gpio, (spi0_mosi, spi0_miso, spi0_sck, spi0_ss0)); +/// // (gpio.pin3, gpio.pin4, gpio.pin5, gpio.pin2) +/// ``` +/// +#[macro_export] +macro_rules! pins { + ( $gpio:ident, ($($name:ident),+) ) => { + ($($crate::pin!($gpio, $name)),+) + } +} diff --git a/hifive1/src/led.rs b/hifive1/src/led.rs new file mode 100644 index 0000000..5530d19 --- /dev/null +++ b/hifive1/src/led.rs @@ -0,0 +1,80 @@ +//! On-board user LEDs +//! +//! Hifive1 (+ revB) +//! - Red = Pin 22 +//! - Green = Pin 19 +//! - Blue = Pin 21 +//! +//! RedV +//! - Blue = Pin 5 + +#[cfg(feature = "board-redv")] +use e310x_hal::gpio::gpio0::Pin5; +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +use e310x_hal::gpio::gpio0::{Pin19, Pin21, Pin22}; +use e310x_hal::gpio::{Invert, Output, Regular}; +use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin}; + +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +/// Red LED +pub type RED = Pin22>>; + +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +/// Green LED +pub type GREEN = Pin19>>; + +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +/// Blue LED +pub type BLUE = Pin21>>; + +#[cfg(feature = "board-redv")] +/// Blue LED +pub type BLUE = Pin5>>; + +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +/// Returns RED, GREEN and BLUE LEDs. +pub fn rgb(red: Pin22, green: Pin19, blue: Pin21) -> (RED, GREEN, BLUE) { + let red: RED = red.into_inverted_output(); + let green: GREEN = green.into_inverted_output(); + let blue: BLUE = blue.into_inverted_output(); + (red, green, blue) +} + +/// Generic LED +pub trait Led { + /// Turns the LED off + fn off(&mut self); + + /// Turns the LED on + fn on(&mut self); + + /// Toggles the LED state + fn toggle(&mut self); +} + +/// Macro to implement the Led trait for each of the board LEDs +macro_rules! led_impl { + ($($LEDTYPE:ident),+) => { + $( + impl Led for $LEDTYPE { + fn off(&mut self) { + self.set_low().unwrap(); + } + + fn on(&mut self) { + self.set_high().unwrap(); + } + + fn toggle(&mut self) { + ToggleableOutputPin::toggle(self).unwrap(); + } + } + )+ + } +} + +/// Call the macro for each LED +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +led_impl!(RED, GREEN); + +led_impl!(BLUE); diff --git a/hifive1/src/lib.rs b/hifive1/src/lib.rs new file mode 100644 index 0000000..f05de91 --- /dev/null +++ b/hifive1/src/lib.rs @@ -0,0 +1,33 @@ +//! Board support crate for HiFive1 and LoFive boards + +#![deny(missing_docs)] +#![no_std] + +pub use e310x_hal as hal; + +pub mod clock; +pub use clock::configure as configure_clocks; + +pub mod flash; + +#[cfg(any( + feature = "board-hifive1", + feature = "board-hifive1-revb", + feature = "board-redv" +))] +pub mod led; +#[cfg(any(feature = "board-hifive1", feature = "board-hifive1-revb"))] +pub use led::{rgb, Led, BLUE, GREEN, RED}; +#[cfg(feature = "board-redv")] +pub use led::{Led, BLUE}; + +pub mod stdout; +pub use stdout::configure as configure_stdout; + +#[doc(hidden)] +#[cfg(any( + feature = "board-hifive1", + feature = "board-hifive1-revb", + feature = "board-redv" +))] +pub mod gpio; diff --git a/hifive1/src/stdout.rs b/hifive1/src/stdout.rs new file mode 100644 index 0000000..59be531 --- /dev/null +++ b/hifive1/src/stdout.rs @@ -0,0 +1,100 @@ +//! Stdout based on the UART hooked up to FTDI or J-Link + +use core::fmt; +use e310x_hal::{ + clock::Clocks, + e310x::Uart0, + gpio::gpio0::{Pin16, Pin17}, + prelude::*, + serial::{Rx, Serial, Tx}, + time::Bps, +}; +use nb::block; +use riscv::interrupt; + +static mut STDOUT: Option = None; + +struct SerialWrapper(Tx); + +impl core::fmt::Write for SerialWrapper { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.as_bytes() { + if *byte == b'\n' { + let res = block!(self.0.write(b'\r')); + + if res.is_err() { + return Err(::core::fmt::Error); + } + } + + let res = block!(self.0.write(*byte)); + + if res.is_err() { + return Err(::core::fmt::Error); + } + } + Ok(()) + } +} + +/// Configures stdout +pub fn configure( + uart: Uart0, + tx: Pin17, + rx: Pin16, + baud_rate: Bps, + clocks: Clocks, +) -> Rx { + let tx = tx.into_iof0(); + let rx = rx.into_iof0(); + let serial = Serial::new(uart, (tx, rx), baud_rate, clocks); + let (tx, rx) = serial.split(); + + interrupt::free(|| unsafe { + STDOUT.replace(SerialWrapper(tx)); + }); + rx +} + +/// Writes string to stdout +pub fn write_str(s: &str) { + interrupt::free(|| unsafe { + if let Some(stdout) = STDOUT.as_mut() { + let _ = stdout.write_str(s); + } + }) +} + +/// Writes formatted string to stdout +pub fn write_fmt(args: fmt::Arguments) { + interrupt::free(|| unsafe { + if let Some(stdout) = STDOUT.as_mut() { + let _ = stdout.write_fmt(args); + } + }) +} + +/// Macro for printing to the serial standard output +#[macro_export] +macro_rules! sprint { + ($s:expr) => { + $crate::stdout::write_str($s) + }; + ($($tt:tt)*) => { + $crate::stdout::write_fmt(format_args!($($tt)*)) + }; +} + +/// Macro for printing to the serial standard output, with a newline. +#[macro_export] +macro_rules! sprintln { + () => { + $crate::stdout::write_str("\n") + }; + ($s:expr) => { + $crate::stdout::write_str(concat!($s, "\n")) + }; + ($s:expr, $($tt:tt)*) => { + $crate::stdout::write_fmt(format_args!(concat!($s, "\n"), $($tt)*)) + }; +}