diff mbox

[v4,2/2] spi: add driver for J-Core SPI controller

Message ID 2a46090a5a7c177b7eca83f68406650844423e7f.1469686300.git.dalias@libc.org (mailing list archive)
State New, archived
Headers show

Commit Message

Rich Felker April 3, 2016, 5:12 a.m. UTC
The J-Core "spi2" device is a PIO-based SPI master controller. It
differs from "bitbang" devices in that that it's clocked in hardware
rather than via soft clock modulation over gpio, and performs
byte-at-a-time transfers between the cpu and SPI controller.

This driver will be extended to support future versions of the J-Core
SPI controller with DMA transfers when they become available.

Signed-off-by: Rich Felker <dalias@libc.org>
---
 drivers/spi/Kconfig     |   4 +
 drivers/spi/Makefile    |   1 +
 drivers/spi/spi-jcore.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 225 insertions(+)
 create mode 100644 drivers/spi/spi-jcore.c

Comments

Mark Brown July 28, 2016, 7:11 p.m. UTC | #1
On Sun, Apr 03, 2016 at 05:12:45AM +0000, Rich Felker wrote:

> +config SPI_JCORE
> +	tristate "J-Core SPI Master"
> +	depends on OF
> +

An architecture or SoC dependency with || COMPILE_TEST would be useful
for avoiding cluttering Kconfig for other users.  Though as this is in a
FPGA it's perhaps likely people will pick this up for other FPGAs so
perhaps a comment to that effect if it seems likely.

