Patchwork [RFC] Add support for Willem EPROM programmer

login
register
about
Submitter Ondrej Zary
Date 2013-03-20 21:58:44
Message ID <201303202258.45144.linux@rainbow-software.org>
Download mbox | patch
Permalink /patch/3890/
State New
Headers show

Comments

Ondrej Zary - 2013-03-20 21:58:44
Hello,
this patch adds support for Willem EPROM programmer to flashrom. It includes
parallel and LPC/FWH chips (using the socket on PCB4.0 - hope that it will
work with LPC/FWH adapter on other PCB versions).

It seems to work, according to some basic tests (W49F002U, AT29C010A,
SST 39SF020A, SST 49LF002A).

I don't like the parallel port access code (taken from rayer_spi) - flashrom
should have a parallel port access layer (.programmer_type = LPT) which could
do the initialization (setting port address/path), caching and access the port
either directly or using ppdev on Linux.
Maybe something simple like would be enough:
lpt_set_data_mask(uint8_t data, uint8_t mask);
lpt_set_ctrl_mask(uint8_t data, uint8_t mask);
lpt_get_status();

Signed-off-by: Ondrej Zary <linux@rainbow-software.org>
Антон Кочков - 2013-03-21 12:38:22
Just for reference - see this patch too
http://patchwork.coreboot.org/patch/1736/

Best regards,
Anton Kochkov.


