From 54c29c1b717884f198b1c11f4dd9f97990264686 Mon Sep 17 00:00:00 2001 From: Levente Kovacs Date: Sat, 30 Aug 2025 21:32:12 +0200 Subject: [PATCH 1/2] gpiod v2 support --- src/CMakeLists.txt | 1 + src/audio.h | 20 +++--- src/gpio_common.c | 176 +++++++++++++++++++++++++++++++++++++++++++++ src/gpio_common.h | 25 +++++++ src/ptt.c | 74 +++++++------------ 5 files changed, 237 insertions(+), 59 deletions(-) create mode 100644 src/gpio_common.c create mode 100644 src/gpio_common.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 33c8c690..b13cb352 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -104,6 +104,7 @@ list(APPEND direwolf_SOURCES dwgpsnmea.c dwgpsd.c mheard.c + gpio_common.c ) if(LINUX) diff --git a/src/audio.h b/src/audio.h index f69fc1d7..b4c14626 100644 --- a/src/audio.h +++ b/src/audio.h @@ -5,7 +5,7 @@ * * Purpose: Interface to audio device commonly called a "sound card" * for historical reasons. - * + * *---------------------------------------------------------------*/ @@ -19,13 +19,14 @@ #include "direwolf.h" /* for MAX_RADIO_CHANS and MAX_TOTAL_CHANS used throughout the application. */ #include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ #include "version.h" - +#include "gpio_common.h" + /* - * PTT control. + * PTT control. */ -enum ptt_method_e { +enum ptt_method_e { PTT_METHOD_NONE, /* VOX or no transmit. */ PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */ PTT_METHOD_GPIO, /* General purpose I/O using sysfs, deprecated after 2020, Linux only. */ @@ -63,7 +64,7 @@ enum medium_e { MEDIUM_NONE = 0, // Channel is not valid for use. typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; - + struct audio_s { @@ -276,7 +277,7 @@ struct audio_s { /* Additional properties for transmit. */ - + /* Originally we had control outputs only for PTT. */ /* In version 1.2, we generalize this to allow others such as DCD. */ /* In version 1.4 we add CON for connected to another station. */ @@ -288,8 +289,8 @@ struct audio_s { #define OCTYPE_CON 2 #define NUM_OCTYPES 3 /* number of values above. i.e. last value +1. */ - - struct { + + struct { ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB, CM108. */ @@ -304,7 +305,7 @@ struct audio_s { /* have a name like /dev/hidraw1 for Linux or */ /* \\?\hid#vid_0d8c&pid_0008&mi_03#8&39d3555&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} */ /* for Windows. Largest observed was 95 but add some extra to be safe. */ - + ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */ ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */ @@ -330,6 +331,7 @@ struct audio_s { int ptt_invert; /* Invert the output. */ int ptt_invert2; /* Invert the secondary output. */ + gpio_num_t gpio_num; /* Handle from libgpiod. Valid only when ptt_method is PTT_METHOD_GPIOD. */ #ifdef USE_HAMLIB diff --git a/src/gpio_common.c b/src/gpio_common.c new file mode 100644 index 00000000..b724dfc9 --- /dev/null +++ b/src/gpio_common.c @@ -0,0 +1,176 @@ +#include +#include +#include + +#include +#include + +#include "gpio_common.h" + +#define GPIO_MAX_LINES 32 +#define GPIO_CONSUMER "FLDIGI" + +//Types +typedef struct gpio_common { + struct gpiod_line_request *request; + unsigned int offset; + bool used; +} gpio_common_t; + +// local variables + +static gpio_common_t gpio[GPIO_MAX_LINES]; + +void gpio_common_init(void) { + fprintf(stderr, "Initializing GPIO common structure\n"); + for (gpio_num_t i = 0; i < GPIO_MAX_LINES; i++) { + gpio[i].used = false; + } +} + + +gpio_num_t gpio_common_open_line(const char *chip_name, unsigned int line, bool active_low) { + gpio_num_t gpio_num; + int ret; + + struct gpiod_request_config *req_cfg = NULL; + struct gpiod_line_settings *settings; + struct gpiod_line_config *line_cfg; + struct gpiod_chip *chip; + + gpio_num = GPIO_COMMON_UNKNOWN; + + if (chip_name == NULL) { + fprintf(stderr, "No chip name supplied.\n"); + goto out; + } + + fprintf(stderr, "Opening GPIO line %d on chip %s\n", line, chip_name); + + // Get a free slot + for (gpio_num_t i = 0; i < GPIO_MAX_LINES; i++) { + if (gpio[i].used == false) { + gpio_num = i; + break; + } + } + + if (gpio_num == GPIO_COMMON_UNKNOWN) { + fprintf(stderr, "Too many GPIOs open.\n"); + goto out; + } + + chip = gpiod_chip_open(chip_name); + + if (chip == NULL) { + fprintf(stderr, "Failed to open GPIO chip %s\n", chip_name); + gpio_num = GPIO_COMMON_UNKNOWN; + goto out; + } + + settings = gpiod_line_settings_new(); + + if (settings == NULL) { + fprintf(stderr, "Unable to allocate memory for line settings \n"); + gpio_num = GPIO_COMMON_UNKNOWN; + goto close_chip; + } + + gpiod_line_settings_set_direction(settings, + GPIOD_LINE_DIRECTION_OUTPUT); + gpiod_line_settings_set_output_value(settings, 0); + gpiod_line_settings_set_active_low(settings, active_low); + + line_cfg = gpiod_line_config_new(); + + if (!line_cfg) { + gpio_num = GPIO_COMMON_UNKNOWN; + goto free_settings; + } + + ret = gpiod_line_config_add_line_settings(line_cfg, &line, 1, + settings); + if (ret < 0) { + fprintf(stderr, "Failed to add line settings\n"); + gpio_num = GPIO_COMMON_UNKNOWN; + goto free_line_config; + } + + req_cfg = gpiod_request_config_new(); + if (!req_cfg) { + goto free_line_config; + } + + gpiod_request_config_set_consumer(req_cfg, "FLDIGI"); + + gpio[gpio_num].request = gpiod_chip_request_lines(chip, req_cfg, line_cfg); + + if (gpio[gpio_num].request == NULL) { + fprintf(stderr, "Failed to request GPIO line %d\n", gpio_num); + gpio_num = GPIO_COMMON_UNKNOWN; + goto free_line_config; + } else { + gpio[gpio_num].used = true; + gpio[gpio_num].offset = line; + } + +free_line_config: + gpiod_line_config_free(line_cfg); + +free_settings: + gpiod_line_settings_free(settings); + +close_chip: + gpiod_chip_close(chip); + + +out: + + return gpio_num; +} + + +int gpio_common_release_line(gpio_num_t gpio_num) { + if (gpio_num >= GPIO_MAX_LINES) { + return -1; + } + + if (gpio[gpio_num].request != NULL) { + gpiod_line_request_release(gpio[gpio_num].request); + gpio[gpio_num].request = NULL; + } + + return 0; +} + + +int gpio_common_set(gpio_num_t gpio_num, bool val) { + if (gpio_num >= GPIO_MAX_LINES || gpio[gpio_num].request == NULL) { + return -1; + } + uint16_t gpiod_val; + + if (val) { + gpiod_val = GPIOD_LINE_VALUE_ACTIVE; + } else { + gpiod_val = GPIOD_LINE_VALUE_INACTIVE; + } + + + int ret = gpiod_line_request_set_value(gpio[gpio_num].request, gpio[gpio_num].offset, gpiod_val); + if (ret < 0) { + fprintf(stderr, "Error setting line\n"); + return -1; + } + return 0; +} + + +int gpio_common_close(void) { + + for (gpio_num_t i = 0; i < GPIO_MAX_LINES; i++) { + gpio_common_release_line(i); + } + + return 0; +} diff --git a/src/gpio_common.h b/src/gpio_common.h new file mode 100644 index 00000000..96fb3048 --- /dev/null +++ b/src/gpio_common.h @@ -0,0 +1,25 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + + +typedef uint16_t gpio_num_t; +#define GPIO_COMMON_UNKNOWN UINT16_MAX +#define GPIO_COMMON_OK 0 +#define GPIO_COMMON_ERR -1 + + +// Public functions + +void gpio_common_init(void); +gpio_num_t gpio_common_open_line(const char *chip_name, unsigned int line, bool active_low); +int gpio_common_close(void); +int gpio_common_set(gpio_num_t gpio_num, bool val); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/ptt.c b/src/ptt.c index 805fc7a0..2f1bb39a 100644 --- a/src/ptt.c +++ b/src/ptt.c @@ -22,7 +22,7 @@ * Module: ptt.c * * Purpose: Activate the output control lines for push to talk (PTT) and other purposes. - * + * * Description: Traditionally this is done with the RTS signal of the serial port. * * If we have two radio channels and only one serial port, DTR @@ -37,7 +37,7 @@ * * This is hardcoded to use the primary motherboard parallel * printer port at I/O address 0x378. This might work with - * a PCI card configured to use the same address if the + * a PCI card configured to use the same address if the * motherboard does not have a built in parallel port. * It won't work with a USB-to-parallel-printer-port adapter. * @@ -130,7 +130,7 @@ USB-Audio adapters. It would be nice to have a little script which lists all of the USB-Audio adapters and the corresponding /dev/hidraw device. ( We now have it. The included "cm108" application. ) - + In version 1.5 we have a flexible, easy to use implementation for Linux. Windows would be a lot of extra work because USB devices are nothing like Linux. We'd be starting from scratch to figure out how to do it. @@ -163,8 +163,8 @@ #endif #ifdef USE_GPIOD -#include -#endif +#include "gpio_common.h" +#endif /* So we can have more common code for fd. */ typedef int HANDLE; @@ -652,33 +652,6 @@ void export_gpio(int ch, int ot, int invert, int direction) get_access_to_gpio (gpio_value_path); } -#if defined(USE_GPIOD) -int gpiod_probe(const char *chip_dev_path, int line_number) -{ - // chip_dev_path must be complete device path such as /dev/gpiochip3 - - struct gpiod_chip *chip; - chip = gpiod_chip_open(chip_dev_path); - if (chip == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Can't open GPIOD chip %s.\n", chip_dev_path); - return -1; - } - - struct gpiod_line *line; - line = gpiod_chip_get_line(chip, line_number); - if (line == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Can't get GPIOD line %d.\n", line_number); - return -1; - } - if (ptt_debug_level >= 2) { - text_color_set(DW_COLOR_DEBUG); - dw_printf("GPIOD probe OK. Chip: %s line: %d\n", chip_dev_path, line_number); - } - return 0; -} -#endif /* USE_GPIOD */ #endif /* not __WIN32__ */ @@ -725,7 +698,7 @@ int gpiod_probe(const char *chip_dev_path, int line_number) * * Outputs: Remember required information for future use. * - * Description: + * Description: * *--------------------------------------------------------------------*/ @@ -734,7 +707,7 @@ int gpiod_probe(const char *chip_dev_path, int line_number) static HANDLE ptt_fd[MAX_RADIO_CHANS][NUM_OCTYPES]; /* Serial port handle or fd. */ - /* Could be the same for two channels */ + /* Could be the same for two channels */ /* if using both RTS and DTR. */ #if USE_HAMLIB static RIG *rig[MAX_RADIO_CHANS][NUM_OCTYPES]; @@ -778,7 +751,7 @@ void ptt_init (struct audio_s *audio_config_p) dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, name=%s, gpio=%d, lpt_bit=%d, invert=%d\n", ch, otnames[ot], - audio_config_p->achan[ch].octrl[ot].ptt_method, + audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_line, audio_config_p->achan[ch].octrl[ot].out_gpio_name, @@ -787,6 +760,7 @@ void ptt_init (struct audio_s *audio_config_p) audio_config_p->achan[ch].octrl[ot].ptt_invert); } } + gpio_common_init(); } /* @@ -837,7 +811,7 @@ void ptt_init (struct audio_s *audio_config_p) } if ( ! same_device_used) { - + #if __WIN32__ char bettername[50]; // Bug fix in release 1.1 - Need to munge name for COM10 and up. @@ -887,7 +861,7 @@ void ptt_init (struct audio_s *audio_config_p) /* * Set initial state off. * ptt_set will invert output signal if appropriate. - */ + */ ptt_set (ot, ch, 0); } /* if serial method. */ @@ -896,7 +870,7 @@ void ptt_init (struct audio_s *audio_config_p) } /* For each channel. */ -/* +/* * Set up GPIO - for Linux only. */ @@ -935,9 +909,9 @@ void ptt_init (struct audio_s *audio_config_p) if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { const char *chip_name = audio_config_p->achan[ch].octrl[ot].out_gpio_name; int line_number = audio_config_p->achan[ch].octrl[ot].out_gpio_num; - int rc = gpiod_probe(chip_name, line_number); - if (rc < 0) { - text_color_set(DW_COLOR_ERROR); + audio_config_p->achan[ch].octrl[ot].gpio_num = gpio_common_open_line(chip_name, line_number, false); + if (audio_config_p->achan[ch].octrl[ot].gpio_num == GPIO_COMMON_UNKNOWN) { + text_color_set(DW_COLOR_ERROR); //No, people won't notice the error message and be confused. Just terminate. //dw_printf ("Disable PTT for channel %d\n", ch); //audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; @@ -953,10 +927,10 @@ void ptt_init (struct audio_s *audio_config_p) } #endif /* USE_GPIOD */ /* - * We should now be able to create the device nodes for + * We should now be able to create the device nodes for * the pins we want to use. */ - + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { @@ -981,7 +955,7 @@ void ptt_init (struct audio_s *audio_config_p) /* * Set up parallel printer port. - * + * * Restrictions: * Only the primary printer port. * For x86 Linux only. @@ -1003,7 +977,7 @@ void ptt_init (struct audio_s *audio_config_p) int same_device_used = 0; int j, k; - + for (j = ch; j >= 0; j--) { if (audio_config_p->chan_medium[j] == MEDIUM_RADIO) { for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) { @@ -1038,12 +1012,12 @@ void ptt_init (struct audio_s *audio_config_p) audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; } - + /* * Set initial state off. * ptt_set will invert output signal if appropriate. - */ + */ ptt_set (ot, ch, 0); } /* if parallel printer port method. */ @@ -1394,7 +1368,7 @@ void ptt_set (int ot, int chan, int ptt_signal) if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { const char *chip = save_audio_config_p->achan[chan].octrl[ot].out_gpio_name; int line = save_audio_config_p->achan[chan].octrl[ot].out_gpio_num; - int rc = gpiod_ctxless_set_value(chip, line, ptt, false, "direwolf", NULL, NULL); + int rc = gpio_common_set(save_audio_config_p->achan[chan].octrl[ot].gpio_num, ptt); if (ptt_debug_level >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf("PTT_METHOD_GPIOD chip: %s line: %d ptt: %d rc: %d\n", chip, line, ptt, rc); @@ -1402,7 +1376,7 @@ void ptt_set (int ot, int chan, int ptt_signal) } #endif /* USE_GPIOD */ #endif - + /* * Using parallel printer port? */ @@ -1413,7 +1387,7 @@ void ptt_set (int ot, int chan, int ptt_signal) ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) { char lpt_data; - //ssize_t n; + //ssize_t n; lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET); if (read (ptt_fd[chan][ot], &lpt_data, (size_t)1) != 1) { From 752c000c3674f40bf75c6b7f846580911a5fb951 Mon Sep 17 00:00:00 2001 From: Levente Kovacs Date: Sat, 30 Aug 2025 22:55:33 +0200 Subject: [PATCH 2/2] Fix initialization location --- src/gpio_common.c | 11 +++++++---- src/gpio_common.h | 13 ++++++++++--- src/ptt.c | 5 ++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/gpio_common.c b/src/gpio_common.c index b724dfc9..a870e8c6 100644 --- a/src/gpio_common.c +++ b/src/gpio_common.c @@ -8,7 +8,7 @@ #include "gpio_common.h" #define GPIO_MAX_LINES 32 -#define GPIO_CONSUMER "FLDIGI" +#define GPIO_CONSUMER "DIREWOLF" //Types typedef struct gpio_common { @@ -21,6 +21,9 @@ typedef struct gpio_common { static gpio_common_t gpio[GPIO_MAX_LINES]; + +// Function implementations + void gpio_common_init(void) { fprintf(stderr, "Initializing GPIO common structure\n"); for (gpio_num_t i = 0; i < GPIO_MAX_LINES; i++) { @@ -132,7 +135,7 @@ gpio_num_t gpio_common_open_line(const char *chip_name, unsigned int line, bool int gpio_common_release_line(gpio_num_t gpio_num) { if (gpio_num >= GPIO_MAX_LINES) { - return -1; + return GPIO_COMMON_ERR; } if (gpio[gpio_num].request != NULL) { @@ -146,7 +149,7 @@ int gpio_common_release_line(gpio_num_t gpio_num) { int gpio_common_set(gpio_num_t gpio_num, bool val) { if (gpio_num >= GPIO_MAX_LINES || gpio[gpio_num].request == NULL) { - return -1; + return GPIO_COMMON_ERR; } uint16_t gpiod_val; @@ -160,7 +163,7 @@ int gpio_common_set(gpio_num_t gpio_num, bool val) { int ret = gpiod_line_request_set_value(gpio[gpio_num].request, gpio[gpio_num].offset, gpiod_val); if (ret < 0) { fprintf(stderr, "Error setting line\n"); - return -1; + return GPIO_COMMON_ERR; } return 0; } diff --git a/src/gpio_common.h b/src/gpio_common.h index 96fb3048..05984ef0 100644 --- a/src/gpio_common.h +++ b/src/gpio_common.h @@ -1,3 +1,7 @@ +#ifndef GPIO_COMMON_H +#define GPIO_COMMON_H + + #ifdef __cplusplus extern "C" { #endif @@ -5,9 +9,10 @@ extern "C" { #include #include - - +// Types typedef uint16_t gpio_num_t; + +// Return values #define GPIO_COMMON_UNKNOWN UINT16_MAX #define GPIO_COMMON_OK 0 #define GPIO_COMMON_ERR -1 @@ -22,4 +27,6 @@ int gpio_common_set(gpio_num_t gpio_num, bool val); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif + +#endif // GPIO_COMMON_H diff --git a/src/ptt.c b/src/ptt.c index 2f1bb39a..90f1d14a 100644 --- a/src/ptt.c +++ b/src/ptt.c @@ -724,6 +724,10 @@ void ptt_init (struct audio_s *audio_config_p) int using_gpio; #endif +#if USE_GPIOD + gpio_common_init(); +#endif + #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("ptt_init ( ... )\n"); @@ -760,7 +764,6 @@ void ptt_init (struct audio_s *audio_config_p) audio_config_p->achan[ch].octrl[ot].ptt_invert); } } - gpio_common_init(); } /*