Linux-SPI Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI
@ 2021-04-16 16:57 matthew.gerlach
  2021-04-16 16:57 ` [PATCH v3 1/2] spi: altera: separate core code from platform code matthew.gerlach
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: matthew.gerlach @ 2021-04-16 16:57 UTC (permalink / raw)
  To: hao.wu, trix, linux-fpga, linux-kernel, yilun.xu,
	russell.h.weight, broonie, linux-spi
  Cc: Matthew Gerlach

From: Matthew Gerlach <matthew.gerlach@linux.intel.com>

This patch set adds Device Feature List (DFL) bus support for
the Altera SPI Master controller.

Patch 1 separates spi-altera.c into spi-altera-core.c and
spi-altera-platform.c.

Patch 2 adds spi-altera-dfl.c.

Matthew Gerlach (2):
  spi: altera: separate core code from platform code
  spi: altera: Add DFL bus driver for Altera API Controller

 drivers/spi/Kconfig               |  18 +-
 drivers/spi/Makefile              |   4 +-
 drivers/spi/spi-altera-core.c     | 222 ++++++++++++++++++++++
 drivers/spi/spi-altera-dfl.c      | 204 ++++++++++++++++++++
 drivers/spi/spi-altera-platform.c | 172 +++++++++++++++++
 drivers/spi/spi-altera.c          | 378 --------------------------------------
 include/linux/spi/altera.h        |  21 +++
 7 files changed, 639 insertions(+), 380 deletions(-)
 create mode 100644 drivers/spi/spi-altera-core.c
 create mode 100644 drivers/spi/spi-altera-dfl.c
 create mode 100644 drivers/spi/spi-altera-platform.c
 delete mode 100644 drivers/spi/spi-altera.c

-- 
1.8.3.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 1/2] spi: altera: separate core code from platform code
  2021-04-16 16:57 [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI matthew.gerlach
@ 2021-04-16 16:57 ` matthew.gerlach
  2021-04-16 16:57 ` [PATCH v3 2/2] spi: altera: Add DFL bus driver for Altera API Controller matthew.gerlach
  2021-04-20 18:35 ` [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI Mark Brown
  2 siblings, 0 replies; 4+ messages in thread
From: matthew.gerlach @ 2021-04-16 16:57 UTC (permalink / raw)
  To: hao.wu, trix, linux-fpga, linux-kernel, yilun.xu,
	russell.h.weight, broonie, linux-spi
  Cc: Matthew Gerlach

From: Matthew Gerlach <matthew.gerlach@linux.intel.com>

In preparation of adding support for a new bus type,
separate the core spi-altera code from the platform
driver code.

Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
---
 drivers/spi/Kconfig               |   9 +-
 drivers/spi/Makefile              |   3 +-
 drivers/spi/spi-altera-core.c     | 222 ++++++++++++++++++++++
 drivers/spi/spi-altera-platform.c | 172 +++++++++++++++++
 drivers/spi/spi-altera.c          | 378 --------------------------------------
 include/linux/spi/altera.h        |  21 +++
 6 files changed, 425 insertions(+), 380 deletions(-)
 create mode 100644 drivers/spi/spi-altera-core.c
 create mode 100644 drivers/spi/spi-altera-platform.c
 delete mode 100644 drivers/spi/spi-altera.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 853cf4c..e59f548 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -58,11 +58,18 @@ config SPI_MEM
 comment "SPI Master Controller Drivers"
 
 config SPI_ALTERA
-	tristate "Altera SPI Controller"
+	tristate "Altera SPI Controller platform driver"
+	select SPI_ALTERA_CORE
 	select REGMAP_MMIO
 	help
 	  This is the driver for the Altera SPI Controller.
 
+config SPI_ALTERA_CORE
+	tristate "Altera SPI Controller core code"
+	select REGMAP
+	help
+	  "The core code for the Altera SPI Controller"
+
 config SPI_AR934X
 	tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
 	depends on ATH79 || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 29fee71..e6be348 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -14,7 +14,8 @@ obj-$(CONFIG_SPI_SPIDEV)		+= spidev.o
 obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
 
 # SPI master controller drivers (bus)
-obj-$(CONFIG_SPI_ALTERA)		+= spi-altera.o
+obj-$(CONFIG_SPI_ALTERA)		+= spi-altera-platform.o
+obj-$(CONFIG_SPI_ALTERA_CORE)		+= spi-altera-core.o
 obj-$(CONFIG_SPI_AR934X)		+= spi-ar934x.o
 obj-$(CONFIG_SPI_ARMADA_3700)		+= spi-armada-3700.o
 obj-$(CONFIG_SPI_ATMEL)			+= spi-atmel.o
diff --git a/drivers/spi/spi-altera-core.c b/drivers/spi/spi-altera-core.c
new file mode 100644
index 0000000..de4d31c
--- /dev/null
+++ b/drivers/spi/spi-altera-core.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ *	Ben Dooks <ben@simtec.co.uk>
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/altera.h>
+#include <linux/spi/spi.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+#define ALTERA_SPI_RXDATA	0
+#define ALTERA_SPI_TXDATA	4
+#define ALTERA_SPI_STATUS	8
+#define ALTERA_SPI_CONTROL	12
+#define ALTERA_SPI_SLAVE_SEL	20
+
+#define ALTERA_SPI_STATUS_ROE_MSK	0x8
+#define ALTERA_SPI_STATUS_TOE_MSK	0x10
+#define ALTERA_SPI_STATUS_TMT_MSK	0x20
+#define ALTERA_SPI_STATUS_TRDY_MSK	0x40
+#define ALTERA_SPI_STATUS_RRDY_MSK	0x80
+#define ALTERA_SPI_STATUS_E_MSK		0x100
+
+#define ALTERA_SPI_CONTROL_IROE_MSK	0x8
+#define ALTERA_SPI_CONTROL_ITOE_MSK	0x10
+#define ALTERA_SPI_CONTROL_ITRDY_MSK	0x40
+#define ALTERA_SPI_CONTROL_IRRDY_MSK	0x80
+#define ALTERA_SPI_CONTROL_IE_MSK	0x100
+#define ALTERA_SPI_CONTROL_SSO_MSK	0x400
+
+static int altr_spi_writel(struct altera_spi *hw, unsigned int reg,
+			   unsigned int val)
+{
+	int ret;
+
+	ret = regmap_write(hw->regmap, hw->regoff + reg, val);
+	if (ret)
+		dev_err(hw->dev, "fail to write reg 0x%x val 0x%x: %d\n",
+			reg, val, ret);
+
+	return ret;
+}
+
+static int altr_spi_readl(struct altera_spi *hw, unsigned int reg,
+			  unsigned int *val)
+{
+	int ret;
+
+	ret = regmap_read(hw->regmap, hw->regoff + reg, val);
+	if (ret)
+		dev_err(hw->dev, "fail to read reg 0x%x: %d\n", reg, ret);
+
+	return ret;
+}
+
+static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
+{
+	return spi_master_get_devdata(sdev->master);
+}
+
+static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
+{
+	struct altera_spi *hw = altera_spi_to_hw(spi);
+
+	if (is_high) {
+		hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
+		altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL, 0);
+	} else {
+		altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL,
+				BIT(spi->chip_select));
+		hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
+	}
+}
+
+static void altera_spi_tx_word(struct altera_spi *hw)
+{
+	unsigned int txd = 0;
+
+	if (hw->tx) {
+		switch (hw->bytes_per_word) {
+		case 1:
+			txd = hw->tx[hw->count];
+			break;
+		case 2:
+			txd = (hw->tx[hw->count * 2]
+				| (hw->tx[hw->count * 2 + 1] << 8));
+			break;
+		case 4:
+			txd = (hw->tx[hw->count * 4]
+				| (hw->tx[hw->count * 4 + 1] << 8)
+				| (hw->tx[hw->count * 4 + 2] << 16)
+				| (hw->tx[hw->count * 4 + 3] << 24));
+			break;
+
+		}
+	}
+
+	altr_spi_writel(hw, ALTERA_SPI_TXDATA, txd);
+}
+
+static void altera_spi_rx_word(struct altera_spi *hw)
+{
+	unsigned int rxd;
+
+	altr_spi_readl(hw, ALTERA_SPI_RXDATA, &rxd);
+	if (hw->rx) {
+		switch (hw->bytes_per_word) {
+		case 1:
+			hw->rx[hw->count] = rxd;
+			break;
+		case 2:
+			hw->rx[hw->count * 2] = rxd;
+			hw->rx[hw->count * 2 + 1] = rxd >> 8;
+			break;
+		case 4:
+			hw->rx[hw->count * 4] = rxd;
+			hw->rx[hw->count * 4 + 1] = rxd >> 8;
+			hw->rx[hw->count * 4 + 2] = rxd >> 16;
+			hw->rx[hw->count * 4 + 3] = rxd >> 24;
+			break;
+
+		}
+	}
+
+	hw->count++;
+}
+
+static int altera_spi_txrx(struct spi_master *master,
+	struct spi_device *spi, struct spi_transfer *t)
+{
+	struct altera_spi *hw = spi_master_get_devdata(master);
+	u32 val;
+
+	hw->tx = t->tx_buf;
+	hw->rx = t->rx_buf;
+	hw->count = 0;
+	hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
+	hw->len = t->len / hw->bytes_per_word;
+
+	if (hw->irq >= 0) {
+		/* enable receive interrupt */
+		hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
+
+		/* send the first byte */
+		altera_spi_tx_word(hw);
+
+		return 1;
+	}
+
+	while (hw->count < hw->len) {
+		altera_spi_tx_word(hw);
+
+		for (;;) {
+			altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
+			if (val & ALTERA_SPI_STATUS_RRDY_MSK)
+				break;
+
+			cpu_relax();
+		}
+
+		altera_spi_rx_word(hw);
+	}
+	spi_finalize_current_transfer(master);
+
+	return 0;
+}
+
+irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+	struct spi_master *master = dev;
+	struct altera_spi *hw = spi_master_get_devdata(master);
+
+	altera_spi_rx_word(hw);
+
+	if (hw->count < hw->len) {
+		altera_spi_tx_word(hw);
+	} else {
+		/* disable receive interrupt */
+		hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
+		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
+
+		spi_finalize_current_transfer(master);
+	}
+
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(altera_spi_irq);
+
+void altera_spi_init_master(struct spi_master *master)
+{
+	struct altera_spi *hw = spi_master_get_devdata(master);
+	u32 val;
+
+	master->transfer_one = altera_spi_txrx;
+	master->set_cs = altera_spi_set_cs;
+
+	/* program defaults into the registers */
+	hw->imr = 0;		/* disable spi interrupts */
+	altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
+	altr_spi_writel(hw, ALTERA_SPI_STATUS, 0);	/* clear status reg */
+	altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
+	if (val & ALTERA_SPI_STATUS_RRDY_MSK)
+		altr_spi_readl(hw, ALTERA_SPI_RXDATA, &val); /* flush rxdata */
+}
+EXPORT_SYMBOL_GPL(altera_spi_init_master);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/spi-altera-platform.c b/drivers/spi/spi-altera-platform.c
new file mode 100644
index 0000000..f7a7c14
--- /dev/null
+++ b/drivers/spi/spi-altera-platform.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ *	Ben Dooks <ben@simtec.co.uk>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/altera.h>
+#include <linux/spi/spi.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+enum altera_spi_type {
+	ALTERA_SPI_TYPE_UNKNOWN,
+	ALTERA_SPI_TYPE_SUBDEV,
+};
+
+static const struct regmap_config spi_altera_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+};
+
+static int altera_spi_probe(struct platform_device *pdev)
+{
+	const struct platform_device_id *platid = platform_get_device_id(pdev);
+	struct altera_spi_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	enum altera_spi_type type = ALTERA_SPI_TYPE_UNKNOWN;
+	struct altera_spi *hw;
+	struct spi_master *master;
+	int err = -ENODEV;
+	u16 i;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
+	if (!master)
+		return err;
+
+	/* setup the master state. */
+	master->bus_num = pdev->id;
+
+	if (pdata) {
+		if (pdata->num_chipselect > ALTERA_SPI_MAX_CS) {
+			dev_err(&pdev->dev,
+				"Invalid number of chipselect: %u\n",
+				pdata->num_chipselect);
+			err = -EINVAL;
+			goto exit;
+		}
+
+		master->num_chipselect = pdata->num_chipselect;
+		master->mode_bits = pdata->mode_bits;
+		master->bits_per_word_mask = pdata->bits_per_word_mask;
+	} else {
+		master->num_chipselect = 16;
+		master->mode_bits = SPI_CS_HIGH;
+		master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16);
+	}
+
+	master->dev.of_node = pdev->dev.of_node;
+
+	hw = spi_master_get_devdata(master);
+	hw->dev = &pdev->dev;
+
+	if (platid)
+		type = platid->driver_data;
+
+	/* find and map our resources */
+	if (type == ALTERA_SPI_TYPE_SUBDEV) {
+		struct resource *regoff;
+
+		hw->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+		if (!hw->regmap) {
+			dev_err(&pdev->dev, "get regmap failed\n");
+			goto exit;
+		}
+
+		regoff = platform_get_resource(pdev, IORESOURCE_REG, 0);
+		if (regoff)
+			hw->regoff = regoff->start;
+	} else {
+		void __iomem *res;
+
+		res = devm_platform_ioremap_resource(pdev, 0);
+		if (IS_ERR(res)) {
+			err = PTR_ERR(res);
+			goto exit;
+		}
+
+		hw->regmap = devm_regmap_init_mmio(&pdev->dev, res,
+						   &spi_altera_config);
+		if (IS_ERR(hw->regmap)) {
+			dev_err(&pdev->dev, "regmap mmio init failed\n");
+			err = PTR_ERR(hw->regmap);
+			goto exit;
+		}
+	}
+
+	altera_spi_init_master(master);
+
+	/* irq is optional */
+	hw->irq = platform_get_irq(pdev, 0);
+	if (hw->irq >= 0) {
+		err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
+				       pdev->name, master);
+		if (err)
+			goto exit;
+	}
+
+	err = devm_spi_register_master(&pdev->dev, master);
+	if (err)
+		goto exit;
+
+	if (pdata) {
+		for (i = 0; i < pdata->num_devices; i++) {
+			if (!spi_new_device(master, pdata->devices + i))
+				dev_warn(&pdev->dev,
+					 "unable to create SPI device: %s\n",
+					 pdata->devices[i].modalias);
+		}
+	}
+
+	dev_info(&pdev->dev, "regoff %u, irq %d\n", hw->regoff, hw->irq);
+
+	return 0;
+exit:
+	spi_master_put(master);
+	return err;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id altera_spi_match[] = {
+	{ .compatible = "ALTR,spi-1.0", },
+	{ .compatible = "altr,spi-1.0", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+#endif /* CONFIG_OF */
+
+static const struct platform_device_id altera_spi_ids[] = {
+	{ DRV_NAME,		ALTERA_SPI_TYPE_UNKNOWN },
+	{ "subdev_spi_altera",	ALTERA_SPI_TYPE_SUBDEV },
+	{ }
+};
+MODULE_DEVICE_TABLE(platform, altera_spi_ids);
+
+static struct platform_driver altera_spi_driver = {
+	.probe = altera_spi_probe,
+	.driver = {
+		.name = DRV_NAME,
+		.pm = NULL,
+		.of_match_table = of_match_ptr(altera_spi_match),
+	},
+	.id_table	= altera_spi_ids,
+};
+module_platform_driver(altera_spi_driver);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/spi/spi-altera.c b/drivers/spi/spi-altera.c
deleted file mode 100644
index 62ea0c9..0000000
--- a/drivers/spi/spi-altera.c
+++ /dev/null
@@ -1,378 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Altera SPI driver
- *
- * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
- *
- * Based on spi_s3c24xx.c, which is:
- * Copyright (c) 2006 Ben Dooks
- * Copyright (c) 2006 Simtec Electronics
- *	Ben Dooks <ben@simtec.co.uk>
- */
-
-#include <linux/interrupt.h>
-#include <linux/errno.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/spi/altera.h>
-#include <linux/spi/spi.h>
-#include <linux/io.h>
-#include <linux/of.h>
-
-#define DRV_NAME "spi_altera"
-
-#define ALTERA_SPI_RXDATA	0
-#define ALTERA_SPI_TXDATA	4
-#define ALTERA_SPI_STATUS	8
-#define ALTERA_SPI_CONTROL	12
-#define ALTERA_SPI_SLAVE_SEL	20
-
-#define ALTERA_SPI_STATUS_ROE_MSK	0x8
-#define ALTERA_SPI_STATUS_TOE_MSK	0x10
-#define ALTERA_SPI_STATUS_TMT_MSK	0x20
-#define ALTERA_SPI_STATUS_TRDY_MSK	0x40
-#define ALTERA_SPI_STATUS_RRDY_MSK	0x80
-#define ALTERA_SPI_STATUS_E_MSK		0x100
-
-#define ALTERA_SPI_CONTROL_IROE_MSK	0x8
-#define ALTERA_SPI_CONTROL_ITOE_MSK	0x10
-#define ALTERA_SPI_CONTROL_ITRDY_MSK	0x40
-#define ALTERA_SPI_CONTROL_IRRDY_MSK	0x80
-#define ALTERA_SPI_CONTROL_IE_MSK	0x100
-#define ALTERA_SPI_CONTROL_SSO_MSK	0x400
-
-#define ALTERA_SPI_MAX_CS		32
-
-enum altera_spi_type {
-	ALTERA_SPI_TYPE_UNKNOWN,
-	ALTERA_SPI_TYPE_SUBDEV,
-};
-
-struct altera_spi {
-	int irq;
-	int len;
-	int count;
-	int bytes_per_word;
-	u32 imr;
-
-	/* data buffers */
-	const unsigned char *tx;
-	unsigned char *rx;
-
-	struct regmap *regmap;
-	u32 regoff;
-	struct device *dev;
-};
-
-static const struct regmap_config spi_altera_config = {
-	.reg_bits = 32,
-	.reg_stride = 4,
-	.val_bits = 32,
-	.fast_io = true,
-};
-
-static int altr_spi_writel(struct altera_spi *hw, unsigned int reg,
-			   unsigned int val)
-{
-	int ret;
-
-	ret = regmap_write(hw->regmap, hw->regoff + reg, val);
-	if (ret)
-		dev_err(hw->dev, "fail to write reg 0x%x val 0x%x: %d\n",
-			reg, val, ret);
-
-	return ret;
-}
-
-static int altr_spi_readl(struct altera_spi *hw, unsigned int reg,
-			  unsigned int *val)
-{
-	int ret;
-
-	ret = regmap_read(hw->regmap, hw->regoff + reg, val);
-	if (ret)
-		dev_err(hw->dev, "fail to read reg 0x%x: %d\n", reg, ret);
-
-	return ret;
-}
-
-static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
-{
-	return spi_master_get_devdata(sdev->master);
-}
-
-static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
-{
-	struct altera_spi *hw = altera_spi_to_hw(spi);
-
-	if (is_high) {
-		hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
-		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
-		altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL, 0);
-	} else {
-		altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL,
-				BIT(spi->chip_select));
-		hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
-		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
-	}
-}
-
-static void altera_spi_tx_word(struct altera_spi *hw)
-{
-	unsigned int txd = 0;
-
-	if (hw->tx) {
-		switch (hw->bytes_per_word) {
-		case 1:
-			txd = hw->tx[hw->count];
-			break;
-		case 2:
-			txd = (hw->tx[hw->count * 2]
-				| (hw->tx[hw->count * 2 + 1] << 8));
-			break;
-		case 4:
-			txd = (hw->tx[hw->count * 4]
-				| (hw->tx[hw->count * 4 + 1] << 8)
-				| (hw->tx[hw->count * 4 + 2] << 16)
-				| (hw->tx[hw->count * 4 + 3] << 24));
-			break;
-
-		}
-	}
-
-	altr_spi_writel(hw, ALTERA_SPI_TXDATA, txd);
-}
-
-static void altera_spi_rx_word(struct altera_spi *hw)
-{
-	unsigned int rxd;
-
-	altr_spi_readl(hw, ALTERA_SPI_RXDATA, &rxd);
-	if (hw->rx) {
-		switch (hw->bytes_per_word) {
-		case 1:
-			hw->rx[hw->count] = rxd;
-			break;
-		case 2:
-			hw->rx[hw->count * 2] = rxd;
-			hw->rx[hw->count * 2 + 1] = rxd >> 8;
-			break;
-		case 4:
-			hw->rx[hw->count * 4] = rxd;
-			hw->rx[hw->count * 4 + 1] = rxd >> 8;
-			hw->rx[hw->count * 4 + 2] = rxd >> 16;
-			hw->rx[hw->count * 4 + 3] = rxd >> 24;
-			break;
-
-		}
-	}
-
-	hw->count++;
-}
-
-static int altera_spi_txrx(struct spi_master *master,
-	struct spi_device *spi, struct spi_transfer *t)
-{
-	struct altera_spi *hw = spi_master_get_devdata(master);
-	u32 val;
-
-	hw->tx = t->tx_buf;
-	hw->rx = t->rx_buf;
-	hw->count = 0;
-	hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
-	hw->len = t->len / hw->bytes_per_word;
-
-	if (hw->irq >= 0) {
-		/* enable receive interrupt */
-		hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
-		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
-
-		/* send the first byte */
-		altera_spi_tx_word(hw);
-
-		return 1;
-	}
-
-	while (hw->count < hw->len) {
-		altera_spi_tx_word(hw);
-
-		for (;;) {
-			altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
-			if (val & ALTERA_SPI_STATUS_RRDY_MSK)
-				break;
-
-			cpu_relax();
-		}
-
-		altera_spi_rx_word(hw);
-	}
-	spi_finalize_current_transfer(master);
-
-	return 0;
-}
-
-static irqreturn_t altera_spi_irq(int irq, void *dev)
-{
-	struct spi_master *master = dev;
-	struct altera_spi *hw = spi_master_get_devdata(master);
-
-	altera_spi_rx_word(hw);
-
-	if (hw->count < hw->len) {
-		altera_spi_tx_word(hw);
-	} else {
-		/* disable receive interrupt */
-		hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
-		altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
-
-		spi_finalize_current_transfer(master);
-	}
-
-	return IRQ_HANDLED;
-}
-
-static int altera_spi_probe(struct platform_device *pdev)
-{
-	const struct platform_device_id *platid = platform_get_device_id(pdev);
-	struct altera_spi_platform_data *pdata = dev_get_platdata(&pdev->dev);
-	enum altera_spi_type type = ALTERA_SPI_TYPE_UNKNOWN;
-	struct altera_spi *hw;
-	struct spi_master *master;
-	int err = -ENODEV;
-	u32 val;
-	u16 i;
-
-	master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
-	if (!master)
-		return err;
-
-	/* setup the master state. */
-	master->bus_num = pdev->id;
-
-	if (pdata) {
-		if (pdata->num_chipselect > ALTERA_SPI_MAX_CS) {
-			dev_err(&pdev->dev,
-				"Invalid number of chipselect: %hu\n",
-				pdata->num_chipselect);
-			err = -EINVAL;
-			goto exit;
-		}
-
-		master->num_chipselect = pdata->num_chipselect;
-		master->mode_bits = pdata->mode_bits;
-		master->bits_per_word_mask = pdata->bits_per_word_mask;
-	} else {
-		master->num_chipselect = 16;
-		master->mode_bits = SPI_CS_HIGH;
-		master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16);
-	}
-
-	master->dev.of_node = pdev->dev.of_node;
-	master->transfer_one = altera_spi_txrx;
-	master->set_cs = altera_spi_set_cs;
-
-	hw = spi_master_get_devdata(master);
-	hw->dev = &pdev->dev;
-
-	if (platid)
-		type = platid->driver_data;
-
-	/* find and map our resources */
-	if (type == ALTERA_SPI_TYPE_SUBDEV) {
-		struct resource *regoff;
-
-		hw->regmap = dev_get_regmap(pdev->dev.parent, NULL);
-		if (!hw->regmap) {
-			dev_err(&pdev->dev, "get regmap failed\n");
-			goto exit;
-		}
-
-		regoff = platform_get_resource(pdev, IORESOURCE_REG, 0);
-		if (regoff)
-			hw->regoff = regoff->start;
-	} else {
-		void __iomem *res;
-
-		res = devm_platform_ioremap_resource(pdev, 0);
-		if (IS_ERR(res)) {
-			err = PTR_ERR(res);
-			goto exit;
-		}
-
-		hw->regmap = devm_regmap_init_mmio(&pdev->dev, res,
-						   &spi_altera_config);
-		if (IS_ERR(hw->regmap)) {
-			dev_err(&pdev->dev, "regmap mmio init failed\n");
-			err = PTR_ERR(hw->regmap);
-			goto exit;
-		}
-	}
-
-	/* program defaults into the registers */
-	hw->imr = 0;		/* disable spi interrupts */
-	altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
-	altr_spi_writel(hw, ALTERA_SPI_STATUS, 0);	/* clear status reg */
-	altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
-	if (val & ALTERA_SPI_STATUS_RRDY_MSK)
-		altr_spi_readl(hw, ALTERA_SPI_RXDATA, &val); /* flush rxdata */
-	/* irq is optional */
-	hw->irq = platform_get_irq(pdev, 0);
-	if (hw->irq >= 0) {
-		err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
-				       pdev->name, master);
-		if (err)
-			goto exit;
-	}
-
-	err = devm_spi_register_master(&pdev->dev, master);
-	if (err)
-		goto exit;
-
-	if (pdata) {
-		for (i = 0; i < pdata->num_devices; i++) {
-			if (!spi_new_device(master, pdata->devices + i))
-				dev_warn(&pdev->dev,
-					 "unable to create SPI device: %s\n",
-					 pdata->devices[i].modalias);
-		}
-	}
-
-	dev_info(&pdev->dev, "regoff %u, irq %d\n", hw->regoff, hw->irq);
-
-	return 0;
-exit:
-	spi_master_put(master);
-	return err;
-}
-
-#ifdef CONFIG_OF
-static const struct of_device_id altera_spi_match[] = {
-	{ .compatible = "ALTR,spi-1.0", },
-	{ .compatible = "altr,spi-1.0", },
-	{},
-};
-MODULE_DEVICE_TABLE(of, altera_spi_match);
-#endif /* CONFIG_OF */
-
-static const struct platform_device_id altera_spi_ids[] = {
-	{ DRV_NAME,		ALTERA_SPI_TYPE_UNKNOWN },
-	{ "subdev_spi_altera",	ALTERA_SPI_TYPE_SUBDEV },
-	{ }
-};
-MODULE_DEVICE_TABLE(platform, altera_spi_ids);
-
-static struct platform_driver altera_spi_driver = {
-	.probe = altera_spi_probe,
-	.driver = {
-		.name = DRV_NAME,
-		.pm = NULL,
-		.of_match_table = of_match_ptr(altera_spi_match),
-	},
-	.id_table	= altera_spi_ids,
-};
-module_platform_driver(altera_spi_driver);
-
-MODULE_DESCRIPTION("Altera SPI driver");
-MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/include/linux/spi/altera.h b/include/linux/spi/altera.h
index 2d42641..2e2a622 100644
--- a/include/linux/spi/altera.h
+++ b/include/linux/spi/altera.h
@@ -5,10 +5,13 @@
 #ifndef __LINUX_SPI_ALTERA_H
 #define __LINUX_SPI_ALTERA_H
 