On Thu, Mar 21, 2013 at 1:58 AM, Ondrej Zary <linux@rainbow-software.org> wrote:
> Hello,
> this patch adds support for Willem EPROM programmer to flashrom. It includes
> parallel and LPC/FWH chips (using the socket on PCB4.0 - hope that it will
> work with LPC/FWH adapter on other PCB versions).
>
> It seems to work, according to some basic tests (W49F002U, AT29C010A,
> SST 39SF020A, SST 49LF002A).
>
> I don't like the parallel port access code (taken from rayer_spi) - flashrom
> should have a parallel port access layer (.programmer_type = LPT) which could
> do the initialization (setting port address/path), caching and access the port
> either directly or using ppdev on Linux.
> Maybe something simple like would be enough:
> lpt_set_data_mask(uint8_t data, uint8_t mask);
> lpt_set_ctrl_mask(uint8_t data, uint8_t mask);
> lpt_get_status();
>
> Signed-off-by: Ondrej Zary <linux@rainbow-software.org>
>
> diff -urp -x .svn flashrom-orig//flashrom.c flashrom/flashrom.c
> --- flashrom-orig//flashrom.c   2013-03-20 22:07:14.000000000 +0100
> +++ flashrom/flashrom.c 2013-03-17 11:32:37.000000000 +0100
> @@ -308,6 +308,19 @@ const struct programmer_entry programmer
>         },
>  #endif
>
> +#if CONFIG_WILLEM == 1
> +       {
> +               .name                   = "willem",
> +               .type                   = OTHER,
> +                                       /* FIXME */
> +               .devs.note              = "Willem EPROM progammer on parallel port\n",
> +               .init                   = willem_init,
> +               .map_flash_region       = fallback_map,
> +               .unmap_flash_region     = fallback_unmap,
> +               .delay                  = internal_delay,
> +       },
> +#endif
> +
>         {0}, /* This entry corresponds to PROGRAMMER_INVALID. */
>  };
>
> diff -urp -x .svn flashrom-orig//Makefile flashrom/Makefile
> --- flashrom-orig//Makefile     2013-03-20 22:07:14.000000000 +0100
> +++ flashrom/Makefile   2013-03-17 11:33:31.000000000 +0100
> @@ -210,6 +210,11 @@ UNSUPPORTED_FEATURES += CONFIG_SATAMV=ye
>  else
>  override CONFIG_SATAMV = no
>  endif
> +ifeq ($(CONFIG_WILLEM), yes)
> +UNSUPPORTED_FEATURES += CONFIG_WILLEM=yes
> +else
> +override CONFIG_WILLEM = no
> +endif
>  endif
>
>  ifeq ($(TARGET_OS), libpayload)
> @@ -289,6 +294,11 @@ UNSUPPORTED_FEATURES += CONFIG_SATAMV=ye
>  else
>  override CONFIG_SATAMV = no
>  endif
> +ifeq ($(CONFIG_WILLEM), yes)
> +UNSUPPORTED_FEATURES += CONFIG_WILLEM=yes
> +else
> +override CONFIG_WILLEM = no
> +endif
>  endif
>
>  ###############################################################################
> @@ -382,6 +392,9 @@ CONFIG_SATAMV ?= yes
>  # Enable Linux spidev interface by default. We disable it on non-Linux targets.
>  CONFIG_LINUX_SPI ?= yes
>
> +# Always enable Willem EPROM programmer hardware for now.
> +CONFIG_WILLEM ?= yes
> +
>  # Disable wiki printing by default. It is only useful if you have wiki access.
>  CONFIG_PRINT_WIKI ?= no
>
> @@ -549,6 +562,13 @@ FEATURE_CFLAGS += $(shell LC_ALL=C grep
>  PROGRAMMER_OBJS += linux_spi.o
>  endif
>
> +ifeq ($(CONFIG_WILLEM), yes)
> +FEATURE_CFLAGS += -D'CONFIG_WILLEM=1'
> +PROGRAMMER_OBJS += willem.o
> +# Actually, NEED_PCI is wrong. NEED_IOPORT_ACCESS would be more correct.
> +NEED_PCI := yes
> +endif
> +
>  ifeq ($(NEED_SERIAL), yes)
>  LIB_OBJS += serial.o
>  endif
> diff -urp -x .svn flashrom-orig//programmer.h flashrom/programmer.h
> --- flashrom-orig//programmer.h 2013-03-20 22:07:14.000000000 +0100
> +++ flashrom/programmer.h       2013-03-20 22:08:55.000000000 +0100
> @@ -87,6 +87,9 @@ enum programmer {
>  #if CONFIG_LINUX_SPI == 1
>         PROGRAMMER_LINUX_SPI,
>  #endif
> +#if CONFIG_WILLEM == 1
> +       PROGRAMMER_WILLEM,
> +#endif
>         PROGRAMMER_INVALID /* This must always be the last entry. */
>  };
>
> @@ -459,6 +462,11 @@ int linux_spi_init(void);
>  int dediprog_init(void);
>  #endif
>
> +/* willem.c */
> +#if CONFIG_WILLEM == 1
> +int willem_init(void);
> +#endif
> +
>  /* flashrom.c */
>  struct decode_sizes {
>         uint32_t parallel;
> --- flashrom-orig/willem.c      1970-01-01 01:00:00.000000000 +0100
> +++ flashrom/willem.c   2013-03-20 22:56:04.000000000 +0100
> @@ -0,0 +1,296 @@
> +/*
> + * This file is part of the flashrom project.
> + *
> + * Copyright (C) 2013 Ondrej Zary
> + * Copyright (C) 2011 Carl-Daniel Hailfinger
> + *
> + * 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; version 2 of the License.
> + *
> + * 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
> + */
> +
> +/* Driver for the Willem EPROM Programmer on parallel port.
> + * This driver can access parallel flash and LPC/FWH chips.
> + * For SPI chips, see rayer_spi.
> + *
> + * Willem HW description:
> + * LPT STROBE signal is used to control VPP (0 means ENABLED)
> + * LPT INIT signal is used to control VCC (1 means ENABLED)
> + * LPT AUTOLF signal is used to switch data direction and flash OE signal (1=4503 enabled, OE disabled)
> + * LPT SELIN signal is called S4 on PCB4.0 schematic and used as flash CE or WE signal (selected by DIP switch)
> + *
> + * LPT ACK input is used for reading flash D0..D7 through 4014 shift register
> + * LPT BUSY input is used for reading data in serial memory programming
> + * LPT PAPER_END, SELECT and ERROR inputs are not used
> + *
> + * LPT D0..D7 pins are used for two purposes, switched by AUTOLF
> + *  1. data output (through 2x4503 buffers) to flash D0..D7
> + *  2. CLK (D0) and D (D1) outputs to 3x4015 address shift registers
> + * D2 is also used as (inverted) CLK for 4014 shift register
> + */
> +
> +#if defined(__i386__) || defined(__x86_64__)
> +
> +#include <stdlib.h>
> +#include <string.h>
> +#include "flash.h"
> +#include "programmer.h"
> +#include "hwaccess.h"
> +
> +static uint16_t lpt_iobase;
> +/* Cached value of last byte sent. */
> +static uint8_t lpt_outbyte[3];
> +
> +static void willem_chip_writeb(const struct flashctx *flash, uint8_t val,
> +                              chipaddr addr);
> +static uint8_t willem_chip_readb(const struct flashctx *flash,
> +                                const chipaddr addr);
> +static const struct par_programmer par_programmer_willem = {
> +               .chip_readb             = willem_chip_readb,
> +               .chip_readw             = fallback_chip_readw,
> +               .chip_readl             = fallback_chip_readl,
> +               .chip_readn             = fallback_chip_readn,
> +               .chip_writeb            = willem_chip_writeb,
> +               .chip_writew            = fallback_chip_writew,
> +               .chip_writel            = fallback_chip_writel,
> +               .chip_writen            = fallback_chip_writen,
> +};
> +
> +static int willem_shutdown(void *data)
> +{
> +       /* Disable VPP and VCC. */
> +       lpt_outbyte[2] = (1 << 0);
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +       return 0;
> +}
> +
> +static void willem_set_address(uint32_t address, int highest_bit);
> +static void willem_set_data(uint8_t data);
> +static uint8_t willem_get_data(void);
> +static void willem_set_write(int enable);
> +
> +int willem_init(void)
> +{
> +       char *arg = NULL;
> +       int lpc_fwh = 0, vpp_enable = 0;
> +       /* This is copied from rayer_spi */
> +       /* Non-default port requested? */
> +       arg = extract_programmer_param("iobase");
> +       if (arg) {
> +               char *endptr = NULL;
> +               unsigned long tmp;
> +               tmp = strtoul(arg, &endptr, 0);
> +               /* Port 0, port >0x10000, unaligned ports and garbage strings
> +                * are rejected.
> +                */
> +               if (!tmp || (tmp >= 0x10000) || (tmp & 0x3) ||
> +                   (*endptr != '\0')) {
> +                       /* Using ports below 0x100 is a really bad idea, and
> +                        * should only be done if no port between 0x100 and
> +                        * 0xfffc works due to routing issues.
> +                        */
> +                       msg_perr("Error: iobase= specified, but the I/O base "
> +                                "given was invalid.\nIt must be a multiple of "
> +                                "0x4 and lie between 0x100 and 0xfffc.\n");
> +                       free(arg);
> +                       return 1;
> +               } else {
> +                       lpt_iobase = (uint16_t)tmp;
> +                       msg_pinfo("Non-default I/O base requested. This will "
> +                                 "not change the hardware settings.\n");
> +               }
> +       } else {
> +               /* Pick a default value for the I/O base. */
> +               lpt_iobase = 0x378;
> +       }
> +       free(arg);
> +
> +       msg_pdbg("Using address 0x%x as I/O base for parallel port access.\n",
> +                lpt_iobase);
> +
> +       arg = extract_programmer_param("type");
> +       if (arg) {
> +               if (!strcmp(arg, "lpc_fwh"))
> +                       lpc_fwh = 1;
> +               else if (!strcmp(arg, "12v"))
> +                       vpp_enable = 1;
> +               free(arg);
> +       }
> +
> +       if (rget_io_perms())
> +               return 1;
> +
> +       /* Get the initial value before writing to any line. */
> +       lpt_outbyte[0] = INB(lpt_iobase);
> +       lpt_outbyte[2] = INB(lpt_iobase + 2);
> +
> +       if (register_shutdown(willem_shutdown, NULL))
> +               return 1;
> +
> +       /* Enable VCC, disable VPP */
> +       lpt_outbyte[2] = (1 << 2) | (1 << 0);
> +       if (lpc_fwh || vpp_enable)      /* enable VPP for LPC/FWH (used to generate VCC) */
> +               lpt_outbyte[2] &= ~(1 << 0); /* inverted */
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +
> +       max_rom_decode.parallel = 1 << 24; /* 24 address lines = 16 MB */
> +       register_par_programmer(&par_programmer_willem, lpc_fwh ? (BUS_LPC | BUS_FWH) : BUS_PARALLEL);
> +
> +       return 0;
> +}
> +
> +/* clock-in the address into 4015 shift registers (3 x 8-bit) that drive A0..A23 signals */
> +/* A23 is not connected anywhere on PCB4.0 */
> +static void willem_set_address(uint32_t address, int highest_bit)
> +{
> +       int i;
> +       lpt_outbyte[2] &= ~(1 << 1); /* set address mode */
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +       /* start with MSB */
> +       for (i = highest_bit; i >= 0; i--) {
> +               /* clear CLK and D */
> +               lpt_outbyte[0] &= ~((1 << 0) | (1 << 1));
> +               if (address & (1 << i))
> +                       lpt_outbyte[0] |= (1 << 1); /* D1 */
> +               OUTB(lpt_outbyte[0], lpt_iobase);
> +               /* set CLK */
> +               lpt_outbyte[0] |= (1 << 0); /* D0 */
> +               OUTB(lpt_outbyte[0], lpt_iobase);
> +       }
> +       /* clear CLK */
> +       lpt_outbyte[0] &= ~(1 << 0); /* D0 */
> +       OUTB(lpt_outbyte[0], lpt_iobase);
> +}
> +
> +/* set data on LPT D0..D7 pins (connected to 4503 buffers that drive D0..D7 signals */
> +static void willem_set_data(uint8_t data)
> +{
> +       lpt_outbyte[2] |= (1 << 1); /* set data mode */
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +
> +       lpt_outbyte[0] = data;
> +       OUTB(lpt_outbyte[0], lpt_iobase);
> +}
> +
> +/* read data through 4014 shift register */
> +static uint8_t willem_get_data(void)
> +{
> +       int i;
> +       uint8_t data = 0;
> +
> +       lpt_outbyte[2] |= (1 << 1); /* set data mode and disable OE# */
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +
> +       lpt_outbyte[2] &= ~(1 << 1); /* set address mode (so data lines are not driven by 4503 buffers) and enable OE# */
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +
> +       lpt_outbyte[0] |= (1 << 1); /* set parallel input - P/S pin high (D1) */
> +       lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
> +       OUTB(lpt_outbyte[0], lpt_iobase);
> +//     programmer_delay(100);
> +
> +       /* pulse CLK to input D0..D8 data into 4014 */
> +       lpt_outbyte[0] &= ~(1 << 2); /* set CLK to high (inverted) (D2) */
> +       OUTB(lpt_outbyte[0], lpt_iobase);
> +
> +       lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
> +       lpt_outbyte[0] &= ~(1 << 1); /* clear parallel input - P/S pin low (D1) */
> +       OUTB(lpt_outbyte[0], lpt_iobase);
> +
> +       /* now clock-out the data (MSB first) and read from ACK pin (inverted) */
> +       for (i = 0; i < 8; i++) {
> +               data <<= 1;
> +               if (!(INB(lpt_iobase + 1) & (1 << 6))) /* inverted */
> +                       data |= 1;
> +
> +               lpt_outbyte[0] &= ~(1 << 2); /* set CLK to high (inverted) (D2) */
> +               OUTB(lpt_outbyte[0], lpt_iobase);
> +               lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
> +               OUTB(lpt_outbyte[0], lpt_iobase);
> +       }
> +
> +       return data;
> +}
> +
> +/* Enable WE (S4 signal) */
> +static void willem_set_write(int enable)
> +{
> +       lpt_outbyte[2] &= ~(1 << 3); /* SELIN */
> +       if (enable)
> +               lpt_outbyte[2] |= (1 << 3);
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +}
> +
> +/* compute the next highest power of 2 */
> +int round_powerof2(unsigned int v)
> +{
> +       v--;
> +       v |= v >> 1;
> +       v |= v >> 2;
> +       v |= v >> 4;
> +       v |= v >> 8;
> +       v |= v >> 16;
> +       v++;
> +
> +       return v;
> +}
> +
> +static void willem_set_vcc(int enable)
> +{
> +       lpt_outbyte[2] &= ~(1 << 2); /* INIT */
> +       if (enable)
> +               lpt_outbyte[2] |= (1 << 2);
> +       OUTB(lpt_outbyte[2], lpt_iobase + 2);
> +}
> +
> +static void willem_chip_set_address(const struct flashctx *flash, chipaddr addr)
> +{
> +       /* speedup: don't set all 24 address bits, just the relevant ones for the current chip */
> +       int chip_bits = ffs(round_powerof2(flash->chip->total_size * 1024)) - 1;
> +
> +       if (flash->chip->bustype == BUS_LPC || flash->chip->bustype == BUS_FWH) {
> +               /* R/C# high */
> +               willem_set_vcc(1);
> +               willem_set_address(addr & 0x7ff, 11);           /* lower 11 bits (row address) */
> +               /* R/C# low */
> +               willem_set_vcc(0);
> +               /* VCC switching is slow so we need a delay */
> +               programmer_delay(50);   /* 15us is enough for PCB4.0 but use 50us to be safe */
> +               willem_set_address((addr >> 11) & 0x7ff, chip_bits - 11);       /* upper (upto) 11 bits (column address) */
> +               /* R/C# high */
> +               willem_set_vcc(1);
> +               /* VCC switching is slow so we need a delay */
> +               programmer_delay(50);   /* 15us is enough for PCB4.0 but use 50us to be safe */
> +       } else {
> +               willem_set_address(addr, chip_bits);
> +       }
> +}
> +
> +static void willem_chip_writeb(const struct flashctx *flash, uint8_t val,
> +                              chipaddr addr)
> +{
> +       willem_chip_set_address(flash, addr);
> +       willem_set_data(val);
> +       willem_set_write(1);
> +       willem_set_write(0);
> +}
> +
> +static uint8_t willem_chip_readb(const struct flashctx *flash,
> +                                const chipaddr addr)
> +{
> +       willem_chip_set_address(flash, addr);
> +       return willem_get_data();
> +}
> +
> +#else
> +#error PCI port I/O access is not supported on this architecture yet.
> +#endif
>
>
> --
> Ondrej Zary
>
> _______________________________________________
> flashrom mailing list
> flashrom@flashrom.org
> http://www.flashrom.org/mailman/listinfo/flashrom

Patch

diff -urp -x .svn flashrom-orig//flashrom.c flashrom/flashrom.c
--- flashrom-orig//flashrom.c	2013-03-20 22:07:14.000000000 +0100
+++ flashrom/flashrom.c	2013-03-17 11:32:37.000000000 +0100
@@ -308,6 +308,19 @@  const struct programmer_entry programmer
 	},
 #endif
 
+#if CONFIG_WILLEM == 1
+	{
+		.name			= "willem",
+		.type			= OTHER,
+					/* FIXME */
+		.devs.note		= "Willem EPROM progammer on parallel port\n",
+		.init			= willem_init,
+		.map_flash_region	= fallback_map,
+		.unmap_flash_region	= fallback_unmap,
+		.delay			= internal_delay,
+	},
+#endif
+
 	{0}, /* This entry corresponds to PROGRAMMER_INVALID. */
 };
 
