scsilsi: Introduce new programmer driver for LSI SCSI adapters

Supports up to 1MiB parallel flash, but needs external address buffers
and is usually set up for less.

Datasheet used:
https://theretroweb.com/chip/documentation/lsi53c875-data-sheet-4-1-66a3b3913650d154581810.pdf

Change-Id: I23291ae7c98ddd5cab48f96aa5e6a69788796d9b
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/529
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/Makefile b/Makefile
index b0f6cbf..ac73011 100644
--- a/Makefile
+++ b/Makefile
@@ -122,6 +122,7 @@
 	CONFIG_OGP_SPI \
 	CONFIG_SATAMV \
 	CONFIG_SATASII \
+	CONFIG_SCSILSI \
 
 DEPENDS_ON_X86_MSR := \
 	CONFIG_INTERNAL_X86 \
@@ -153,6 +154,7 @@
 	CONFIG_OGP_SPI \
 	CONFIG_SATAMV \
 	CONFIG_SATASII \
+	CONFIG_SCSILSI \
 
 DEPENDS_ON_LIBUSB1 := \
 	CONFIG_CH341A_SPI \
@@ -439,6 +441,9 @@
 # Always enable SiI SATA controllers for now.
 CONFIG_SATASII ?= yes
 
+# Enable LSI SCSI controllers.
+CONFIG_SCSILSI ?= yes
+
 # Highpoint (HPT) ATA/RAID controller support.
 # IMPORTANT: This code is not yet working!
 CONFIG_ATAHPT ?= no
@@ -652,6 +657,11 @@
 PROGRAMMER_OBJS += satasii.o
 endif
 
+ifeq ($(CONFIG_SCSILSI), yes)
+FEATURE_FLAGS += -D'CONFIG_SCSILSI=1'
+PROGRAMMER_OBJS += scsilsi.o
+endif
+
 ifeq ($(CONFIG_ATAHPT), yes)
 FEATURE_FLAGS += -D'CONFIG_ATAHPT=1'
 PROGRAMMER_OBJS += atahpt.o
diff --git a/flashprog.8.tmpl b/flashprog.8.tmpl
index 35738dc..2048da7 100644
--- a/flashprog.8.tmpl
+++ b/flashprog.8.tmpl
@@ -314,6 +314,8 @@
 .sp
 .BR "* satasii" " (for flash ROMs on Silicon Image SATA/IDE controllers)"
 .sp
+.BR "* scsilsi" " (for flash ROMs on LSI SCSI controllers)"
+.sp
 .BR "* satamv" " (for flash ROMs on Marvell SATA controllers)"
 .sp
 .BR "* atahpt" " (for flash ROMs on Highpoint ATA/RAID controllers)"
@@ -760,7 +762,8 @@
 .SS
 .BR "nic3com" , " nicrealtek" , " nicnatsemi" , " nicintel", " nicintel_eeprom"\
 , " nicintel_spi" , " gfxnvidia" , " ogp_spi" , " drkaiser" , " satasii"\
-, " satamv" , " atahpt", " atavia ", " atapromise " and " it8212 " programmers
+, " scsilsi", " satamv" , " atahpt", " atavia ", " atapromise " and " it8212 "\
+programmers
 .IP
 These programmers have an option to specify the PCI address of the card
 your want to use, which must be specified if more than one card supported
@@ -1559,7 +1562,7 @@
 .B rayer_spi
 needs raw I/O port access.
 .sp
-.BR satasii ", " nicintel ", " nicintel_eeprom " and " nicintel_spi
+.BR satasii ", " scsilsi ", " nicintel ", " nicintel_eeprom " and " nicintel_spi
 need PCI configuration space read access and raw memory access.
 .sp
 .BR satamv " and " atapromise
@@ -1582,7 +1585,8 @@
 needs no access permissions at all.
 .sp
 .BR internal ", " nic3com ", " nicrealtek ", " nicnatsemi ", "
-.BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt ", " atavia " and " atapromise
+.BR gfxnvidia ", " drkaiser ", " satamv ", " satasii ", " scsilsi ", "
+.BR atahpt ", " atavia " and " atapromise
 have to be run as superuser/root, and need additional raw access permission.
 .sp
 .BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi ", " \
diff --git a/include/programmer.h b/include/programmer.h
index 7da687d..a2de74e 100644
--- a/include/programmer.h
+++ b/include/programmer.h
@@ -93,6 +93,7 @@
 extern const struct programmer_entry programmer_rayer_spi;
 extern const struct programmer_entry programmer_satamv;
 extern const struct programmer_entry programmer_satasii;
