From 5d1cff57f9f5acd740a8b5f8c941beefdcc00176 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sun, 28 Jun 2020 16:42:21 +0200 Subject: sw: configure si5351 --- sw/deps/si5351/.gitignore | 3 + sw/deps/si5351/.travis.yml | 54 ++++ sw/deps/si5351/Cargo.toml | 19 ++ sw/deps/si5351/LICENSE-APACHE | 201 ++++++++++++++ sw/deps/si5351/LICENSE-MIT | 25 ++ sw/deps/si5351/README.md | 24 ++ sw/deps/si5351/ci/install.sh | 21 ++ sw/deps/si5351/ci/script.sh | 16 ++ sw/deps/si5351/src/lib.rs | 633 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 996 insertions(+) create mode 100644 sw/deps/si5351/.gitignore create mode 100644 sw/deps/si5351/.travis.yml create mode 100644 sw/deps/si5351/Cargo.toml create mode 100644 sw/deps/si5351/LICENSE-APACHE create mode 100644 sw/deps/si5351/LICENSE-MIT create mode 100644 sw/deps/si5351/README.md create mode 100644 sw/deps/si5351/ci/install.sh create mode 100644 sw/deps/si5351/ci/script.sh create mode 100644 sw/deps/si5351/src/lib.rs (limited to 'sw/deps/si5351') diff --git a/sw/deps/si5351/.gitignore b/sw/deps/si5351/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/sw/deps/si5351/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/sw/deps/si5351/.travis.yml b/sw/deps/si5351/.travis.yml new file mode 100644 index 0000000..2c773e0 --- /dev/null +++ b/sw/deps/si5351/.travis.yml @@ -0,0 +1,54 @@ +# Based on the "trust" template v0.1.2 +# https://github.com/japaric/trust/tree/v0.1.2 + +language: rust +services: docker + +matrix: + include: + # Linux + - env: TARGET=x86_64-unknown-linux-gnu + rust: nightly + + # Bare metal + - env: TARGET=thumbv6m-none-eabi + rust: nightly + - env: TARGET=thumbv7m-none-eabi + rust: nightly + - env: TARGET=thumbv7em-none-eabi + rust: nightly + - env: TARGET=thumbv7em-none-eabihf + rust: nightly + +before_install: + - set -e + - rustup self update + +install: + - sh ci/install.sh + - source ~/.cargo/env || true + +script: + - bash ci/script.sh + +after_script: set +e + +cache: + cargo: true + directories: + - $HOME/.xargo + +before_cache: + # Travis can't cache files that are not readable by "others" + - chmod -R a+r $HOME/.cargo + +branches: + only: + - /^v\d+\.\d+\.\d+.*$/ + - auto + - master + - try + +notifications: + email: +on_success: never diff --git a/sw/deps/si5351/Cargo.toml b/sw/deps/si5351/Cargo.toml new file mode 100644 index 0000000..147ae69 --- /dev/null +++ b/sw/deps/si5351/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "si5351" +version = "0.2.0" +keywords = ["embedded", "no-std", "embedded-hal-driver", "clock", "ham-radio"] +categories = ["embedded", "no-std"] +authors = ["Ilya Epifanov "] +description = "A platform agnostic driver for the Si5351 clock generator" +license = "MIT OR Apache-2.0" +exclude = [".travis.yml", ".gitignore"] +repository = "https://github.com/ilya-epifanov/si5351" +edition = "2018" + +[badges.travis-ci] +branch = "master" +repository = "jamwaffles/ssd1306" + +[dependencies] +embedded-hal = "0.2.2" +bitflags = "1.0" diff --git a/sw/deps/si5351/LICENSE-APACHE b/sw/deps/si5351/LICENSE-APACHE new file mode 100644 index 0000000..3a55053 --- /dev/null +++ b/sw/deps/si5351/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Ilya Epifanov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sw/deps/si5351/LICENSE-MIT b/sw/deps/si5351/LICENSE-MIT new file mode 100644 index 0000000..b8f5571 --- /dev/null +++ b/sw/deps/si5351/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Ilya Epifanov + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sw/deps/si5351/README.md b/sw/deps/si5351/README.md new file mode 100644 index 0000000..e40eb02 --- /dev/null +++ b/sw/deps/si5351/README.md @@ -0,0 +1,24 @@ +# si5351 + +[![docs](https://docs.rs/si5351/badge.svg)](https://docs.rs/si5351) +[![crates.io](https://img.shields.io/crates/v/si5351.svg)](https://crates.io/crates/si5351) +[![ci](https://travis-ci.org/ilya-epifanov/si5351.svg)](https://travis-ci.org/ilya-epifanov/si5351) + +## Documentation + +On [docs.rs](https://docs.rs/si5351) + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) + * MIT license ([LICENSE-MIT](LICENSE-MIT)) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/sw/deps/si5351/ci/install.sh b/sw/deps/si5351/ci/install.sh new file mode 100644 index 0000000..4892d1a --- /dev/null +++ b/sw/deps/si5351/ci/install.sh @@ -0,0 +1,21 @@ +set -ex + +main() { + # This fetches latest stable release of Xargo + local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/xargo \ + | cut -d/ -f3 \ + | grep -E '^v[0.1.0-9.]+$' \ + | sort --version-sort \ + | tail -n1) + + curl -LSfs https://japaric.github.io/trust/install.sh | \ + sh -s -- \ + --force \ + --git japaric/xargo \ + --tag $tag \ + --target x86_64-unknown-linux-musl + + rustup component add rust-src +} + +main diff --git a/sw/deps/si5351/ci/script.sh b/sw/deps/si5351/ci/script.sh new file mode 100644 index 0000000..f6bca6f --- /dev/null +++ b/sw/deps/si5351/ci/script.sh @@ -0,0 +1,16 @@ +# This script takes care of testing your crate + +set -euxo pipefail + +main() { + case $TARGET in + x86_64-unknown-linux-gnu) + cargo check --target $TARGET + ;; + *) + xargo check --target $TARGET + ;; + esac +} + +main diff --git a/sw/deps/si5351/src/lib.rs b/sw/deps/si5351/src/lib.rs new file mode 100644 index 0000000..a23d4f9 --- /dev/null +++ b/sw/deps/si5351/src/lib.rs @@ -0,0 +1,633 @@ +/* + Copyright 2018 Ilya Epifanov + + Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be + copied, modified, or distributed except according to those terms. +*/ +/*! +A platform agnostic Rust driver for the [Si5351], based on the +[`embedded-hal`] traits. + +## The Device + +The Silicon Labs [Si5351] is an any-frequency CMOS clock generator. + +The device has an I²C interface. + +## Usage + +Import this crate and an `embedded_hal` implementation: + +``` +extern crate stm32f103xx_hal as hal; +extern crate si5351; +``` + +Initialize I²C bus (differs between `embedded_hal` implementations): + +```no_run +# extern crate stm32f103xx_hal as hal; +use hal::i2c::I2c; +type I2C = ...; + +# fn main() { +let i2c: I2C = initialize_i2c(); +# } +``` + +Then instantiate the device: + +```no_run +# extern crate stm32f103xx_hal as hal; +# extern crate si5351; +use si5351; +use si5351::{Si5351, Si5351Device}; + +# fn main() { +let mut clock = Si5351Device::new(i2c, false, 25_000_000); +clock.init(si5351::CrystalLoad::_10)?; +# } +``` + +Or, if you have an [Adafruit module], you can use shortcut functions to initializate it: +```no_run +# extern crate stm32f103xx_hal as hal; +# extern crate si5351; +use si5351; +use si5351::{Si5351, Si5351Device}; + +# fn main() { +let mut clock = Si5351Device::new_adafruit_module(i2c); +clock.init_adafruit_module()?; +# } +``` + +And set frequency on one of the outputs: + +```no_run +use si5351; + +clock.set_frequency(si5351::PLL::A, si5351::ClockOutput::Clk0, 14_175_000)?; +``` + +[Si5351]: https://www.silabs.com/documents/public/data-sheets/Si5351-B.pdf +[`embedded-hal`]: https://github.com/japaric/embedded-hal +[Adafruit module]: https://www.adafruit.com/product/2045 +*/ +//#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] + +#[macro_use] +extern crate bitflags; +use embedded_hal as hal; + +use crate::hal::blocking::i2c::{Write, WriteRead}; + +#[derive(Debug)] +pub enum Error { + CommunicationError, + InvalidParameter, +} + +#[derive(Debug, Copy, Clone)] +pub enum CrystalLoad { + _6, + _8, + _10, +} + +#[derive(Debug, Copy, Clone)] +pub enum PLL { + A, + B, +} + +#[derive(Debug, Copy, Clone)] +pub enum FeedbackMultisynth { + MSNA, + MSNB, +} + +#[derive(Debug, Copy, Clone)] +pub enum Multisynth { + MS0, + MS1, + MS2, + MS3, + MS4, + MS5, +} + +#[derive(Debug, Copy, Clone)] +pub enum SimpleMultisynth { + MS6, + MS7, +} + +#[derive(Debug, Copy, Clone)] +pub enum ClockOutput { + Clk0 = 0, + Clk1, + Clk2, + Clk3, + Clk4, + Clk5, + Clk6, + Clk7, +} + +#[derive(Debug, Copy, Clone)] +pub enum OutputDivider { + Div1 = 0, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +const ADDRESS: u8 = 0b0110_0000; + +impl PLL { + pub fn multisynth(&self) -> FeedbackMultisynth { + match *self { + PLL::A => FeedbackMultisynth::MSNA, + PLL::B => FeedbackMultisynth::MSNB, + } + } +} + +trait FractionalMultisynth { + fn base_addr(&self) -> u8; + fn ix(&self) -> u8; +} + +impl FractionalMultisynth for FeedbackMultisynth { + fn base_addr(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 26, + FeedbackMultisynth::MSNB => 34, + } + } + fn ix(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 6, + FeedbackMultisynth::MSNB => 7, + } + } +} + +impl FractionalMultisynth for Multisynth { + fn base_addr(&self) -> u8 { + match *self { + Multisynth::MS0 => 42, + Multisynth::MS1 => 50, + Multisynth::MS2 => 58, + Multisynth::MS3 => 66, + Multisynth::MS4 => 74, + Multisynth::MS5 => 82, + } + } + fn ix(&self) -> u8 { + match *self { + Multisynth::MS0 => 0, + Multisynth::MS1 => 1, + Multisynth::MS2 => 2, + Multisynth::MS3 => 3, + Multisynth::MS4 => 4, + Multisynth::MS5 => 5, + } + } +} + +impl SimpleMultisynth { + pub fn base_addr(&self) -> u8 { + match *self { + SimpleMultisynth::MS6 => 90, + SimpleMultisynth::MS7 => 91, + } + } +} + +#[derive(Debug, Copy, Clone)] +enum Register { + DeviceStatus = 0, + OutputEnable = 3, + Clk0 = 16, + Clk1 = 17, + Clk2 = 18, + Clk3 = 19, + Clk4 = 20, + Clk5 = 21, + Clk6 = 22, + Clk7 = 23, + PLLReset = 177, + CrystalLoad = 183, +} + +impl Register { + pub fn addr(&self) -> u8 { + *self as u8 + } +} + +bitflags! { + pub struct DeviceStatusBits: u8 { + const SYS_INIT = 0b1000_0000; + const LOL_B = 0b0100_0000; + const LOL_A = 0b0010_0000; + const LOS = 0b0001_0000; + } +} + +bitflags! { + struct CrystalLoadBits: u8 { + const RESERVED = 0b00_010010; + const CL_MASK = 0b11_000000; + const CL_6 = 0b01_000000; + const CL_8 = 0b10_000000; + const CL_10 = 0b11_000000; + } +} + +bitflags! { + struct ClockControlBits: u8 { + const CLK_PDN = 0b1000_0000; + const MS_INT = 0b0100_0000; + const MS_SRC = 0b0010_0000; + const CLK_INV = 0b0001_0000; + const CLK_SRC_MASK = 0b0000_1100; + const CLK_SRC_XTAL = 0b0000_0000; + const CLK_SRC_CLKIN = 0b0000_0100; + const CLK_SRC_MS_ALT = 0b0000_1000; + const CLK_SRC_MS = 0b0000_1100; + const CLK_DRV_MASK = 0b0000_0011; + const CLK_DRV_2 = 0b0000_0000; + const CLK_DRV_4 = 0b0000_0001; + const CLK_DRV_6 = 0b0000_0010; + const CLK_DRV_8 = 0b0000_0011; + } +} + +bitflags! { + struct PLLResetBits: u8 { + const PLLB_RST = 0b1000_0000; + const PLLA_RST = 0b0010_0000; + } +} + +impl ClockOutput { + fn register(self) -> Register { + match self { + ClockOutput::Clk0 => Register::Clk0, + ClockOutput::Clk1 => Register::Clk1, + ClockOutput::Clk2 => Register::Clk2, + ClockOutput::Clk3 => Register::Clk3, + ClockOutput::Clk4 => Register::Clk4, + ClockOutput::Clk5 => Register::Clk5, + ClockOutput::Clk6 => Register::Clk6, + ClockOutput::Clk7 => Register::Clk7, + } + } + + fn ix(&self) -> u8 { + *self as u8 + } +} + +impl OutputDivider { + fn bits(&self) -> u8 { + *self as u8 + } + + fn min_divider(desired_divider: u16) -> Result { + match 16 - (desired_divider.max(1) - 1).leading_zeros() { + 0 => Ok(OutputDivider::Div1), + 1 => Ok(OutputDivider::Div2), + 2 => Ok(OutputDivider::Div4), + 3 => Ok(OutputDivider::Div8), + 4 => Ok(OutputDivider::Div16), + 5 => Ok(OutputDivider::Div32), + 6 => Ok(OutputDivider::Div64), + 7 => Ok(OutputDivider::Div128), + _ => Err(Error::InvalidParameter) + } + } + + fn denominator_u8(&self) -> u8 { + match *self { + OutputDivider::Div1 => 1, + OutputDivider::Div2 => 2, + OutputDivider::Div4 => 4, + OutputDivider::Div8 => 8, + OutputDivider::Div16 => 16, + OutputDivider::Div32 => 32, + OutputDivider::Div64 => 64, + OutputDivider::Div128 => 128, + } + } +} + +fn i2c_error(_: E) -> Error { + Error::CommunicationError +} + +/// Si5351 driver +pub struct Si5351Device { + i2c: I2C, + address: u8, + xtal_freq: u32, + clk_enabled_mask: u8, + ms_int_mode_mask: u8, + ms_src_mask: u8, +} + +pub trait Si5351 { + fn init_adafruit_module(&mut self) -> Result<(), Error>; + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error>; + fn read_device_status(&mut self) -> Result; + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error>; + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error>; + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error>; + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool); + + fn flush_output_enabled(&mut self) -> Result<(), Error>; + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error>; + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error>; + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error>; + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error>; + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error>; + fn select_clock_pll(&mut self, clocl: ClockOutput, pll: PLL); +} + +impl Si5351Device + where + I2C: WriteRead + Write, +{ + /// Creates a new driver from a I2C peripheral + pub fn new(i2c: I2C, address_bit: bool, xtal_freq: u32) -> Self { + let si5351 = Si5351Device { + i2c, + address: ADDRESS | if address_bit { 1 } else { 0 }, + xtal_freq, + clk_enabled_mask: 0, + ms_int_mode_mask: 0, + ms_src_mask: 0, + }; + + si5351 + } + + pub fn new_adafruit_module(i2c: I2C) -> Self { + Si5351Device::new(i2c, false, 25_000_000) + } + + fn write_ms_config(&mut self, ms: MS, int: u16, frac_num: u32, frac_denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if frac_denom == 0 { + return Err(Error::InvalidParameter); + } + if frac_num > 0xfffff { + return Err(Error::InvalidParameter); + } + if frac_denom > 0xfffff { + return Err(Error::InvalidParameter); + } + + let p1: u32; + let p2: u32; + let p3: u32; + + if frac_num == 0 { + p1 = 128 * int as u32 - 512; + p2 = 0; + p3 = 1; + } else { + let ratio = (128u64 * (frac_num as u64) / (frac_denom as u64)) as u32; + + p1 = 128 * int as u32 + ratio - 512; + p2 = 128 * frac_num - frac_denom * ratio; + p3 = frac_denom; + } + + self.write_synth_registers(ms, [ + ((p3 & 0x0000FF00) >> 8) as u8, + p3 as u8, + ((p1 & 0x00030000) >> 16) as u8 | r_div.bits(), + ((p1 & 0x0000FF00) >> 8) as u8, + p1 as u8, + (((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16)) as u8, + ((p2 & 0x0000FF00) >> 8) as u8, + p2 as u8, + ])?; + + if frac_num == 0 { + self.ms_int_mode_mask |= ms.ix(); + } else { + self.ms_int_mode_mask &= !ms.ix(); + } + + Ok(()) + } + + pub fn reset_pll(&mut self, pll: PLL) -> Result<(), Error> { + self.write_register(Register::PLLReset, match pll { + PLL::A => PLLResetBits::PLLA_RST.bits(), + PLL::B => PLLResetBits::PLLB_RST.bits(), + })?; + + Ok(()) + } + + fn read_register(&mut self, reg: Register) -> Result { + let mut buffer = [0u8; 1]; + self.i2c.write_read(self.address, &[reg.addr()], &mut buffer).map_err(i2c_error)?; + Ok(buffer[0]) + } + + fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), Error> { + self.i2c.write(self.address, &[reg.addr(), byte]).map_err(i2c_error) + } + + fn write_synth_registers(&mut self, ms: MS, params: [u8; 8]) -> Result<(), Error> { + self.i2c.write(self.address, &[ms.base_addr(), + params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7] + ]).map_err(i2c_error) + } +} + +impl Si5351 for Si5351Device where + I2C: WriteRead + Write +{ + fn init_adafruit_module(&mut self) -> Result<(), Error> { + self.init(CrystalLoad::_10) + } + + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error> { + loop { + let device_status = self.read_device_status()?; + if !device_status.contains(DeviceStatusBits::SYS_INIT) { + break; + } + } + + self.flush_output_enabled()?; + const CLK_REGS: [Register; 8] = [Register::Clk0, Register::Clk1, + Register::Clk2, Register::Clk3, Register::Clk4, + Register::Clk5, Register::Clk6, Register::Clk7]; + for ® in CLK_REGS.iter() { + self.write_register(reg, ClockControlBits::CLK_PDN.bits())?; + } + + self.write_register(Register::CrystalLoad, + (CrystalLoadBits::RESERVED | match xtal_load { + CrystalLoad::_6 => CrystalLoadBits::CL_6, + CrystalLoad::_8 => CrystalLoadBits::CL_8, + CrystalLoad::_10 => CrystalLoadBits::CL_10, + }).bits())?; + + Ok(()) + } + + fn read_device_status(&mut self) -> Result { + Ok(DeviceStatusBits::from_bits_truncate(self.read_register(Register::DeviceStatus)?)) + } + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error> { + let total_divider = (max_pll_freq / freq) as u16; + + let r_div = OutputDivider::min_divider(total_divider / 900)?; + + let ms_div = (total_divider / (2 * r_div.denominator_u8() as u16) * 2).max(6); + if ms_div > 1800 { + return Err(Error::InvalidParameter); + } + + Ok((ms_div, r_div)) + } + + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error> { + if denom == 0 || denom > 0xfffff { + return Err(Error::InvalidParameter); + } + + let pll_freq = freq * total_div; + + let mult = (pll_freq / self.xtal_freq) as u8; + let f = ((pll_freq % self.xtal_freq) as u64 * denom as u64 / self.xtal_freq as u64) as u32; + + Ok((mult, f)) + } + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error> { + let denom: u32 = 1048575; + + let (ms_divider, r_div) = self.find_int_dividers_for_max_pll_freq(900_000_000, freq)?; + let total_div = ms_divider as u32 * r_div.denominator_u8() as u32; + let (mult, num) = self.find_pll_coeffs_for_dividers(total_div, denom, freq)?; + + let ms = match clk { + ClockOutput::Clk0 => Multisynth::MS0, + ClockOutput::Clk1 => Multisynth::MS1, + ClockOutput::Clk2 => Multisynth::MS2, + ClockOutput::Clk3 => Multisynth::MS3, + ClockOutput::Clk4 => Multisynth::MS4, + ClockOutput::Clk5 => Multisynth::MS5, + _ => return Err(Error::InvalidParameter), + }; + + self.setup_pll(pll, mult, num, denom)?; + self.setup_multisynth_int(ms, ms_divider, r_div)?; + self.select_clock_pll(clk, pll); + self.set_clock_enabled(clk, true); + self.flush_clock_control(clk)?; + self.reset_pll(pll)?; + self.flush_output_enabled()?; + + Ok(()) + } + + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool) { + let bit = 1u8 << clk.ix(); + if enabled { + self.clk_enabled_mask |= bit; + } else { + self.clk_enabled_mask &= !bit; + } + } + + fn flush_output_enabled(&mut self) -> Result<(), Error> { + let mask = self.clk_enabled_mask; + self.write_register(Register::OutputEnable, !mask) + } + + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error> { + let bit = 1u8 << clk.ix(); + let clk_control_pdn = if self.clk_enabled_mask & bit != 0 { + ClockControlBits::empty() + } else { + ClockControlBits::CLK_PDN + }; + + let ms_int_mode = if self.ms_int_mode_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_INT + }; + + let ms_src = if self.ms_src_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_SRC + }; + + let base = ClockControlBits::CLK_SRC_MS | ClockControlBits::CLK_DRV_8; + + self.write_register(clk.register(), (clk_control_pdn | ms_int_mode | ms_src | base).bits()) + } + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error> { + self.setup_pll(pll, mult, 0, 1) + } + + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error> { + if mult < 15 || mult > 90 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(pll.multisynth(), mult.into(), num, denom, OutputDivider::Div1)?; + + if mult % 2 == 0 && num == 0 {} else {} + + Ok(()) + } + + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error> { + self.setup_multisynth(ms, mult, 0, 1, r_div) + } + + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if div < 6 || div > 1800 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(ms, div, num, denom, r_div)?; + + Ok(()) + } + + fn select_clock_pll(&mut self, clock: ClockOutput, pll: PLL) { + let bit = 1u8 << clock.ix(); + match pll { + PLL::A => self.ms_src_mask &= !bit, + PLL::B => self.ms_src_mask |= bit, + } + } +} -- cgit v1.2.3