Files
NANOKVM-MaixCDK-MIRROR/components/peripheral/port/linux_common/maix_spi.cpp
2026-03-13 19:25:17 +08:00

303 lines
9.4 KiB
C++
Executable File

/**
* @author iawak9lkm@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#include "maix_spi.hpp"
#include "maix_log.hpp"
#include "cstdio"
#include <linux/types.h>
#include <linux/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/spi/spidev.h>
#include <errno.h>
#include "maix_spi_port.hpp"
namespace maix::peripheral::spi
{
static inline void __close_fd(int* fd)
{
::close(*fd);
*fd = -1;
}
static inline std::string __get_errno_msg(void)
{
int errsv = errno;
char buffer[64];
::strerror_r(errsv, buffer, sizeof(buffer));
return std::string(buffer);
}
static inline err::Exception __ioctl_error(int* fd)
{
__close_fd(fd);
return err::Exception(err::ERR_RUNTIME, __get_errno_msg());
}
static inline int __spi_transfer(int fd, unsigned char used_soft_cs,
unsigned int speed_hz, unsigned char bits,
const uint8_t *txbuf, uint8_t *rxbuf, size_t len) {
struct spi_ioc_transfer transfer;
::memset(&transfer, 0, sizeof(struct spi_ioc_transfer));
transfer.tx_buf = (uintptr_t)txbuf;
transfer.rx_buf = (uintptr_t)rxbuf;
transfer.len = len;
transfer.delay_usecs = 0;
transfer.speed_hz = speed_hz;
transfer.bits_per_word = bits;
transfer.cs_change = used_soft_cs;
// log::info("[__spi_transfer] cs_change = %d", transfer.cs_change);
#if PLATFORM_MAIXCAM2
if (txbuf) {
auto data_size = len;
auto p_data = (uint8_t *)txbuf;
uint8_t tmp_data = 0;
if (data_size > 50) {
if (data_size % 4 == 0) {
for (size_t i = 0; i < data_size; i += 4) {
for (size_t j = i; j < i + 2; j++) {
tmp_data = p_data[j];
p_data[j] = p_data[i + i + 4 - j - 1];
p_data[i + i + 4 - j - 1] = tmp_data;
}
}
} else if (data_size % 2 == 0) {
for (size_t i = 0; i < data_size; i += 2) {
tmp_data = p_data[i];
p_data[i] = p_data[i + 1];
p_data[i + 1] = tmp_data;
}
}
}
}
int res = ::ioctl(fd, SPI_IOC_MESSAGE(1), &transfer);
if (res < 1) {
log::error("[__spi_transfer] run ioctl failed! res:%d", res);
return res;
}
if (rxbuf) {
auto data_size = len;
auto p_data = (uint8_t *)rxbuf;
uint8_t tmp_data = 0;
if (data_size > 50) {
if (data_size % 4 == 0) {
for (size_t i = 0; i < data_size; i += 4) {
for (size_t j = i; j < i + 2; j++) {
tmp_data = p_data[j];
p_data[j] = p_data[i + i + 4 - j - 1];
p_data[i + i + 4 - j - 1] = tmp_data;
}
}
} else if (data_size % 2 == 0) {
for (size_t i = 0; i < data_size; i += 2) {
tmp_data = p_data[i];
p_data[i] = p_data[i + 1];
p_data[i + 1] = tmp_data;
}
}
}
}
#else
if (::ioctl(fd, SPI_IOC_MESSAGE(1), &transfer) < 1)
return -1;
#endif
return 0;
}
SPI::SPI(int id, spi::Mode mode, int freq, int polarity, int phase, int bits,
int hw_cs, std::string soft_cs,
bool cs_active_low)
:_bits(bits), _freq(freq)
{
if (mode != spi::Mode::MASTER)
throw err::Exception(err::ERR_ARGS, "only spi::Mode::MASTER");
if ((polarity & ~0x01) | (phase & ~0x01))
throw err::Exception(err::ERR_ARGS, "spi polarity/phase value should be in 0/1");
if(!soft_cs.empty()) // use soft cs
{
_used_soft_cs = true;
auto pull = cs_active_low ? gpio::Pull::PULL_DOWN : gpio::Pull::PULL_UP;
_cs = new gpio::GPIO(soft_cs, gpio::Mode::OUT, pull);
if (nullptr == _cs)
throw err::Exception(err::ERR_NO_MEM, "cannot alloc maix::peripheral::gpio");
}
else
{
_used_soft_cs = false;
if(!cs_active_low)
throw err::Exception(err::ERR_ARGS, "hw_cs only support active low");
}
_cs_active_value = cs_active_low ? 0 : 1;
if(hw_cs < 0)
hw_cs = maix_spi_port_get_default_hw_cs(id);
std::string dev_name("/dev/spidev"+std::to_string(id)+"."+std::to_string(hw_cs));
log::debug("try to open %s...", dev_name.c_str());
_fd = ::open(dev_name.c_str(), O_RDWR);
if (_fd < 0)
throw err::Exception(err::ERR_IO, "cannot open "+dev_name);
uint32_t d8 = ((polarity << 1)|(phase));
if (!cs_active_low && !_used_soft_cs) {
d8 |= SPI_CS_HIGH;
}
if (::ioctl(_fd, SPI_IOC_WR_MODE, &d8) < 0)
throw __ioctl_error(&_fd);
uint32_t rmode = d8;
if (::ioctl(_fd, SPI_IOC_RD_MODE, &rmode) < 0)
throw __ioctl_error(&_fd);
if (d8 != rmode) {
// log::error("SPI Write Mode: 0x%x", d8);
// log::error("SPI Read Mode: 0x%x", rmode);
// throw err::Exception(err::ERR_WRITE, "SPI write mode failed");
log::debug("SPI write MODE: 0x%x, SPI read MODE: 0x%x", d8, rmode);
}
if (::ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &freq) < 0)
throw __ioctl_error(&_fd);
if (::ioctl(_fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0)
throw __ioctl_error(&_fd);
}
SPI::~SPI()
{
if (_used_soft_cs) {
delete _cs;
}
__close_fd(&_fd);
}
Bytes *SPI::read(int length)
{
return this->write_read(nullptr, length);
}
std::vector<unsigned char> SPI::read(unsigned int length)
{
return this->write_read(std::vector<unsigned char>(), length);
}
int SPI::write(std::vector<unsigned char> data)
{
this->write_read(data, data.size());
return data.size();
}
int SPI::write(Bytes *data)
{
this->write_read(data, data->data_len);
return data->data_len;
}
std::vector<unsigned char> SPI::write_read(std::vector<unsigned char> data, int read_len)
{
if (read_len <= 0)
return std::vector<unsigned char>();
auto w_size = data.size();
size_t len = std::max(w_size, static_cast<size_t>(read_len));
if (len != w_size) {
data.resize(len, 0x00);
}
auto wbuf = new unsigned char[len];
if (nullptr == wbuf) {
log::error("cannot alloc %d bytes for SPI::write_read", len);
return std::vector<unsigned char>();
}
::memset(wbuf, 0x00, len);
::memcpy(wbuf, data.data(), data.size());
auto rbuf = new unsigned char[len];
if (nullptr == rbuf) {
log::error("cannot alloc %d bytes for SPI::write_read", len);
delete[] wbuf;
return std::vector<unsigned char>();
}
if (_used_soft_cs)
this->enable_cs(true);
if (__spi_transfer(_fd, _used_soft_cs, _freq, _bits, wbuf, rbuf, len) < 0) {
delete[] wbuf;
delete[] rbuf;
return std::vector<unsigned char>();
}
if (_used_soft_cs)
this->enable_cs(false);
auto res = std::vector<unsigned char>(rbuf, rbuf+read_len);
delete[] wbuf;
delete[] rbuf;
return res;
}
Bytes *SPI::write_read(Bytes *data, int read_len)
{
if (read_len <= 0)
return nullptr;
unsigned long w_size = 0;
if (nullptr != data)
w_size = data->size();
size_t len = std::max(w_size, static_cast<size_t>(read_len));
auto wbuf = new unsigned char[len];
if (wbuf == nullptr) {
log::error("cannot alloc %d bytes for SPI::write_read", len);
return nullptr;
}
::memset(wbuf, 0x00, len);
if (nullptr != data && data->size() > 0) {
std::copy(data->data, data->data+data->data_len, wbuf);
}
auto rbuf = new unsigned char[len];
if (rbuf == nullptr) {
log::error("cannot alloc %d bytes for SPI::write_read", len);
delete[] wbuf;
return nullptr;
}
if (_used_soft_cs)
this->enable_cs(true);
if (__spi_transfer(_fd, _used_soft_cs, _freq, _bits, wbuf, rbuf, len) < 0) {
delete[] rbuf;
delete[] wbuf;
return nullptr;
}
if (_used_soft_cs)
this->enable_cs(false);
auto res = new Bytes(rbuf, read_len);
delete[] wbuf;
delete[] rbuf;
return res;
}
bool SPI::is_busy()
{
return false;
}
void SPI::enable_cs(bool enable)
{
if (enable)
_cs->value(_cs_active_value);
else
_cs->value(!_cs_active_value);
}
}; // namespace maix