diff options
author | Martin Braun <martin.braun@ettus.com> | 2017-04-25 17:00:34 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:03:52 -0800 |
commit | 151ba5fb06dfdb6fcc46ccfdabf5f1e064236981 (patch) | |
tree | fa941b0589b09a22957e8b7e3966679748a9b202 /mpm | |
parent | 1262dfb3ccf5a9916685b3399587593174c6583e (diff) | |
download | uhd-151ba5fb06dfdb6fcc46ccfdabf5f1e064236981.tar.gz uhd-151ba5fb06dfdb6fcc46ccfdabf5f1e064236981.tar.bz2 uhd-151ba5fb06dfdb6fcc46ccfdabf5f1e064236981.zip |
mpm: Major refactoring
- Created clean interfaces for SPI and registers
- Severed most links to UHD
- Added a lockable class which allows exposing mutexes into Python
Diffstat (limited to 'mpm')
41 files changed, 1176 insertions, 479 deletions
diff --git a/mpm/CMakeLists.txt b/mpm/CMakeLists.txt index 65d01b114..b1e1efe2a 100644 --- a/mpm/CMakeLists.txt +++ b/mpm/CMakeLists.txt @@ -114,7 +114,6 @@ INCLUDE_DIRECTORIES( ${UHD_HOST_ROOT}/include ) -ADD_SUBDIRECTORY(dboards) ADD_SUBDIRECTORY(lib) MESSAGE("usrp_periphs objects: ${usrp_periphs_objects}") diff --git a/mpm/include/mpm/CMakeLists.txt b/mpm/include/mpm/CMakeLists.txt index a4ce92931..116899797 100644 --- a/mpm/include/mpm/CMakeLists.txt +++ b/mpm/include/mpm/CMakeLists.txt @@ -16,9 +16,12 @@ # INSTALL(FILES xbar_iface.hpp + exception.hpp DESTINATION ${INCLUDE_DIR}/mpm ) -ADD_SUBDIRECTORY(dboards) -ADD_SUBDIRECTORY(lmk04828) + ADD_SUBDIRECTORY(ad937x) +ADD_SUBDIRECTORY(chips) +ADD_SUBDIRECTORY(dboards) ADD_SUBDIRECTORY(spi) +ADD_SUBDIRECTORY(types) diff --git a/mpm/include/mpm/ad937x/ad937x_ctrl.hpp b/mpm/include/mpm/ad937x/ad937x_ctrl.hpp index 995f4c379..4d47a631f 100644 --- a/mpm/include/mpm/ad937x/ad937x_ctrl.hpp +++ b/mpm/include/mpm/ad937x/ad937x_ctrl.hpp @@ -19,17 +19,21 @@ #include "ad937x_ctrl_types.hpp" +#include <mpm/exception.hpp> +#include <mpm/spi/spi_iface.hpp> + #include <uhd/types/direction.hpp> #include <uhd/types/ranges.hpp> -#include <uhd/exception.hpp> -#include <uhd/types/serial.hpp> #include <boost/noncopyable.hpp> + #include <memory> #include <functional> #include <set> #include <mutex> +namespace mpm { namespace chips { + /*! AD937x Control Interface * * A sane API for configuring AD937x chips. @@ -58,7 +62,7 @@ public: */ static sptr make( std::shared_ptr<std::mutex> spi_mutex, - uhd::spi_iface::sptr iface, + mpm::types::regs_iface::sptr iface, mpm::ad937x::gpio::gain_pins_t gain_pins); virtual ~ad937x_ctrl(void) {} @@ -141,7 +145,8 @@ public: virtual double set_freq(const std::string &which, double value) = 0; /*! \brief get the RF frequency for the direction specified in which - /* Gets the RF frequency. This is a per direction setting. + * + * Returns the RF frequency. This is a per direction setting. * \param which frontend string to specify direction to get * \return actual frequency */ @@ -164,12 +169,13 @@ public: virtual void set_gain_pin_step_sizes(const std::string &which, double inc_step, double dec_step) = 0; }; +}}; /* namespace mpm::chips */ + #ifdef LIBMPM_PYTHON void export_mykonos(){ LIBMPM_BOOST_PREAMBLE("ad937x") - + using namespace mpm::chips; bp::class_<ad937x_ctrl, boost::noncopyable, std::shared_ptr<ad937x_ctrl> >("ad937x_ctrl", bp::no_init) - .def("make", &ad937x_ctrl::make) .def("begin_initialization", &ad937x_ctrl::begin_initialization) .def("finish_initialization", &ad937x_ctrl::finish_initialization) .def("start_jesd_rx", &ad937x_ctrl::start_jesd_rx) diff --git a/mpm/include/mpm/ad937x/ad937x_spi_iface.hpp b/mpm/include/mpm/ad937x/ad937x_spi_iface.hpp new file mode 100644 index 000000000..85617c8ef --- /dev/null +++ b/mpm/include/mpm/ad937x/ad937x_spi_iface.hpp @@ -0,0 +1,33 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <mpm/types/regs_iface.hpp> + +namespace mpm { namespace chips { + + /*! Return a peek/poke interface to the LMK04828 + * + * Assumption is it is attached to a spidev + */ + mpm::types::regs_iface::sptr make_ad937x_iface( + const std::string &spi_device + ); + +}}; /* namespace mpm::chips */ + diff --git a/mpm/include/mpm/ad937x/adi_ctrl.hpp b/mpm/include/mpm/ad937x/adi_ctrl.hpp index 34d9d891c..eef71c5bf 100644 --- a/mpm/include/mpm/ad937x/adi_ctrl.hpp +++ b/mpm/include/mpm/ad937x/adi_ctrl.hpp @@ -17,8 +17,7 @@ #pragma once -#include <uhd/types/serial.hpp> - +#include <mpm/types/regs_iface.hpp> #include <chrono> struct ad9371_spiSettings_t @@ -27,12 +26,12 @@ struct ad9371_spiSettings_t return reinterpret_cast<ad9371_spiSettings_t *>(sps); } - explicit ad9371_spiSettings_t(uhd::spi_iface* uhd_iface); + explicit ad9371_spiSettings_t(mpm::types::regs_iface*); // spiSetting_t MUST be the first data member so that the // reinterpret_cast in make() works spiSettings_t spi_settings; - uhd::spi_iface* spi_iface; + mpm::types::regs_iface* spi_iface; std::chrono::time_point<std::chrono::steady_clock> timeout_start; std::chrono::microseconds timeout_duration; }; diff --git a/mpm/include/mpm/lmk04828/CMakeLists.txt b/mpm/include/mpm/chips/CMakeLists.txt index 35df8d624..35df8d624 100644 --- a/mpm/include/mpm/lmk04828/CMakeLists.txt +++ b/mpm/include/mpm/chips/CMakeLists.txt diff --git a/mpm/include/mpm/chips/lmk04828_spi_iface.hpp b/mpm/include/mpm/chips/lmk04828_spi_iface.hpp new file mode 100644 index 000000000..ae897f02f --- /dev/null +++ b/mpm/include/mpm/chips/lmk04828_spi_iface.hpp @@ -0,0 +1,33 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <mpm/types/regs_iface.hpp> + +namespace mpm { namespace chips { + + /*! Return a peek/poke interface to the LMK04828 + * + * Assumption is it is attached to a spidev + */ + mpm::types::regs_iface::sptr make_lmk04828_iface( + const std::string &spi_device + ); + +}}; /* namespace mpm::chips */ + diff --git a/mpm/include/mpm/dboards/magnesium_manager.hpp b/mpm/include/mpm/dboards/magnesium_manager.hpp index 34c81292e..beb5a3cc2 100644 --- a/mpm/include/mpm/dboards/magnesium_manager.hpp +++ b/mpm/include/mpm/dboards/magnesium_manager.hpp @@ -17,43 +17,53 @@ #pragma once -#include "mpm/spi/spidev_iface.hpp" -#include "mpm/lmk04828/lmk04828_spi_iface.hpp" -#include "mpm/ad937x/ad937x_ctrl.hpp" +#include <mpm/types/lockable.hpp> +#include <mpm/types/regs_iface.hpp> +#include <mpm/ad937x/ad937x_ctrl.hpp> #include <memory> +#include <mutex> namespace mpm { namespace dboards { - class magnesium_periph_manager// : public dboard_periph_manager + class magnesium_manager// : public dboard_periph_manager { public: - magnesium_periph_manager(std::string lmk_spidev, std::string mykonos_spidev); + magnesium_manager( + const std::string &lmk_spidev, + const std::string &mykonos_spidev + ); - /*! Return a reference to the clock chip + /*! Return a reference to the SPI mutex */ - lmk04828_iface::sptr get_clock_ctrl(){return _clock_ctrl;}; + mpm::types::lockable::sptr get_spi_lock() { return _spi_lock; } - /*! Return a reference to the radio chip + /*! Return a reference to the clock chip controls */ - ad937x_ctrl::sptr get_radio_ctrl(){return _mykonos_ctrl;}; + mpm::types::regs_iface::sptr get_clock_ctrl(){ return _clock_ctrl; } + + /*! Return a reference to the radio chip controls + */ + mpm::chips::ad937x_ctrl::sptr get_radio_ctrl(){ return _mykonos_ctrl; } private: - //cpld control std::shared_ptr<std::mutex> _spi_mutex; - lmk04828_spi_iface::sptr _clock_spi; - lmk04828_iface::sptr _clock_ctrl; - mpm::spi::spidev_iface::sptr _mykonos_spi; - ad937x_ctrl::sptr _mykonos_ctrl; - }; -}}; + // TODO: cpld control + + mpm::types::lockable::sptr _spi_lock; + mpm::types::regs_iface::sptr _clock_ctrl; + mpm::chips::ad937x_ctrl::sptr _mykonos_ctrl; + }; +}}; /* namespace mpm::dboards */ #ifdef LIBMPM_PYTHON -void export_dboards(){ +void export_magnesium(){ LIBMPM_BOOST_PREAMBLE("dboards") - bp::class_<mpm::dboards::magnesium_periph_manager>("magnesium_periph_manager", bp::init<std::string, std::string>()) - .def("get_clock_ctrl", &mpm::dboards::magnesium_periph_manager::get_clock_ctrl) - .def("get_radio_ctrl", &mpm::dboards::magnesium_periph_manager::get_radio_ctrl) + using namespace mpm::dboards; + bp::class_<mpm::dboards::magnesium_manager>("magnesium_manager", bp::init<std::string, std::string>()) + .def("get_spi_lock", &mpm::dboards::magnesium_manager::get_spi_lock) + .def("get_clock_ctrl", &mpm::dboards::magnesium_manager::get_clock_ctrl) + .def("get_radio_ctrl", &mpm::dboards::magnesium_manager::get_radio_ctrl) ; } #endif diff --git a/mpm/include/mpm/exception.hpp b/mpm/include/mpm/exception.hpp new file mode 100644 index 000000000..3e06ae0d4 --- /dev/null +++ b/mpm/include/mpm/exception.hpp @@ -0,0 +1,152 @@ +// +// Copyright 2017 Ettus Research (National Instruments Corp.) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <stdexcept> +#include <string> + +namespace mpm { + + struct exception : std::runtime_error{ + exception(const std::string &what); + virtual unsigned code(void) const = 0; + virtual exception *dynamic_clone(void) const = 0; + virtual void dynamic_throw(void) const = 0; + }; + + struct assertion_error : exception{ + assertion_error(const std::string &what); + virtual unsigned code(void) const; + virtual assertion_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct lookup_error : exception{ + lookup_error(const std::string &what); + virtual unsigned code(void) const; + virtual lookup_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct index_error : lookup_error{ + index_error(const std::string &what); + virtual unsigned code(void) const; + virtual index_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct key_error : lookup_error{ + key_error(const std::string &what); + virtual unsigned code(void) const; + virtual key_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct type_error : exception{ + type_error(const std::string &what); + virtual unsigned code(void) const; + virtual type_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct value_error : exception{ + value_error(const std::string &what); + virtual unsigned code(void) const; + virtual value_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct runtime_error : exception{ + runtime_error(const std::string &what); + virtual unsigned code(void) const; + virtual runtime_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct not_implemented_error : runtime_error{ + not_implemented_error(const std::string &what); + virtual unsigned code(void) const; + virtual not_implemented_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct environment_error : exception{ + environment_error(const std::string &what); + virtual unsigned code(void) const; + virtual environment_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct io_error : environment_error{ + io_error(const std::string &what); + virtual unsigned code(void) const; + virtual io_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct os_error : environment_error{ + os_error(const std::string &what); + virtual unsigned code(void) const; + virtual os_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct system_error : exception{ + system_error(const std::string &what); + virtual unsigned code(void) const; + virtual system_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct syntax_error : exception{ + syntax_error(const std::string &what); + virtual unsigned code(void) const; + virtual syntax_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + /*! + * Create a formatted string with throw-site information. + * Fills in the function name, file name, and line number. + * \param what the std::exception message + * \return the formatted exception message + */ + #define MPM_THROW_SITE_INFO(what) std::string( \ + std::string(what) + "\n" + \ + " in " + std::string(__PRETTY_FUNCTION__) + "\n" + \ + " at " + std::string(__FILE__) + ":" + BOOST_STRINGIZE(__LINE__) + "\n" \ + ) + + /*! + * Throws an invalid code path exception with throw-site information. + * Use this macro in places that code execution is not supposed to go. + */ + #define MPM_THROW_INVALID_CODE_PATH() \ + throw mpm::system_error(MPM_THROW_SITE_INFO("invalid code path")) + + /*! + * Assert the result of the code evaluation. + * If the code evaluates to false, throw an assertion error. + * \param code the code that resolved to a boolean + */ + #define MPM_ASSERT_THROW(code) {if (not (code)) \ + throw mpm::assertion_error(MPM_THROW_SITE_INFO(#code)); \ + } + +} /* namespace mpm */ + diff --git a/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp b/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp deleted file mode 100644 index 4fe124298..000000000 --- a/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "lmk04828.hpp" -#include "uhd/types/serial.hpp" -#include <boost/shared_ptr.hpp> - -class lmk04828_spi_iface -{ -public: - using sptr = boost::shared_ptr<lmk04828_spi_iface>; - lmk04828_spi_iface(uhd::spi_iface::sptr iface); - lmk04828_iface::write_fn_t get_write_fn(); - lmk04828_iface::read_fn_t get_read_fn(); - static sptr make(uhd::spi_iface::sptr iface); - -private: - const int LMK_SPI_NUM_BITS = 24; - const int LMK_SPI_READ_FLAG = 1; - const int LMK_SPI_READ_FLAG_OFFSET = 23; - const int LMK_SPI_READ_ADDR_OFFSET = 8; - const int LMK_SPI_RESERVED_FIELD_MASK = ~(0x3 << 21); - const int DEFAULT_SLAVE = 1; - - uhd::spi_iface::sptr _spi_iface; - uhd::spi_config_t config; - - void spi_write(std::vector<uint32_t> writes); - uint8_t spi_read(uint32_t addr); -}; - -#ifdef LIBMPM_PYTHON -void export_lmk(){ - LIBMPM_BOOST_PREAMBLE("lmk04828") - bp::class_<lmk04828_iface, boost::shared_ptr<lmk04828_iface>, boost::noncopyable >("lmk04828_iface", bp::no_init) - .def("verify_chip_id", &lmk04828_iface::verify_chip_id) - .def("get_chip_id", &lmk04828_iface::get_chip_id) - .def("init", &lmk04828_iface::init) - .def("enable_sysref_pulse", &lmk04828_iface::enable_sysref_pulse) - ; -} -// .def("make", &lmk04828_iface::make) -#endif diff --git a/mpm/include/mpm/spi/CMakeLists.txt b/mpm/include/mpm/spi/CMakeLists.txt index 0833764ff..0413c3e04 100644 --- a/mpm/include/mpm/spi/CMakeLists.txt +++ b/mpm/include/mpm/spi/CMakeLists.txt @@ -16,6 +16,7 @@ # INSTALL(FILES - spidev_iface.hpp + spi_iface.hpp + spi_regs_iface.hpp DESTINATION ${INCLUDE_DIR}/mpm/spi ) diff --git a/mpm/include/mpm/spi/spi_iface.hpp b/mpm/include/mpm/spi/spi_iface.hpp new file mode 100644 index 000000000..8f8eaa2ec --- /dev/null +++ b/mpm/include/mpm/spi/spi_iface.hpp @@ -0,0 +1,53 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <memory> +#include <string> + +namespace mpm { namespace spi { + + /*! Implementation of a uhd::spi_iface that uses Linux' spidev underneath. + */ + class spi_iface + { + public: + using sptr = std::shared_ptr<spi_iface>; + + /*! Convenience function: SPI xfer is 24 bits write, 8 bits read. + * + * \param data The write data for this xfer + * + * \return 8 bits worth of the return xfer + */ + virtual uint32_t transfer24_8( + const uint32_t data + ) = 0; + + /*! + * \param device The path to the spidev used (e.g. "/dev/spidev0.0") + * \param speed_hz Transaction speed in Hz + */ + static sptr make_spidev( + const std::string &device, + const int speed_hz + ); + }; + +}}; /* namespace mpm */ + diff --git a/mpm/include/mpm/spi/spi_regs_iface.hpp b/mpm/include/mpm/spi/spi_regs_iface.hpp new file mode 100644 index 000000000..87f398db1 --- /dev/null +++ b/mpm/include/mpm/spi/spi_regs_iface.hpp @@ -0,0 +1,34 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <mpm/types/regs_iface.hpp> +#include <mpm/spi/spi_iface.hpp> + +namespace mpm { namespace spi { + + mpm::types::regs_iface::sptr make_spi_regs_iface( + mpm::spi::spi_iface::sptr spi_iface, + uint32_t addr_shift, + uint32_t data_shift, + uint32_t read_flags, + uint32_t write_flags = 0 + ); + +}}; /* namespace mpm::spi */ + diff --git a/mpm/include/mpm/spi/spidev_iface.hpp b/mpm/include/mpm/spi/spidev_iface.hpp deleted file mode 100644 index ef5303f7b..000000000 --- a/mpm/include/mpm/spi/spidev_iface.hpp +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright 2017 Ettus Research (National Instruments) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#pragma once - -#include "uhd/types/serial.hpp" -#include <boost/shared_ptr.hpp> - -namespace mpm { namespace spi { - - /*! Implementation of a uhd::spi_iface that uses Linux' spidev underneath. - */ - class spidev_iface : public uhd::spi_iface - { - public: - typedef boost::shared_ptr<spidev_iface> sptr; - virtual uint32_t read_spi( - int which_slave, - const uhd::spi_config_t &config, - uint32_t data, - size_t num_bits - ) = 0; - - virtual void write_spi( - int which_slave, - const uhd::spi_config_t &config, - uint32_t data, - size_t num_bits - ) = 0; - - virtual uint32_t transact_spi( - int /* which_slave */, - const uhd::spi_config_t & /* config */, - uint32_t data, - size_t num_bits, - bool readback - ) = 0; - /*! - * \param device The path to the spidev used. - */ - static sptr make(const std::string &device); - }; - -}}; /* namespace mpm */ - -//void export_spi(){ - //// Register submodule spi - //bp::object spi_module(bp::handle<>(bp::borrowed(PyImport_AddModule("libpyusrp_periphs.spi")))); - //bp::scope().attr("spi") = spi_module; - //bp::scope io_scope = spi_module; - - //bp::class_<spi_lock, boost::noncopyable, boost::shared_ptr<spi_lock> >("spi_lock", bp::no_init) - //.def("make", &spi_lock::make) - //.def("get_spidev", &spi_lock::get_spidev) - //; - - //bp::class_<mpm::spi_iface, boost::noncopyable>("spi_iface", bp::no_init) - //.def("write_byte", &mpm::spi_iface::write_byte) - //.def("write_bytes", &mpm::spi_iface::write_bytes) - //.def("read_byte", &mpm::spi_iface::read_byte) - //.def("write_field", &mpm::spi_iface::write_field) - //.def("read_field", &mpm::spi_iface::read_field) - //.def("get_wire_mode", &mpm::spi_iface::get_wire_mode) - //.def("get_endianness", &mpm::spi_iface::get_endianness) - //.def("get_chip_select", &mpm::spi_iface::get_chip_select) - //; - - //bp::enum_<mpm::spi_iface::spi_endianness_t>("spi_endianness") - //.value("lsb_first", mpm::spi_iface::spi_endianness_t::LSB_FIRST) - //.value("msb_first", mpm::spi_iface::spi_endianness_t::MSB_FIRST) - //; - - //bp::enum_<mpm::spi_iface::spi_wire_mode_t>("spi_wire_mode") - //.value("three_wire_mode", mpm::spi_iface::spi_wire_mode_t::THREE_WIRE_MODE) - //.value("four_wire_mode", mpm::spi_iface::spi_wire_mode_t::FOUR_WIRE_MODE) - //; -//} - diff --git a/mpm/include/mpm/types/CMakeLists.txt b/mpm/include/mpm/types/CMakeLists.txt new file mode 100644 index 000000000..a74a92519 --- /dev/null +++ b/mpm/include/mpm/types/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +INSTALL(FILES + regs_iface.hpp + DESTINATION ${INCLUDE_DIR}/mpm/types +) diff --git a/mpm/include/mpm/types/lockable.hpp b/mpm/include/mpm/types/lockable.hpp new file mode 100644 index 000000000..70e694be6 --- /dev/null +++ b/mpm/include/mpm/types/lockable.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <boost/noncopyable.hpp> +#include <memory> +#include <mutex> + +namespace mpm { namespace types { + + /*! A lockable object + * + * Don't tell anyone, but's really just a wrapper around a mutex. This + * class is primarily to make it easy to safely expose that mutex into + * Python. + */ + class lockable : public boost::noncopyable + { + public: + using sptr = std::shared_ptr<lockable>; + + /*! Lock the lock + */ + virtual void lock() = 0; + + /*! Unlock the lock + */ + virtual void unlock() = 0; + + static sptr make( + std::shared_ptr<std::mutex> spi_mutex + ); + }; + +}}; /* namespace mpm::types */ + diff --git a/mpm/include/mpm/types/regs_iface.hpp b/mpm/include/mpm/types/regs_iface.hpp new file mode 100644 index 000000000..78e590b23 --- /dev/null +++ b/mpm/include/mpm/types/regs_iface.hpp @@ -0,0 +1,47 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include <boost/noncopyable.hpp> +#include <memory> + +namespace mpm { namespace types { + + /*! Interface to a register reader/writer interface + */ + class regs_iface : public boost::noncopyable + { + public: + using sptr = std::shared_ptr<regs_iface>; + + /*! Return an 8-bit value from a given address + */ + virtual uint8_t peek8( + const uint32_t addr + ) = 0; + + /*! Write an 8-bit value to a given address + */ + virtual void poke8( + const uint32_t addr, + const uint8_t data + ) = 0; + }; + +}}; /* namespace mpm::regs */ + diff --git a/mpm/include/mpm/types/types_python.hpp b/mpm/include/mpm/types/types_python.hpp new file mode 100644 index 000000000..a0a00aa48 --- /dev/null +++ b/mpm/include/mpm/types/types_python.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#pragma once + +#include "lockable.hpp" +#include "regs_iface.hpp" + +void export_types() { + LIBMPM_BOOST_PREAMBLE("types") + using namespace mpm::types; + bp::class_<lockable, boost::noncopyable, std::shared_ptr<lockable> >("lockable", bp::no_init) + .def("lock", &lockable::lock) + .def("unlock", &lockable::unlock) + ; + + bp::class_<regs_iface, boost::noncopyable, std::shared_ptr<regs_iface> >("regs_iface", bp::no_init) + .def("peek8", ®s_iface::peek8) + .def("poke8", ®s_iface::poke8) + ; +} + diff --git a/mpm/lib/CMakeLists.txt b/mpm/lib/CMakeLists.txt index cfdde2d03..57dae3be2 100644 --- a/mpm/lib/CMakeLists.txt +++ b/mpm/lib/CMakeLists.txt @@ -15,15 +15,18 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -######################################################################## -# This file included, use CMake directory variables -######################################################################## +SET(UHD_HOST_ROOT ${CMAKE_SOURCE_DIR}/../host) - -ADD_SUBDIRECTORY(spi) +ADD_SUBDIRECTORY(dboards) +ADD_SUBDIRECTORY(chips) ADD_SUBDIRECTORY(mykonos) -ADD_SUBDIRECTORY(lmk04828) +ADD_SUBDIRECTORY(spi) +ADD_SUBDIRECTORY(types) USRP_PERIPHS_ADD_OBJECT(periphs - xbar_iface.cpp - ) + exception.cpp + xbar_iface.cpp + ${UHD_HOST_ROOT}/lib/exception.cpp +) + +# vim: set sw=4 et: diff --git a/mpm/lib/chips/CMakeLists.txt b/mpm/lib/chips/CMakeLists.txt new file mode 100644 index 000000000..519ab1fe8 --- /dev/null +++ b/mpm/lib/chips/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +#MACRO(ETTUS_PYTHON_GEN_SOURCE pyfile outfile) + ##ensure that the directory exists for outfile + #GET_FILENAME_COMPONENT(outfile_dir ${outfile} PATH) + #FILE(MAKE_DIRECTORY ${outfile_dir}) + #IF(NOT PYTHON_EXECUTABLE) + #MESSAGE( FATAL_ERROR "No python executable found to generate ic_regmaps!" ) + #ENDIF(NOT PYTHON_EXECUTABLE) + ##make the outfile depend on the python script + #ADD_CUSTOM_COMMAND( + #OUTPUT ${outfile} DEPENDS ${pyfile} ${ETTUS_PYTHON_GEN_SOURCE_DEPS} + #COMMAND ${PYTHON_EXECUTABLE} -B ${pyfile} ${outfile} + #COMMENT "Generating ${outfile}" + #) + + ##make lmk04828 depend on the outfile + #LIST(APPEND lmk04828_srcs ${ARGV}) +#ENDMACRO(ETTUS_PYTHON_GEN_SOURCE) + +#################################################### +# LMK04828 +#################################################### + +# Register definitions need to be generated +#SET(UHD_HOST_ROOT ${CMAKE_SOURCE_DIR}/../host) +#MESSAGE("uhd host root: ${UHD_HOST_ROOT}") +#SET(UHD_IC_REG_MAP_PATH ${UHD_HOST_ROOT}/lib/ic_reg_maps) + +#SET(ETTUS_PYTHON_GEN_SOURCE_DEPS ${UHD_IC_REG_MAP_PATH}/common.py) +#ETTUS_PYTHON_GEN_SOURCE( + #${UHD_IC_REG_MAP_PATH}/gen_lmk04828_regs.py + #${CMAKE_CURRENT_BINARY_DIR}/lmk04828_regs.hpp +#) +#SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) + +# Define the object +USRP_PERIPHS_ADD_OBJECT(chips + lmk04828_spi_iface.cpp +) diff --git a/mpm/lib/chips/lmk04828_spi_iface.cpp b/mpm/lib/chips/lmk04828_spi_iface.cpp new file mode 100644 index 000000000..6f21a0f46 --- /dev/null +++ b/mpm/lib/chips/lmk04828_spi_iface.cpp @@ -0,0 +1,40 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <mpm/chips/lmk04828_spi_iface.hpp> +#include <mpm/spi/spi_regs_iface.hpp> + +using namespace mpm::spi; + +static const int LMK_SPI_SPEED_HZ = 1000000; +static const size_t LMK_ADDR_SHIFT = 8; +static const size_t LMK_DATA_SHIFT = 0; +static const size_t LMK_READ_FLAG = 1 << 23; +static const size_t LMK_WRITE_FLAG = 0; + +mpm::types::regs_iface::sptr mpm::chips::make_lmk04828_iface( + const std::string &spi_device +) { + return make_spi_regs_iface( + spi_iface::make_spidev(spi_device, LMK_SPI_SPEED_HZ), + LMK_ADDR_SHIFT, + LMK_DATA_SHIFT, + LMK_READ_FLAG, + LMK_WRITE_FLAG + ); +} + diff --git a/mpm/dboards/CMakeLists.txt b/mpm/lib/dboards/CMakeLists.txt index 9947d8656..349be0b93 100644 --- a/mpm/dboards/CMakeLists.txt +++ b/mpm/lib/dboards/CMakeLists.txt @@ -22,6 +22,5 @@ USRP_PERIPHS_ADD_OBJECT(dboards magnesium_manager.cpp - ) +) -TARGET_INCLUDE_DIRECTORIES(dboards PUBLIC ${UHD_HOST_ROOT}/lib/usrp/common) diff --git a/mpm/dboards/magnesium_manager.cpp b/mpm/lib/dboards/magnesium_manager.cpp index adbac5918..f66364aec 100644 --- a/mpm/dboards/magnesium_manager.cpp +++ b/mpm/lib/dboards/magnesium_manager.cpp @@ -15,18 +15,25 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "mpm/dboards/magnesium_manager.hpp" -#include "mpm/spi/spidev_iface.hpp" -#include <boost/make_shared.hpp> +#include <mpm/dboards/magnesium_manager.hpp> +#include <mpm/chips/lmk04828_spi_iface.hpp> +#include <mpm/ad937x/ad937x_spi_iface.hpp> using namespace mpm::dboards; +using namespace mpm::chips; -magnesium_periph_manager::magnesium_periph_manager( - std::string lmk_spidev, std::string mykonos_spidev - ): _spi_mutex(std::make_shared<std::mutex>()) +magnesium_manager::magnesium_manager( + const std::string &lmk_spidev, + const std::string &mykonos_spidev +) : _spi_mutex(std::make_shared<std::mutex>()) + , _spi_lock(mpm::types::lockable::make(_spi_mutex)) + , _clock_ctrl(mpm::chips::make_lmk04828_iface(lmk_spidev)) + , _mykonos_ctrl(ad937x_ctrl::make( + _spi_mutex, + make_ad937x_iface(mykonos_spidev), + mpm::ad937x::gpio::gain_pins_t() + )) { - _clock_spi = lmk04828_spi_iface::make(mpm::spi::spidev_iface::make(lmk_spidev)); - _clock_ctrl = boost::make_shared<lmk04828_iface>(lmk04828_iface(_clock_spi->get_write_fn(), _clock_spi->get_read_fn())); - _mykonos_spi = mpm::spi::spidev_iface::make(mykonos_spidev); - _mykonos_ctrl = ad937x_ctrl::make(_spi_mutex, _mykonos_spi, mpm::ad937x::gpio::gain_pins_t()); -}; + +} + diff --git a/mpm/lib/exception.cpp b/mpm/lib/exception.cpp new file mode 100644 index 000000000..6a6677a71 --- /dev/null +++ b/mpm/lib/exception.cpp @@ -0,0 +1,46 @@ +// +// Copyright 2017 Ettus Research (National Instruments Corp.) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <mpm/exception.hpp> +#include <boost/format.hpp> +#include <functional> + +using namespace mpm; + +exception::exception(const std::string &what): + std::runtime_error(what){/* NOP */} + +#define make_exception_impl(name, class, base) \ + class::class(const std::string &what): \ + base(str(boost::format("%s: %s") % name % what)){} \ + unsigned class::code(void) const{return std::hash<std::string>()(#class) & 0xfff;} \ + class *class::dynamic_clone(void) const{return new class(*this);} \ + void class::dynamic_throw(void) const{throw *this;} + +make_exception_impl("AssertionError", assertion_error, exception) +make_exception_impl("LookupError", lookup_error, exception) +make_exception_impl("IndexError", index_error, lookup_error) +make_exception_impl("KeyError", key_error, lookup_error) +make_exception_impl("TypeError", type_error, exception) +make_exception_impl("ValueError", value_error, exception) +make_exception_impl("RuntimeError", runtime_error, exception) +make_exception_impl("NotImplementedError", not_implemented_error, runtime_error) +make_exception_impl("EnvironmentError", environment_error, exception) +make_exception_impl("IOError", io_error, environment_error) +make_exception_impl("OSError", os_error, environment_error) +make_exception_impl("SystemError", system_error, exception) + diff --git a/mpm/lib/lmk04828/CMakeLists.txt b/mpm/lib/lmk04828/CMakeLists.txt deleted file mode 100644 index b3621034a..000000000 --- a/mpm/lib/lmk04828/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -MACRO(ETTUS_PYTHON_GEN_SOURCE pyfile outfile) - #ensure that the directory exists for outfile - GET_FILENAME_COMPONENT(outfile_dir ${outfile} PATH) - FILE(MAKE_DIRECTORY ${outfile_dir}) - IF(NOT PYTHON_EXECUTABLE) - MESSAGE( FATAL_ERROR "No python executable found to generate ic_regmaps!" ) - ENDIF(NOT PYTHON_EXECUTABLE) - #make the outfile depend on the python script - ADD_CUSTOM_COMMAND( - OUTPUT ${outfile} DEPENDS ${pyfile} ${ETTUS_PYTHON_GEN_SOURCE_DEPS} - COMMAND ${PYTHON_EXECUTABLE} -B ${pyfile} ${outfile} - COMMENT "Generating ${outfile}" - ) - - #make lmk04828 depend on the outfile - LIST(APPEND lmk04828_srcs ${ARGV}) -ENDMACRO(ETTUS_PYTHON_GEN_SOURCE) - -#################################################### -# LMK04828 -#################################################### - -# Register definitions need to be generated -SET(UHD_HOST_ROOT ${CMAKE_SOURCE_DIR}/../host) -MESSAGE("uhd host root: ${UHD_HOST_ROOT}") -SET(UHD_IC_REG_MAP_PATH ${UHD_HOST_ROOT}/lib/ic_reg_maps) - -SET(ETTUS_PYTHON_GEN_SOURCE_DEPS ${UHD_IC_REG_MAP_PATH}/common.py) -ETTUS_PYTHON_GEN_SOURCE( - ${UHD_IC_REG_MAP_PATH}/gen_lmk04828_regs.py - ${CMAKE_CURRENT_BINARY_DIR}/lmk04828_regs.hpp -) -SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) - - -# LMK04828 driver files -LIST(APPEND lmk04828_srcs - ${UHD_HOST_ROOT}/lib/usrp/common/lmk04828.cpp - ${UHD_HOST_ROOT}/lib/types/serial.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lmk04828_spi_iface.cpp - ) - -# Extra files needed -LIST(APPEND lmk04828_srcs ${UHD_HOST_ROOT}/lib/exception.cpp) - -# For include/uhd/exception.hpp and include/uhd/config.h - -USRP_PERIPHS_ADD_OBJECT(lmk04828 ${lmk04828_srcs}) -TARGET_INCLUDE_DIRECTORIES(lmk04828 PUBLIC ${UHD_HOST_ROOT}/include ${CMAKE_CURRENT_BINARY_DIR} ${UHD_HOST_ROOT}/lib/usrp/common) diff --git a/mpm/lib/lmk04828/lmk04828_spi_iface.cpp b/mpm/lib/lmk04828/lmk04828_spi_iface.cpp deleted file mode 100644 index 1251f4535..000000000 --- a/mpm/lib/lmk04828/lmk04828_spi_iface.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include <mpm/lmk04828/lmk04828_spi_iface.hpp> -#include <uhd/exception.hpp> -#include <boost/make_shared.hpp> -#include <functional> - -lmk04828_spi_iface::lmk04828_spi_iface(uhd::spi_iface::sptr iface) : _spi_iface(iface) - { - // Use default SPI Config options - config = uhd::spi_config_t(uhd::spi_config_t::EDGE_RISE); - } - -lmk04828_iface::write_fn_t lmk04828_spi_iface::get_write_fn() - { - return std::bind(&lmk04828_spi_iface::spi_write, this, std::placeholders::_1); - } - -lmk04828_iface::read_fn_t lmk04828_spi_iface::get_read_fn() - { - return std::bind(&lmk04828_spi_iface::spi_read, this, std::placeholders::_1); - } - -void lmk04828_spi_iface::spi_write(std::vector<uint32_t> writes) { - for (uint32_t write : writes) { - _spi_iface->write_spi(DEFAULT_SLAVE, config, write, LMK_SPI_NUM_BITS); - } - } - -uint8_t lmk04828_spi_iface::spi_read(uint32_t addr) { - // Format LMK SPI read transaction - // r/w[23] 0[22:21] addr[20:8] data[7:0] = 24 bits - uint32_t transaction = 0; - transaction |= LMK_SPI_READ_FLAG << LMK_SPI_READ_FLAG_OFFSET; - //transaction &= LMK_SPI_RESERVED_FIELD_MASK; - transaction |= addr << LMK_SPI_READ_ADDR_OFFSET; - - uint32_t data = _spi_iface->read_spi(DEFAULT_SLAVE, config, transaction, LMK_SPI_NUM_BITS); - - if ((data & 0xFFFFFF00) != 0) { - // There's more than 8 bits of data! - throw uhd::runtime_error("LMK SPI read returned too much data"); - } - - return data & 0xFF; - } - -lmk04828_spi_iface::sptr lmk04828_spi_iface::make(uhd::spi_iface::sptr iface){ - return boost::make_shared<lmk04828_spi_iface>(iface); -} diff --git a/mpm/lib/mykonos/CMakeLists.txt b/mpm/lib/mykonos/CMakeLists.txt index f5e64064e..b79474cea 100644 --- a/mpm/lib/mykonos/CMakeLists.txt +++ b/mpm/lib/mykonos/CMakeLists.txt @@ -28,6 +28,7 @@ ENDMACRO(MYKONOS_APPEND_SOURCES) SET(mykonos_sources ${CMAKE_CURRENT_SOURCE_DIR}/ad937x_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad937x_device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ad937x_spi_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/adi_ctrl.cpp ${UHD_HOST_ROOT}/lib/types/ranges.cpp ) diff --git a/mpm/lib/mykonos/ad937x_ctrl.cpp b/mpm/lib/mykonos/ad937x_ctrl.cpp index d6360dad6..69ffeb6c2 100644 --- a/mpm/lib/mykonos/ad937x_ctrl.cpp +++ b/mpm/lib/mykonos/ad937x_ctrl.cpp @@ -18,12 +18,15 @@ #include "ad937x_device.hpp" #include "adi/mykonos.h" #include "mpm/ad937x/ad937x_ctrl.hpp" +#include <mpm/exception.hpp> #include <sstream> #include <set> #include <functional> #include <iostream> +#include <algorithm> +using namespace mpm::chips; using namespace mpm::ad937x::device; static uhd::direction_t _get_direction_from_antenna(const std::string& antenna) @@ -36,7 +39,7 @@ static uhd::direction_t _get_direction_from_antenna(const std::string& antenna) return uhd::direction_t::TX_DIRECTION; } else { - throw uhd::runtime_error("ad937x_ctrl got an invalid channel string."); + throw mpm::runtime_error("ad937x_ctrl got an invalid channel string."); } return uhd::direction_t::RX_DIRECTION; } @@ -51,7 +54,7 @@ static chain_t _get_chain_from_antenna(const std::string& antenna) return chain_t::TWO; } else { - throw uhd::runtime_error("ad937x_ctrl got an invalid channel string."); + throw mpm::runtime_error("ad937x_ctrl got an invalid channel string."); } return chain_t::ONE; } @@ -66,7 +69,7 @@ std::set<size_t> _get_valid_fir_lengths(const std::string& which) case uhd::direction_t::TX_DIRECTION: return{ 16, 32, 48, 64, 80, 96 }; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); return std::set<size_t>(); } } @@ -111,7 +114,7 @@ uhd::meta_range_t ad937x_ctrl::get_gain_range(const std::string &which) case uhd::direction_t::TX_DIRECTION: return uhd::meta_range_t(ad937x_device::MIN_TX_GAIN, ad937x_device::MAX_TX_GAIN, ad937x_device::TX_GAIN_STEP); default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); return uhd::meta_range_t(); } } @@ -121,7 +124,7 @@ class ad937x_ctrl_impl : public ad937x_ctrl public: ad937x_ctrl_impl( std::shared_ptr<std::mutex> spi_mutex, - uhd::spi_iface::sptr iface, + mpm::types::regs_iface::sptr iface, mpm::ad937x::gpio::gain_pins_t gain_pins) : spi_mutex(spi_mutex), device(iface.get(), gain_pins), @@ -251,7 +254,7 @@ public: auto dir = _get_direction_from_antenna(which); if (dir != uhd::direction_t::RX_DIRECTION) { - throw uhd::runtime_error("set_agc not valid for non-rx channels"); + throw mpm::runtime_error("set_agc not valid for non-rx channels"); } ad937x_device::gain_mode_t gain_mode; @@ -266,7 +269,7 @@ public: gain_mode = ad937x_device::gain_mode_t::HYBRID; } else { - throw uhd::runtime_error("invalid agc mode"); + throw mpm::runtime_error("invalid agc mode"); } std::lock_guard<std::mutex> lock(*spi_mutex); @@ -319,7 +322,7 @@ public: auto lengths = _get_valid_fir_lengths(which); if (std::find(lengths.begin(), lengths.end(), fir.size()) == lengths.end()) { - throw uhd::value_error("invalid filter length"); + throw mpm::value_error("invalid filter length"); } std::lock_guard<std::mutex> lock(*spi_mutex); @@ -370,7 +373,7 @@ public: // double comparison here should be okay because of clipping if (inc_step != dec_step) { - throw uhd::value_error("TX gain increment and decrement steps must be equal"); + throw mpm::value_error("TX gain increment and decrement steps must be equal"); } } @@ -381,11 +384,14 @@ public: private: ad937x_device device; std::shared_ptr<std::mutex> spi_mutex; - uhd::spi_iface::sptr _iface; + mpm::types::regs_iface::sptr _iface; }; -ad937x_ctrl::sptr ad937x_ctrl::make(std::shared_ptr<std::mutex> spi_mutex, uhd::spi_iface::sptr iface, mpm::ad937x::gpio::gain_pins_t gain_pins) -{ +ad937x_ctrl::sptr ad937x_ctrl::make( + std::shared_ptr<std::mutex> spi_mutex, + mpm::types::regs_iface::sptr iface, + mpm::ad937x::gpio::gain_pins_t gain_pins +) { return std::make_shared<ad937x_ctrl_impl>(spi_mutex, iface, gain_pins); } diff --git a/mpm/lib/mykonos/ad937x_device.cpp b/mpm/lib/mykonos/ad937x_device.cpp index 138e4e165..68065e67d 100644 --- a/mpm/lib/mykonos/ad937x_device.cpp +++ b/mpm/lib/mykonos/ad937x_device.cpp @@ -200,7 +200,7 @@ void ad937x_device::begin_initialization() uint8_t product_id = get_product_id(); if (product_id != AD9371_PRODUCT_ID) { - throw runtime_error(str( + throw mpm::runtime_error(str( boost::format("AD9371 product ID does not match expected ID! Read: %X Expected: %X") % int(product_id) % int(AD9371_PRODUCT_ID) )); @@ -208,7 +208,7 @@ void ad937x_device::begin_initialization() if (!get_pll_lock_status(pll_t::CLK_SYNTH)) { - throw runtime_error("AD937x CLK_SYNTH PLL failed to lock in initialize()"); + throw mpm::runtime_error("AD937x CLK_SYNTH PLL failed to lock in initialize()"); } uint8_t mcs_status = 0; @@ -223,7 +223,7 @@ void ad937x_device::finish_initialization() if ((mcs_status & 0x0A) != 0x0A) { - throw runtime_error("Multichip sync failed!"); + throw mpm::runtime_error("Multichip sync failed!"); } _call_api_function(std::bind(MYKONOS_initSubRegisterTables, mykonos_config.device)); @@ -289,18 +289,14 @@ void ad937x_device::enable_jesd_loopback(uint8_t enable) _call_api_function(std::bind(MYKONOS_setRxFramerDataSource, mykonos_config.device, enable)); } -ad937x_device::ad937x_device(spi_iface* iface, gain_pins_t gain_pins) : +ad937x_device::ad937x_device( + mpm::types::regs_iface* iface, + gain_pins_t gain_pins +) : full_spi_settings(iface), mykonos_config(&full_spi_settings.spi_settings), gain_ctrl(gain_pins) { - std::cout << "full spi settings addr " << &full_spi_settings << std::endl; - iface->read_spi(0, uhd::spi_config_t::EDGE_RISE, 400, 24); - std::cout << "adi spi settings addr " << &(full_spi_settings.spi_settings) << std::endl; - std::cout << "iface addr " << std::hex << iface << std::dec << std::endl; - - std::cout << "myk dev addr " << std::hex << mykonos_config.device->spiSettings << std::dec << std::endl; - } uint8_t ad937x_device::get_product_id() @@ -373,7 +369,7 @@ double ad937x_device::tune(direction_t direction, double value) mykonos_config.device->rx->rxPllLoFrequency_Hz = integer_value; break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } _call_api_function(std::bind(MYKONOS_setRfPllFrequency, mykonos_config.device, pll, integer_value)); @@ -393,7 +389,7 @@ double ad937x_device::get_freq(direction_t direction) case TX_DIRECTION: pll = TX_PLL; break; case RX_DIRECTION: pll = RX_PLL; break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } // TODO: coercion here causes extra device accesses, when the formula is provided on pg 119 of the user guide @@ -420,7 +416,7 @@ bool ad937x_device::get_pll_lock_status(pll_t pll) case pll_t::CALPLL_SDM: return (pll_status & 0x10) > 0; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); return false; } } @@ -470,7 +466,7 @@ double ad937x_device::set_gain(direction_t direction, chain_t chain, double valu func = MYKONOS_setTx2Attenuation; break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } _call_api_function(std::bind(func, mykonos_config.device, attenuation)); break; @@ -490,13 +486,13 @@ double ad937x_device::set_gain(direction_t direction, chain_t chain, double valu func = MYKONOS_setRx2ManualGain; break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } _call_api_function(std::bind(func, mykonos_config.device, gain)); break; } default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } return coerced_value; } @@ -518,10 +514,10 @@ void ad937x_device::set_agc_mode(direction_t direction, gain_mode_t mode) _call_api_function(std::bind(MYKONOS_setRxGainControlMode, mykonos_config.device, HYBRID)); break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } } @@ -540,7 +536,7 @@ void ad937x_device::set_fir( mykonos_config.rx_fir_config.set_fir(gain, fir); break; default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } } @@ -556,7 +552,7 @@ std::vector<int16_t> ad937x_device::get_fir( case RX_DIRECTION: return mykonos_config.rx_fir_config.get_fir(gain); default: - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } } @@ -585,7 +581,7 @@ void ad937x_device::set_gain_pin_step_sizes(direction_t direction, chain_t chain gain_ctrl.config.at(direction).at(chain).dec_step = static_cast<uint8_t>(inc_step / 0.05); gain_ctrl.config.at(direction).at(chain).inc_step = static_cast<uint8_t>(dec_step / 0.05); } else { - UHD_THROW_INVALID_CODE_PATH(); + MPM_THROW_INVALID_CODE_PATH(); } _apply_gain_pins(direction, chain); } @@ -600,7 +596,7 @@ void ad937x_device::_apply_gain_pins(direction_t direction, chain_t chain) // TX direction does not support different steps per direction if (direction == TX_DIRECTION) { - UHD_ASSERT_THROW(chan.inc_step == chan.dec_step); + MPM_ASSERT_THROW(chan.inc_step == chan.dec_step); } switch (direction) diff --git a/mpm/lib/mykonos/ad937x_device.hpp b/mpm/lib/mykonos/ad937x_device.hpp index 729912adf..17a09f249 100644 --- a/mpm/lib/mykonos/ad937x_device.hpp +++ b/mpm/lib/mykonos/ad937x_device.hpp @@ -27,8 +27,8 @@ #include "adi/t_mykonos_gpio.h" #include "adi/mykonos_debug/t_mykonos_dbgjesd.h" -#include <uhd/exception.hpp> - +#include <mpm/spi/spi_iface.hpp> +#include <mpm/exception.hpp> #include <boost/noncopyable.hpp> #include <memory> #include <functional> @@ -39,7 +39,10 @@ public: enum class gain_mode_t { MANUAL, AUTOMATIC, HYBRID }; enum class pll_t {CLK_SYNTH, RX_SYNTH, TX_SYNTH, SNIFF_SYNTH, CALPLL_SDM}; - ad937x_device(uhd::spi_iface* iface, mpm::ad937x::gpio::gain_pins_t gain_pins); + ad937x_device( + mpm::types::regs_iface* iface, + mpm::ad937x::gpio::gain_pins_t gain_pins + ); void begin_initialization(); void finish_initialization(); diff --git a/mpm/lib/mykonos/ad937x_spi_iface.cpp b/mpm/lib/mykonos/ad937x_spi_iface.cpp new file mode 100644 index 000000000..66ea95d67 --- /dev/null +++ b/mpm/lib/mykonos/ad937x_spi_iface.cpp @@ -0,0 +1,41 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <mpm/ad937x/ad937x_spi_iface.hpp> +#include <mpm/spi/spi_regs_iface.hpp> + +using namespace mpm::spi; + +static const int MYK_SPI_SPEED_HZ = 1000000; +static const size_t MYK_ADDR_SHIFT = 8; +static const size_t MYK_DATA_SHIFT = 0; +static const size_t MYK_READ_FLAG = 1 << 23; +static const size_t MYK_WRITE_FLAG = 0; + +mpm::types::regs_iface::sptr mpm::chips::make_ad937x_iface( + const std::string &spi_device +) { + return make_spi_regs_iface( + spi_iface::make_spidev(spi_device, MYK_SPI_SPEED_HZ), + MYK_ADDR_SHIFT, + MYK_DATA_SHIFT, + MYK_READ_FLAG, + MYK_WRITE_FLAG + ); +} + + diff --git a/mpm/lib/mykonos/adi_ctrl.cpp b/mpm/lib/mykonos/adi_ctrl.cpp index 9a5f73607..be3fa0ddb 100644 --- a/mpm/lib/mykonos/adi_ctrl.cpp +++ b/mpm/lib/mykonos/adi_ctrl.cpp @@ -24,10 +24,10 @@ #include <chrono> #include <thread> -static const uint32_t MYKONOS_READ_BIT = (1 << 23); - -ad9371_spiSettings_t::ad9371_spiSettings_t(uhd::spi_iface* uhd_iface) : - spi_iface(uhd_iface) +ad9371_spiSettings_t::ad9371_spiSettings_t( + mpm::types::regs_iface* spi_iface_ +) : + spi_iface(spi_iface_) { spi_settings.chipSelectIndex = 0; // set later spi_settings.writeBitPolarity = 1; // unused @@ -41,11 +41,6 @@ ad9371_spiSettings_t::ad9371_spiSettings_t(uhd::spi_iface* uhd_iface) : spi_settings.spiClkFreq_Hz = 250000000; // currently unused } -uhd::spi_config_t::edge_t _get_edge(const spiSettings_t & sps) -{ - return (sps.CPOL ^ sps.CPHA) ? uhd::spi_config_t::EDGE_FALL : uhd::spi_config_t::EDGE_RISE; -} - // TODO: change // not implemented to meaningful errors // close hardware pointers @@ -90,14 +85,14 @@ commonErr_t CMB_setSPIChannel(uint16_t chipSelectIndex) // single SPI byte write function commonErr_t CMB_SPIWriteByte(spiSettings_t *spiSettings, uint16_t addr, uint8_t data) { - // TODO: crash and burn for these errors? - if (spiSettings == nullptr || spiSettings->MSBFirst == 0) return COMMONERR_FAILED; + if (spiSettings == nullptr || spiSettings->MSBFirst == 0) { + // TODO: crash and burn for these errors? + return COMMONERR_FAILED; + } - ad9371_spiSettings_t *mpm_spi = ad9371_spiSettings_t::make(spiSettings); - uhd::spi_config_t config(_get_edge(*spiSettings)); - uint32_t data_word = (0) | (addr << 8) | (data); + ad9371_spiSettings_t *spi = ad9371_spiSettings_t::make(spiSettings); try { - mpm_spi->spi_iface->write_spi(spiSettings->chipSelectIndex, config, data_word, 24); + spi->spi_iface->poke8(addr, data); return COMMONERR_OK; } catch (const std::exception &e) { std::cout << "AAAAAAAAAAAAH" << std::endl; @@ -107,22 +102,22 @@ commonErr_t CMB_SPIWriteByte(spiSettings_t *spiSettings, uint16_t addr, uint8_t commonErr_t CMB_SPIWriteBytes(spiSettings_t *spiSettings, uint16_t *addr, uint8_t *data, uint32_t count) { - // TODO: crash and burn for these errors? if (spiSettings == nullptr || addr == nullptr || data == nullptr || spiSettings->MSBFirst == 0) { + // TODO: crash and burn for these errors? return COMMONERR_FAILED; } - ad9371_spiSettings_t *mpm_spi = ad9371_spiSettings_t::make(spiSettings); - uhd::spi_config_t config(_get_edge(*spiSettings)); + ad9371_spiSettings_t *spi = ad9371_spiSettings_t::make(spiSettings); try { for (size_t i = 0; i < count; ++i) { uint32_t data_word = (0) | (addr[i] << 8) | (data[i]); - mpm_spi->spi_iface->write_spi(spiSettings->chipSelectIndex, config, data_word, 24); + + spi->spi_iface->poke8(addr[i], data[i]); } return COMMONERR_OK; } catch (const std::exception &e) { @@ -141,13 +136,9 @@ commonErr_t CMB_SPIReadByte (spiSettings_t *spiSettings, uint16_t addr, uint8_t return COMMONERR_FAILED; } - ad9371_spiSettings_t *mpm_spi = ad9371_spiSettings_t::make(spiSettings); - uhd::spi_config_t config(_get_edge(*spiSettings)); - uint32_t read_word = MYKONOS_READ_BIT | (addr << 8); - + ad9371_spiSettings_t *spi = ad9371_spiSettings_t::make(spiSettings); try { - *readdata = static_cast<uint8_t>( - mpm_spi->spi_iface->read_spi(spiSettings->chipSelectIndex, config, read_word, 24)); + *readdata = spi->spi_iface->peek8(addr); return COMMONERR_OK; } catch (const std::exception &e) { std::cout << "AAAAAAAAAAAAH READ" << std::endl; @@ -162,15 +153,12 @@ commonErr_t CMB_SPIWriteField( uint16_t addr, uint8_t field_val, uint8_t mask, uint8_t start_bit ) { - ad9371_spiSettings_t *mpm_spi = ad9371_spiSettings_t::make(spiSettings); - uhd::spi_config_t config(_get_edge(*spiSettings)); - uint32_t read_word = (0) | (addr << 8); + ad9371_spiSettings_t *spi = ad9371_spiSettings_t::make(spiSettings); try { - uint32_t current_value = mpm_spi->spi_iface->read_spi(spiSettings->chipSelectIndex, config, read_word, 24); - uint8_t new_value = static_cast<uint8_t>((current_value & ~mask) | (field_val << start_bit)); - uint32_t write_word = (0) | (addr << 8) | new_value; - mpm_spi->spi_iface->write_spi(spiSettings->chipSelectIndex, config, write_word, 24); + uint8_t current_value = spi->spi_iface->peek8(addr); + uint8_t new_value = ((current_value & ~mask) | (field_val << start_bit)); + spi->spi_iface->poke8(addr, new_value); return COMMONERR_OK; } catch (const std::exception &e) { std::cout << "AAAAAAAAAAAAH WRITE FIELD" << std::endl; @@ -186,12 +174,10 @@ commonErr_t CMB_SPIReadField( uint16_t addr, uint8_t *field_val, uint8_t mask, uint8_t start_bit ) { - ad9371_spiSettings_t *mpm_spi = ad9371_spiSettings_t::make(spiSettings); - uhd::spi_config_t config(_get_edge(*spiSettings)); - uint32_t read_word = MYKONOS_READ_BIT | (addr << 8); + ad9371_spiSettings_t *spi = ad9371_spiSettings_t::make(spiSettings); try { - uint32_t value = mpm_spi->spi_iface->read_spi(spiSettings->chipSelectIndex, config, read_word, 24); + uint8_t value = spi->spi_iface->peek8(addr); *field_val = static_cast<uint8_t>((value & mask) >> start_bit); return COMMONERR_OK; } catch (const std::exception &e) { diff --git a/mpm/lib/spi/CMakeLists.txt b/mpm/lib/spi/CMakeLists.txt index 517e88561..d9c704a6a 100644 --- a/mpm/lib/spi/CMakeLists.txt +++ b/mpm/lib/spi/CMakeLists.txt @@ -1,5 +1,7 @@ SET(SPI_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/spidev_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spi_regs_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spidev.c ) USRP_PERIPHS_ADD_OBJECT(spi ${SPI_SOURCES}) diff --git a/mpm/lib/spi/spi_regs_iface.cpp b/mpm/lib/spi/spi_regs_iface.cpp new file mode 100644 index 000000000..eb6e229f9 --- /dev/null +++ b/mpm/lib/spi/spi_regs_iface.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <mpm/types/regs_iface.hpp> +#include <mpm/spi/spi_iface.hpp> +#include <mpm/spi/spi_regs_iface.hpp> +#include <mpm/exception.hpp> + +using mpm::types::regs_iface; + +/*! SPI implementation of the regs iface + * + * Uses spidev + */ +class spi_regs_iface_impl : public regs_iface +{ +public: + + spi_regs_iface_impl( + mpm::spi::spi_iface::sptr spi_iface, + uint32_t addr_shift, + uint32_t data_shift, + uint32_t read_flags, + uint32_t write_flags = 0 + ) : _spi_iface(spi_iface), + _addr_shift(addr_shift), + _data_shift(data_shift), + _read_flags(read_flags), + _write_flags(write_flags) + { + /* nop */ + } + + uint8_t peek8( + const uint32_t addr + ) { + uint32_t transaction = 0 + | (addr << _addr_shift) + | _read_flags + ; + + uint32_t data = _spi_iface->transfer24_8(transaction); + if ((data & 0xFFFFFF00) != 0) { + throw mpm::runtime_error("SPI read returned too much data"); + } + + return uint8_t(data & 0xFF); + } + + void poke8( + const uint32_t addr, + const uint8_t data + ) { + uint32_t transaction = 0 + | _write_flags + | (addr << _addr_shift) + | (data << _data_shift) + ; + + _spi_iface->transfer24_8(transaction); + } + +private: + mpm::spi::spi_iface::sptr _spi_iface; + + uint32_t _addr_shift; + uint32_t _data_shift; + uint32_t _read_flags; + uint32_t _write_flags; +}; + +regs_iface::sptr mpm::spi::make_spi_regs_iface( + mpm::spi::spi_iface::sptr spi_iface, + uint32_t addr_shift, + uint32_t data_shift, + uint32_t read_flags, + uint32_t write_flags +) { + return std::make_shared<spi_regs_iface_impl>( + spi_iface, + addr_shift, + data_shift, + read_flags, + write_flags + ); +} diff --git a/mpm/lib/spi/spidev.c b/mpm/lib/spi/spidev.c new file mode 100644 index 000000000..929b61aa9 --- /dev/null +++ b/mpm/lib/spi/spidev.c @@ -0,0 +1,116 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "spidev.h" +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/types.h> +#include <linux/spi/spidev.h> +#include <stdio.h> + +int init_spi(int *fd, const char *device, + const uint32_t mode, + const uint32_t speed_hz, + const uint8_t bits_per_word, + const uint16_t delay_us +) { + int err; + + *fd = open(device, O_RDWR); + if (*fd < 0) { + fprintf(stderr, "%s: Failed to open device\n", __func__); + return err; + } + + uint32_t requested_mode = mode; + err = ioctl(*fd, SPI_IOC_WR_MODE32, &mode); + if (err < 0) { + fprintf(stderr, "%s: Failed to set mode\n", __func__); + return err;; + } + + err = ioctl(*fd, SPI_IOC_RD_MODE32, &mode); + if (err < 0) { + fprintf(stderr, "%s: Failed to get mode\n", __func__); + return err; + } + if (requested_mode != mode) { + return 2; + } + + uint8_t requested_bits_per_word; + err = ioctl(*fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word); + if (err < 0) { + fprintf(stderr, "%s: Failed to set bits per word\n", __func__); + return err; + } + err = ioctl(*fd, SPI_IOC_RD_BITS_PER_WORD, &bits_per_word); + if (err) { + fprintf(stderr, "%s: Failed to get bits per word\n", __func__); + return err; + } + if (requested_bits_per_word != bits_per_word) { + return 2; + } + + uint32_t requested_speed_hz = speed_hz; + err = ioctl(*fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed_hz); + if (err < 0) { + fprintf(stderr, "%s: Failed to set speed\n", __func__); + return err; + } + err = ioctl(*fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed_hz); + if (err < 0) { + fprintf(stderr, "%s: Failed to get speed\n", __func__); + return err; + } + if (requested_speed_hz != speed_hz) { + return 2; + } + + return 0; +} + +int transfer( + int fd, + uint8_t *tx, uint8_t *rx, uint32_t len, + uint32_t speed_hz, uint8_t bits_per_word, uint16_t delay_us +) { + int err; + + struct spi_ioc_transfer tr = { + .tx_buf = (unsigned long) tx, + .rx_buf = (unsigned long) rx, + .len = len, + .speed_hz = speed_hz, + .delay_usecs = delay_us, + .bits_per_word = bits_per_word, + .cs_change = 0, + .tx_nbits = 1, // Standard SPI + .rx_nbits = 1 // Standard SPI + }; + + err = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + if (err < 0) { + fprintf(stderr, "%s: Failed ioctl: %d\n", __func__, err); + perror("ioctl: \n"); + return err; + } + + return 0; +} + diff --git a/mpm/lib/spi/spidev.h b/mpm/lib/spi/spidev.h new file mode 100644 index 000000000..30ea9ecc5 --- /dev/null +++ b/mpm/lib/spi/spidev.h @@ -0,0 +1,56 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <stdint.h> + +/*! Initialize a spidev interface + * + * \param fd Return value of the file descriptor + * \param device Path of spidev device, e.g. "/dev/spidev0.0" + * \param mode SPI mode. See linux/spi/spidev.h + * \param speed_hz The *maximum* SPI speed in Hz + * \param bits_per_word Just set it to 8. + * \param delay_us Delay between writes in microseconds + * + * \returns 0 if all is good, or an error code otherwise + */ +int init_spi(int *fd, const char *device, + const uint32_t mode, + const uint32_t speed_hz, + const uint8_t bits_per_word, + const uint16_t delay_us +); + +/*! Do a SPI transaction over spidev + * + * \param tx Buffer of data to be written + * \param rx Must match tx buffer length; result will be written here + * \param len Total number of bytes in this transaction + * \param speed_hz Speed of this transaction in Hz + * \param bits_per_word 8, dude + * \param delay_us Delay between transfers + * + * Assumption: spidev was configured properly beforehand. + * + * \returns 0 if all is golden + */ +int transfer( + int fd, + uint8_t *tx, uint8_t *rx, uint32_t len, + uint32_t speed_hz, uint8_t bits_per_word, uint16_t delay_us +); + diff --git a/mpm/lib/spi/spidev_iface.cpp b/mpm/lib/spi/spidev_iface.cpp index 919cf338e..fe37f16d5 100644 --- a/mpm/lib/spi/spidev_iface.cpp +++ b/mpm/lib/spi/spidev_iface.cpp @@ -15,14 +15,15 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "mpm/spi/spidev_iface.hpp" -#include <fcntl.h> -#include <sys/ioctl.h> -#include <linux/types.h> +#include <mpm/spi/spi_iface.hpp> +#include <mpm/exception.hpp> +extern "C" { +#include "spidev.h" +} #include <linux/spi/spidev.h> -#include <boost/format.hpp> +#include <boost/format.hpp> #include <iostream> using namespace mpm::spi; @@ -30,68 +31,29 @@ using namespace mpm::spi; /****************************************************************************** * Implementation *****************************************************************************/ -class spidev_iface_impl : public spidev_iface +class spidev_iface_impl : public spi_iface { public: spidev_iface_impl( - const std::string &device - ) { - int ret; - - _fd = open(device.c_str(), O_RDWR); - if (_fd < 0) { - throw std::runtime_error(str( - boost::format("Could not open spidev device %s") - % device - )); - } - - int MODE = 3; - ret = ioctl(_fd, SPI_IOC_WR_MODE32, &MODE); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not set spidev mode to %X for spidev %s") - % uint16_t(_mode) % device - )); - } - - ret = ioctl(_fd, SPI_IOC_RD_MODE32, &_mode); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not get spidev mode for spidev %s") - % device - )); - } - - ret = ioctl(_fd, SPI_IOC_WR_BITS_PER_WORD, &_bits); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not set spidev bits per word to %d for spidev %s") - % uint16_t(_bits) % device - )); - } + const std::string &device, + const int max_speed_hz + ) : _speed(max_speed_hz) + { - ret = ioctl(_fd, SPI_IOC_RD_BITS_PER_WORD, &_bits); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not get spidev bits per word for spidev %s") + if (!init_spi( + &_fd, + device.c_str(), + _mode, _speed, _bits, _delay + )) { + throw mpm::runtime_error(str( + boost::format("Could not initialize spidev device %s") % device )); } - - ret = ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &_speed); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not set spidev max speed to %d for spidev %s") - % _speed % device - )); - } - - ret = ioctl(_fd, SPI_IOC_RD_MAX_SPEED_HZ, &_speed); - if (ret == -1) { - throw std::runtime_error(str( - boost::format("Could not get spidev max speed for spidev %s") + if (_fd < 0) { + throw mpm::runtime_error(str( + boost::format("Could not open spidev device %s") % device )); } @@ -102,60 +64,32 @@ public: close(_fd); } - uint32_t transact_spi( - int /* which_slave */, - const uhd::spi_config_t & /* config */, - uint32_t data, - size_t num_bits, - bool readback + uint32_t transfer24_8( + const uint32_t data_ ) { int ret(0); + uint32_t data = data_; uint8_t *tx_data = reinterpret_cast<uint8_t *>(&data); - assert(num_bits == 24); - uint8_t tx[] = {tx_data[2], tx_data[1], tx_data[0]}; - + // Create tx and rx buffers: + uint8_t tx[] = {tx_data[2], tx_data[1], tx_data[0]}; // FIXME guarantee endianness uint8_t rx[3]; // Buffer length must match tx buffer - struct spi_ioc_transfer tr; - tr.tx_buf = (unsigned long) &tx[0]; - tr.rx_buf = (unsigned long) &rx[0]; - tr.len = num_bits >> 3; - tr.speed_hz = _speed; - tr.delay_usecs = _delay; - tr.bits_per_word = _bits; - tr.cs_change = 0; - tr.tx_nbits = 1; // Standard SPI - tr.rx_nbits = 1; // Standard SPI - - ret = ioctl(_fd, SPI_IOC_MESSAGE(1), &tr); - if (ret < 1) - throw std::runtime_error("Could not send spidev message"); - - return rx[2]; // Assumes that only a single byte is being read. - } - - uint32_t read_spi( - int which_slave, - const uhd::spi_config_t &config, - uint32_t data, - size_t num_bits - ) { - return transact_spi( - which_slave, config, data, num_bits, true - ); - } + if (transfer( + _fd, + &tx[0], &rx[0], + 3, + _speed, _bits, _delay + ) != 0) { + throw mpm::runtime_error(str( + boost::format("SPI Transaction failed!") + )); + } - void write_spi( - int which_slave, - const uhd::spi_config_t &config, - uint32_t data, - size_t num_bits - ) { - transact_spi( - which_slave, config, data, num_bits, false - ); + // Assumes that only a single byte is being read. + // TODO the function does not advertise this. Should probably fix. + return uint32_t(rx[2]); } private: @@ -169,9 +103,12 @@ private: /****************************************************************************** * Factory *****************************************************************************/ -spidev_iface::sptr spidev_iface::make( - const std::string &device +spi_iface::sptr spi_iface::make_spidev( + const std::string &device, + const int speed_hz ) { - return sptr(new spidev_iface_impl(device)); + return std::make_shared<spidev_iface_impl>( + device, speed_hz + ); } diff --git a/mpm/lib/types/CMakeLists.txt b/mpm/lib/types/CMakeLists.txt new file mode 100644 index 000000000..d4f52f7ed --- /dev/null +++ b/mpm/lib/types/CMakeLists.txt @@ -0,0 +1,5 @@ +SET(TYPES_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/lockable.cpp +) + +USRP_PERIPHS_ADD_OBJECT(types ${TYPES_SOURCES}) diff --git a/mpm/lib/types/lockable.cpp b/mpm/lib/types/lockable.cpp new file mode 100644 index 000000000..75c3aa784 --- /dev/null +++ b/mpm/lib/types/lockable.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2017 Ettus Research (National Instruments) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <mpm/types/lockable.hpp> + +using namespace mpm::types; + +class lockable_impl : public lockable +{ +public: + lockable_impl( + std::shared_ptr<std::mutex> spi_mutex + ) : _spi_mutex(spi_mutex) + { + /* nop */ + } + + void lock() + { + _spi_mutex->lock(); + } + + void unlock() + { + _spi_mutex->unlock(); + } + +private: + std::shared_ptr<std::mutex> _spi_mutex; +}; + +lockable::sptr lockable::make( + std::shared_ptr<std::mutex> spi_mutex +) { + return std::make_shared<lockable_impl>( + spi_mutex + ); +} + diff --git a/mpm/lib/xbar_iface.cpp b/mpm/lib/xbar_iface.cpp index 8bd89533c..4a86d9ba6 100644 --- a/mpm/lib/xbar_iface.cpp +++ b/mpm/lib/xbar_iface.cpp @@ -16,7 +16,7 @@ // #include "mpm/xbar_iface.hpp" -#include <uhd/exception.hpp> +#include <mpm/exception.hpp> #include <boost/format.hpp> #include <sys/ioctl.h> #include <fcntl.h> @@ -38,7 +38,7 @@ void xbar_iface::set_route(uint8_t dst_addr, uint8_t dst_port) { rfnoc_crossbar_cmd cmd = {.dest_addr = dst_addr, .dest_port = dst_port}; int err = ioctl(_fd, RFNCBWROUTIOC, &cmd); if (err < 0) { - throw uhd::os_error(str(boost::format("setting crossbar route failed! Error: %d") % err)); + throw mpm::os_error(str(boost::format("setting crossbar route failed! Error: %d") % err)); } } @@ -47,7 +47,7 @@ void xbar_iface::del_route(uint8_t dst_addr, uint8_t dst_port){ rfnoc_crossbar_cmd cmd = {.dest_addr = dst_addr, .dest_port = dst_port}; int err = ioctl(_fd, RFNCDELROUTIOC, &cmd); if (err < 0){ - throw uhd::os_error(str(boost::format("deleting crossbar route failed! Error: %d") % err)); + throw mpm::os_error(str(boost::format("deleting crossbar route failed! Error: %d") % err)); } } diff --git a/mpm/python/pyusrp_periphs.cpp b/mpm/python/pyusrp_periphs.cpp index cb370d874..2c6ea869b 100644 --- a/mpm/python/pyusrp_periphs.cpp +++ b/mpm/python/pyusrp_periphs.cpp @@ -27,12 +27,11 @@ //#include "types.hpp" #include "converters.hpp" -#include "mpm/xbar_iface.hpp" +#include <mpm/xbar_iface.hpp> +#include <mpm/types/types_python.hpp> #include <mpm/ad937x/ad937x_ctrl.hpp> -#include <mpm/lmk04828//lmk04828_spi_iface.hpp> -#include "mpm/dboards/magnesium_manager.hpp" -//#include "lib_periphs.hpp" -//#include "dboards.hpp" +#include <mpm/chips/lmk04828_spi_iface.hpp> +#include <mpm/dboards/magnesium_manager.hpp> #include <boost/noncopyable.hpp> namespace bp = boost::python; @@ -42,10 +41,9 @@ BOOST_PYTHON_MODULE(libpyusrp_periphs) bp::object package = bp::scope(); package.attr("__path__") = "libpyusrp_periphs"; export_converter(); - //export_types(); + export_types(); //export_spi(); - export_lmk(); export_mykonos(); export_xbar(); - export_dboards(); + export_magnesium(); } |