diff --git a/ci/archlinux.sh b/ci/archlinux.sh index 037794a..fd659cb 100755 --- a/ci/archlinux.sh +++ b/ci/archlinux.sh @@ -20,6 +20,7 @@ pacman -Syu --noconfirm \ libftdi-compat \ libyaml \ systemd-libs \ + libgpiod \ pkgconf \ meson \ $PKGS_CC diff --git a/ci/debian.cross-compile.sh b/ci/debian.cross-compile.sh index de01ea2..04be957 100755 --- a/ci/debian.cross-compile.sh +++ b/ci/debian.cross-compile.sh @@ -22,6 +22,7 @@ apt install -y --no-install-recommends \ libftdi-dev:${ARCH} \ libudev-dev:${ARCH} \ libyaml-dev:${ARCH} \ + libgpiod-dev:${ARCH} \ gcc-`dpkg-architecture -a ${ARCH} -q DEB_TARGET_GNU_TYPE` echo "Install finished: $0" diff --git a/ci/debian.i386.sh b/ci/debian.i386.sh index 089c7b2..100cacc 100755 --- a/ci/debian.i386.sh +++ b/ci/debian.i386.sh @@ -23,6 +23,7 @@ apt install -y --no-install-recommends \ libftdi-dev:i386 \ libudev-dev:i386 \ libyaml-dev:i386 \ + libgpiod-dev:i386 \ $PKGS_CC echo "Install finished: $0" diff --git a/ci/debian.sh b/ci/debian.sh index 8683ab9..2fdc362 100755 --- a/ci/debian.sh +++ b/ci/debian.sh @@ -32,6 +32,7 @@ apt install -y --no-install-recommends \ libftdi-dev \ libudev-dev \ libyaml-dev \ + libgpiod-dev \ meson \ $PKGS_CC diff --git a/ci/fedora.sh b/ci/fedora.sh index 720c88a..72f186a 100755 --- a/ci/fedora.sh +++ b/ci/fedora.sh @@ -20,6 +20,7 @@ dnf -y install \ libftdi-devel \ libudev-devel \ libyaml-devel \ + libgpiod-devel \ meson \ $PKGS_CC diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..83166b1 --- /dev/null +++ b/config.h.in @@ -0,0 +1,7 @@ +#ifndef __CDBA_CONFIG_H__ +#define __CDBA_CONFIG_H__ + +/* libgpio major version */ +#define GPIOD_MAJOR_VERSION @gpiod_major_version@ + +#endif diff --git a/device.h b/device.h index b41e66a..e4c22f6 100644 --- a/device.h +++ b/device.h @@ -90,6 +90,7 @@ extern const struct control_ops alpaca_ops; extern const struct control_ops cdb_assist_ops; extern const struct control_ops conmux_ops; extern const struct control_ops ftdi_gpio_ops; +extern const struct control_ops local_gpio_ops; extern const struct control_ops external_ops; extern const struct control_ops qcomlt_dbg_ops; diff --git a/device_parser.c b/device_parser.c index d9f2800..c4279cf 100644 --- a/device_parser.c +++ b/device_parser.c @@ -147,6 +147,9 @@ static void parse_board(struct device_parser *dp) } else if (!strcmp(key, "ftdi_gpio")) { dev->control_dev = strdup(value); set_control_ops(dev, &ftdi_gpio_ops); + } else if (!strcmp(key, "local_gpio")) { + dev->control_dev = strdup(value); + set_control_ops(dev, &local_gpio_ops); } else if (!strcmp(key, "external")) { dev->control_dev = strdup(value); set_control_ops(dev, &external_ops); diff --git a/local-gpio-v1.c b/local-gpio-v1.c new file mode 100644 index 0000000..4b8fee6 --- /dev/null +++ b/local-gpio-v1.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local-gpio implementation for libgpiod major version 1 */ + +#include "local-gpio.h" + +#include + +int local_gpio_init(struct local_gpio *local_gpio) +{ + int i; + + local_gpio->chip = gpiod_chip_open_lookup(local_gpio->gpiochip_desc); + if (!local_gpio->chip) { + err(1, "Unable to open gpiochip '%s'", local_gpio->gpiochip_desc); + return -1; + } + + for (i = 0; i < GPIO_COUNT; ++i) { + struct gpiod_line_request_config cfg; + + if (!local_gpio->gpio_present[i]) + continue; + + cfg.consumer = "cdba"; + cfg.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT; + cfg.flags = 0; + + if (local_gpio->gpio_polarity[i]) + cfg.flags = GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW; + + local_gpio->gpio_line[i] = gpiod_chip_get_line(local_gpio->chip, + local_gpio->gpio_offset[i]); + + if (!local_gpio->gpio_line[i]) { + err(1, "Unable to find gpio %d offset %u", i, local_gpio->gpio_offset[i]); + return -1; + } + + if (gpiod_line_request(local_gpio->gpio_line[i], &cfg, 0)) { + err(1, "Unable to request gpio %d offset %u", i, local_gpio->gpio_offset[i]); + return -1; + } + } + + return 0; +} + +int local_gpio_set_value(struct local_gpio *local_gpio, unsigned int gpio, bool on) +{ + return gpiod_line_set_value(local_gpio->gpio_line[gpio], on); +} diff --git a/local-gpio-v2.c b/local-gpio-v2.c new file mode 100644 index 0000000..35f62ee --- /dev/null +++ b/local-gpio-v2.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local-gpio implementation for libgpiod major version 2 */ + +#include "local-gpio.h" + +#include + +int local_gpio_init(struct local_gpio *local_gpio) +{ + struct gpiod_request_config *req_cfg; + char *gpiochip_path; + int i; + + if (asprintf(&gpiochip_path, "/dev/%s", local_gpio->gpiochip_desc) < 0) { + free(local_gpio); + return -1; + } + + local_gpio->chip = gpiod_chip_open(gpiochip_path); + if (!local_gpio->chip) { + err(1, "Unable to open gpiochip '%s'", local_gpio->gpiochip_desc); + return -1; + } + + req_cfg = gpiod_request_config_new(); + if (!req_cfg) { + err(1, "Unable to allocate request config"); + return -1; + } + gpiod_request_config_set_consumer(req_cfg, "cdba"); + + for (i = 0; i < GPIO_COUNT; ++i) { + struct gpiod_line_settings *line_settings; + struct gpiod_line_config *line_cfg; + + if (!local_gpio->gpio_present[i]) + continue; + + line_settings = gpiod_line_settings_new(); + if (!line_settings) { + err(1, "Unable to allocate gpio line settings"); + return -1; + } + if (gpiod_line_settings_set_direction(line_settings, + GPIOD_LINE_DIRECTION_OUTPUT) < 0) { + err(1, "Unable to set line direction"); + return -1; + } + if (local_gpio->gpio_polarity[i]) { + gpiod_line_settings_set_active_low(line_settings, true); + } + if (gpiod_line_settings_set_output_value(line_settings, + GPIOD_LINE_VALUE_INACTIVE) < 0) { + err(1, "Unable to set line output value"); + return -1; + } + + line_cfg = gpiod_line_config_new(); + if (!line_cfg) { + err(1, "Unable to allocate gpio line settings"); + return -1; + } + if (gpiod_line_config_add_line_settings(line_cfg, &local_gpio->gpio_offset[i], 1, + line_settings) < 0) { + err(1, "Unable to set line config"); + return -1; + } + + local_gpio->gpio_line[i] = gpiod_chip_request_lines(local_gpio->chip, + req_cfg, line_cfg); + + if (!local_gpio->gpio_line[i]) { + err(1, "Unable to request gpio %d offset %u", i, local_gpio->gpio_offset[i]); + return -1; + } + } + + return 0; +} + +int local_gpio_set_value(struct local_gpio *local_gpio, unsigned int gpio, bool on) +{ + return gpiod_line_request_set_value(local_gpio->gpio_line[gpio], 0, + on ? GPIOD_LINE_VALUE_ACTIVE + : GPIOD_LINE_VALUE_INACTIVE); +} diff --git a/local-gpio.c b/local-gpio.c new file mode 100644 index 0000000..221f4a7 --- /dev/null +++ b/local-gpio.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdba-server.h" +#include "device.h" +#include "local-gpio.h" + +static int local_gpio_device_power(struct local_gpio *local_gpio, bool on); +static void local_gpio_device_usb(struct local_gpio *local_gpio, bool on); + +/* + * fdio_gpio parameter: ;[;...] + * - gpiod chip description: "gpiochip0" + * - gpios: type,id,polarity + * - type: POWER, FASTBOOT_KEY, POWER_KEY or USB_DISCONNECT + * - offset: line offset in chip + * - polarity: ACTIVE_HIGH or ACTIVE_LOW + * + * Example: gpiochip0;POWER,0,ACTIVE_LOW;FASTBOOT_KEY,1,ACTIVE_HIGH;POWER_KEY,2,ACTIVE_HIGH;USB_DISCONNECT,3,ACTIVE_LOW + */ + +static void local_gpio_parse_config(struct local_gpio *local_gpio, char *control_dev) +{ + char *c; + size_t device_len; + + // First liblocal description + c = strchr(control_dev, ';'); + if (!c) + device_len = strlen(control_dev); + else + device_len = c - control_dev; + + local_gpio->gpiochip_desc = strndup(control_dev, device_len); + + if (!c) + return; + + // GPIOs + while(c) { + char *name, *off, *pol; + unsigned gpio_type; + unsigned gpio_offset; + unsigned gpio_polarity; + + name = c + 1; + off = strchr(name, ','); + if (!off) + errx(1, "GPIOs config invalid"); + off += 1; + pol = strchr(off, ','); + if (!pol) + errx(1, "GPIOs config invalid"); + pol += 1; + + c = strchr(pol, ';'); + + if (strncmp("POWER", name, off - name - 1) == 0) + gpio_type = GPIO_POWER; + else if (strncmp("FASTBOOT_KEY", name, off - name - 1) == 0) + gpio_type = GPIO_FASTBOOT_KEY; + else if (strncmp("POWER_KEY", name, off - name - 1) == 0) + gpio_type = GPIO_POWER_KEY; + else if (strncmp("USB_DISCONNECT", name, off - name - 1) == 0) + gpio_type = GPIO_USB_DISCONNECT; + else + errx(1, "GPIOs type invalid: '%s'", name); + + gpio_offset = strtoul(off, NULL, 0); + + if (strncmp("ACTIVE_HIGH", pol, c - pol - 1) == 0) + gpio_polarity = GPIO_ACTIVE_HIGH; + else if (strncmp("ACTIVE_LOW", pol, c - pol - 1) == 0) + gpio_polarity = GPIO_ACTIVE_LOW; + else + errx(1, "GPIOs polarity invalid: '%s'", pol); + + local_gpio->gpio_present[gpio_type] = 1; + local_gpio->gpio_offset[gpio_type] = gpio_offset; + local_gpio->gpio_polarity[gpio_type] = gpio_polarity; + } +} + +static void *local_gpio_open(struct device *dev) +{ + struct local_gpio *local_gpio; + + local_gpio = calloc(1, sizeof(*local_gpio)); + + local_gpio_parse_config(local_gpio, dev->control_dev); + + if (local_gpio_init(local_gpio) < 0) + return NULL; + + if (local_gpio->gpio_present[GPIO_POWER_KEY]) + dev->has_power_key = true; + + local_gpio_device_power(local_gpio, 0); + + if (dev->usb_always_on) + local_gpio_device_usb(local_gpio, 1); + else + local_gpio_device_usb(local_gpio, 0); + + usleep(500000); + + return local_gpio; +} + +static int local_gpio_toggle_io(struct local_gpio *local_gpio, unsigned int gpio, bool on) +{ + if (!local_gpio->gpio_present[gpio]) + return -EINVAL; + + if (local_gpio_set_value(local_gpio, gpio, on) < 0) + warnx("%s:%d unable to set value", __func__, __LINE__); + + return 0; +} + +static int local_gpio_device_power(struct local_gpio *local_gpio, bool on) +{ + return local_gpio_toggle_io(local_gpio, GPIO_POWER, on); +} + +static void local_gpio_device_usb(struct local_gpio *local_gpio, bool on) +{ + local_gpio_toggle_io(local_gpio, GPIO_USB_DISCONNECT, on); +} + +static int local_gpio_power(struct device *dev, bool on) +{ + struct local_gpio *local_gpio = dev->cdb; + + return local_gpio_device_power(local_gpio, on); +} + +static void local_gpio_usb(struct device *dev, bool on) +{ + struct local_gpio *local_gpio = dev->cdb; + + local_gpio_device_usb(local_gpio, on); +} + +static void local_gpio_key(struct device *dev, int key, bool asserted) +{ + struct local_gpio *local_gpio = dev->cdb; + + switch (key) { + case DEVICE_KEY_FASTBOOT: + local_gpio_toggle_io(local_gpio, GPIO_FASTBOOT_KEY, asserted); + break; + case DEVICE_KEY_POWER: + local_gpio_toggle_io(local_gpio, GPIO_POWER_KEY, asserted); + break; + } +} + +const struct control_ops local_gpio_ops = { + .open = local_gpio_open, + .power = local_gpio_power, + .usb = local_gpio_usb, + .key = local_gpio_key, +}; diff --git a/local-gpio.h b/local-gpio.h new file mode 100644 index 0000000..2e07bb4 --- /dev/null +++ b/local-gpio.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _LOCAL_GPIO_H_ +#define _LOCAL_GPIO_H_ + +enum { + GPIO_POWER = 0, // Power input enable + GPIO_FASTBOOT_KEY, // Usually volume key + GPIO_POWER_KEY, // Key to power the device + GPIO_USB_DISCONNECT, // Simulate main USB connection + GPIO_COUNT +}; + +enum { + GPIO_ACTIVE_HIGH = 0, + GPIO_ACTIVE_LOW, +}; + +struct local_gpio { + char * gpiochip_desc; + void *chip; + unsigned int gpio_present[GPIO_COUNT]; + unsigned int gpio_offset[GPIO_COUNT]; + unsigned int gpio_polarity[GPIO_COUNT]; + void *gpio_line[GPIO_COUNT]; +}; + +int local_gpio_init(struct local_gpio *local_gpio); +int local_gpio_set_value(struct local_gpio *local_gpio, unsigned int gpio, bool on); + +#endif /* _LOCAL_GPIO_H_ */ diff --git a/meson.build b/meson.build index 15254b6..c35420f 100644 --- a/meson.build +++ b/meson.build @@ -52,8 +52,10 @@ if not ftdi_dep.found() ftdi_dep = dependency('libftdi') endif +gpiod_dep = dependency('libgpiod') server_deps = [dependency('libudev'), dependency('yaml-0.1'), + gpiod_dep, ftdi_dep] server_srcs = ['cdba-server.c', 'cdb_assist.c', @@ -65,9 +67,17 @@ server_srcs = ['cdba-server.c', 'fastboot.c', 'alpaca.c', 'ftdi-gpio.c', + 'local-gpio.c', 'console.c', 'qcomlt_dbg.c', 'ppps.c'] + +if gpiod_dep.version().version_compare('>=2.0') + server_srcs += ['local-gpio-v2.c'] +else + server_srcs += ['local-gpio-v1.c'] +endif + executable('cdba-server', server_srcs, dependencies : server_deps,