diff -urp -x .svn flashrom-orig//Makefile flashrom/Makefile
--- flashrom-orig//Makefile	2013-03-20 22:07:14.000000000 +0100
+++ flashrom/Makefile	2013-03-17 11:33:31.000000000 +0100
@@ -210,6 +210,11 @@  UNSUPPORTED_FEATURES += CONFIG_SATAMV=ye
 else
 override CONFIG_SATAMV = no
 endif
+ifeq ($(CONFIG_WILLEM), yes)
+UNSUPPORTED_FEATURES += CONFIG_WILLEM=yes
+else
+override CONFIG_WILLEM = no
+endif
 endif
 
 ifeq ($(TARGET_OS), libpayload)
@@ -289,6 +294,11 @@  UNSUPPORTED_FEATURES += CONFIG_SATAMV=ye
 else
 override CONFIG_SATAMV = no
 endif
+ifeq ($(CONFIG_WILLEM), yes)
+UNSUPPORTED_FEATURES += CONFIG_WILLEM=yes
+else
+override CONFIG_WILLEM = no
+endif
 endif
 
 ###############################################################################
@@ -382,6 +392,9 @@  CONFIG_SATAMV ?= yes
 # Enable Linux spidev interface by default. We disable it on non-Linux targets.
 CONFIG_LINUX_SPI ?= yes
 
