mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-20 16:56:17 +00:00
Very hacky first attempt at usermod ech341
This commit is contained in:
parent
fb9f361052
commit
4f08580358
@ -92,6 +92,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
|
||||
#include "linux/LinuxHardwareI2C.h"
|
||||
#include "mesh/raspihttp/PiWebServer.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include "platform/portduino/USBHal.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@ -822,8 +823,7 @@ void setup()
|
||||
if (settingsMap[use_sx1262]) {
|
||||
if (!rIf) {
|
||||
LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
|
||||
LockingArduinoHal *RadioLibHAL =
|
||||
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
|
||||
Ch341Hal *RadioLibHAL = new Ch341Hal(0);
|
||||
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
|
||||
settingsMap[busy]);
|
||||
if (!rIf->init()) {
|
||||
|
@ -57,6 +57,8 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
digitalWrite(settingsMap[sx126x_ant_sw], HIGH);
|
||||
pinMode(settingsMap[sx126x_ant_sw], OUTPUT);
|
||||
}
|
||||
// lora.XTAL = true;
|
||||
// lora.standbyXOSC = true;
|
||||
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
|
||||
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
|
||||
float tcxoVoltage =
|
||||
|
@ -311,7 +311,7 @@ void portduinoSetup()
|
||||
// Need to bind all the configured GPIO pins so they're not simulated
|
||||
// TODO: Can we do this in the for loop above?
|
||||
// TODO: If one of these fails, we should log and terminate
|
||||
if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
|
||||
/*if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
|
||||
if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) {
|
||||
settingsMap[cs] = RADIOLIB_NC;
|
||||
}
|
||||
@ -350,7 +350,7 @@ void portduinoSetup()
|
||||
if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) {
|
||||
settingsMap[txen] = RADIOLIB_NC;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
if (settingsMap[displayPanel] != no_screen) {
|
||||
if (settingsMap[displayCS] > 0)
|
||||
|
244
src/platform/portduino/USBHal.h
Normal file
244
src/platform/portduino/USBHal.h
Normal file
@ -0,0 +1,244 @@
|
||||
#ifndef PI_HAL_LGPIO_H
|
||||
#define PI_HAL_LGPIO_H
|
||||
|
||||
// include RadioLib
|
||||
#include <RadioLib.h>
|
||||
#include <csignal>
|
||||
#include <libpinedio-usb.h>
|
||||
|
||||
// include the library for Raspberry GPIO pins
|
||||
|
||||
#define PI_RISING (PINEDIO_INT_MODE_RISING)
|
||||
#define PI_FALLING (PINEDIO_INT_MODE_FALLING)
|
||||
#define PI_INPUT (0)
|
||||
#define PI_OUTPUT (1)
|
||||
#define PI_LOW (0)
|
||||
#define PI_HIGH (1)
|
||||
#define PI_MAX_USER_GPIO (31)
|
||||
|
||||
#define CH341_PIN_CS (101)
|
||||
#define CH341_PIN_IRQ (102)
|
||||
|
||||
// forward declaration of alert handler that will be used to emulate interrupts
|
||||
// static void lgpioAlertHandler(int num_alerts, lgGpioAlert_p alerts, void *userdata);
|
||||
|
||||
// the HAL must inherit from the base RadioLibHal class
|
||||
// and implement all of its virtual methods
|
||||
class Ch341Hal : public RadioLibHal
|
||||
{
|
||||
public:
|
||||
// default constructor - initializes the base HAL and any needed private members
|
||||
Ch341Hal(uint8_t spiChannel, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0)
|
||||
: RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING)
|
||||
{
|
||||
}
|
||||
|
||||
void init() override
|
||||
{
|
||||
// now the SPI
|
||||
spiBegin();
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
// stop the SPI
|
||||
spiEnd();
|
||||
}
|
||||
|
||||
// GPIO-related methods (pinMode, digitalWrite etc.) should check
|
||||
// RADIOLIB_NC as an alias for non-connected pins
|
||||
void pinMode(uint32_t pin, uint32_t mode) override
|
||||
{
|
||||
if (pin == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
if (pin == CH341_PIN_CS || pin == CH341_PIN_IRQ) {
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "pinMode for pin %d and mode %d is not supported!\n", pin, mode);
|
||||
}
|
||||
|
||||
void digitalWrite(uint32_t pin, uint32_t value) override
|
||||
{
|
||||
if (pin == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
if (pin == CH341_PIN_CS) {
|
||||
pinedio_set_cs(&pinedio, value == 0);
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "digitalWrite for pin %d is not supported!\n", pin);
|
||||
}
|
||||
|
||||
uint32_t digitalRead(uint32_t pin) override
|
||||
{
|
||||
if (pin == RADIOLIB_NC) {
|
||||
return 0;
|
||||
}
|
||||
if (pin == CH341_PIN_IRQ) {
|
||||
|
||||
return pinedio_get_irq_state(&pinedio);
|
||||
}
|
||||
fprintf(stderr, "digitalRead for pin %d is not supported!\n", pin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
|
||||
{
|
||||
if ((interruptNum == RADIOLIB_NC) || (interruptNum > PI_MAX_USER_GPIO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pinedio_attach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum, (pinedio_int_mode)mode, interruptCb);
|
||||
|
||||
// set lgpio alert callback
|
||||
// int result = lgGpioClaimAlert(_gpioHandle, 0, mode, interruptNum, -1);
|
||||
// if(result < 0) {
|
||||
// fprintf(stderr, "Could not claim pin %" PRIu32 " for alert: %s\n", interruptNum, lguErrorText(result));
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // enable emulated interrupt
|
||||
// interruptEnabled[interruptNum] = true;
|
||||
// interruptModes[interruptNum] = mode;
|
||||
// interruptCallbacks[interruptNum] = interruptCb;
|
||||
|
||||
// lgGpioSetAlertsFunc(_gpioHandle, interruptNum, lgpioAlertHandler, (void *)this);
|
||||
}
|
||||
|
||||
void detachInterrupt(uint32_t interruptNum) override
|
||||
{
|
||||
if ((interruptNum == RADIOLIB_NC) || (interruptNum > PI_MAX_USER_GPIO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pinedio_deattach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum);
|
||||
|
||||
// // clear emulated interrupt
|
||||
// interruptEnabled[interruptNum] = false;
|
||||
// interruptModes[interruptNum] = 0;
|
||||
// interruptCallbacks[interruptNum] = NULL;
|
||||
|
||||
// disable lgpio alert callback
|
||||
// lgGpioFree(_gpioHandle, interruptNum);
|
||||
// lgGpioSetAlertsFunc(_gpioHandle, interruptNum, NULL, NULL);
|
||||
}
|
||||
|
||||
void delay(unsigned long ms) override
|
||||
{
|
||||
if (ms == 0) {
|
||||
sched_yield();
|
||||
return;
|
||||
}
|
||||
|
||||
usleep(ms * 1000);
|
||||
// lguSleep(ms / 1000.0);
|
||||
}
|
||||
|
||||
void delayMicroseconds(unsigned long us) override
|
||||
{
|
||||
if (us == 0) {
|
||||
sched_yield();
|
||||
return;
|
||||
}
|
||||
usleep(us);
|
||||
|
||||
// lguSleep(us / 1000000.0);
|
||||
}
|
||||
|
||||
void yield() override { sched_yield(); }
|
||||
|
||||
unsigned long millis() override
|
||||
{
|
||||
// uint32_t time = lguTimestamp() / 1000000UL;
|
||||
// return time;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000ULL);
|
||||
}
|
||||
|
||||
unsigned long micros() override
|
||||
{
|
||||
// uint32_t time = lguTimestamp() / 1000UL;
|
||||
// return time;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (tv.tv_sec * 1000000ULL) + tv.tv_usec;
|
||||
}
|
||||
|
||||
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override
|
||||
{
|
||||
fprintf(stderr, "pulseIn for pin %d is not supported!\n", pin);
|
||||
}
|
||||
|
||||
void spiBegin()
|
||||
{
|
||||
if (!pinedio_is_init) {
|
||||
int32_t ret = pinedio_init(&pinedio, NULL);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Could not open SPI: %d\n", ret);
|
||||
} else {
|
||||
pinedio_is_init = true;
|
||||
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spiBeginTransaction() {}
|
||||
|
||||
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
|
||||
{
|
||||
int32_t result = pinedio_transceive(&this->pinedio, out, in, len);
|
||||
// int result = lgSpiXfer(_spiHandle, (char *)out, (char*)in, len);
|
||||
if (result < 0) {
|
||||
fprintf(stderr, "Could not perform SPI transfer: %d\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
void spiEndTransaction() {}
|
||||
|
||||
void spiEnd()
|
||||
{
|
||||
if (pinedio_is_init) {
|
||||
pinedio_deinit(&pinedio);
|
||||
pinedio_is_init = false;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) {
|
||||
lgTxPwm(_gpioHandle, pin, frequency, 50, 0, duration);
|
||||
}
|
||||
|
||||
void noTone(uint32_t pin) {
|
||||
lgTxPwm(_gpioHandle, pin, 0, 0, 0, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
// the HAL can contain any additional private members
|
||||
pinedio_inst pinedio;
|
||||
bool pinedio_is_init = false;
|
||||
};
|
||||
|
||||
#if 0
|
||||
// this handler emulates interrupts
|
||||
static void lgpioAlertHandler(int num_alerts, lgGpioAlert_p alerts, void *userdata) {
|
||||
if(!userdata)
|
||||
return;
|
||||
|
||||
// Ch341Hal instance is passed via the user data
|
||||
Ch341Hal* hal = (Ch341Hal*)userdata;
|
||||
|
||||
// check the interrupt is enabled, the level matches and a callback exists
|
||||
for(lgGpioAlert_t *alert = alerts; alert < (alerts + num_alerts); alert++) {
|
||||
if((hal->interruptEnabled[alert->report.gpio]) &&
|
||||
(hal->interruptModes[alert->report.gpio] == alert->report.level) &&
|
||||
(hal->interruptCallbacks[alert->report.gpio])) {
|
||||
hal->interruptCallbacks[alert->report.gpio]();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
536
src/platform/portduino/libpinedio-usb.c
Normal file
536
src/platform/portduino/libpinedio-usb.c
Normal file
@ -0,0 +1,536 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* Copyright (C) 2024 Marek Kraus <gamelaster@outlook.com>
|
||||
*
|
||||
* This code is heavily based on ch341a_spi.c from the flashrom project.
|
||||
* The plan is to rework parts of code, but until that, original developers deserves to be mentioned.
|
||||
* Copyright (C) 2011 asbokid <ballymunboy@gmail.com>
|
||||
* Copyright (C) 2014 Pluto Yang <yangyj.ee@gmail.com>
|
||||
* Copyright (C) 2015-2016 Stefan Tauner
|
||||
* Copyright (C) 2015 Urja Rannikko <urjaman@gmail.com>
|
||||
*/
|
||||
|
||||
#include "libpinedio-usb.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
#define pinedio_mutex_lock(...) \
|
||||
{ \
|
||||
printf("Locking %s\n", __func__); \
|
||||
pthread_mutex_lock(__VA_ARGS__); \
|
||||
}
|
||||
#define pinedio_mutex_unlock(...) \
|
||||
{ \
|
||||
printf("Unlocking %s\n", __func__); \
|
||||
pthread_mutex_unlock(__VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
#define pinedio_mutex_lock(...) pthread_mutex_lock(__VA_ARGS__);
|
||||
#define pinedio_mutex_unlock(...) pthread_mutex_unlock(__VA_ARGS__);
|
||||
#endif
|
||||
|
||||
#define CH341_USB_TIMEOUT 1000
|
||||
#define CH341_WRITE_EP 0x02
|
||||
#define CH341_READ_EP 0x82
|
||||
#define CH341_PACKET_LENGTH 0x20
|
||||
|
||||
#define CH341_CMD_SPI_STREAM 0xA8
|
||||
|
||||
#define CH341_CMD_UIO_STREAM 0xAB
|
||||
#define CH341_CMD_UIO_STM_OUT 0x80
|
||||
#define CH341_CMD_UIO_STM_DIR 0x40
|
||||
#define CH341_CMD_UIO_STM_END 0x20
|
||||
|
||||
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
||||
|
||||
enum trans_state { TRANS_ACTIVE = -2, TRANS_ERR = -1, TRANS_IDLE = 0 };
|
||||
|
||||
static void platform_sleep(uint32_t msecs)
|
||||
{
|
||||
#ifdef __WIN32
|
||||
Sleep(msecs);
|
||||
#else
|
||||
usleep(msecs * 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cb_common(const char *func, struct libusb_transfer *transfer)
|
||||
{
|
||||
int *transfer_cnt = (int *)transfer->user_data;
|
||||
|
||||
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
|
||||
// Silently ACK and exit.
|
||||
*transfer_cnt = TRANS_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
||||
fprintf(stderr, "%s: error: %s\n", func, libusb_error_name(transfer->status));
|
||||
*transfer_cnt = TRANS_ERR;
|
||||
} else {
|
||||
*transfer_cnt = transfer->actual_length;
|
||||
}
|
||||
}
|
||||
|
||||
// callback for bulk out async transfer
|
||||
static void LIBUSB_CALL cb_out(struct libusb_transfer *transfer)
|
||||
{
|
||||
cb_common(__func__, transfer);
|
||||
}
|
||||
|
||||
// callback for bulk in async transfer
|
||||
static void LIBUSB_CALL cb_in(struct libusb_transfer *transfer)
|
||||
{
|
||||
cb_common(__func__, transfer);
|
||||
}
|
||||
|
||||
static int32_t usb_transfer(struct pinedio_inst *inst, const char *func, unsigned int writecnt, unsigned int readcnt,
|
||||
const uint8_t *writearr, uint8_t *readarr, bool lock)
|
||||
{
|
||||
int state_out = TRANS_IDLE;
|
||||
inst->transfer_out->buffer = (uint8_t *)writearr;
|
||||
inst->transfer_out->length = writecnt;
|
||||
inst->transfer_out->user_data = &state_out;
|
||||
|
||||
if (lock) {
|
||||
pinedio_mutex_lock(&inst->usb_access_mutex);
|
||||
}
|
||||
|
||||
/* Schedule write first */
|
||||
if (writecnt > 0) {
|
||||
state_out = TRANS_ACTIVE;
|
||||
int ret = libusb_submit_transfer(inst->transfer_out);
|
||||
if (ret) {
|
||||
fprintf(stderr, "%s: failed to submit OUT transfer: %s\n", func, libusb_error_name(ret));
|
||||
state_out = TRANS_ERR;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle all asynchronous packets as long as we have stuff to write or read. The write(s) simply need
|
||||
* to complete but we need to scheduling reads as long as we are not done. */
|
||||
unsigned int free_idx = 0; /* The IN transfer we expect to be free next. */
|
||||
unsigned int in_idx = 0; /* The IN transfer we expect to be completed next. */
|
||||
unsigned int in_done = 0;
|
||||
unsigned int in_active = 0;
|
||||
unsigned int out_done = 0;
|
||||
uint8_t *in_buf = readarr;
|
||||
int state_in[USB_IN_TRANSFERS] = {0};
|
||||
do {
|
||||
/* Schedule new reads as long as there are free transfers and unscheduled bytes to read. */
|
||||
while ((in_done + in_active) < readcnt && state_in[free_idx] == TRANS_IDLE) {
|
||||
unsigned int cur_todo = MIN(CH341_PACKET_LENGTH - 1, readcnt - in_done - in_active);
|
||||
inst->transfer_ins[free_idx]->length = cur_todo;
|
||||
inst->transfer_ins[free_idx]->buffer = in_buf;
|
||||
inst->transfer_ins[free_idx]->user_data = &state_in[free_idx];
|
||||
int ret = libusb_submit_transfer(inst->transfer_ins[free_idx]);
|
||||
if (ret) {
|
||||
state_in[free_idx] = TRANS_ERR;
|
||||
fprintf(stderr, "%s: failed to submit IN transfer: %s\n", func, libusb_error_name(ret));
|
||||
goto err;
|
||||
}
|
||||
in_buf += cur_todo;
|
||||
in_active += cur_todo;
|
||||
state_in[free_idx] = TRANS_ACTIVE;
|
||||
free_idx = (free_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
|
||||
}
|
||||
|
||||
/* Actually get some work done. */
|
||||
libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
|
||||
|
||||
/* Check for the write */
|
||||
if (out_done < writecnt) {
|
||||
if (state_out == TRANS_ERR) {
|
||||
goto err;
|
||||
} else if (state_out > 0) {
|
||||
out_done += state_out;
|
||||
state_out = TRANS_IDLE;
|
||||
}
|
||||
}
|
||||
/* Check for completed transfers. */
|
||||
while (state_in[in_idx] != TRANS_IDLE && state_in[in_idx] != TRANS_ACTIVE) {
|
||||
if (state_in[in_idx] == TRANS_ERR) {
|
||||
goto err;
|
||||
}
|
||||
/* If a transfer is done, record the number of bytes read and reuse it later. */
|
||||
in_done += state_in[in_idx];
|
||||
in_active -= state_in[in_idx];
|
||||
state_in[in_idx] = TRANS_IDLE;
|
||||
in_idx = (in_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
|
||||
}
|
||||
} while ((out_done < writecnt) || (in_done < readcnt));
|
||||
|
||||
if (lock) {
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
/* Clean up on errors. */
|
||||
fprintf(stderr, "%s: Failed to %s %d bytes\n", func, (state_out == TRANS_ERR) ? "write" : "read",
|
||||
(state_out == TRANS_ERR) ? writecnt : readcnt);
|
||||
/* First, we must cancel any ongoing requests and wait for them to be canceled. */
|
||||
if ((writecnt > 0) && (state_out == TRANS_ACTIVE)) {
|
||||
if (libusb_cancel_transfer(inst->transfer_out) != 0)
|
||||
state_out = TRANS_ERR;
|
||||
}
|
||||
if (readcnt > 0) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < USB_IN_TRANSFERS; i++) {
|
||||
if (state_in[i] == TRANS_ACTIVE)
|
||||
if (libusb_cancel_transfer(inst->transfer_ins[i]) != 0)
|
||||
state_in[i] = TRANS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for cancellations to complete. */
|
||||
while (1) {
|
||||
bool finished = true;
|
||||
if ((writecnt > 0) && (state_out == TRANS_ACTIVE))
|
||||
finished = false;
|
||||
if (readcnt > 0) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < USB_IN_TRANSFERS; i++) {
|
||||
if (state_in[i] == TRANS_ACTIVE)
|
||||
finished = false;
|
||||
}
|
||||
}
|
||||
if (finished)
|
||||
break;
|
||||
libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
|
||||
}
|
||||
if (lock) {
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint8_t reverse_byte(uint8_t x)
|
||||
{
|
||||
x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
|
||||
x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
|
||||
x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
int32_t pinedio_set_cs(struct pinedio_inst *inst, bool active)
|
||||
{
|
||||
uint8_t buf[] = {CH341_CMD_UIO_STREAM, CH341_CMD_UIO_STM_DIR | 0x3f, CH341_CMD_UIO_STM_OUT | (active ? 0x36 : 0x37),
|
||||
CH341_CMD_UIO_STM_END};
|
||||
|
||||
int32_t ret = usb_transfer(inst, __func__, sizeof(buf), 0, buf, NULL, true);
|
||||
if (ret < 0) {
|
||||
printf("Failed to set CS pin.\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int32_t pinedio_write_read(struct pinedio_inst *inst, uint8_t *writearr, uint32_t writecnt, uint8_t *readarr, uint32_t readcnt)
|
||||
{
|
||||
/* How many packets ... */
|
||||
const size_t packets = (writecnt + readcnt + CH341_PACKET_LENGTH - 2) / (CH341_PACKET_LENGTH - 1);
|
||||
|
||||
/* We pluck CS/timeout handling into the first packet thus we need to allocate one extra package. */
|
||||
uint8_t wbuf[packets * CH341_PACKET_LENGTH];
|
||||
uint8_t rbuf[writecnt + readcnt];
|
||||
/* Initialize the write buffer to zero to prevent writing random stack contents to device. */
|
||||
memset(wbuf, 0, CH341_PACKET_LENGTH);
|
||||
|
||||
uint8_t *ptr = wbuf;
|
||||
/* CS usage is optimized by doing both transitions in one packet.
|
||||
* Final transition to deselected state is in the pin disable. */
|
||||
// pluck_cs(ptr, &data->stored_delay_us);
|
||||
if (inst->options[PINEDIO_OPTION_AUTO_CS]) {
|
||||
pinedio_set_cs(inst, true);
|
||||
}
|
||||
unsigned int write_left = writecnt;
|
||||
unsigned int read_left = readcnt;
|
||||
unsigned int p;
|
||||
for (p = 0; p < packets; p++) {
|
||||
unsigned int write_now = MIN(CH341_PACKET_LENGTH - 1, write_left);
|
||||
unsigned int read_now = MIN((CH341_PACKET_LENGTH - 1) - write_now, read_left);
|
||||
ptr = &wbuf[p * CH341_PACKET_LENGTH];
|
||||
*ptr++ = CH341_CMD_SPI_STREAM;
|
||||
unsigned int i;
|
||||
for (i = 0; i < write_now; ++i)
|
||||
*ptr++ = reverse_byte(*writearr++);
|
||||
if (read_now) {
|
||||
memset(ptr, 0xFF, read_now);
|
||||
read_left -= read_now;
|
||||
}
|
||||
write_left -= write_now;
|
||||
}
|
||||
|
||||
int32_t ret = usb_transfer(inst, __func__, packets + writecnt + readcnt, writecnt + readcnt, wbuf, rbuf, true);
|
||||
if (inst->options[PINEDIO_OPTION_AUTO_CS]) {
|
||||
pinedio_set_cs(inst, false);
|
||||
}
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < readcnt; i++) {
|
||||
*readarr++ = reverse_byte(rbuf[writecnt + i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t pinedio_transceive(struct pinedio_inst *inst, uint8_t *write_buf, uint8_t *read_buf, uint32_t count)
|
||||
{
|
||||
const size_t packets = (count + CH341_PACKET_LENGTH - 2) / (CH341_PACKET_LENGTH - 1);
|
||||
|
||||
uint8_t wbuf[packets * CH341_PACKET_LENGTH];
|
||||
|
||||
uint8_t *ptr = wbuf;
|
||||
if (inst->options[PINEDIO_OPTION_AUTO_CS]) {
|
||||
pinedio_set_cs(inst, true);
|
||||
}
|
||||
unsigned int write_left = count;
|
||||
unsigned int read_left = count;
|
||||
unsigned int p;
|
||||
for (p = 0; p < packets; p++) {
|
||||
unsigned int write_now = MIN(CH341_PACKET_LENGTH - 1, write_left);
|
||||
unsigned int read_now = MIN((CH341_PACKET_LENGTH - 1) - write_now, read_left);
|
||||
ptr = &wbuf[p * CH341_PACKET_LENGTH];
|
||||
*ptr++ = CH341_CMD_SPI_STREAM;
|
||||
unsigned int i;
|
||||
for (i = 0; i < write_now; ++i)
|
||||
*ptr++ = reverse_byte(*write_buf++);
|
||||
if (read_now) {
|
||||
memset(ptr, 0xFF, read_now);
|
||||
read_left -= read_now;
|
||||
}
|
||||
write_left -= write_now;
|
||||
}
|
||||
|
||||
int32_t ret = usb_transfer(inst, __func__, packets + count, count, wbuf, read_buf, true);
|
||||
if (inst->options[PINEDIO_OPTION_AUTO_CS]) {
|
||||
pinedio_set_cs(inst, false);
|
||||
}
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
*read_buf++ = reverse_byte(*read_buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t pinedio_init(struct pinedio_inst *inst, void *driver)
|
||||
{
|
||||
int32_t ret;
|
||||
inst->int_running_cnt = 0;
|
||||
inst->pin_poll_thread_exit = false;
|
||||
for (int i = 0; i < PINEDIO_INT_PIN_MAX; i++) {
|
||||
inst->interrupts[i].callback = NULL;
|
||||
}
|
||||
|
||||
inst->options[PINEDIO_OPTION_AUTO_CS] = 1;
|
||||
|
||||
ret = pthread_mutex_init(&inst->usb_access_mutex, NULL);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Failed to initialize mutex, res: %d.\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = libusb_init(NULL);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Couldn't initialize libusb!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
|
||||
uint16_t vid = 0x1A86;
|
||||
uint16_t pid = 0x5512;
|
||||
inst->handle = libusb_open_device_with_vid_pid(NULL, vid, pid);
|
||||
if (inst->handle == NULL) {
|
||||
// TODO: Rework this so we can receive error and print it.
|
||||
fprintf(stderr, "Couldn't open LoRa Adapator device.\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
// On Windows, driver needs to be replaced manually by Zadig
|
||||
ret = libusb_detach_kernel_driver(inst->handle, 0);
|
||||
if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND) {
|
||||
fprintf(stderr, "Cannot detach the existing USB driver. Claiming the interface may fail: %s\n", libusb_error_name(ret));
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = libusb_claim_interface(inst->handle, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Failed to claim interface 0: %s\n", libusb_error_name(ret));
|
||||
goto deinit_on_error;
|
||||
}
|
||||
|
||||
// Allocate and pre-fill transfer structures.
|
||||
inst->transfer_out = libusb_alloc_transfer(0);
|
||||
if (!inst->transfer_out) {
|
||||
fprintf(stderr, "Failed to alloc libusb OUT transfer.\n");
|
||||
goto deinit_on_error;
|
||||
}
|
||||
for (int i = 0; i < USB_IN_TRANSFERS; i++) {
|
||||
inst->transfer_ins[i] = libusb_alloc_transfer(0);
|
||||
if (inst->transfer_ins[i] == NULL) {
|
||||
fprintf(stderr, "Failed to alloc libusb IN transfer %d.\n", i);
|
||||
goto deinit_on_error;
|
||||
}
|
||||
}
|
||||
|
||||
// We use these helpers but don't fill the actual buffer yet.
|
||||
libusb_fill_bulk_transfer(inst->transfer_out, inst->handle, CH341_WRITE_EP, NULL, 0, cb_out, NULL, CH341_USB_TIMEOUT);
|
||||
for (int i = 0; i < USB_IN_TRANSFERS; i++)
|
||||
libusb_fill_bulk_transfer(inst->transfer_ins[i], inst->handle, CH341_READ_EP, NULL, 0, cb_in, NULL, CH341_USB_TIMEOUT);
|
||||
|
||||
/**
|
||||
* We don't need to initialize SPI at all, as by default it's configured properly.
|
||||
* Only thing required is pinmux, what is anyway configured by CS change function.
|
||||
*/
|
||||
|
||||
pinedio_set_cs(inst, false);
|
||||
|
||||
return 0;
|
||||
|
||||
deinit_on_error:
|
||||
pinedio_deinit(inst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pinedio_deinit(struct pinedio_inst *inst)
|
||||
{
|
||||
pinedio_mutex_lock(&inst->usb_access_mutex);
|
||||
if (inst->int_running_cnt != 0) {
|
||||
inst->pin_poll_thread_exit = true;
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
pthread_join(inst->pin_poll_thread, NULL);
|
||||
} else {
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
}
|
||||
|
||||
for (int i = 0; i < USB_IN_TRANSFERS; i++) {
|
||||
if (inst->transfer_ins[i] != NULL) {
|
||||
libusb_free_transfer(inst->transfer_ins[i]);
|
||||
}
|
||||
}
|
||||
if (inst->transfer_out != NULL) {
|
||||
libusb_free_transfer(inst->transfer_out);
|
||||
}
|
||||
|
||||
if (inst->handle != NULL) {
|
||||
// We don't know if claim of interface was successful, but libusb handles this.
|
||||
libusb_release_interface(inst->handle, 0);
|
||||
#ifdef __linux__
|
||||
libusb_attach_kernel_driver(inst->handle, 0);
|
||||
#endif
|
||||
libusb_close(inst->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t pinedio_get_input(struct pinedio_inst *inst, uint32_t *input)
|
||||
{
|
||||
uint8_t buf[] = {
|
||||
0xA0,
|
||||
};
|
||||
uint8_t output[6];
|
||||
|
||||
int32_t ret = usb_transfer(inst, __func__, sizeof(buf), sizeof(output), buf, output, true);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not get input pins.\n");
|
||||
}
|
||||
*input = ((output[2] & 0x80) << 16) | ((output[1] & 0xef) << 8) | output[0];
|
||||
return ret;
|
||||
}
|
||||
|
||||
int32_t pinedio_get_irq_state(struct pinedio_inst *inst)
|
||||
{
|
||||
uint32_t input;
|
||||
int32_t ret = pinedio_get_input(inst, &input);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return (input & (1 << 10)) != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
static void *pinedio_pin_poll_thread(void *arg)
|
||||
{
|
||||
struct pinedio_inst *inst = arg;
|
||||
int32_t ret = 0;
|
||||
bool should_exit = false;
|
||||
uint32_t pin_masks[PINEDIO_INT_PIN_MAX];
|
||||
pin_masks[PINEDIO_INT_PIN_IRQ] = 1 << 10;
|
||||
|
||||
uint32_t input;
|
||||
// pin_masks[PINEDIO_INT_PIN_BUSY] = 10 << 1;
|
||||
while (!should_exit) {
|
||||
ret = pinedio_get_input(inst, &input);
|
||||
pinedio_mutex_lock(&inst->usb_access_mutex);
|
||||
if (ret != 0)
|
||||
continue;
|
||||
for (uint8_t int_pin = 0; int_pin < PINEDIO_INT_PIN_MAX; int_pin++) {
|
||||
struct pinedio_inst_int *inst_int = &inst->interrupts[int_pin];
|
||||
if (inst_int->callback == NULL)
|
||||
continue;
|
||||
uint8_t state = (input & pin_masks[int_pin]) != 0;
|
||||
if (inst_int->previous_state != 255 && inst_int->previous_state != state) {
|
||||
enum pinedio_int_mode mode =
|
||||
inst_int->previous_state == false && state == true ? PINEDIO_INT_MODE_RISING : PINEDIO_INT_MODE_FALLING;
|
||||
if (inst_int->mode & mode) {
|
||||
inst_int->callback();
|
||||
}
|
||||
}
|
||||
inst_int->previous_state = state;
|
||||
}
|
||||
|
||||
should_exit = inst->pin_poll_thread_exit;
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
platform_sleep(1000 / 30);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t pinedio_attach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin, enum pinedio_int_mode int_mode,
|
||||
void (*callback)(void))
|
||||
{
|
||||
int32_t res = 0;
|
||||
pinedio_mutex_lock(&inst->usb_access_mutex);
|
||||
inst->interrupts[int_pin].previous_state = 255;
|
||||
inst->interrupts[int_pin].mode = int_mode;
|
||||
inst->interrupts[int_pin].callback = callback;
|
||||
if (inst->int_running_cnt == 0) {
|
||||
inst->pin_poll_thread_exit = false;
|
||||
res = pthread_create(&inst->pin_poll_thread, NULL, pinedio_pin_poll_thread, inst);
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "Failed to create thread, res: %d\n", res);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
inst->int_running_cnt++;
|
||||
|
||||
unlock:
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t pinedio_deattach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin)
|
||||
{
|
||||
pinedio_mutex_lock(&inst->usb_access_mutex);
|
||||
inst->interrupts[int_pin].callback = NULL;
|
||||
inst->int_running_cnt--;
|
||||
if (inst->int_running_cnt == 0) {
|
||||
inst->pin_poll_thread_exit = true;
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
pthread_join(inst->pin_poll_thread, NULL);
|
||||
return 0;
|
||||
}
|
||||
pinedio_mutex_unlock(&inst->usb_access_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t pinedio_set_option(struct pinedio_inst *inst, enum pinedio_option option, uint32_t value)
|
||||
{
|
||||
inst->options[option] = value;
|
||||
}
|
71
src/platform/portduino/libpinedio-usb.h
Normal file
71
src/platform/portduino/libpinedio-usb.h
Normal file
@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright 2024 Marek Kraus (@gamelaster)
|
||||
|
||||
#ifndef PINEDIO_USB_LORA_ADAPTER_SDK_LIBPINEDIO_USB_H
|
||||
#define PINEDIO_USB_LORA_ADAPTER_SDK_LIBPINEDIO_USB_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define USB_IN_TRANSFERS 32
|
||||
|
||||
enum pinedio_int_pin {
|
||||
PINEDIO_INT_PIN_IRQ,
|
||||
// PINEDIO_INT_PIN_BUSY, // not implemented yet
|
||||
PINEDIO_INT_PIN_MAX
|
||||
};
|
||||
|
||||
enum pinedio_int_mode {
|
||||
PINEDIO_INT_MODE_RISING = 0x01,
|
||||
PINEDIO_INT_MODE_FALLING = 0x02,
|
||||
};
|
||||
|
||||
enum pinedio_option { PINEDIO_OPTION_AUTO_CS, PINEDIO_OPTION_MAX };
|
||||
|
||||
struct pinedio_inst_int {
|
||||
uint8_t previous_state;
|
||||
enum pinedio_int_mode mode;
|
||||
void (*callback)(void);
|
||||
};
|
||||
|
||||
/* Even this will work mostly on desktop (so malloc is available), I still prefer static allocation.
|
||||
* That's why structure definition is in header. */
|
||||
|
||||
struct pinedio_inst {
|
||||
struct libusb_device_handle *handle;
|
||||
|
||||
/* We need to use many queued IN transfers for any resemblance of performance (especially on Windows)
|
||||
* because USB spec says that transfers end on non-full packets and the device sends the 31 reply
|
||||
* data bytes to each 32-byte packet with command + 31 bytes of data... */
|
||||
struct libusb_transfer *transfer_out;
|
||||
struct libusb_transfer *transfer_ins[USB_IN_TRANSFERS];
|
||||
uint8_t int_running_cnt;
|
||||
pthread_mutex_t usb_access_mutex;
|
||||
pthread_t pin_poll_thread;
|
||||
bool pin_poll_thread_exit;
|
||||
struct pinedio_inst_int interrupts[PINEDIO_INT_PIN_MAX];
|
||||
uint32_t options[PINEDIO_OPTION_MAX];
|
||||
};
|
||||
|
||||
int32_t pinedio_init(struct pinedio_inst *inst, void *driver);
|
||||
int32_t pinedio_set_option(struct pinedio_inst *inst, enum pinedio_option option, uint32_t value);
|
||||
int32_t pinedio_set_cs(struct pinedio_inst *inst, bool active);
|
||||
int32_t pinedio_write_read(struct pinedio_inst *inst, uint8_t *writearr, uint32_t writecnt, uint8_t *readarr, uint32_t readcnt);
|
||||
int32_t pinedio_transceive(struct pinedio_inst *inst, uint8_t *write_buf, uint8_t *read_buf, uint32_t count);
|
||||
int32_t pinedio_get_irq_state(struct pinedio_inst *inst);
|
||||
int32_t pinedio_attach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin, enum pinedio_int_mode int_mode,
|
||||
void (*callback)(void));
|
||||
int32_t pinedio_deattach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin);
|
||||
void pinedio_deinit(struct pinedio_inst *inst);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // PINEDIO_USB_LORA_ADAPTER_SDK_LIBPINEDIO_USB_H
|
Loading…
Reference in New Issue
Block a user