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/lib/spi | |
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/lib/spi')
-rw-r--r-- | mpm/lib/spi/CMakeLists.txt | 2 | ||||
-rw-r--r-- | mpm/lib/spi/spi_regs_iface.cpp | 100 | ||||
-rw-r--r-- | mpm/lib/spi/spidev.c | 116 | ||||
-rw-r--r-- | mpm/lib/spi/spidev.h | 56 | ||||
-rw-r--r-- | mpm/lib/spi/spidev_iface.cpp | 153 |
5 files changed, 319 insertions, 108 deletions
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 + ); } |