Steve Markgraf has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/71801 )
Change subject: programmer: Add bitbanging programmer driver for Linux libgpiod ......................................................................
programmer: Add bitbanging programmer driver for Linux libgpiod
With this driver, any single board computer, old smartphone, etc. with a few spare GPIOs can be used for flashrom.
An example invocation looks like this:
flashrom -p linux_gpiod:gpiochip=0,cs=18,sck=19,mosi=13,miso=56
This uses /dev/gpiochip0 with the specified GPIO numbers for the SPI lines. All arguments are required.
Reading of a 2048 kB flash chip on a Qualcomm MSM8916 SoC @800 MHz:
Found GigaDevice flash chip "GD25LQ16" (2048 kB, SPI) on linux_gpiod. real 1m 33.96s
Change-Id: Icad3eb7764f28feaea51bda3a7893da724c86d06 Signed-off-by: Steve Markgraf steve@steve-m.de --- M Makefile M include/programmer.h A linux_gpiod.c M programmer_table.c 4 files changed, 225 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/01/71801/1
diff --git a/Makefile b/Makefile index 425b58c..33de024 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,9 @@ CONFIG_REALTEK_MST_I2C_SPI \ CONFIG_MEDIATEK_I2C_SPI \
+DEPENDS_ON_LIBGPIOD := \ + CONFIG_LINUX_GPIOD \ + ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no) $(call disable_all,$(DEPENDS_ON_LIBUSB1)) endif @@ -233,6 +236,10 @@ CONFIG_LIBPCI_CFLAGS := $(call dependency_cflags, libpci) CONFIG_LIBPCI_LDFLAGS := $(call dependency_ldflags, libpci)
+CONFIG_LIBGPIOD_VERSION := $(call dependency_version, libgpiod) +CONFIG_LIBGPIOD_CFLAGS := $(call dependency_cflags, libgpiod) +CONFIG_LIBGPIOD_LDFLAGS := $(call dependency_ldflags, libgpiod) + # Determine the destination OS, architecture and endian # IMPORTANT: The following lines must be placed before TARGET_OS, ARCH or ENDIAN # is ever used (of course), but should come after any lines setting CC because @@ -247,6 +254,7 @@ HAS_LIBJAYLINK := $(call find_dependency, libjaylink) HAS_LIBUSB1 := $(call find_dependency, libusb-1.0) HAS_LIBPCI := $(call find_dependency, libpci) +HAS_LIBGPIOD := $(call find_dependency, libgpiod)
HAS_PCI_OLD_GET_DEV := $(call c_compile_test, Makefile.d/pci_old_get_dev_test.c, $(CONFIG_LIBPCI_CFLAGS)) HAS_FT232H := $(call c_compile_test, Makefile.d/ft232h_test.c, $(CONFIG_LIBFTDI1_CFLAGS)) @@ -350,6 +358,10 @@ $(call mark_unsupported,$(DEPENDS_ON_LIBUSB1)) endif
+ifeq ($(HAS_LIBGPIOD), no) +$(call mark_unsupported,$(DEPENDS_ON_LIBGPIOD)) +endif + ifeq ($(HAS_SERIAL), no) $(call mark_unsupported, $(DEPENDS_ON_SERIAL)) endif @@ -506,9 +518,10 @@ # Always enable Marvell SATA controllers for now. CONFIG_SATAMV ?= yes
-# Enable Linux spidev and MTD interfaces by default. We disable them on non-Linux targets. +# Enable Linux spidev, MTD and gpiod interfaces by default. We disable them on non-Linux targets. CONFIG_LINUX_MTD ?= yes CONFIG_LINUX_SPI ?= yes +CONFIG_LINUX_GPIOD ?= yes
# Always enable ITE IT8212F PATA controllers for now. CONFIG_IT8212 ?= yes @@ -757,6 +770,11 @@ PROGRAMMER_OBJS += linux_spi.o endif
+ifeq ($(CONFIG_LINUX_GPIOD), yes) +FEATURE_FLAGS += -D'CONFIG_LINUX_GPIOD=1' +PROGRAMMER_OBJS += linux_gpiod.o +endif + ifeq ($(CONFIG_MSTARDDC_SPI), yes) FEATURE_FLAGS += -D'CONFIG_MSTARDDC_SPI=1' PROGRAMMER_OBJS += mstarddc_spi.o @@ -865,6 +883,12 @@ endif endif
+USE_LIBGPIOD := $(if $(call filter_deps,$(DEPENDS_ON_LIBGPIOD)),yes,no) +ifeq ($(USE_LIBGPIOD), yes) +override CFLAGS += $(CONFIG_LIBGPIOD_CFLAGS) +override LDFLAGS += $(CONFIG_LIBGPIOD_LDFLAGS) +endif + USE_LIB_NI845X := $(if $(call filter_deps,$(DEPENDS_ON_LIB_NI845X)),yes,no) ifeq ($(USE_LIB_NI845X), yes) override CFLAGS += $(CONFIG_LIB_NI845X_CFLAGS) @@ -943,6 +967,11 @@ echo " CFLAGS: $(CONFIG_LIBFTDI1_CFLAGS)"; \ echo " LDFLAGS: $(CONFIG_LIBFTDI1_LDFLAGS)"; \ fi + @echo Dependency libgpiod found: $(HAS_LIBGPIOD) $(CONFIG_LIBGPIOD_VERSION) + @if [ $(HAS_LIBGPIOD) = yes ]; then \ + echo " CFLAGS: $(CONFIG_LIBGPIOD_CFLAGS)"; \ + echo " LDFLAGS: $(CONFIG_LIBGPIOD_LDFLAGS)"; \ + fi @echo "Checking for header "mtd/mtd-user.h": $(HAS_LINUX_MTD)" @echo "Checking for header "linux/spi/spidev.h": $(HAS_LINUX_SPI)" @echo "Checking for header "linux/i2c-dev.h": $(HAS_LINUX_I2C)" diff --git a/include/programmer.h b/include/programmer.h index 67e7b4a..e364c63 100644 --- a/include/programmer.h +++ b/include/programmer.h @@ -72,6 +72,7 @@ extern const struct programmer_entry programmer_jlink_spi; extern const struct programmer_entry programmer_linux_mtd; extern const struct programmer_entry programmer_linux_spi; +extern const struct programmer_entry programmer_linux_gpiod; extern const struct programmer_entry programmer_parade_lspcon; extern const struct programmer_entry programmer_mediatek_i2c_spi; extern const struct programmer_entry programmer_mstarddc_spi; diff --git a/linux_gpiod.c b/linux_gpiod.c new file mode 100644 index 0000000..1e0fa67 --- /dev/null +++ b/linux_gpiod.c @@ -0,0 +1,165 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2023 Steve Markgraf steve@steve-m.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <gpiod.h> +#include "programmer.h" +#include "spi.h" +#include "flash.h" + +#define CONSUMER "flashrom" + +struct gpiod_spi_data { + struct gpiod_chip *chip; + struct gpiod_line_bulk bulk; + struct gpiod_line *cs_line, *sck_line, *mosi_line, *miso_line; +}; + +static void linux_gpiod_bitbang_set_cs(int val, void *spi_data) +{ + struct gpiod_spi_data *data = spi_data; + if (gpiod_line_set_value(data->cs_line, val) < 0) + msg_perr("Setting cs line failed\n"); +} + +static void linux_gpiod_bitbang_set_sck(int val, void *spi_data) +{ + struct gpiod_spi_data *data = spi_data; + if (gpiod_line_set_value(data->sck_line, val) < 0) + msg_perr("Setting sck line failed\n"); +} + +static void linux_gpiod_bitbang_set_mosi(int val, void *spi_data) +{ + struct gpiod_spi_data *data = spi_data; + if (gpiod_line_set_value(data->mosi_line, val) < 0) + msg_perr("Setting sck line failed\n"); +} + +static int linux_gpiod_bitbang_get_miso(void *spi_data) +{ + struct gpiod_spi_data *data = spi_data; + int r = gpiod_line_get_value(data->miso_line); + if (r < 0) + msg_perr("Getting miso line failed\n"); + return r; +} + +static const struct bitbang_spi_master bitbang_spi_master_gpiod = { + .set_cs = linux_gpiod_bitbang_set_cs, + .set_sck = linux_gpiod_bitbang_set_sck, + .set_mosi = linux_gpiod_bitbang_set_mosi, + .get_miso = linux_gpiod_bitbang_get_miso, +}; + +static int linux_gpiod_spi_shutdown(void *spi_data) +{ + struct gpiod_spi_data *data = spi_data; + + if (gpiod_line_bulk_num_lines(&data->bulk) > 0) + gpiod_line_release_bulk(&data->bulk); + + if (data->chip) + gpiod_chip_close(data->chip); + + free(data); + + return 0; +} + +static int linux_gpiod_spi_init(const struct programmer_cfg *cfg) +{ + struct gpiod_spi_data *data = NULL; + struct gpiod_chip *chip = NULL; + const char *param_str[] = { "cs", "sck", "mosi", "miso", "gpiochip" }; + unsigned int param_int[ARRAY_SIZE(param_str)]; + unsigned int i; + int r; + + for (i = 0; i < ARRAY_SIZE(param_str); i++) { + char *param = extract_programmer_param_str(cfg, param_str[i]); + if (param) { + param_int[i] = atoi(param); + free(param); + } else { + msg_perr("Missing required programmer parameter %s=<n>\n", param_str[i]); + goto err_exit; + } + } + + data = calloc(1, sizeof(*data)); + if (!data) { + msg_perr("Unable to allocate space for SPI master data\n"); + goto err_exit; + } + + chip = gpiod_chip_open_by_number(param_int[4]); + if (!chip) { + msg_perr("Failed to open gpiochip: %s\n", strerror(errno)); + goto err_exit; + } + + data->chip = chip; + + if (gpiod_chip_get_lines(chip, param_int, 4, &data->bulk)) { + msg_perr("Error getting GPIO lines\n"); + goto err_exit; + } + + data->cs_line = gpiod_line_bulk_get_line(&data->bulk, 0); + data->sck_line = gpiod_line_bulk_get_line(&data->bulk, 1); + data->mosi_line = gpiod_line_bulk_get_line(&data->bulk, 2); + data->miso_line = gpiod_line_bulk_get_line(&data->bulk, 3); + + r = gpiod_line_request_output(data->cs_line, CONSUMER, 1); + r |= gpiod_line_request_output(data->sck_line, CONSUMER, 1); + r |= gpiod_line_request_output(data->mosi_line, CONSUMER, 1); + r |= gpiod_line_request_input(data->miso_line, CONSUMER); + + if (r < 0) { + msg_perr("Requesting GPIO lines failed\n"); + goto err_exit; + } + + if (register_shutdown(linux_gpiod_spi_shutdown, data)) + goto err_exit; + + if (register_spi_bitbang_master(&bitbang_spi_master_gpiod, data)) + return 1; /* shutdown function does the cleanup */ + + return 0; + +err_exit: + if (data) { + if (gpiod_line_bulk_num_lines(&data->bulk) > 0) + gpiod_line_release_bulk(&data->bulk); + + free(data); + } + + if (chip) + gpiod_chip_close(chip); + return 1; +} + +const struct programmer_entry programmer_linux_gpiod = { + .name = "linux_gpiod", + .type = OTHER, + .devs.note = "Device file /dev/gpiochip<n>\n", + .init = linux_gpiod_spi_init, +}; diff --git a/programmer_table.c b/programmer_table.c index d58a155..848cd4e 100644 --- a/programmer_table.c +++ b/programmer_table.c @@ -124,6 +124,10 @@ &programmer_linux_spi, #endif
+#if CONFIG_LINUX_GPIOD == 1 + &programmer_linux_gpiod, +#endif + #if CONFIG_PARADE_LSPCON == 1 &programmer_parade_lspcon, #endif