ardupilot/libraries/AP_HAL_Linux/SPIDevice.cpp
Lucas De Marchi 61ef653181 AP_HAL_Linux: implement SPIDevice
The way this code is structured is a little bit different from the
SPIDriver implementation:

 - We only open the bus once, no matter how many devices we have in it

 - There's a single transfer() method which uses half-duplex mode
   instead of full duplex. The reason is that for all cases in the
   codebase we are using half-duplex transfers using the full-duplex
   API, i.e. a single SPI msg with both tx and rx buffers. This is
   cumbersome because the buffers need to be of the same size and the
   receive buffer using an offset of the same length as the actux data
   being written. This means the high level APIs need to copy buffers
   around.

   If later we have uses for a real full duplex case it's just a matter
   of adding another transfer_fullduplex() method or something like
   this.

 - The methods are implemented in the SPIDevice class instead of having
   proxy methods to SPIDeviceManager as is the case of SPIDriver

Also from now on we refer to the SPIDriver objects as "descriptors"
because they have the parameters of each device in the
SPIDeviceManager::devices[] table. When SPIDeviceDriver is completely
replaced we can rename them to SPIDeviceProperties.
2016-02-16 19:49:09 -02:00

267 lines
6.0 KiB
C++

/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
/*
* Copyright (C) 2015 Intel Corporation. All rights reserved.
*
* This file 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 file 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 "SPIDevice.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/spi/spidev.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include <AP_HAL/AP_HAL.h>
#include <AP_HAL/utility/OwnPtr.h>
#include "Util.h"
namespace Linux {
static const AP_HAL::HAL &hal = AP_HAL::get_HAL();
/* Private struct to maintain for each bus */
class SPIBus {
public:
~SPIBus()
{
if (fd >= 0) {
::close(fd);
}
}
int open(uint16_t bus_, uint16_t kernel_cs_)
{
char path[sizeof("/dev/spidevXXXXX.XXXXX")];
if (fd > 0) {
return -EBUSY;
}
snprintf(path, sizeof(path), "/dev/spidev%u.%u", bus_, kernel_cs_);
fd = ::open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
AP_HAL::panic("SPI: unable to open SPI bus %s: %s",
path, strerror(errno));
}
bus = bus_;
kernel_cs = kernel_cs_;
return fd;
}
Semaphore sem;
int fd = -1;
uint16_t bus;
uint16_t kernel_cs;
uint8_t ref;
};
SPIDevice::SPIDevice(SPIBus &bus, SPIDeviceDriver &device_desc)
: _bus(bus)
, _desc(device_desc)
{
_desc.init();
_cs_release();
}
SPIDevice::~SPIDevice()
{
// Unregister itself from the SPIDeviceManager
SPIDeviceManager::from(hal.spi)->_unregister(_bus);
}
bool SPIDevice::set_speed(AP_HAL::Device::Speed speed)
{
switch (speed) {
case AP_HAL::Device::SPEED_HIGH:
_desc._speed = _desc._highspeed;
break;
case AP_HAL::Device::SPEED_LOW:
_desc._speed = _desc._lowspeed;
break;
}
return true;
}
bool SPIDevice::transfer(const uint8_t *send, uint32_t send_len,
uint8_t *recv, uint32_t recv_len)
{
struct spi_ioc_transfer msgs[2] = { };
unsigned nmsgs = 0;
assert(_bus.fd >= 0);
if (send && send_len != 0) {
msgs[nmsgs].tx_buf = (uint64_t) send;
msgs[nmsgs].rx_buf = 0;
msgs[nmsgs].len = send_len;
msgs[nmsgs].speed_hz = _desc._speed;
msgs[nmsgs].delay_usecs = 0;
msgs[nmsgs].bits_per_word = _desc._bitsPerWord;
msgs[nmsgs].cs_change = 0;
nmsgs++;
}
if (recv && recv_len != 0) {
msgs[nmsgs].tx_buf = 0;
msgs[nmsgs].rx_buf = (uint64_t) recv;
msgs[nmsgs].len = recv_len;
msgs[nmsgs].speed_hz = _desc._speed;
msgs[nmsgs].delay_usecs = 0;
msgs[nmsgs].bits_per_word = _desc._bitsPerWord;
msgs[nmsgs].cs_change = 0;
nmsgs++;
}
if (!nmsgs) {
return false;
}
int r = ioctl(_bus.fd, SPI_IOC_WR_MODE, &_desc._mode);
if (r < 0) {
hal.console->printf("SPIDevice: error on setting mode fd=%d (%s)\n",
_bus.fd, strerror(errno));
return false;
}
_cs_assert();
r = ioctl(_bus.fd, SPI_IOC_MESSAGE(nmsgs), &msgs);
_cs_release();
if (r == -1) {
hal.console->printf("SPIDevice: error transferring data fd=%d (%s)\n",
_bus.fd, strerror(errno));
return false;
}
return true;
}
void SPIDevice::_cs_assert()
{
if (_desc._cs_pin == SPI_CS_KERNEL) {
return;
}
_desc._cs->write(0);
}
void SPIDevice::_cs_release()
{
if (_desc._cs_pin == SPI_CS_KERNEL) {
return;
}
_desc._cs->write(1);
}
AP_HAL::Semaphore *SPIDevice::get_semaphore()
{
return &_bus.sem;
}
int SPIDevice::get_fd()
{
return _bus.fd;
}
AP_HAL::OwnPtr<AP_HAL::SPIDevice>
SPIDeviceManager::get_device(const char *name)
{
SPIDeviceDriver *desc = nullptr;
/* Find the bus description in the table */
for (uint8_t i = 0; i < _n_device_desc; i++) {
if (!strcmp(_device[i]._name, name)) {
desc = &_device[i];
break;
}
}
if (!desc) {
AP_HAL::panic("SPI: invalid device name");
}
return get_device(*desc);
}
AP_HAL::OwnPtr<AP_HAL::SPIDevice>
SPIDeviceManager::get_device(SPIDeviceDriver &desc)
{
/* Find if bus is already open */
for (uint8_t i = 0, n = _buses.size(); i < n; i++) {
if (_buses[i]->bus == desc._bus &&
_buses[i]->kernel_cs == desc._subdev) {
return _create_device(*_buses[i], desc);
}
}
/* Bus not found for this device, create a new one */
AP_HAL::OwnPtr<SPIBus> b{new SPIBus()};
if (!b) {
return nullptr;
}
if (b->open(desc._bus, desc._subdev) < 0) {
return nullptr;
}
auto dev = _create_device(*b, desc);
if (!dev) {
return nullptr;
}
_buses.push_back(b.leak());
return dev;
}
/* Create a new device increasing the bus reference */
AP_HAL::OwnPtr<AP_HAL::SPIDevice>
SPIDeviceManager::_create_device(SPIBus &b, SPIDeviceDriver &desc) const
{
auto dev = AP_HAL::OwnPtr<AP_HAL::SPIDevice>(new SPIDevice(b, desc));
if (!dev) {
return nullptr;
}
b.ref++;
return dev;
}
void SPIDeviceManager::_unregister(SPIBus &b)
{
if (b.ref == 0 || --b.ref > 0) {
return;
}
for (auto it = _buses.begin(); it != _buses.end(); it++) {
if ((*it)->bus == b.bus && (*it)->kernel_cs == b.kernel_cs) {
_buses.erase(it);
delete &b;
break;
}
}
}
}