All of lore.kernel.org
 help / color / mirror / Atom feed
From: Hauke Mehrtens <hauke-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
To: broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org
Cc: linux-spi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	hauke.mehrtens-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
	thomas.langer-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org,
	daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	john-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org,
	nbd-Vt+b4OUoWG0@public.gmane.org,
	Hauke Mehrtens <hauke-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
Subject: [PATCH] spi: intel-ssc: add support for Intel SSC SPI controller
Date: Sun,  2 Oct 2016 23:44:08 +0200	[thread overview]
Message-ID: <20161002214408.30329-1-hauke@hauke-m.de> (raw)

From: Daniel Schwierzeck <daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

This driver supports the Intel SSC SPI controller in master
mode. This controller is found on Intel (former Lantiq) SoCs like
the Danube, Falcon, xRX200, xRX300.

Signed-off-by: Daniel Schwierzeck <daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Hauke Mehrtens <hauke-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
---
 .../devicetree/bindings/spi/spi-intel-ssc.txt      |   30 +
 drivers/spi/Kconfig                                |    8 +
 drivers/spi/Makefile                               |    1 +
 drivers/spi/spi-intel-ssc.c                        | 1116 ++++++++++++++++++++
 4 files changed, 1155 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/spi/spi-intel-ssc.txt
 create mode 100644 drivers/spi/spi-intel-ssc.c

diff --git a/Documentation/devicetree/bindings/spi/spi-intel-ssc.txt b/Documentation/devicetree/bindings/spi/spi-intel-ssc.txt
new file mode 100644
index 0000000..cf11b00
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi-intel-ssc.txt
@@ -0,0 +1,30 @@
+Intel Synchronous Serial Controller (SSC) SPI master driver
+
+Required properties:
+- compatible: "intel,ase-spi", "intel,falcon-spi", "intel,xrx100-spi"
+- #address-cells: see spi-bus.txt
+- #size-cells: see spi-bus.txt
+- reg: address and length of the spi master registers
+- interrupts: should contain the "spi_rx", "spi_tx" and "spi_err" interrupt.
+
+
+Optional properties:
+- cs-gpios: see spi-bus.txt
+- clocks: spi clock phandle
+- num-cs: see spi-bus.txt, set to 8 if unset
+- base-cs: the number of the first chip select, set to 1 if unset.
+
+Example:
+
+
+spi: spi@E100800 {
+	compatible = "intel,xrx200-spi", "intel,xrx100-spi";
+	reg = <0xE100800 0x100>;
+	interrupt-parent = <&icu0>;
+	interrupts = <22 23 24>;
+	interrupt-names = "spi_rx", "spi_tx", "spi_err";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	num-cs = <6>;
+	base-cs = <1>;
+};
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index d6fb8d4..8b84e04 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -386,6 +386,14 @@ config SPI_NUC900
 	help
 	  SPI driver for Nuvoton NUC900 series ARM SoCs
 
+config SPI_INTEL_SSC
+	tristate "Intel SSC SPI controller"
+	depends on LANTIQ
+	help
+	  This driver supports the Intel SSC SPI controller in master
+	  mode. This controller is found on Intel (former Lantiq) SoCs like
+	  the Danube, Falcon, xRX200, xRX300.
+
 config SPI_OC_TINY
 	tristate "OpenCores tiny SPI"
 	depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 185367e..206f32e 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SPI_FSL_SPI)		+= spi-fsl-spi.o
 obj-$(CONFIG_SPI_GPIO)			+= spi-gpio.o
 obj-$(CONFIG_SPI_IMG_SPFI)		+= spi-img-spfi.o
 obj-$(CONFIG_SPI_IMX)			+= spi-imx.o
+obj-$(CONFIG_SPI_INTEL_SSC)		+= spi-intel-ssc.o
 obj-$(CONFIG_SPI_LM70_LLP)		+= spi-lm70llp.o
 obj-$(CONFIG_SPI_LP8841_RTC)		+= spi-lp8841-rtc.o
 obj-$(CONFIG_SPI_MESON_SPIFC)		+= spi-meson-spifc.o