+#include <linux/interrupt.h>
 #include <linux/regmap.h>
 #include <linux/spi/spi.h>
 #include <linux/types.h>
 
+#define ALTERA_SPI_MAX_CS		32
+
 /**
  * struct altera_spi_platform_data - Platform data of the Altera SPI driver
  * @mode_bits:		Mode bits of SPI master.
@@ -26,4 +29,22 @@ struct altera_spi_platform_data {
 	struct spi_board_info		*devices;
 };
 
+struct altera_spi {
+	int irq;
+	int len;
+	int count;
+	int bytes_per_word;
+	u32 imr;
+
+	/* data buffers */
+	const unsigned char *tx;
+	unsigned char *rx;
+
+	struct regmap *regmap;
+	u32 regoff;
+	struct device *dev;
+};
+
+extern irqreturn_t altera_spi_irq(int irq, void *dev);
+extern void altera_spi_init_master(struct spi_master *master);
 #endif /* __LINUX_SPI_ALTERA_H */
-- 
1.8.3.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 2/2] spi: altera: Add DFL bus driver for Altera API Controller
  2021-04-16 16:57 [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI matthew.gerlach
  2021-04-16 16:57 ` [PATCH v3 1/2] spi: altera: separate core code from platform code matthew.gerlach
@ 2021-04-16 16:57 ` matthew.gerlach
  2021-04-20 18:35 ` [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI Mark Brown
  2 siblings, 0 replies; 4+ messages in thread
From: matthew.gerlach @ 2021-04-16 16:57 UTC (permalink / raw)
  To: hao.wu, trix, linux-fpga, linux-kernel, yilun.xu,
	russell.h.weight, broonie, linux-spi
  Cc: Matthew Gerlach

From: Matthew Gerlach <matthew.gerlach@linux.intel.com>

This patch adds a Device Feature List (DFL) bus driver for the
Altera SPI Master controller.  The SPI master is connected to an
Intel SPI Slave to Avalon Bridge inside an Intel MAX10
BMC Chip.

Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
---
v3: Instantiate SPI device instead of creating a sub-platform device.
    Changed comment style as per Mark Brown.

v2: moved drivers/fpga/dfl-spi-altera.c to drivers/spi/spi-altera-dfl.c
---
 drivers/spi/Kconfig          |   9 ++
 drivers/spi/Makefile         |   1 +
 drivers/spi/spi-altera-dfl.c | 204 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 214 insertions(+)
 create mode 100644 drivers/spi/spi-altera-dfl.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index e59f548..8b161ec 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -70,6 +70,15 @@ config SPI_ALTERA_CORE
 	help
 	  "The core code for the Altera SPI Controller"
 
+config SPI_ALTERA_DFL
+	tristate "DFL bus driver for Altera SPI Controller"
+	depends on FPGA_DFL
+	select SPI_ALTERA_CORE
+	help
+	  This is a Device Feature List (DFL) bus driver for the
+	  Altera SPI master controller.  The SPI master is connected
+	  to a SPI slave to Avalon bridge in a Intel MAX BMC.
+
 config SPI_AR934X
 	tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
 	depends on ATH79 || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index e6be348..13e54c4 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_ALTERA)		+= spi-altera-platform.o
 obj-$(CONFIG_SPI_ALTERA_CORE)		+= spi-altera-core.o
+obj-$(CONFIG_SPI_ALTERA_DFL)		+= spi-altera-dfl.o
 obj-$(CONFIG_SPI_AR934X)		+= spi-ar934x.o
 obj-$(CONFIG_SPI_ARMADA_3700)		+= spi-armada-3700.o
 obj-$(CONFIG_SPI_ATMEL)			+= spi-atmel.o
diff --git a/drivers/spi/spi-altera-dfl.c b/drivers/spi/spi-altera-dfl.c
new file mode 100644
index 0000000..3e32e4f
--- /dev/null
+++ b/drivers/spi/spi-altera-dfl.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// DFL bus driver for Altera SPI Master
+//
+// Copyright (C) 2020 Intel Corporation, Inc.
+//
+// Authors:
+//   Matthew Gerlach <matthew.gerlach@linux.intel.com>
+//
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/bitfield.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/altera.h>
+#include <linux/dfl.h>
+
+#define FME_FEATURE_ID_MAX10_SPI	0xe
+#define FME_FEATURE_REV_MAX10_SPI_N5010	0x1
+
+#define SPI_CORE_PARAMETER      0x8
+#define SHIFT_MODE              BIT_ULL(1)
+#define SHIFT_MODE_MSB          0
+#define SHIFT_MODE_LSB          1
+#define DATA_WIDTH              GENMASK_ULL(7, 2)
+#define NUM_CHIPSELECT          GENMASK_ULL(13, 8)
+#define CLK_POLARITY            BIT_ULL(14)
+#define CLK_PHASE               BIT_ULL(15)
+#define PERIPHERAL_ID           GENMASK_ULL(47, 32)
+#define SPI_CLK                 GENMASK_ULL(31, 22)
+#define SPI_INDIRECT_ACC_OFST   0x10
+
+#define INDIRECT_ADDR           (SPI_INDIRECT_ACC_OFST+0x0)
+#define INDIRECT_WR             BIT_ULL(8)
+#define INDIRECT_RD             BIT_ULL(9)
+#define INDIRECT_RD_DATA        (SPI_INDIRECT_ACC_OFST+0x8)
+#define INDIRECT_DATA_MASK      GENMASK_ULL(31, 0)
+#define INDIRECT_DEBUG          BIT_ULL(32)
+#define INDIRECT_WR_DATA        (SPI_INDIRECT_ACC_OFST+0x10)
+#define INDIRECT_TIMEOUT        10000
+
+static int indirect_bus_reg_read(void *context, unsigned int reg,
+				 unsigned int *val)
+{
+	void __iomem *base = context;
+	int loops;
+	u64 v;
+
+	writeq((reg >> 2) | INDIRECT_RD, base + INDIRECT_ADDR);
+
+	loops = 0;
+	while ((readq(base + INDIRECT_ADDR) & INDIRECT_RD) &&
+	       (loops++ < INDIRECT_TIMEOUT))
+		cpu_relax();
+
+	if (loops >= INDIRECT_TIMEOUT) {
+		pr_err("%s timed out %d\n", __func__, loops);
+		return -ETIME;
+	}
+
+	v = readq(base + INDIRECT_RD_DATA);
+
+	*val = v & INDIRECT_DATA_MASK;
+
+	return 0;
+}
+
+static int indirect_bus_reg_write(void *context, unsigned int reg,
+				  unsigned int val)
+{
+	void __iomem *base = context;
+	int loops;
+
+	writeq(val, base + INDIRECT_WR_DATA);
+	writeq((reg >> 2) | INDIRECT_WR, base + INDIRECT_ADDR);
+
+	loops = 0;
+	while ((readq(base + INDIRECT_ADDR) & INDIRECT_WR) &&
+	       (loops++ < INDIRECT_TIMEOUT))
+		cpu_relax();
+
+	if (loops >= INDIRECT_TIMEOUT) {
+		pr_err("%s timed out %d\n", __func__, loops);
+		return -ETIME;
+	}
+	return 0;
+}
+
+static const struct regmap_config indirect_regbus_cfg = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.max_register = 24,
+
+	.reg_write = indirect_bus_reg_write,
+	.reg_read = indirect_bus_reg_read,
+};
+
+static struct spi_board_info m10_bmc_info = {
+	.modalias = "m10-d5005",
+	.max_speed_hz = 12500000,
+	.bus_num = 0,
+	.chip_select = 0,
+};
+
+static void config_spi_master(void __iomem *base, struct spi_master *master)
+{
+	u64 v;
+
+	v = readq(base + SPI_CORE_PARAMETER);
+
+	master->mode_bits = SPI_CS_HIGH;
+	if (FIELD_GET(CLK_POLARITY, v))
+		master->mode_bits |= SPI_CPOL;
+	if (FIELD_GET(CLK_PHASE, v))
+		master->mode_bits |= SPI_CPHA;
+
+	master->num_chipselect = FIELD_GET(NUM_CHIPSELECT, v);
+	master->bits_per_word_mask =
+		SPI_BPW_RANGE_MASK(1, FIELD_GET(DATA_WIDTH, v));
+}
+
+static int dfl_spi_altera_probe(struct dfl_device *dfl_dev)
+{
+	struct device *dev = &dfl_dev->dev;
+	struct spi_master *master;
+	struct altera_spi *hw;
+	void __iomem *base;
+	int err = -ENODEV;
+
+	master = spi_alloc_master(dev, sizeof(struct altera_spi));
+	if (!master)
+		return -ENOMEM;
+
+	master->bus_num = dfl_dev->id;
+
+	hw = spi_master_get_devdata(master);
+
+	hw->dev = dev;
+
+	base = devm_ioremap_resource(dev, &dfl_dev->mmio_res);
+
+	if (IS_ERR(base)) {
+		dev_err(dev, "%s get mem resource fail!\n", __func__);
+		return PTR_ERR(base);
+	}
+
+	config_spi_master(base, master);
+	dev_dbg(dev, "%s cs %u bpm 0x%x mode 0x%x\n", __func__,
+		master->num_chipselect, master->bits_per_word_mask,
+		master->mode_bits);
+
+	hw->regmap = devm_regmap_init(dev, NULL, base, &indirect_regbus_cfg);
+	if (IS_ERR(hw->regmap))
+		return PTR_ERR(hw->regmap);
+
+	hw->irq = -EINVAL;
+
+	altera_spi_init_master(master);
+
+	err = devm_spi_register_master(dev, master);
+	if (err) {
+		dev_err(dev, "%s failed to register spi master %d\n", __func__, err);
+		goto exit;
+	}
+
+	if (!spi_new_device(master,  &m10_bmc_info)) {
+		dev_err(dev, "%s failed to create SPI device: %s\n",
+			__func__, m10_bmc_info.modalias);
+	}
+
+	return 0;
+exit:
+	spi_master_put(master);
+	return err;
+}
+
+static const struct dfl_device_id dfl_spi_altera_ids[] = {
+	{ FME_ID, FME_FEATURE_ID_MAX10_SPI },
+	{ }
+};
+
+static struct dfl_driver dfl_spi_altera_driver = {
+	.drv	= {
+		.name       = "dfl-spi-altera",
+	},
+	.id_table = dfl_spi_altera_ids,
+	.probe   = dfl_spi_altera_probe,
+};
+
+module_dfl_driver(dfl_spi_altera_driver);
+
+MODULE_DEVICE_TABLE(dfl, dfl_spi_altera_ids);
+MODULE_DESCRIPTION("DFL spi altera driver");
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
-- 
1.8.3.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI
  2021-04-16 16:57 [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI matthew.gerlach
  2021-04-16 16:57 ` [PATCH v3 1/2] spi: altera: separate core code from platform code matthew.gerlach
  2021-04-16 16:57 ` [PATCH v3 2/2] spi: altera: Add DFL bus driver for Altera API Controller matthew.gerlach
@ 2021-04-20 18:35 ` Mark Brown
  2 siblings, 0 replies; 4+ messages in thread
From: Mark Brown @ 2021-04-20 18:35 UTC (permalink / raw)
  To: linux-spi, russell.h.weight, linux-kernel, trix, yilun.xu,
	linux-fpga, matthew.gerlach, hao.wu
  Cc: Mark Brown

On Fri, 16 Apr 2021 09:57:18 -0700, matthew.gerlach@linux.intel.com wrote:
> This patch set adds Device Feature List (DFL) bus support for
> the Altera SPI Master controller.
> 
> Patch 1 separates spi-altera.c into spi-altera-core.c and
> spi-altera-platform.c.
> 
> Patch 2 adds spi-altera-dfl.c.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next

Thanks!

[1/2] spi: altera: separate core code from platform code
      commit: b0c3d9354de1f87eebc00694d5218b6611265933
[2/2] spi: altera: Add DFL bus driver for Altera API Controller
      commit: ba2fc167e9447596a812e828842d0130ea9cd0e4

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, back to index

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-16 16:57 [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI matthew.gerlach
2021-04-16 16:57 ` [PATCH v3 1/2] spi: altera: separate core code from platform code matthew.gerlach
2021-04-16 16:57 ` [PATCH v3 2/2] spi: altera: Add DFL bus driver for Altera API Controller matthew.gerlach
2021-04-20 18:35 ` [PATCH v3 0/2] spi: altera: Add DFL bus support for Altera SPI Mark Brown

Linux-SPI Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-spi/0 linux-spi/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-spi linux-spi/ https://lore.kernel.org/linux-spi \
		linux-spi@vger.kernel.org
	public-inbox-index linux-spi

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-spi


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git