Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ftdi gpio yaml parsing #44

Merged
merged 2 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,29 @@ devices:
fastboot: cacafada
fastboot_set_active: true
fastboot_key_timeout: 2

- board: testboard2
console: /dev/serial/by-id/usb-1234-if00-port0
name: FTDI controller board
ftdi_gpio:
vendor: "0x0403"
product: "0x6011"
index: 0
power:
interface: D
line: 7
active_low: true
fastboot_key:
interface: D
line: 8
active_low: true
power_key:
interface: D
line: 3
active_low: true
usb_disconnect:
interface: D
line: 2
fastboot: cacafada
fastboot_set_active: true
fastboot_key_timeout: 2
8 changes: 5 additions & 3 deletions device_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ static void parse_board(struct device_parser *dp)
if (dev->control_options)
set_control_ops(dev, &local_gpio_ops);
continue;
} else if (!strcmp(key, "ftdi_gpio")) {
dev->control_options = ftdi_gpio_ops.parse_options(dp);
if (dev->control_options)
set_control_ops(dev, &ftdi_gpio_ops);
continue;
}

device_parser_expect(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH);
Expand All @@ -154,9 +159,6 @@ static void parse_board(struct device_parser *dp)
} else if (!strcmp(key, "alpaca")) {
dev->control_dev = strdup(value);
set_control_ops(dev, &alpaca_ops);
} else if (!strcmp(key, "ftdi_gpio")) {
dev->control_dev = strdup(value);
set_control_ops(dev, &ftdi_gpio_ops);
} else if (!strcmp(key, "external")) {
dev->control_dev = strdup(value);
set_control_ops(dev, &external_ops);
Expand Down
232 changes: 193 additions & 39 deletions ftdi-gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
Expand All @@ -39,9 +40,14 @@
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <yaml.h>

#include "cdba-server.h"
#include "device.h"
#include "device_parser.h"

#define TOKEN_LENGTH 16384
#define FTDI_INTERFACE_COUNT 4

#include <ftdi.h>

Expand All @@ -54,19 +60,27 @@ enum {
GPIO_COUNT
};

enum {
GPIO_ACTIVE_HIGH = 0,
GPIO_ACTIVE_LOW,
struct ftdi_gpio_options {
struct {
char *description;
char *vendor;
char *product;
char *serial;
unsigned int index;
char *devicenode;
} ftdi;
struct {
bool present;
unsigned int interface;
unsigned int offset;
bool active_low;
} gpios[GPIO_COUNT];
};

struct ftdi_gpio {
struct ftdi_context *gpio;
char *ftdi_device;
unsigned int ftdi_interface;
unsigned int gpio_present[GPIO_COUNT];
unsigned int gpio_offset[GPIO_COUNT];
unsigned int gpio_polarity[GPIO_COUNT];
unsigned char gpio_lines;
struct ftdi_gpio_options *options;
struct ftdi_context *interface[FTDI_INTERFACE_COUNT];
unsigned char gpio_lines[FTDI_INTERFACE_COUNT];
};

static int ftdi_gpio_device_power(struct ftdi_gpio *ftdi_gpio, bool on);
Expand All @@ -85,19 +99,20 @@ static int ftdi_gpio_toggle_io(struct ftdi_gpio *ftdi_gpio, unsigned int gpio, b
* Example: s:0xVEND:0xPROD:SERIAL;D;POWER,0,ACTIVE_LOW;FASTBOOT_KEY,1,ACTIVE_HIGH;POWER_KEY,2,ACTIVE_HIGH;USB_DISCONNECT,3,ACTIVE_LOW
*/

static void ftdi_gpio_parse_config(struct ftdi_gpio *ftdi_gpio, char *control_dev)
static void ftdi_gpio_parse_config(struct ftdi_gpio_options *options, char *value)
{
unsigned ftdi_interface = 0;
char *c, *interface;
size_t device_len;

// First libftdi description
c = strchr(control_dev, ';');
c = strchr(value, ';');
if (!c)
device_len = strlen(control_dev);
device_len = strlen(value);
else
device_len = c - control_dev;
device_len = c - value;

ftdi_gpio->ftdi_device = strndup(control_dev, device_len);
options->ftdi.description = strndup(value, device_len);

if (!c)
return;
Expand All @@ -110,7 +125,7 @@ static void ftdi_gpio_parse_config(struct ftdi_gpio *ftdi_gpio, char *control_de
*interface != 'D') {
errx(1, "Invalid interface '%c'", *interface);
}
ftdi_gpio->ftdi_interface = *interface - 'A';
ftdi_interface = *interface - 'A';

c = strchr(interface, ';');

Expand All @@ -119,7 +134,7 @@ static void ftdi_gpio_parse_config(struct ftdi_gpio *ftdi_gpio, char *control_de
char *name, *off, *pol;
unsigned gpio_type;
unsigned gpio_offset;
unsigned gpio_polarity;
bool active_low;

name = c + 1;
off = strchr(name, ',');
Expand Down Expand Up @@ -151,39 +166,173 @@ static void ftdi_gpio_parse_config(struct ftdi_gpio *ftdi_gpio, char *control_de
errx(1, "GPIOs offset invalid: '%u'", gpio_offset);

if (strncmp("ACTIVE_HIGH", pol, c - pol - 1) == 0)
gpio_polarity = GPIO_ACTIVE_HIGH;
active_low = false;
else if (strncmp("ACTIVE_LOW", pol, c - pol - 1) == 0)
gpio_polarity = GPIO_ACTIVE_LOW;
active_low = true;
else
errx(1, "GPIOs polarity invalid: '%s'", pol);

ftdi_gpio->gpio_present[gpio_type] = 1;
ftdi_gpio->gpio_offset[gpio_type] = gpio_offset;
ftdi_gpio->gpio_polarity[gpio_type] = gpio_polarity;
options->gpios[gpio_type].present = true;
options->gpios[gpio_type].interface = ftdi_interface;
options->gpios[gpio_type].offset = gpio_offset;
options->gpios[gpio_type].active_low = active_low;
}
}

void *ftdi_gpio_parse_options(struct device_parser *dp)
{
struct ftdi_gpio_options *options;
char value[TOKEN_LENGTH];
char key[TOKEN_LENGTH];

options = calloc(1, sizeof(*options));

/* Still accept legacy string */
if (device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH)) {
warnx("Please switch to yaml config for ftdi_gpio configuration");
ftdi_gpio_parse_config(options, value);
return options;
}

device_parser_accept(dp, YAML_MAPPING_START_EVENT, NULL, 0);

while (device_parser_accept(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH)) {
unsigned int gpio_id;

if (!strcmp(key, "power")) {
gpio_id = GPIO_POWER;
} else if (!strcmp(key, "fastboot_key")) {
gpio_id = GPIO_FASTBOOT_KEY;
} else if (!strcmp(key, "power_key")) {
gpio_id = GPIO_POWER_KEY;
} else if (!strcmp(key, "usb_disconnect")) {
gpio_id = GPIO_USB_DISCONNECT;
} else if (!strcmp(key, "output_enable")) {
gpio_id = GPIO_OUTPUT_ENABLE;
} else {
if (!device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH))
errx(1, "%s: expected value for \"%s\"", __func__, key);

if (!strcmp(key, "vendor")) {
options->ftdi.vendor = strdup(value);
} else if (!strcmp(key, "product")) {
options->ftdi.product = strdup(value);
} else if (!strcmp(key, "index")) {
options->ftdi.index = strtoul(value, NULL, 0);
} else if (!strcmp(key, "serial")) {
options->ftdi.serial = strdup(value);
} else if (!strcmp(key, "devicenode")) {
options->ftdi.devicenode = strdup(value);
} else
errx(1, "%s: unknown type \"%s\"", __func__, key);

continue;
}

device_parser_expect(dp, YAML_MAPPING_START_EVENT, NULL, 0);

while (device_parser_accept(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH)) {
device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH);

if (!strcmp(key, "line")) {
options->gpios[gpio_id].offset = strtoul(value, NULL, 0);
} else if (!strcmp(key, "interface")) {
if (*value != 'A' &&
*value != 'B' &&
*value != 'C' &&
*value != 'D') {
errx(1, "Invalid interface '%c' for gpio %u",
*value, gpio_id);
}
options->gpios[gpio_id].interface = *value - 'A';
} else if (!strcmp(key, "active_low")) {
if (!strcmp(value, "true"))
options->gpios[gpio_id].active_low = true;
} else {
errx(1, "%s: unknown option \"%s\"", __func__, key);
exit(1);
}
}

device_parser_expect(dp, YAML_MAPPING_END_EVENT, NULL, 0);

options->gpios[gpio_id].present = true;
}

device_parser_expect(dp, YAML_MAPPING_END_EVENT, NULL, 0);

return options;
}

static void *ftdi_gpio_open(struct device *dev)
{
struct ftdi_gpio *ftdi_gpio;
int ret;
int i, ret;

ftdi_gpio = calloc(1, sizeof(*ftdi_gpio));

ftdi_gpio_parse_config(ftdi_gpio, dev->control_dev);
ftdi_gpio->options = dev->control_options;

/* Setup libftdi string */
if (!ftdi_gpio->options->ftdi.description) {
if (ftdi_gpio->options->ftdi.devicenode)
asprintf(&ftdi_gpio->options->ftdi.description,
"d:%s", ftdi_gpio->options->ftdi.devicenode);
else if (ftdi_gpio->options->ftdi.vendor &&
ftdi_gpio->options->ftdi.product &&
ftdi_gpio->options->ftdi.serial)
asprintf(&ftdi_gpio->options->ftdi.description,
"s:%s:%s:%s",
ftdi_gpio->options->ftdi.vendor,
ftdi_gpio->options->ftdi.product,
ftdi_gpio->options->ftdi.serial);
else if (ftdi_gpio->options->ftdi.vendor &&
ftdi_gpio->options->ftdi.product &&
ftdi_gpio->options->ftdi.index > 0)
asprintf(&ftdi_gpio->options->ftdi.description,
"i:%s:%s:%u",
ftdi_gpio->options->ftdi.vendor,
ftdi_gpio->options->ftdi.product,
ftdi_gpio->options->ftdi.index);
else if (ftdi_gpio->options->ftdi.vendor &&
ftdi_gpio->options->ftdi.product)
asprintf(&ftdi_gpio->options->ftdi.description,
"i:%s:%s",
ftdi_gpio->options->ftdi.vendor,
ftdi_gpio->options->ftdi.product);
else
errx(1, "Incomplete FTDI description properties");
}

for (i = 0; i < GPIO_COUNT; ++i) {
unsigned int ftdi_interface;

if (!ftdi_gpio->options->gpios[i].present)
continue;

if ((ftdi_gpio->gpio = ftdi_new()) == 0)
errx(1, "failed to allocate ftdi gpio struct");
ftdi_interface = ftdi_gpio->options->gpios[i].interface;

ftdi_set_interface(ftdi_gpio->gpio, INTERFACE_A + ftdi_gpio->ftdi_interface);
/* Skip if interface already opened */
if (ftdi_gpio->interface[ftdi_interface])
continue;

ret = ftdi_usb_open_string(ftdi_gpio->gpio, ftdi_gpio->ftdi_device);
if (ret < 0)
errx(1, "failed to open ftdi gpio device '%s' (%d)", ftdi_gpio->ftdi_device, ret);
if ((ftdi_gpio->interface[ftdi_interface] = ftdi_new()) == 0)
errx(1, "failed to allocate ftdi gpio struct");

ftdi_set_bitmode(ftdi_gpio->gpio, 0xFF, BITMODE_BITBANG);
ftdi_set_interface(ftdi_gpio->interface[ftdi_interface],
INTERFACE_A + ftdi_interface);

if (ftdi_gpio->gpio_present[GPIO_POWER_KEY])
ret = ftdi_usb_open_string(ftdi_gpio->interface[ftdi_interface],
ftdi_gpio->options->ftdi.description);
if (ret < 0)
errx(1, "failed to open ftdi gpio device '%s' (%d)",
ftdi_gpio->options->ftdi.description, ret);

ftdi_set_bitmode(ftdi_gpio->interface[ftdi_interface],
0xFF, BITMODE_BITBANG);
}

if (ftdi_gpio->options->gpios[GPIO_POWER_KEY].present)
dev->has_power_key = true;

ftdi_gpio_device_power(ftdi_gpio, 0);
Expand All @@ -193,7 +342,7 @@ static void *ftdi_gpio_open(struct device *dev)
else
ftdi_gpio_device_usb(ftdi_gpio, 0);

if (ftdi_gpio->gpio_present[GPIO_OUTPUT_ENABLE])
if (ftdi_gpio->options->gpios[GPIO_OUTPUT_ENABLE].present)
ftdi_gpio_toggle_io(ftdi_gpio, GPIO_OUTPUT_ENABLE, 1);

usleep(500000);
Expand All @@ -203,22 +352,26 @@ static void *ftdi_gpio_open(struct device *dev)

static int ftdi_gpio_toggle_io(struct ftdi_gpio *ftdi_gpio, unsigned int gpio, bool on)
{
unsigned int ftdi_interface;
unsigned int bit;

if (!ftdi_gpio->gpio_present[gpio])
if (!ftdi_gpio->options->gpios[gpio].present)
return -EINVAL;

bit = ftdi_gpio->gpio_offset[gpio];
ftdi_interface = ftdi_gpio->options->gpios[gpio].interface;

bit = ftdi_gpio->options->gpios[gpio].offset;

if (ftdi_gpio->gpio_polarity[gpio])
if (ftdi_gpio->options->gpios[gpio].active_low)
on = !on;

if (on)
ftdi_gpio->gpio_lines |= (1 << bit);
ftdi_gpio->gpio_lines[ftdi_interface] |= (1 << bit);
else
ftdi_gpio->gpio_lines &= ~(1 << bit);
ftdi_gpio->gpio_lines[ftdi_interface] &= ~(1 << bit);

return ftdi_write_data(ftdi_gpio->gpio, &ftdi_gpio->gpio_lines, 1);
return ftdi_write_data(ftdi_gpio->interface[ftdi_interface],
&ftdi_gpio->gpio_lines[ftdi_interface], 1);
}

static int ftdi_gpio_device_power(struct ftdi_gpio *ftdi_gpio, bool on)
Expand Down Expand Up @@ -260,6 +413,7 @@ static void ftdi_gpio_key(struct device *dev, int key, bool asserted)
}

const struct control_ops ftdi_gpio_ops = {
.parse_options = ftdi_gpio_parse_options,
.open = ftdi_gpio_open,
.power = ftdi_gpio_power,
.usb = ftdi_gpio_usb,
Expand Down