+extern const struct programmer_entry programmer_scsilsi;
 extern const struct programmer_entry programmer_serprog;
 extern const struct programmer_entry programmer_stlinkv3_spi;
 extern const struct programmer_entry programmer_usbblaster_spi;
diff --git a/meson.build b/meson.build
index 026a804..d7865d4 100644
--- a/meson.build
+++ b/meson.build
@@ -431,6 +431,14 @@
     'srcs'    : files('satasii.c', 'pcidev.c'),
     'flags'   : [ '-DCONFIG_SATASII=1' ],
   },
+  'scsilsi' : {
+    'systems' : systems_hwaccess,
+    'cpu_families' : [ cpus_raw_mem ],
+    'deps'    : [ libpci ],
+    'groups'  : [ group_pci, group_internal ],
+    'srcs'    : files('scsilsi.c', 'pcidev.c'),
+    'flags'   : [ '-DCONFIG_SCSILSI=1' ],
+  },
   'serprog' : {
     'systems' : systems_serial,
     'groups'  : [ group_serial, group_external ],
diff --git a/meson_options.txt b/meson_options.txt
index cd0dde0..d466bd4 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -15,5 +15,5 @@
         'jlink_spi', 'linux_gpio_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi', 'mstarddc_spi', 'nic3com',
         'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi', 'nicrealtek', 'ogp_spi', 'parade_lspcon',
         'pickit2_spi', 'pony_spi', 'raiden_debug_spi', 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv',
-        'satasii', 'serprog',  'stlinkv3_spi', 'usbblaster_spi',
+        'satasii', 'scsilsi', 'serprog', 'stlinkv3_spi', 'usbblaster_spi',
 ], description: 'Active programmers')
diff --git a/programmer_table.c b/programmer_table.c
index c4dd999..b4b8a4d 100644
--- a/programmer_table.c
+++ b/programmer_table.c
@@ -48,6 +48,10 @@
     &programmer_satasii,
 #endif
 
+#if CONFIG_SCSILSI == 1
+    &programmer_scsilsi,
+#endif
+
 #if CONFIG_ATAHPT == 1
     &programmer_atahpt,
 #endif
diff --git a/scsilsi.c b/scsilsi.c
new file mode 100644
index 0000000..c6885f3
--- /dev/null
+++ b/scsilsi.c
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the flashprog project.
+ *
+ * Copyright (C) 2026 Nico Huber <nico.h@gmx.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 <stddef.h>
+#include <stdint.h>
+
+#include "programmer.h"
+#include "platform/pci.h"
+#include "hwaccess_physmap.h"
+#include "programmer/physmap.h"
+
+#define PCI_VENDOR_ID_LSI	0x1000
+
+static const struct dev_entry scsi_lsi[] = {
+	{PCI_VENDOR_ID_LSI, 0x000f, OK, "LSI", "53c875" },
+
+	{0},
+};
+
+static const struct par_master par_master_scsilsi = {
+	.chip_readb	= mmio_chip_readb,
+	.chip_readw	= mmio_chip_readw,
+	.chip_readl	= mmio_chip_readl,
+	.chip_readn	= mmio_chip_readn,
+	.chip_writeb	= mmio_chip_writeb,
+	.chip_writew	= mmio_chip_writew,
+	.chip_writel	= mmio_chip_writel,
+	.chip_writen	= fallback_chip_writen,
+	.map_flash	= physmap,
+	.unmap_flash	= physunmap,
+};
+
+static int scsilsi_init(struct flashprog_programmer *const prog)
+{
+	struct pci_dev *const lsi = pcidev_init(scsi_lsi, PCI_BASE_ADDRESS_1);
+	if (!lsi)
+		return 1;
+
+	const uint16_t command = pci_read_word(lsi, PCI_COMMAND);
+	if (!(command & PCI_COMMAND_MEMORY)) {
+		msg_perr("Error: PCI memory access is disabled.\n");
+		return 1;
+	}
+
+	const uint32_t rom_base = pci_read_long(lsi, PCI_ROM_ADDRESS);
+	if (!rom_base) {
+		msg_perr("Error: PCI ROM base is not configured.\n");
+		return 1;
+	}
+	rpci_write_long(lsi, PCI_ROM_ADDRESS, rom_base | 1);
+
+	/* TODO: Use ROM BAR size detection. */
+
+	return register_par_master(&par_master_scsilsi, BUS_PARALLEL, rom_base & ~1u, 1*MiB, NULL);
+}
+
+const struct programmer_entry programmer_scsilsi = {
+	.name			= "scsilsi",
+	.type			= PCI,
+	.devs.dev		= scsi_lsi,
+	.init			= scsilsi_init,
+};