diff --git a/drivers/spi/spi-intel-ssc.c b/drivers/spi/spi-intel-ssc.c
new file mode 100644
index 0000000..7f55133
--- /dev/null
+++ b/drivers/spi/spi-intel-ssc.c
@@ -0,0 +1,1116 @@
+/*
+ * Copyright (C) 2011-2015 Daniel Schwierzeck <daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ * Copyright (C) 2016 Hauke Mehrtens <hauke-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
+ *
+ * This program is free software; you can distribute it and/or modify it
+ * under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+
+#include <lantiq_soc.h>
+
+#define SPI_RX_IRQ_NAME		"spi_rx"
+#define SPI_TX_IRQ_NAME		"spi_tx"
+#define SPI_ERR_IRQ_NAME	"spi_err"
+#define SPI_FRM_IRQ_NAME	"spi_frm"
+
+#define SPI_CLC			0x00
+#define SPI_PISEL		0x04
+#define SPI_ID			0x08
+#define SPI_CON			0x10
+#define SPI_STAT		0x14
+#define SPI_WHBSTATE		0x18
+#define SPI_TB			0x20
+#define SPI_RB			0x24
+#define SPI_RXFCON		0x30
+#define SPI_TXFCON		0x34
+#define SPI_FSTAT		0x38
+#define SPI_BRT			0x40
+#define SPI_BRSTAT		0x44
+#define SPI_SFCON		0x60
+#define SPI_SFSTAT		0x64
+#define SPI_GPOCON		0x70
+#define SPI_GPOSTAT		0x74
+#define SPI_FPGO		0x78
+#define SPI_RXREQ		0x80
+#define SPI_RXCNT		0x84
+#define SPI_DMACON		0xec
+#define SPI_IRNEN		0xf4
+#define SPI_IRNICR		0xf8
+#define SPI_IRNCR		0xfc
+
+#define SPI_CLC_SMC_S		16	/* Clock divider for sleep mode */
+#define SPI_CLC_SMC_M		(0xFF << SPI_CLC_SMC_S)
+#define SPI_CLC_RMC_S		8	/* Clock divider for normal run mode */
+#define SPI_CLC_RMC_M		(0xFF << SPI_CLC_RMC_S)
+#define SPI_CLC_DISS		BIT(1)	/* Disable status bit */
+#define SPI_CLC_DISR		BIT(0)	/* Disable request bit */
+
+#define SPI_ID_TXFS_S		24	/* Implemented TX FIFO size */
+#define SPI_ID_TXFS_M		(0x3F << SPI_ID_TXFS_S)
+#define SPI_ID_RXFS_S		16	/* Implemented RX FIFO size */
+#define SPI_ID_RXFS_M		(0x3F << SPI_ID_RXFS_S)
+#define SPI_ID_MOD_S		8	/* Module ID */
+#define SPI_ID_MOD_M		(0xff << SPI_ID_MOD_S)
+#define SPI_ID_CFG_S		5	/* DMA interface support */
+#define SPI_ID_CFG_M		(1 << SPI_ID_CFG_S)
+#define SPI_ID_REV_M		0x1F	/* Hardware revision number */
+
+#define SPI_CON_BM_S		16	/* Data width selection */
+#define SPI_CON_BM_M		(0x1F << SPI_CON_BM_S)
+#define SPI_CON_EM		BIT(24)	/* Echo mode */
+#define SPI_CON_IDLE		BIT(23)	/* Idle bit value */
+#define SPI_CON_ENBV		BIT(22)	/* Enable byte valid control */
+#define SPI_CON_RUEN		BIT(12)	/* Receive underflow error enable */
+#define SPI_CON_TUEN		BIT(11)	/* Transmit underflow error enable */
+#define SPI_CON_AEN		BIT(10)	/* Abort error enable */
+#define SPI_CON_REN		BIT(9)	/* Receive overflow error enable */
+#define SPI_CON_TEN		BIT(8)	/* Transmit overflow error enable */
+#define SPI_CON_LB		BIT(7)	/* Loopback control */
+#define SPI_CON_PO		BIT(6)	/* Clock polarity control */
+#define SPI_CON_PH		BIT(5)	/* Clock phase control */
+#define SPI_CON_HB		BIT(4)	/* Heading control */
+#define SPI_CON_RXOFF		BIT(1)	/* Switch receiver off */
+#define SPI_CON_TXOFF		BIT(0)	/* Switch transmitter off */
+
+#define SPI_STAT_RXBV_S		28
+#define SPI_STAT_RXBV_M		(0x7 << SPI_STAT_RXBV_S)
+#define SPI_STAT_BSY		BIT(13)	/* Busy flag */
+#define SPI_STAT_RUE		BIT(12)	/* Receive underflow error flag */
+#define SPI_STAT_TUE		BIT(11)	/* Transmit underflow error flag */
+#define SPI_STAT_AE		BIT(10)	/* Abort error flag */
+#define SPI_STAT_RE		BIT(9)	/* Receive error flag */
+#define SPI_STAT_TE		BIT(8)	/* Transmit error flag */
+#define SPI_STAT_ME		BIT(7)	/* Mode error flag */
+#define SPI_STAT_MS		BIT(1)	/* Master/slave select bit */
+#define SPI_STAT_EN		BIT(0)	/* Enable bit */
+
+#define SPI_WHBSTATE_SETTUE	BIT(15)	/* Set transmit underflow error flag */
+#define SPI_WHBSTATE_SETAE	BIT(14)	/* Set abort error flag */
+#define SPI_WHBSTATE_SETRE	BIT(13)	/* Set receive error flag */
+#define SPI_WHBSTATE_SETTE	BIT(12)	/* Set transmit error flag */
+#define SPI_WHBSTATE_CLRTUE	BIT(11)	/* Clear transmit underflow error flag */
+#define SPI_WHBSTATE_CLRAE	BIT(10)	/* Clear abort error flag */
+#define SPI_WHBSTATE_CLRRE	BIT(9)	/* Clear receive error flag */
+#define SPI_WHBSTATE_CLRTE	BIT(8)	/* Clear transmit error flag */
+#define SPI_WHBSTATE_SETME	BIT(7)	/* Set mode error flag */
+#define SPI_WHBSTATE_CLRME	BIT(6)	/* Clear mode error flag */
+#define SPI_WHBSTATE_SETRUE	BIT(5)	/* Set receive underflow error flag */
+#define SPI_WHBSTATE_CLRRUE	BIT(4)	/* Clear receive underflow error flag */
+#define SPI_WHBSTATE_SETMS	BIT(3)	/* Set master select bit */
+#define SPI_WHBSTATE_CLRMS	BIT(2)	/* Clear master select bit */
+#define SPI_WHBSTATE_SETEN	BIT(1)	/* Set enable bit (operational mode) */
+#define SPI_WHBSTATE_CLREN	BIT(0)	/* Clear enable bit (config mode */
+#define SPI_WHBSTATE_CLR_ERRORS	0x0F50
+
+#define SPI_RXFCON_RXFITL_S	8	/* FIFO interrupt trigger level */
+#define SPI_RXFCON_RXFITL_M	(0x3F << SPI_RXFCON_RXFITL_S)
+#define SPI_RXFCON_RXFLU	BIT(1)	/* FIFO flush */
+#define SPI_RXFCON_RXFEN	BIT(0)	/* FIFO enable */
+
+#define SPI_TXFCON_TXFITL_S	8	/* FIFO interrupt trigger level */
+#define SPI_TXFCON_TXFITL_M	(0x3F << SPI_TXFCON_TXFITL_S)
+#define SPI_TXFCON_TXFLU	BIT(1)	/* FIFO flush */
+#define SPI_TXFCON_TXFEN	BIT(0)	/* FIFO enable */
+
+#define SPI_FSTAT_RXFFL_S	0
+#define SPI_FSTAT_RXFFL_M	(0x3f << SPI_FSTAT_RXFFL_S)
+#define SPI_FSTAT_TXFFL_S	8
+#define SPI_FSTAT_TXFFL_M	(0x3f << SPI_FSTAT_TXFFL_S)
+
+#define SPI_GPOCON_ISCSBN_S	8
+#define SPI_GPOCON_INVOUTN_S	0
+
+#define SPI_FGPO_SETOUTN_S	8
+#define SPI_FGPO_CLROUTN_S	0
+
+#define SPI_RXREQ_RXCNT_M	0xFFFF	/* Receive count value */
+#define SPI_RXCNT_TODO_M	0xFFFF	/* Recevie to-do value */
+
+#define SPI_IRNEN_TFI		BIT(4)	/* TX finished interrupt */
+#define SPI_IRNEN_F		BIT(3)	/* Frame end interrupt request */
+#define SPI_IRNEN_E		BIT(2)	/* Error end interrupt request */
+#define SPI_IRNEN_T_XWAY	BIT(1)	/* Transmit end interrupt request */
+#define SPI_IRNEN_R_XWAY	BIT(0)	/* Receive end interrupt request */
+#define SPI_IRNEN_R_XRX		BIT(1)	/* Transmit end interrupt request */
+#define SPI_IRNEN_T_XRX		BIT(0)	/* Receive end interrupt request */
+#define SPI_IRNEN_ALL		0x1F
+
+struct intel_ssc_spi_hwcfg {
+	unsigned int irnen_r;
+	unsigned int irnen_t;
+};
+
+struct intel_ssc_spi {
+	struct spi_master		*master;
+	struct device			*dev;
+	void __iomem			*regbase;
+	struct clk			*spi_clk;
+	struct clk			*fpi_clk;
+	const struct intel_ssc_spi_hwcfg	*hwcfg;
+
+	spinlock_t			lock;
+	struct completion		xfer_complete;
+
+	const u8			*tx;
+	u8				*rx;
+	unsigned int			tx_todo;
+	unsigned int			rx_todo;
+	unsigned int			bits_per_word;
+	unsigned int			speed_hz;
+	int				status;
+	unsigned long			timeout;
+	unsigned int			cs_delay;
+	unsigned int			tx_fifo_size;
+	unsigned int			rx_fifo_size;
+	unsigned int			base_cs;
+};
+
+struct intel_ssc_spi_cstate {
+	int cs_gpio;
+};
+
+static u32 intel_ssc_spi_readl(const struct intel_ssc_spi *spi, u32 reg)
+{
+	return __raw_readl(spi->regbase + reg);
+}
+
+static void intel_ssc_spi_writel(const struct intel_ssc_spi *spi, u32 val,
+				 u32 reg)
+{
+	__raw_writel(val, spi->regbase + reg);
+}
+
+static void intel_ssc_spi_maskl(const struct intel_ssc_spi *spi, u32 clr,
+				u32 set, u32 reg)
+{
+	u32 val = __raw_readl(spi->regbase + reg);
+	val &= ~clr;
+	val |= set;
+	__raw_writel(val, spi->regbase + reg);
+}
+
+static unsigned int tx_fifo_level(const struct intel_ssc_spi *spi)
+{
+	u32 fstat = intel_ssc_spi_readl(spi, SPI_FSTAT);
+	return (fstat & SPI_FSTAT_TXFFL_M) >> SPI_FSTAT_TXFFL_S;
+}
+
+static unsigned int rx_fifo_level(const struct intel_ssc_spi *spi)
+{
+	u32 fstat = intel_ssc_spi_readl(spi, SPI_FSTAT);
+	return fstat & SPI_FSTAT_RXFFL_M;
+}
+
+static unsigned int tx_fifo_free(const struct intel_ssc_spi *spi)
+{
+	return spi->tx_fifo_size - tx_fifo_level(spi);
+}
+
+static void rx_fifo_reset(const struct intel_ssc_spi *spi)
+{
+	u32 val = spi->rx_fifo_size << SPI_RXFCON_RXFITL_S;
+	val |= SPI_RXFCON_RXFEN | SPI_RXFCON_RXFLU;
+	intel_ssc_spi_writel(spi, val, SPI_RXFCON);
+}
+
+static void tx_fifo_reset(const struct intel_ssc_spi *spi)
+{
+	u32 val = 1 << SPI_TXFCON_TXFITL_S;
+	val |= SPI_TXFCON_TXFEN | SPI_TXFCON_TXFLU;
+	intel_ssc_spi_writel(spi, val, SPI_TXFCON);
+}
+
+static void rx_fifo_flush(const struct intel_ssc_spi *spi)
+{
+	intel_ssc_spi_maskl(spi, 0, SPI_RXFCON_RXFLU, SPI_RXFCON);
+}
+
+static void tx_fifo_flush(const struct intel_ssc_spi *spi)
+{
+	intel_ssc_spi_maskl(spi, 0, SPI_TXFCON_TXFLU, SPI_TXFCON);
+}
+
+static int hw_is_busy(const struct intel_ssc_spi *spi)
+{
+	u32 stat = intel_ssc_spi_readl(spi, SPI_STAT);
+	return stat & SPI_STAT_BSY;
+}
+
+static void hw_enter_config_mode(const struct intel_ssc_spi *spi)
+{
+	intel_ssc_spi_writel(spi, SPI_WHBSTATE_CLREN, SPI_WHBSTATE);
+}
+
+static void hw_enter_active_mode(const struct intel_ssc_spi *spi)
+{
+	intel_ssc_spi_writel(spi, SPI_WHBSTATE_SETEN, SPI_WHBSTATE);
+}
+
+static void hw_setup_speed_hz(const struct intel_ssc_spi *spi,
+			      unsigned int max_speed_hz)
+{
+	u32 spi_clk, brt;
+
+	/*
+	 * SPI module clock is derived from FPI bus clock dependent on
+	 * divider value in CLC.RMS which is always set to 1.
+	 *
+	 *                 f_SPI
+	 * baudrate = --------------
+	 *             2 * (BR + 1)
+	 */
+	spi_clk = clk_get_rate(spi->fpi_clk) / 2;
+
+	if (max_speed_hz > spi_clk)
+		brt = 0;
+	else
+		brt = spi_clk / max_speed_hz - 1;
+
+	if (brt > 0xFFFF)
+		brt = 0xFFFF;
+
+	dev_dbg(spi->dev, "spi_clk %u, max_speed_hz %u, brt %u\n",
+		spi_clk, max_speed_hz, brt);
+
+	intel_ssc_spi_writel(spi, brt, SPI_BRT);
+}
+
+static void hw_setup_bits_per_word(const struct intel_ssc_spi *spi,
+					unsigned int bits_per_word)
+{
+	u32 bm;
+
+	/* CON.BM value = bits_per_word - 1 */
+	bm = (bits_per_word - 1) << SPI_CON_BM_S;
+
+	intel_ssc_spi_maskl(spi, SPI_CON_BM_M, bm, SPI_CON);
+}
+
+static void hw_setup_clock_mode(const struct intel_ssc_spi *spi,
+				unsigned int mode)
+{
+	u32 con_set = 0, con_clr = 0;
+
+	/*
+	 * SPI mode mapping in CON register:
+	 * Mode CPOL CPHA CON.PO CON.PH
+	 *  0    0    0      0      1
+	 *  1    0    1      0      0
+	 *  2    1    0      1      1
+	 *  3    1    1      1      0
+	 */
+	if (mode & SPI_CPHA)
+		con_clr |= SPI_CON_PH;
+	else
+		con_set |= SPI_CON_PH;
+
+	if (mode & SPI_CPOL)
+		con_set |= SPI_CON_PO | SPI_CON_IDLE;
+	else
+		con_clr |= SPI_CON_PO | SPI_CON_IDLE;
+
+	/* Set heading control */
+	if (mode & SPI_LSB_FIRST)
+		con_clr |= SPI_CON_HB;
+	else
+		con_set |= SPI_CON_HB;
+
+	/* Set loopback mode */
+	if (mode & SPI_LOOP)
+		con_set |= SPI_CON_LB;
+	else
+		con_clr |= SPI_CON_LB;
+
+	intel_ssc_spi_maskl(spi, con_clr, con_set, SPI_CON);
+}
+
+static void intel_ssc_spi_hw_init(const struct intel_ssc_spi *spi)
+{
+	/*
+	 * Set clock divider for run mode to 1 to
+	 * run at same frequency as FPI bus
+	 */
+	intel_ssc_spi_writel(spi, 1 << SPI_CLC_RMC_S, SPI_CLC);
+
+	/* Put controller into config mode */
+	hw_enter_config_mode(spi);
+
+	/* Disable all interrupts */
+	intel_ssc_spi_writel(spi, 0, SPI_IRNEN);
+
+	/* Clear error flags */
+	intel_ssc_spi_maskl(spi, 0, SPI_WHBSTATE_CLR_ERRORS, SPI_WHBSTATE);
+
+	/* Enable error checking, disable TX/RX */
+	intel_ssc_spi_writel(spi, SPI_CON_RUEN | SPI_CON_AEN | SPI_CON_TEN |
+		SPI_CON_REN | SPI_CON_TXOFF | SPI_CON_RXOFF, SPI_CON);
+
+	/* Setup default SPI mode */
+	hw_setup_bits_per_word(spi, spi->bits_per_word);
+	hw_setup_clock_mode(spi, SPI_MODE_0);
+
+	/* Enable master mode and clear error flags */
+	intel_ssc_spi_writel(spi, SPI_WHBSTATE_SETMS | SPI_WHBSTATE_CLR_ERRORS,
+				  SPI_WHBSTATE);
+
+	/* Reset GPIO/CS registers */
+	intel_ssc_spi_writel(spi, 0, SPI_GPOCON);
+	intel_ssc_spi_writel(spi, 0xFF00, SPI_FPGO);
+
+	/* Enable and flush FIFOs */
+	rx_fifo_reset(spi);
+	tx_fifo_reset(spi);
+}
+
+static void hw_chipselect_set(struct intel_ssc_spi *spi, unsigned int cs)
+{
+	u32 fgpo = (1 << (cs - spi->base_cs + SPI_FGPO_SETOUTN_S));
+	intel_ssc_spi_writel(spi, fgpo, SPI_FPGO);
+}
+
+static void hw_chipselect_clear(struct intel_ssc_spi *spi, unsigned int cs)
+{
+	u32 fgpo = (1 << (cs - spi->base_cs));
+	intel_ssc_spi_writel(spi, fgpo, SPI_FPGO);
+}
+
+static void hw_chipselect_init(struct intel_ssc_spi *spi, unsigned int cs,
+			       unsigned int cs_high)
+{
+	u32 gpocon;
+
+	/* set GPO pin to CS mode */
+	gpocon = 1 << ((cs - spi->base_cs) + SPI_GPOCON_ISCSBN_S);
+
+	/* invert GPO pin */
+	if (cs_high)
+		gpocon |= 1 << (cs - spi->base_cs);
+
+	intel_ssc_spi_maskl(spi, 0, gpocon, SPI_GPOCON);
+}
+
+static void chipselect_enable(struct spi_device *spidev)
+{
+	struct intel_ssc_spi *spi = spi_master_get_devdata(spidev->master);
+	struct intel_ssc_spi_cstate *cstate = spi_get_ctldata(spidev);
+
+	if (cstate->cs_gpio >= 0)
+		gpio_set_value(cstate->cs_gpio, spidev->mode & SPI_CS_HIGH);
+	else
+		hw_chipselect_clear(spi, spidev->chip_select);
+
+	/* CS setup/recovery time */
+	if (spi->cs_delay)
+		ndelay(spi->cs_delay);
+}
+
+static void chipselect_disable(struct spi_device *spidev)
+{
+	struct intel_ssc_spi *spi = spi_master_get_devdata(spidev->master);
+	struct intel_ssc_spi_cstate *cstate = spi_get_ctldata(spidev);
+
+	/* CS hold time */
+	if (spi->cs_delay)
+		ndelay(spi->cs_delay);
+
+	if (cstate->cs_gpio >= 0)
+		gpio_set_value(cstate->cs_gpio, !(spidev->mode & SPI_CS_HIGH));
+	else
+		hw_chipselect_set(spi, spidev->chip_select);
+
+	/* CS setup/recovery time */
+	if (spi->cs_delay)
+		ndelay(spi->cs_delay);
+}
+
+static int intel_ssc_spi_setup(struct spi_device *spidev)
+{
+	struct spi_master *master = spidev->master;
+	struct intel_ssc_spi *spi = spi_master_get_devdata(master);
+	struct intel_ssc_spi_cstate *cstate = spi_get_ctldata(spidev);
+	int err;
+
+	if (cstate)
+		return 0;
+
+	cstate = kzalloc(sizeof(*cstate), GFP_KERNEL);
+	if (!cstate)
+		return -ENOMEM;
+
+	spi_set_ctldata(spidev, cstate);
+	cstate->cs_gpio = -ENOENT;
+
+	if (spidev->cs_gpio >= 0) {
+		dev_dbg(spi->dev, "using chipselect %u on GPIO %d\n",
+			spidev->chip_select, spidev->cs_gpio);
+
+		err = gpio_request(spidev->cs_gpio, dev_name(spi->dev));
+		if (err)
+			return err;
+
+		gpio_direction_output(spidev->cs_gpio,
+			!(spidev->mode & SPI_CS_HIGH));
+
+		cstate->cs_gpio = spidev->cs_gpio;
+	} else {
+		dev_dbg(spi->dev, "using internal chipselect %u\n",
+			spidev->chip_select);
+
+		if (spidev->chip_select < spi->base_cs ||
+		    spidev->chip_select >= spi->base_cs +
+					   master->num_chipselect) {
+			dev_err(spi->dev,
+				"chipselect %i out of range (%i - %i)\n",
+				spidev->chip_select, spi->base_cs,
+				spi->base_cs + master->num_chipselect);
+			return -EINVAL;
+		}
+
+		hw_chipselect_init(spi, spidev->chip_select,
+			spidev->mode & SPI_CS_HIGH);
+		hw_chipselect_set(spi, spidev->chip_select);
+	}
+
+	return 0;
+}
+
+static void intel_ssc_spi_cleanup(struct spi_device *spidev)
+{
+	struct intel_ssc_spi_cstate *cstate = spi_get_ctldata(spidev);
+
+	if (cstate->cs_gpio >= 0)
+		gpio_free(cstate->cs_gpio);
+
+	kfree(cstate);
+	spi_set_ctldata(spidev, NULL);
+}
+
+static void hw_setup_message(const struct intel_ssc_spi *spi,
+				struct spi_device *spidev)
+{
+	const struct intel_ssc_spi_hwcfg *hwcfg = spi->hwcfg;
+
+	hw_enter_config_mode(spi);
+	hw_setup_clock_mode(spi, spidev->mode);
+	hw_enter_active_mode(spi);
+
+	/* Enable interrupts */
+	intel_ssc_spi_writel(spi, hwcfg->irnen_t | hwcfg->irnen_r | SPI_IRNEN_E,
+			     SPI_IRNEN);
+}
+
+static void hw_setup_transfer(struct intel_ssc_spi *spi,
+			      struct spi_device *spidev, struct spi_transfer *t)
+{
+	unsigned int speed_hz, bits_per_word;
+	u32 con;
+
+	if (t->speed_hz)
+		speed_hz = t->speed_hz;
+	else
+		speed_hz = spidev->max_speed_hz;
+
+	if (t->bits_per_word)
+		bits_per_word = t->bits_per_word;
+	else
+		bits_per_word = spidev->bits_per_word;
+
+	if (bits_per_word != spi->bits_per_word ||
+		speed_hz != spi->speed_hz) {
+		hw_enter_config_mode(spi);
+		hw_setup_speed_hz(spi, speed_hz);
+		hw_setup_bits_per_word(spi, bits_per_word);
+		hw_enter_active_mode(spi);
+
+		spi->speed_hz = speed_hz;
+		spi->bits_per_word = bits_per_word;
+	}
+
+	/* Configure transmitter and receiver */
+	con = intel_ssc_spi_readl(spi, SPI_CON);
+	if (t->tx_buf)
+		con &= ~SPI_CON_TXOFF;
+	else
+		con |= SPI_CON_TXOFF;
+
+	if (t->rx_buf)
+		con &= ~SPI_CON_RXOFF;
+	else
+		con |= SPI_CON_RXOFF;
+
+	intel_ssc_spi_writel(spi, con, SPI_CON);
+}
+
+static void hw_finish_message(const struct intel_ssc_spi *spi)
+{
+	/* Disable interrupts */
+	intel_ssc_spi_writel(spi, 0, SPI_IRNEN);
+
+	/* Disable transmitter and receiver */
+	intel_ssc_spi_maskl(spi, 0, SPI_CON_TXOFF | SPI_CON_RXOFF, SPI_CON);
+}
+
+static void tx_fifo_write(struct intel_ssc_spi *spi)
+{
+	const u8 *tx8;
+	const u16 *tx16;
+	const u32 *tx32;
+	u32 data;
+	unsigned int tx_free = tx_fifo_free(spi);
+
+	while (spi->tx_todo && tx_free) {
+		switch (spi->bits_per_word) {
+		case 2 ... 8:
+			tx8 = spi->tx;
+			data = *tx8;
+			spi->tx_todo--;
+			spi->tx++;
+			break;
+		case 16:
+			tx16 = (u16 *) spi->tx;
+			data = *tx16;
+			spi->tx_todo -= 2;
+			spi->tx += 2;
+			break;
+		case 24:
+		case 32:
+			tx32 = (u32 *) spi->tx;
+			data = *tx32;
+			spi->tx_todo -= 4;
+			spi->tx += 4;
+			break;
+		default:
+			WARN_ON(1);
+		}
+
+		intel_ssc_spi_writel(spi, data, SPI_TB);
+		tx_free--;
+	}
+}
+
+static void rx_fifo_read_full_duplex(struct intel_ssc_spi *spi)
+{
+	u8 *rx8;
+	u16 *rx16;
+	u32 *rx32;
+	u32 data;
+	unsigned int rx_fill = rx_fifo_level(spi);
+
+	while (rx_fill) {
+		data = intel_ssc_spi_readl(spi, SPI_RB);
+
+		switch (spi->bits_per_word) {
+		case 2 ... 8:
+			rx8 = spi->rx;
+			*rx8 = data;
+			spi->rx_todo--;
+			spi->rx++;
+			break;
+		case 16:
+			rx16 = (u16 *) spi->rx;
+			*rx16 = data;
+			spi->rx_todo -= 2;
+			spi->rx += 2;
+			break;
+		case 24:
+		case 32:
+			rx32 = (u32 *) spi->rx;
+			*rx32 = data;
+			spi->rx_todo -= 4;
+			spi->rx += 4;
+			break;
+		default:
+			WARN_ON(1);
+		}
+
+		rx_fill--;
+	}
+}
+
+static void rx_fifo_read_half_duplex(struct intel_ssc_spi *spi)
+{
+	u32 data, *rx32;
+	u8 *rx8;
+	unsigned int rxbv, shift;
+	unsigned int rx_fill = rx_fifo_level(spi);
+
+	/*
+	 * In RX-only mode the bits per word value is ignored by HW. A value
+	 * of 32 is used instead. Thus all 4 bytes per FIFO must be read.
+	 * If remaining RX bytes are less than 4, the FIFO must be read
+	 * differently. The amount of received and valid bytes is indicated
+	 * by STAT.RXBV register value.
+	 */
+	while (rx_fill) {
+		if (spi->rx_todo < 4)  {
+			rxbv = (intel_ssc_spi_readl(spi, SPI_STAT) &
+				SPI_STAT_RXBV_M) >> SPI_STAT_RXBV_S;
+			data = intel_ssc_spi_readl(spi, SPI_RB);
+
+			shift = (rxbv - 1) * 8;
+			rx8 = spi->rx;
+
+			while (rxbv) {
+				*rx8++ = (data >> shift) & 0xFF;
+				rxbv--;
+				shift -= 8;
+				spi->rx_todo--;
+				spi->rx++;
+			}
+		} else {
+			data = intel_ssc_spi_readl(spi, SPI_RB);
+			rx32 = (u32 *) spi->rx;
+
+			*rx32++ = data;
+			spi->rx_todo -= 4;
+			spi->rx += 4;
+		}
+		rx_fill--;
+	}
+}
+
+static void rx_request(struct intel_ssc_spi *spi)
+{
+	unsigned int rxreq, rxreq_max;
+
+	/*
+	 * To avoid receive overflows at high clocks it is better to request
+	 * only the amount of bytes that fits into all FIFOs. This value
+	 * depends on the FIFO size implemented in hardware.
+	 */
+	rxreq = spi->rx_todo;
+	rxreq_max = spi->rx_fifo_size * 4;
+	if (rxreq > rxreq_max)
+		rxreq = rxreq_max;
+
+	intel_ssc_spi_writel(spi, rxreq, SPI_RXREQ);
+}
+
+static irqreturn_t intel_ssc_spi_xmit_interrupt(int irq, void *data)
+{
+	struct intel_ssc_spi *spi = data;
+
+	/* handle possible interrupts after device initialization */
+	if (!spi->rx && !spi->tx)
+		return IRQ_HANDLED;
+
+	if (spi->tx) {
+		if (spi->rx && spi->rx_todo)
+			rx_fifo_read_full_duplex(spi);
+
+		if (spi->tx_todo)
+			tx_fifo_write(spi);
+		else
+			goto completed;
+	} else if (spi->rx) {
+		if (spi->rx_todo) {
+			rx_fifo_read_half_duplex(spi);
+
+			if (spi->rx_todo)
+				rx_request(spi);
+			else
+				goto completed;
+		} else
+			goto completed;
+	}
+
+	return IRQ_HANDLED;
+
+completed:
+	spi->status = 0;
+	complete(&spi->xfer_complete);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t intel_ssc_spi_err_interrupt(int irq, void *data)
+{
+	struct intel_ssc_spi *spi = data;
+	u32 stat = intel_ssc_spi_readl(spi, SPI_STAT);
+
+	if (stat & SPI_STAT_RUE)
+		dev_err(spi->dev, "receive underflow error\n");
+	if (stat & SPI_STAT_TUE)
+		dev_err(spi->dev, "transmit underflow error\n");
+	if (stat & SPI_STAT_RE)
+		dev_err(spi->dev, "receive overflow error\n");
+	if (stat & SPI_STAT_TE)
+		dev_err(spi->dev, "transmit overflow error\n");
+	if (stat & SPI_STAT_ME)
+		dev_err(spi->dev, "mode error\n");
+
+	/* Disable all interrupts */
+	intel_ssc_spi_writel(spi, 0, SPI_IRNEN);
+
+	/* Clear error flags */
+	intel_ssc_spi_maskl(spi, 0, SPI_WHBSTATE_CLR_ERRORS, SPI_WHBSTATE);
+
+	/* flush FIFOs */
+	rx_fifo_flush(spi);
+	tx_fifo_flush(spi);
+
+	/* set bad status so it can be retried */
+	spi->status = -EIO;
+	complete(&spi->xfer_complete);
+
+	return IRQ_HANDLED;
+}
+
+static int transfer_start(struct intel_ssc_spi *spi, struct spi_device *spidev,
+			  struct spi_transfer *t)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&spi->lock, flags);
+
+	spi->tx = t->tx_buf;
+	spi->rx = t->rx_buf;
+	spi->status = -EINPROGRESS;
+
+	if (t->tx_buf) {
+		spi->tx_todo = t->len;
+
+		/* initially fill TX FIFO */
+		tx_fifo_write(spi);
+	}
+
+	if (spi->rx) {
+		spi->rx_todo = t->len;
+
+		/* start shift clock in RX-only mode */
+		if (!spi->tx)
+			rx_request(spi);
+	}
+
+	spin_unlock_irqrestore(&spi->lock, flags);
+
+	return 0;
+}
+
+static int transfer_wait_finished(struct intel_ssc_spi *spi)
+{
+	unsigned long timeout;
+
+	/* wait for completion by interrupt */
+	timeout = wait_for_completion_timeout(&spi->xfer_complete,
+			msecs_to_jiffies(spi->timeout));
+	if (!timeout)
+		return -EIO;
+
+	/* make sure that HW is idle */
+	timeout = jiffies + msecs_to_jiffies(spi->timeout);
+	do {
+		if (!hw_is_busy(spi))
+			return 0;
+
+		cond_resched();
+	} while (!time_after_eq(jiffies, timeout));
+
+	/* flush FIFOs on timeout */
+	rx_fifo_flush(spi);
+	tx_fifo_flush(spi);
+
+	return -EIO;
+}
+
+static int intel_ssc_spi_transfer_one_message(struct spi_master *master,
+					      struct spi_message *msg)
+{
+	struct intel_ssc_spi *spi = spi_master_get_devdata(master);
+	struct spi_device *spidev = msg->spi;
+	struct spi_transfer *t;
+	int status;
+	unsigned int cs_change = 1;
+	unsigned int bits_per_word;
+
+	hw_setup_message(spi, spidev);
+
+	list_for_each_entry(t, &msg->transfers, transfer_list) {
+		reinit_completion(&spi->xfer_complete);
+
+		if (t->bits_per_word)
+			bits_per_word = t->bits_per_word;
+		else
+			bits_per_word = spidev->bits_per_word;
+
+		switch (bits_per_word) {
+		case 2 ... 8:
+		case 16:
+		case 24:
+		case 32:
+			/* This is supported by the chip */
+			break;
+		default:
+			dev_warn(spi->dev, "%i bits per word not supported\n",
+				 bits_per_word);
+			status = -EINVAL;
+			goto done;
+		}
+
+		hw_setup_transfer(spi, spidev, t);
+
+		if (cs_change)
+			chipselect_enable(spidev);
+		cs_change = t->cs_change;
+
+		status = transfer_start(spi, spidev, t);
+		if (status) {
+			dev_err(spi->dev, "failed to start transfer\n");
+			goto done;
+		}
+
+		status = transfer_wait_finished(spi);
+		if (status) {
+			dev_err(spi->dev, "transfer timeout\n");
+			goto done;
+		}
+
+		status = spi->status;
+		if (status) {
+			dev_err(spi->dev, "transfer failed\n");
+			goto done;
+		}
+
+		msg->actual_length += t->len;
+
+		if (t->delay_usecs)
+			udelay(t->delay_usecs);
+
+		if (cs_change)
+			chipselect_disable(spidev);
+	}
+
+done:
+	msg->status = status;
+
+	if (!(status == 0 && cs_change))
+		chipselect_disable(spidev);
+
+	spi_finalize_current_message(master);
+	hw_finish_message(spi);
+
+	return status;
+}
+
+static int intel_ssc_spi_prepare_transfer(struct spi_master *master)
+{
+	struct intel_ssc_spi *spi = spi_master_get_devdata(master);
+
+	pm_runtime_get_sync(spi->dev);
+
+	return 0;
+}
+
+static int intel_ssc_spi_unprepare_transfer(struct spi_master *master)
+{
+	struct intel_ssc_spi *spi = spi_master_get_devdata(master);
+
+	pm_runtime_put(spi->dev);
+
+	return 0;
+}
+
+static const struct intel_ssc_spi_hwcfg spi_xway = {
+	.irnen_r = SPI_IRNEN_R_XWAY,
+	.irnen_t = SPI_IRNEN_T_XWAY,
+};
+
+static const struct intel_ssc_spi_hwcfg spi_xrx = {
+	.irnen_r = SPI_IRNEN_R_XRX,
+	.irnen_t = SPI_IRNEN_T_XRX,
+};
+
+static const struct of_device_id intel_ssc_spi_match[] = {
+	{ .compatible = "intel,ase-spi", .data = &spi_xway, },
+	{ .compatible = "intel,falcon-spi", .data = &spi_xrx, },
+	{ .compatible = "intel,xrx100-spi", .data = &spi_xrx, },
+	{},
+};
+MODULE_DEVICE_TABLE(of, intel_ssc_spi_match);
+
+static int intel_ssc_spi_probe(struct platform_device *pdev)
+{
+	struct spi_master *master;
+	struct resource *res;
+	struct intel_ssc_spi *spi;
+	const struct intel_ssc_spi_hwcfg *hwcfg;
+	const struct of_device_id *match;
+	int err, rx_irq, tx_irq, err_irq;
+	u32 id, supports_dma, revision;
+	unsigned int num_cs;
+
+	match = of_match_device(intel_ssc_spi_match, &pdev->dev);
+	if (!match) {
+		dev_err(&pdev->dev, "no device match\n");
+		return -EINVAL;
+	}
+	hwcfg = match->data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "failed to get resources\n");
+		return -ENXIO;
+	}
+
+	rx_irq = platform_get_irq_byname(pdev, SPI_RX_IRQ_NAME);
+	if (rx_irq < 0) {
+		dev_err(&pdev->dev, "failed to get %s\n", SPI_RX_IRQ_NAME);
+		return -ENXIO;
+	}
+
+	tx_irq = platform_get_irq_byname(pdev, SPI_TX_IRQ_NAME);
+	if (tx_irq < 0) {
+		dev_err(&pdev->dev, "failed to get %s\n", SPI_TX_IRQ_NAME);
+		return -ENXIO;
+	}
+
+	err_irq = platform_get_irq_byname(pdev, SPI_ERR_IRQ_NAME);
+	if (err_irq < 0) {
+		dev_err(&pdev->dev, "failed to get %s\n", SPI_ERR_IRQ_NAME);
+		return -ENXIO;
+	}
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct intel_ssc_spi));
+	if (!master)
+		return -ENOMEM;
+
+	spi = spi_master_get_devdata(master);
+	spi->master = master;
+	spi->dev = &pdev->dev;
+	spi->hwcfg = hwcfg;
+	platform_set_drvdata(pdev, spi);
+
+	spi->regbase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(spi->regbase)) {
+		err = PTR_ERR(spi->regbase);
+		goto err_master_put;
+	}
+
+	err = devm_request_irq(&pdev->dev, rx_irq, intel_ssc_spi_xmit_interrupt,
+			       0, SPI_RX_IRQ_NAME, spi);
+	if (err)
+		goto err_master_put;
+
+	err = devm_request_irq(&pdev->dev, tx_irq, intel_ssc_spi_xmit_interrupt,
+			       0, SPI_TX_IRQ_NAME, spi);
+	if (err)
+		goto err_master_put;
+
+	err = devm_request_irq(&pdev->dev, err_irq, intel_ssc_spi_err_interrupt,
+			       0, SPI_ERR_IRQ_NAME, spi);
+	if (err)
+		goto err_master_put;
+
+	spi->spi_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(spi->spi_clk)) {
+		err = PTR_ERR(spi->spi_clk);
+		goto err_master_put;
+	}
+	clk_prepare_enable(spi->spi_clk);
+
+	spi->fpi_clk = clk_get_fpi();
+	if (IS_ERR(spi->fpi_clk)) {
+		err = PTR_ERR(spi->fpi_clk);
+		goto err_clk_disable;
+	}
+
+	num_cs = 8;
+	of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs);
+
+	spi->base_cs = 1;
+	of_property_read_u32(pdev->dev.of_node, "base-cs", &spi->base_cs);
+
+	init_completion(&spi->xfer_complete);
+	spin_lock_init(&spi->lock);
+	spi->timeout = 2000;
+	spi->cs_delay = 100;
+	spi->bits_per_word = 8;
+	spi->speed_hz = 0;
+
+	master->dev.of_node = pdev->dev.of_node;
+	master->bus_num = 0;
+	master->num_chipselect = num_cs;
+	master->setup = intel_ssc_spi_setup;
+	master->cleanup = intel_ssc_spi_cleanup;
+	master->prepare_transfer_hardware = intel_ssc_spi_prepare_transfer;
+	master->transfer_one_message = intel_ssc_spi_transfer_one_message;
+	master->unprepare_transfer_hardware = intel_ssc_spi_unprepare_transfer;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_CS_HIGH |
+				SPI_LOOP;
+
+	id = intel_ssc_spi_readl(spi, SPI_ID);
+	spi->tx_fifo_size = (id & SPI_ID_TXFS_M) >> SPI_ID_TXFS_S;
+	spi->rx_fifo_size = (id & SPI_ID_RXFS_M) >> SPI_ID_RXFS_S;
+	supports_dma = (id & SPI_ID_CFG_M) >> SPI_ID_CFG_S;
+	revision = id & SPI_ID_REV_M;
+
+	intel_ssc_spi_hw_init(spi);
+
+	err = devm_spi_register_master(&pdev->dev, master);
+	if (err) {
+		dev_err(&pdev->dev, "failed to register spi_master\n");
+		goto err_clk_put;
+	}
+
+	dev_info(&pdev->dev,
+		"Intel SSC SPI controller (Rev %i, TXFS %u, RXFS %u, DMA %u)\n",
+		revision, spi->tx_fifo_size, spi->rx_fifo_size, supports_dma);
+
+	return 0;
+
+err_clk_put:
+	clk_put(spi->fpi_clk);
+err_clk_disable:
+	clk_disable_unprepare(spi->spi_clk);
+err_master_put:
+	spi_master_put(master);
+
+	return err;
+}
+
+static int intel_ssc_spi_remove(struct platform_device *pdev)
+{
+	struct intel_ssc_spi *spi = platform_get_drvdata(pdev);
+
+	intel_ssc_spi_writel(spi, 0, SPI_IRNEN);
+	rx_fifo_flush(spi);
+	tx_fifo_flush(spi);
+	hw_enter_config_mode(spi);
+
+	clk_disable_unprepare(spi->spi_clk);
+	clk_put(spi->fpi_clk);
+
+	return 0;
+}
+
+static struct platform_driver intel_ssc_spi_driver = {
+	.probe = intel_ssc_spi_probe,
+	.remove = intel_ssc_spi_remove,
+	.driver = {
+		.name = "spi-intel-ssc",
+		.owner = THIS_MODULE,
+		.of_match_table = intel_ssc_spi_match,
+	},
+};
+module_platform_driver(intel_ssc_spi_driver);
+
+MODULE_DESCRIPTION("Intel SSC SPI controller driver");
+MODULE_AUTHOR("Daniel Schwierzeck <daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:spi-intel-ssc");
-- 
2.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

             reply	other threads:[~2016-10-02 21:44 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-10-02 21:44 Hauke Mehrtens [this message]
     [not found] ` <20161002214408.30329-1-hauke-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
2016-10-04  3:13   ` [PATCH] spi: intel-ssc: add support for Intel SSC SPI controller Mark Brown
     [not found]     ` <20161004031303.mwn6rk54owuu6eto-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
2016-10-04 21:56       ` Hauke Mehrtens
     [not found]         ` <c90eff61-2aa9-130e-54de-fd8160ce97cd-5/S+JYg5SzeELgA04lAiVw@public.gmane.org>
2016-10-05 15:25           ` Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20161002214408.30329-1-hauke@hauke-m.de \
    --to=hauke-5/s+jyg5szeelga04laivw@public.gmane.org \
    --cc=broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org \
    --cc=daniel.schwierzeck-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=hauke.mehrtens-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
    --cc=john-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org \
    --cc=linux-spi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=nbd-Vt+b4OUoWG0@public.gmane.org \
    --cc=thomas.langer-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.