> +static void jcore_spi_baudrate(struct jcore_spi *hw, int speed)
> +{
> +	if (speed == hw->speed_hz) return;
> +	hw->speed_hz = speed;
> +	if (speed >= hw->clock_freq/2)

Coding style, spaces around /.

> +	return count<len ? -EREMOTEIO : 0;

Again here, and please also write this as a normal if statement so it's
easier to read.

> +	/* The SPI clock rate controlled via a configurable clock divider
> +	 * which is applied to the reference clock. A 50 MHz reference is
> +	 * most suitable for obtaining standard SPI clock rates, but some
> +	 * designs may have a different reference clock, and the DT must
> +	 * make the driver aware so that it can properly program the
> +	 * requested rate. If omitted, 50 MHz is assumed. */
> +	clock_freq = 50000000;
> +	of_property_read_u32(node, "clock-frequency", &clock_freq);
> +	hw->clock_freq = clock_freq;

Why are you not using the clock API for this?  Just require a clock and
use clk_get_rate() to find out what rate it is.

> +	pdev->dev.dma_mask = 0;

Why are we doing this?  There's no DMA code...

> +	dev_info(&pdev->dev, "base %p, noirq\n", hw->base);

This is just adding noise to the boot, just remove it - it's useful to
log information we get from the silicon but things like the base address
don't really add anything and end up cluttering (and slowing) the boot
when everything does it.
Rich Felker July 28, 2016, 7:40 p.m. UTC | #2
On Thu, Jul 28, 2016 at 08:11:53PM +0100, Mark Brown wrote:
> On Sun, Apr 03, 2016 at 05:12:45AM +0000, Rich Felker wrote:
> 
> > +config SPI_JCORE
> > +	tristate "J-Core SPI Master"
> > +	depends on OF
> > +
> 
> An architecture or SoC dependency with || COMPILE_TEST would be useful
> for avoiding cluttering Kconfig for other users.  Though as this is in a
> FPGA it's perhaps likely people will pick this up for other FPGAs so
> perhaps a comment to that effect if it seems likely.

Unlike some of the other SoC hardware (interrupt controller) that's
more closely tied to the SH cpu trap behavior, the SPI master seems
like something that would be nice and easy to reuse elsewhere. I don't
feel strongly about it either way though; I can add the arch dep if
you want.

> > +static void jcore_spi_baudrate(struct jcore_spi *hw, int speed)
> > +{
> > +	if (speed == hw->speed_hz) return;
> > +	hw->speed_hz = speed;
> > +	if (speed >= hw->clock_freq/2)
> 
> Coding style, spaces around /.

OK.

> > +	return count<len ? -EREMOTEIO : 0;
> 
> Again here, and please also write this as a normal if statement so it's
> easier to read.

OK.

> > +	/* The SPI clock rate controlled via a configurable clock divider
> > +	 * which is applied to the reference clock. A 50 MHz reference is
> > +	 * most suitable for obtaining standard SPI clock rates, but some
> > +	 * designs may have a different reference clock, and the DT must
> > +	 * make the driver aware so that it can properly program the
> > +	 * requested rate. If omitted, 50 MHz is assumed. */
> > +	clock_freq = 50000000;
> > +	of_property_read_u32(node, "clock-frequency", &clock_freq);
> > +	hw->clock_freq = clock_freq;
> 
> Why are you not using the clock API for this?  Just require a clock and
> use clk_get_rate() to find out what rate it is.

I thought about that but I'm not familiar with it. I can try to figure
it out quickly and test that approach; don't see any reason it
shouldn't work. Would you insist on having full support for
enabling/disabling the clk when it's in use, or would you be happy
with treating it as a fixed clock that's always-on for now and
possibly extending it with more functionality later if there's ever
hardware where that's relevant/helpful?

> > +	pdev->dev.dma_mask = 0;
> 
> Why are we doing this?  There's no DMA code...

There will be later, but I can remove this for now if it's not needed.

> > +	dev_info(&pdev->dev, "base %p, noirq\n", hw->base);
> 
> This is just adding noise to the boot, just remove it - it's useful to
> log information we get from the silicon but things like the base address
> don't really add anything and end up cluttering (and slowing) the boot
> when everything does it.

OK.

Rich
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown July 28, 2016, 7:51 p.m. UTC | #3
On Thu, Jul 28, 2016 at 03:40:45PM -0400, Rich Felker wrote:
> On Thu, Jul 28, 2016 at 08:11:53PM +0100, Mark Brown wrote:

> > An architecture or SoC dependency with || COMPILE_TEST would be useful
> > for avoiding cluttering Kconfig for other users.  Though as this is in a
> > FPGA it's perhaps likely people will pick this up for other FPGAs so
> > perhaps a comment to that effect if it seems likely.

> Unlike some of the other SoC hardware (interrupt controller) that's
> more closely tied to the SH cpu trap behavior, the SPI master seems
> like something that would be nice and easy to reuse elsewhere. I don't
> feel strongly about it either way though; I can add the arch dep if
> you want.

I guess it depends if anyone is actually doing that or not, if nobody is
the dependency would be better.

> > Why are you not using the clock API for this?  Just require a clock and
> > use clk_get_rate() to find out what rate it is.

> I thought about that but I'm not familiar with it. I can try to figure
> it out quickly and test that approach; don't see any reason it
> shouldn't work. Would you insist on having full support for
> enabling/disabling the clk when it's in use, or would you be happy
> with treating it as a fixed clock that's always-on for now and
> possibly extending it with more functionality later if there's ever
> hardware where that's relevant/helpful?

It's fine to just enable it at startup and leave it on, though the
runtime PM ops are trivial and you can set auto_runtime_pm to have the
core do the gets and puts.
Rich Felker July 30, 2016, 3:34 a.m. UTC | #4
On Thu, Jul 28, 2016 at 08:51:25PM +0100, Mark Brown wrote:
> On Thu, Jul 28, 2016 at 03:40:45PM -0400, Rich Felker wrote:
> > On Thu, Jul 28, 2016 at 08:11:53PM +0100, Mark Brown wrote:
> 
> > > An architecture or SoC dependency with || COMPILE_TEST would be useful
> > > for avoiding cluttering Kconfig for other users.  Though as this is in a
> > > FPGA it's perhaps likely people will pick this up for other FPGAs so
> > > perhaps a comment to that effect if it seems likely.
> 
> > Unlike some of the other SoC hardware (interrupt controller) that's
> > more closely tied to the SH cpu trap behavior, the SPI master seems
> > like something that would be nice and easy to reuse elsewhere. I don't
> > feel strongly about it either way though; I can add the arch dep if
> > you want.
> 
> I guess it depends if anyone is actually doing that or not, if nobody is
> the dependency would be better.

OK, let's add the dependency for now with the intent to remove it
if/when there's a need.

> > > Why are you not using the clock API for this?  Just require a clock and
> > > use clk_get_rate() to find out what rate it is.
> 
> > I thought about that but I'm not familiar with it. I can try to figure
> > it out quickly and test that approach; don't see any reason it
> > shouldn't work. Would you insist on having full support for
> > enabling/disabling the clk when it's in use, or would you be happy
> > with treating it as a fixed clock that's always-on for now and
> > possibly extending it with more functionality later if there's ever
> > hardware where that's relevant/helpful?
> 
> It's fine to just enable it at startup and leave it on, though the
> runtime PM ops are trivial and you can set auto_runtime_pm to have the
> core do the gets and puts.

I was able to get it working via the clk api and I'll include support
for this in the next version of the patch, but to actually use it
depends on changing arch/sh to use the common clk framework; otherwise
there's no way to provide a suitable clk in the DT and have
[devm_]clk_get actually pick it up. Should I keep around the option of
using clock-frequency too? That would be most convenient.

I do have a pending patch from Sato-san to switch arch/sh over to CCF
but it's part of a series and I don't think it's ready to merge. I may
be able to merge just a minimal, safe subset that won't break legacy
non-DT configurations, though.

Rich
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown Aug. 1, 2016, 6:12 p.m. UTC | #5
On Fri, Jul 29, 2016 at 11:34:41PM -0400, Rich Felker wrote:

> I was able to get it working via the clk api and I'll include support
> for this in the next version of the patch, but to actually use it
> depends on changing arch/sh to use the common clk framework; otherwise
> there's no way to provide a suitable clk in the DT and have
> [devm_]clk_get actually pick it up. Should I keep around the option of
> using clock-frequency too? That would be most convenient.

It sounds like at the minute the devices all have one frequency anyway
in which case just having a default frequency in there that you fall
back to if you get -ENOENT from a failed clock lookup might be enough?
Rich Felker Aug. 2, 2016, 1:45 a.m. UTC | #6
On Mon, Aug 01, 2016 at 07:12:45PM +0100, Mark Brown wrote:
> On Fri, Jul 29, 2016 at 11:34:41PM -0400, Rich Felker wrote:
> 
> > I was able to get it working via the clk api and I'll include support
> > for this in the next version of the patch, but to actually use it
> > depends on changing arch/sh to use the common clk framework; otherwise
> > there's no way to provide a suitable clk in the DT and have
> > [devm_]clk_get actually pick it up. Should I keep around the option of
> > using clock-frequency too? That would be most convenient.
> 
> It sounds like at the minute the devices all have one frequency anyway
> in which case just having a default frequency in there that you fall
> back to if you get -ENOENT from a failed clock lookup might be enough?

Yes. 50 MHz is the natural default frequency, but I found out at the
last minute from the hardware engineers that clocking the current SoC
up to 62.5 MHz (for faster cpu) will require the SPI timing to be
programmed based on the faster reference clock. This messes up the
ability to get optimal SD card transfer rates, so we'll probably end
up having a real 50 MHz clock for the SPI anyway, but I thought it was
important to be able to handle this issue in the DT binding anyway.

Since it's not an immediate need right now anyway I'll drop the
clock-frequency property proposal and just use the more general clocks
approach you suggested.

Rich
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rob Landley Aug. 2, 2016, 6:11 p.m. UTC | #7
On 08/01/2016 08:45 PM, Rich Felker wrote:
> Yes. 50 MHz is the natural default frequency, but I found out at the
> last minute from the hardware engineers that clocking the current SoC
> up to 62.5 MHz (for faster cpu) will require the SPI timing to be
> programmed based on the faster reference clock. This messes up the
> ability to get optimal SD card transfer rates, so we'll probably end
> up having a real 50 MHz clock for the SPI anyway, but I thought it was
> important to be able to handle this issue in the DT binding anyway.

For those of you following along at home, the first open source VHDL
release of http://j-core.org runs at 33 mhz on Spartan 6 FPGAs, the
second at 50mhz on the same hardware, and you'd think the third would be
66mhz as the chip continues to be optimized, but one of the new I/O
busses requires a multiple of 12.5 mhz and they didn't want to add a
clock domain crossing thing for it, hence 62.5 mhz.

Earlier it was easy to get everything to work off a single master clock,
but as we add more peripherals to the SOC and speed things up stuff's
likely to change. (Development is ongoing, and we have a public
http://j-core.org/roadmap.html out through 2019. It you're waiting for
stuff to stop changing before getting basic board support in that runs
on existing releases, it's might be a while.)

Rob
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Geert Uytterhoeven Aug. 4, 2016, 1:05 p.m. UTC | #8
Hi Rich,

On Sat, Jul 30, 2016 at 5:34 AM, Rich Felker <dalias@libc.org> wrote:
> On Thu, Jul 28, 2016 at 08:51:25PM +0100, Mark Brown wrote:
>> On Thu, Jul 28, 2016 at 03:40:45PM -0400, Rich Felker wrote:
>> > On Thu, Jul 28, 2016 at 08:11:53PM +0100, Mark Brown wrote:
>> > > Why are you not using the clock API for this?  Just require a clock and
>> > > use clk_get_rate() to find out what rate it is.
>>
>> > I thought about that but I'm not familiar with it. I can try to figure
>> > it out quickly and test that approach; don't see any reason it
>> > shouldn't work. Would you insist on having full support for
>> > enabling/disabling the clk when it's in use, or would you be happy
>> > with treating it as a fixed clock that's always-on for now and
>> > possibly extending it with more functionality later if there's ever
>> > hardware where that's relevant/helpful?
>>
>> It's fine to just enable it at startup and leave it on, though the
>> runtime PM ops are trivial and you can set auto_runtime_pm to have the
>> core do the gets and puts.
>
> I was able to get it working via the clk api and I'll include support
> for this in the next version of the patch, but to actually use it
> depends on changing arch/sh to use the common clk framework; otherwise
> there's no way to provide a suitable clk in the DT and have
> [devm_]clk_get actually pick it up. Should I keep around the option of
> using clock-frequency too? That would be most convenient.
>
> I do have a pending patch from Sato-san to switch arch/sh over to CCF
> but it's part of a series and I don't think it's ready to merge. I may
> be able to merge just a minimal, safe subset that won't break legacy
> non-DT configurations, though.

I think you can use non-CCF clocks with DT, if you register them first.
Cfr. the clk_names[] array and shmobile_clk_workaround() function in
v3.18:arch/arm/mach-shmobile/board-koelsch-reference.c and
v3.18:arch/arm/mach-shmobile/clock.c

Or was that the other way around?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rich Felker Aug. 4, 2016, 5:03 p.m. UTC | #9
On Thu, Aug 04, 2016 at 03:05:16PM +0200, Geert Uytterhoeven wrote:
> Hi Rich,
> 
> On Sat, Jul 30, 2016 at 5:34 AM, Rich Felker <dalias@libc.org> wrote:
> > On Thu, Jul 28, 2016 at 08:51:25PM +0100, Mark Brown wrote:
> >> On Thu, Jul 28, 2016 at 03:40:45PM -0400, Rich Felker wrote:
> >> > On Thu, Jul 28, 2016 at 08:11:53PM +0100, Mark Brown wrote:
> >> > > Why are you not using the clock API for this?  Just require a clock and
> >> > > use clk_get_rate() to find out what rate it is.
> >>
> >> > I thought about that but I'm not familiar with it. I can try to figure
> >> > it out quickly and test that approach; don't see any reason it
> >> > shouldn't work. Would you insist on having full support for
> >> > enabling/disabling the clk when it's in use, or would you be happy
> >> > with treating it as a fixed clock that's always-on for now and
> >> > possibly extending it with more functionality later if there's ever
> >> > hardware where that's relevant/helpful?
> >>
> >> It's fine to just enable it at startup and leave it on, though the
> >> runtime PM ops are trivial and you can set auto_runtime_pm to have the
> >> core do the gets and puts.
> >
> > I was able to get it working via the clk api and I'll include support
> > for this in the next version of the patch, but to actually use it
> > depends on changing arch/sh to use the common clk framework; otherwise
> > there's no way to provide a suitable clk in the DT and have
> > [devm_]clk_get actually pick it up. Should I keep around the option of
> > using clock-frequency too? That would be most convenient.
> >
> > I do have a pending patch from Sato-san to switch arch/sh over to CCF
> > but it's part of a series and I don't think it's ready to merge. I may
> > be able to merge just a minimal, safe subset that won't break legacy
> > non-DT configurations, though.
> 
> I think you can use non-CCF clocks with DT, if you register them first.
> Cfr. the clk_names[] array and shmobile_clk_workaround() function in
> v3.18:arch/arm/mach-shmobile/board-koelsch-reference.c and
> v3.18:arch/arm/mach-shmobile/clock.c
> 
> Or was that the other way around?

You can, but they will only be registered if a board file or
equivalent registers them. The intent is not to have any board files
for boards supported through device tree and eventually to remove all
the board files from arch/sh.

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

Patch

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 4b931ec..630ad7c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -285,6 +285,10 @@  config SPI_IMX
 	  This enables using the Freescale i.MX SPI controllers in master
 	  mode.
 
+config SPI_JCORE
+	tristate "J-Core SPI Master"
+	depends on OF
+
 config SPI_LM70_LLP
 	tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)"
 	depends on PARPORT
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 3c74d00..7e11fef 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_JCORE)			+= spi-jcore.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-jcore.c b/drivers/spi/spi-jcore.c
new file mode 100644
index 0000000..7d328e0
--- /dev/null
+++ b/drivers/spi/spi-jcore.c
@@ -0,0 +1,220 @@ 
+/*
+ * J-Core SPI controller driver
+ *
+ * Copyright (C) 2012-2016 Smart Energy Instruments, Inc.
+ *
+ * Current version by Rich Felker
+ * Based loosely on initial version by Oleksandr G Zhadan
+ *
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+
+#define DRV_NAME "jcore_spi"
+
+#define CTRL_REG	0x0
+#define DATA_REG	0x4
+
+#define JCORE_SPI_CTRL_XMIT		0x02
+#define JCORE_SPI_STAT_BUSY		0x02
+#define JCORE_SPI_CTRL_LOOP		0x08
+#define JCORE_SPI_CTRL_CS_BITS		0x15
+
+#define JCORE_SPI_WAIT_RDY_MAX_LOOP	2000000
+
+struct jcore_spi {
+	struct spi_master *master;
+	void __iomem *base;
+	unsigned int cs_reg;
+	unsigned int speed_reg;
+	unsigned int speed_hz;
+	unsigned int clock_freq;
+};
+
+static int jcore_spi_wait(void __iomem *ctrl_reg)
+{
+	unsigned timeout = JCORE_SPI_WAIT_RDY_MAX_LOOP;
+
+	do {
+		if (!(readl(ctrl_reg) & JCORE_SPI_STAT_BUSY))
+			return 0;
+		cpu_relax();
+	} while (--timeout);
+
+	return -EBUSY;
+}
+
+static void jcore_spi_program(struct jcore_spi *hw)
+{
+	void __iomem *ctrl_reg = hw->base + CTRL_REG;
+
+	if (jcore_spi_wait(ctrl_reg))
+		dev_err(hw->master->dev.parent,
+			"timeout waiting to program ctrl reg.\n");
+
+	writel(hw->cs_reg | hw->speed_reg, ctrl_reg);
+}
+
+static void jcore_spi_chipsel(struct spi_device *spi, bool value)
+{
+	struct jcore_spi *hw = spi_master_get_devdata(spi->master);
+	u32 csbit = 1U << (2 * spi->chip_select);
+
+	dev_dbg(hw->master->dev.parent, "chipselect %d\n", spi->chip_select);
+
+	if (value)
+		hw->cs_reg |= csbit;
+	else
+		hw->cs_reg &= ~csbit;
+
+	jcore_spi_program(hw);
+}
+
+static void jcore_spi_baudrate(struct jcore_spi *hw, int speed)
+{
+	if (speed == hw->speed_hz) return;
+	hw->speed_hz = speed;
+	if (speed >= hw->clock_freq/2)
+		hw->speed_reg = 0;
+	else
+		hw->speed_reg = ((hw->clock_freq / 2 / speed) - 1) << 27;
+	jcore_spi_program(hw);
+	dev_dbg(hw->master->dev.parent, "speed=%d reg=0x%x\n",
+		speed, hw->speed_reg);
+}
+
+static int jcore_spi_txrx(struct spi_master *master, struct spi_device *spi,
+			  struct spi_transfer *t)
+{
+	struct jcore_spi *hw = spi_master_get_devdata(master);
+
+	void __iomem *ctrl_reg = hw->base + CTRL_REG;
+	void __iomem *data_reg = hw->base + DATA_REG;
+	u32 xmit;
+
+	/* data buffers */
+	const unsigned char *tx;
+	unsigned char *rx;
+	unsigned int len;
+	unsigned int count;
+
+	jcore_spi_baudrate(hw, t->speed_hz);
+
+	xmit = hw->cs_reg | hw->speed_reg | JCORE_SPI_CTRL_XMIT;
+	tx = t->tx_buf;
+	rx = t->rx_buf;
+	len = t->len;
+
+	for (count = 0; count < len; count++) {
+		if (jcore_spi_wait(ctrl_reg))
+			break;
+
+		writel(tx ? *tx++ : 0, data_reg);
+		writel(xmit, ctrl_reg);
+
+		if (jcore_spi_wait(ctrl_reg))
+			break;
+
+		if (rx)
+			*rx++ = readl(data_reg);
+	}
+
+	spi_finalize_current_transfer(master);
+
+	return count<len ? -EREMOTEIO : 0;
+}
+
+static int jcore_spi_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct jcore_spi *hw;
+	struct spi_master *master;
+	struct resource *res;
+	u32 clock_freq;
+	int err = -ENODEV;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct jcore_spi));
+	if (!master)
+		return err;
+
+	/* setup the master state. */
+	master->num_chipselect = 3;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+	master->transfer_one = jcore_spi_txrx;
+	master->set_cs = jcore_spi_chipsel;
+	master->dev.of_node = node;
+	master->bus_num = pdev->id;
+
+	hw = spi_master_get_devdata(master);
+	hw->master = master;
+	platform_set_drvdata(pdev, hw);
+
+	/* find and map our resources */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		goto exit_busy;
+	if (!devm_request_mem_region
+	    (&pdev->dev, res->start, resource_size(res), pdev->name))
+		goto exit_busy;
+	hw->base =
+	    devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
+	if (!hw->base)
+		goto exit_busy;
+
+	/* The SPI clock rate controlled via a configurable clock divider
+	 * which is applied to the reference clock. A 50 MHz reference is
+	 * most suitable for obtaining standard SPI clock rates, but some
+	 * designs may have a different reference clock, and the DT must
+	 * make the driver aware so that it can properly program the
+	 * requested rate. If omitted, 50 MHz is assumed. */
+	clock_freq = 50000000;
+	of_property_read_u32(node, "clock-frequency", &clock_freq);
+	hw->clock_freq = clock_freq;
+
+	/* Initialize all CS bits to high. */
+	hw->cs_reg = JCORE_SPI_CTRL_CS_BITS;
+	jcore_spi_baudrate(hw, 400000);
+
+	pdev->dev.dma_mask = 0;
+	/* register our spi controller */
+	err = devm_spi_register_master(&pdev->dev, master);
+	if (err)
+		goto exit;
+	dev_info(&pdev->dev, "base %p, noirq\n", hw->base);
+
+	return 0;
+
+exit_busy:
+	err = -EBUSY;
+exit:
+	platform_set_drvdata(pdev, NULL);
+	spi_master_put(master);
+	return err;
+}
+
+static const struct of_device_id jcore_spi_of_match[] = {
+	{ .compatible = "jcore,spi2" },
+	{},
+};
+
+static struct platform_driver jcore_spi_driver = {
+	.probe = jcore_spi_probe,
+	.driver = {
+		.name = DRV_NAME,
+		.of_match_table = jcore_spi_of_match,
+	},
+};
+
+module_platform_driver(jcore_spi_driver);
+
+MODULE_DESCRIPTION("J-Core SPI driver");
+MODULE_AUTHOR("Rich Felker <dalias@libc.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);