+# Always enable Willem EPROM programmer hardware for now.
+CONFIG_WILLEM ?= yes
+
 # Disable wiki printing by default. It is only useful if you have wiki access.
 CONFIG_PRINT_WIKI ?= no
 
@@ -549,6 +562,13 @@  FEATURE_CFLAGS += $(shell LC_ALL=C grep
 PROGRAMMER_OBJS += linux_spi.o
 endif
 
+ifeq ($(CONFIG_WILLEM), yes)
+FEATURE_CFLAGS += -D'CONFIG_WILLEM=1'
+PROGRAMMER_OBJS += willem.o
+# Actually, NEED_PCI is wrong. NEED_IOPORT_ACCESS would be more correct.
+NEED_PCI := yes
+endif
+
 ifeq ($(NEED_SERIAL), yes)
 LIB_OBJS += serial.o
 endif
diff -urp -x .svn flashrom-orig//programmer.h flashrom/programmer.h
--- flashrom-orig//programmer.h	2013-03-20 22:07:14.000000000 +0100
+++ flashrom/programmer.h	2013-03-20 22:08:55.000000000 +0100
@@ -87,6 +87,9 @@  enum programmer {
 #if CONFIG_LINUX_SPI == 1
 	PROGRAMMER_LINUX_SPI,
 #endif
+#if CONFIG_WILLEM == 1
+	PROGRAMMER_WILLEM,
+#endif
 	PROGRAMMER_INVALID /* This must always be the last entry. */
 };
 
@@ -459,6 +462,11 @@  int linux_spi_init(void);
 int dediprog_init(void);
 #endif
 
+/* willem.c */
+#if CONFIG_WILLEM == 1
+int willem_init(void);
+#endif
+
 /* flashrom.c */
 struct decode_sizes {
 	uint32_t parallel;
--- flashrom-orig/willem.c	1970-01-01 01:00:00.000000000 +0100
+++ flashrom/willem.c	2013-03-20 22:56:04.000000000 +0100
@@ -0,0 +1,296 @@ 
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2013 Ondrej Zary
+ * Copyright (C) 2011 Carl-Daniel Hailfinger
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Driver for the Willem EPROM Programmer on parallel port.
+ * This driver can access parallel flash and LPC/FWH chips.
+ * For SPI chips, see rayer_spi.
+ *
+ * Willem HW description:
+ * LPT STROBE signal is used to control VPP (0 means ENABLED)
+ * LPT INIT signal is used to control VCC (1 means ENABLED)
+ * LPT AUTOLF signal is used to switch data direction and flash OE signal (1=4503 enabled, OE disabled)
+ * LPT SELIN signal is called S4 on PCB4.0 schematic and used as flash CE or WE signal (selected by DIP switch)
+ *
+ * LPT ACK input is used for reading flash D0..D7 through 4014 shift register
+ * LPT BUSY input is used for reading data in serial memory programming
+ * LPT PAPER_END, SELECT and ERROR inputs are not used
+ *
+ * LPT D0..D7 pins are used for two purposes, switched by AUTOLF
+ *  1. data output (through 2x4503 buffers) to flash D0..D7
+ *  2. CLK (D0) and D (D1) outputs to 3x4015 address shift registers
+ * D2 is also used as (inverted) CLK for 4014 shift register
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdlib.h>
+#include <string.h>
+#include "flash.h"
+#include "programmer.h"
+#include "hwaccess.h"
+
+static uint16_t lpt_iobase;
+/* Cached value of last byte sent. */
+static uint8_t lpt_outbyte[3];
+
+static void willem_chip_writeb(const struct flashctx *flash, uint8_t val,
+			       chipaddr addr);
+static uint8_t willem_chip_readb(const struct flashctx *flash,
+				 const chipaddr addr);
+static const struct par_programmer par_programmer_willem = {
+		.chip_readb		= willem_chip_readb,
+		.chip_readw		= fallback_chip_readw,
+		.chip_readl		= fallback_chip_readl,
+		.chip_readn		= fallback_chip_readn,
+		.chip_writeb		= willem_chip_writeb,
+		.chip_writew		= fallback_chip_writew,
+		.chip_writel		= fallback_chip_writel,
+		.chip_writen		= fallback_chip_writen,
+};
+
+static int willem_shutdown(void *data)
+{
+	/* Disable VPP and VCC. */
+	lpt_outbyte[2] = (1 << 0);
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+	return 0;
+}
+
+static void willem_set_address(uint32_t address, int highest_bit);
+static void willem_set_data(uint8_t data);
+static uint8_t willem_get_data(void);
+static void willem_set_write(int enable);
+
+int willem_init(void)
+{
+	char *arg = NULL;
+	int lpc_fwh = 0, vpp_enable = 0;
+	/* This is copied from rayer_spi */
+	/* Non-default port requested? */
+	arg = extract_programmer_param("iobase");
+	if (arg) {
+		char *endptr = NULL;
+		unsigned long tmp;
+		tmp = strtoul(arg, &endptr, 0);
+		/* Port 0, port >0x10000, unaligned ports and garbage strings
+		 * are rejected.
+		 */
+		if (!tmp || (tmp >= 0x10000) || (tmp & 0x3) ||
+		    (*endptr != '\0')) {
+			/* Using ports below 0x100 is a really bad idea, and
+			 * should only be done if no port between 0x100 and
+			 * 0xfffc works due to routing issues.
+			 */
+			msg_perr("Error: iobase= specified, but the I/O base "
+				 "given was invalid.\nIt must be a multiple of "
+				 "0x4 and lie between 0x100 and 0xfffc.\n");
+			free(arg);
+			return 1;
+		} else {
+			lpt_iobase = (uint16_t)tmp;
+			msg_pinfo("Non-default I/O base requested. This will "
+				  "not change the hardware settings.\n");
+		}
+	} else {
+		/* Pick a default value for the I/O base. */
+		lpt_iobase = 0x378;
+	}
+	free(arg);
+
+	msg_pdbg("Using address 0x%x as I/O base for parallel port access.\n",
+		 lpt_iobase);
+
+	arg = extract_programmer_param("type");
+	if (arg) {
+		if (!strcmp(arg, "lpc_fwh"))
+			lpc_fwh = 1;
+		else if (!strcmp(arg, "12v"))
+			vpp_enable = 1;
+		free(arg);
+	}
+
+	if (rget_io_perms())
+		return 1;
+
+	/* Get the initial value before writing to any line. */
+	lpt_outbyte[0] = INB(lpt_iobase);
+	lpt_outbyte[2] = INB(lpt_iobase + 2);
+
+	if (register_shutdown(willem_shutdown, NULL))
+		return 1;
+
+	/* Enable VCC, disable VPP */
+	lpt_outbyte[2] = (1 << 2) | (1 << 0);
+	if (lpc_fwh || vpp_enable)	/* enable VPP for LPC/FWH (used to generate VCC) */
+		lpt_outbyte[2] &= ~(1 << 0); /* inverted */
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+
+	max_rom_decode.parallel = 1 << 24; /* 24 address lines = 16 MB */
+	register_par_programmer(&par_programmer_willem, lpc_fwh ? (BUS_LPC | BUS_FWH) : BUS_PARALLEL);
+
+	return 0;
+}
+
+/* clock-in the address into 4015 shift registers (3 x 8-bit) that drive A0..A23 signals */
+/* A23 is not connected anywhere on PCB4.0 */
+static void willem_set_address(uint32_t address, int highest_bit)
+{
+	int i;
+	lpt_outbyte[2] &= ~(1 << 1); /* set address mode */
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+	/* start with MSB */
+	for (i = highest_bit; i >= 0; i--) {
+		/* clear CLK and D */
+		lpt_outbyte[0] &= ~((1 << 0) | (1 << 1));
+		if (address & (1 << i))
+			lpt_outbyte[0] |= (1 << 1); /* D1 */
+		OUTB(lpt_outbyte[0], lpt_iobase);
+		/* set CLK */
+		lpt_outbyte[0] |= (1 << 0); /* D0 */
+		OUTB(lpt_outbyte[0], lpt_iobase);
+	}
+	/* clear CLK */
+	lpt_outbyte[0] &= ~(1 << 0); /* D0 */
+	OUTB(lpt_outbyte[0], lpt_iobase);
+}
+
+/* set data on LPT D0..D7 pins (connected to 4503 buffers that drive D0..D7 signals */
+static void willem_set_data(uint8_t data)
+{
+	lpt_outbyte[2] |= (1 << 1); /* set data mode */
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+
+	lpt_outbyte[0] = data;
+	OUTB(lpt_outbyte[0], lpt_iobase);
+}
+
+/* read data through 4014 shift register */
+static uint8_t willem_get_data(void)
+{
+	int i;
+	uint8_t data = 0;
+
+	lpt_outbyte[2] |= (1 << 1); /* set data mode and disable OE# */
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+
+	lpt_outbyte[2] &= ~(1 << 1); /* set address mode (so data lines are not driven by 4503 buffers) and enable OE# */
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+
+	lpt_outbyte[0] |= (1 << 1); /* set parallel input - P/S pin high (D1) */
+	lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
+	OUTB(lpt_outbyte[0], lpt_iobase);
+//	programmer_delay(100);
+
+	/* pulse CLK to input D0..D8 data into 4014 */
+	lpt_outbyte[0] &= ~(1 << 2); /* set CLK to high (inverted) (D2) */
+	OUTB(lpt_outbyte[0], lpt_iobase);
+
+	lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
+	lpt_outbyte[0] &= ~(1 << 1); /* clear parallel input - P/S pin low (D1) */
+	OUTB(lpt_outbyte[0], lpt_iobase);
+
+	/* now clock-out the data (MSB first) and read from ACK pin (inverted) */
+	for (i = 0; i < 8; i++) {
+		data <<= 1;
+		if (!(INB(lpt_iobase + 1) & (1 << 6))) /* inverted */
+			data |= 1;
+
+		lpt_outbyte[0] &= ~(1 << 2); /* set CLK to high (inverted) (D2) */
+		OUTB(lpt_outbyte[0], lpt_iobase);
+		lpt_outbyte[0] |= (1 << 2); /* set CLK to low (inverted) (D2) */
+		OUTB(lpt_outbyte[0], lpt_iobase);
+	}
+
+	return data;
+}
+
+/* Enable WE (S4 signal) */
+static void willem_set_write(int enable)
+{
+	lpt_outbyte[2] &= ~(1 << 3); /* SELIN */
+	if (enable)
+		lpt_outbyte[2] |= (1 << 3);
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+}
+
+/* compute the next highest power of 2 */
+int round_powerof2(unsigned int v)
+{
+	v--;
+	v |= v >> 1;
+	v |= v >> 2;
+	v |= v >> 4;
+	v |= v >> 8;
+	v |= v >> 16;
+	v++;
+
+	return v;
+}
+
+static void willem_set_vcc(int enable)
+{
+	lpt_outbyte[2] &= ~(1 << 2); /* INIT */
+	if (enable)
+		lpt_outbyte[2] |= (1 << 2);
+	OUTB(lpt_outbyte[2], lpt_iobase + 2);
+}
+
+static void willem_chip_set_address(const struct flashctx *flash, chipaddr addr)
+{
+	/* speedup: don't set all 24 address bits, just the relevant ones for the current chip */
+	int chip_bits = ffs(round_powerof2(flash->chip->total_size * 1024)) - 1;
+
+	if (flash->chip->bustype == BUS_LPC || flash->chip->bustype == BUS_FWH) {
+		/* R/C# high */
+		willem_set_vcc(1);
+		willem_set_address(addr & 0x7ff, 11);		/* lower 11 bits (row address) */
+		/* R/C# low */
+		willem_set_vcc(0);
+		/* VCC switching is slow so we need a delay */
+		programmer_delay(50);	/* 15us is enough for PCB4.0 but use 50us to be safe */
+		willem_set_address((addr >> 11) & 0x7ff, chip_bits - 11);	/* upper (upto) 11 bits (column address) */
+		/* R/C# high */
+		willem_set_vcc(1);
+		/* VCC switching is slow so we need a delay */
+		programmer_delay(50);	/* 15us is enough for PCB4.0 but use 50us to be safe */
+	} else {
+		willem_set_address(addr, chip_bits);
+	}
+}
+
+static void willem_chip_writeb(const struct flashctx *flash, uint8_t val,
+			       chipaddr addr)
+{
+	willem_chip_set_address(flash, addr);
+	willem_set_data(val);
+	willem_set_write(1);
+	willem_set_write(0);
+}
+
+static uint8_t willem_chip_readb(const struct flashctx *flash,
+				 const chipaddr addr)
+{
+	willem_chip_set_address(flash, addr);
+	return willem_get_data();
+}
+
+#else
+#error PCI port I/O access is not supported on this architecture yet.
+#endif