2015-07-12 10:54:39

by Martin Devera

[permalink] [raw]
Subject: [PATCH 1/2] Add NXP LPC32XX SPI driver

This SPI device is found at least on NXP LPC32XX ARM
family. It has 64 entry FIFO and is quite fast when
using only IRQ.
The driver uses generic SPI and OF frameworks to
minimize its size.
It is tested in HW (SPI flash with JFFS2).
---
.../devicetree/bindings/spi/spi_lpc32xx.txt | 32 +++
drivers/spi/Kconfig | 8 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-lpc32xx.c | 265 ++++++++++++++++++++
4 files changed, 306 insertions(+)
create mode 100644 Documentation/devicetree/bindings/spi/spi_lpc32xx.txt
create mode 100644 drivers/spi/spi-lpc32xx.c

diff --git a/Documentation/devicetree/bindings/spi/spi_lpc32xx.txt b/Documentation/devicetree/bindings/spi/spi_lpc32xx.txt
new file mode 100644
index 0000000..aef86f4
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_lpc32xx.txt
@@ -0,0 +1,32 @@
+NXP LPC32XX SPI controller
+
+Required properties:
+- compatible : "nxp,lpc3220-spi"
+- reg : Offset and length of the register set for the device
+- interrupts : Should contain SPI controller interrupt
+- cs-gpios : should specify GPIOs used for chipselects.
+ The gpios will be referred to as reg = <index> in the SPI child nodes.
+
+SPI slave nodes must be children of the SPI master node and can
+contain the following properties.
+
+ spi-max-frequency = <hz>;
+ spi-cpol;
+ spi-cpha;
+
+Example:
+ spi1: spi@20088000 {
+ status="okay";
+ interrupts = <55 0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ num-cs = <1>;
+ cs-gpios = <&gpio 3 5 1>;
+ m25p80@1 {
+ compatible = "st,m25p80";
+ reg = <0>;
+ spi-max-frequency = <1000000>;
+ spi-cpol;
+ spi-cpha;
+ };
+ };
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 72b0590..373f013 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -375,6 +375,14 @@ config SPI_ORION
help
This enables using the SPI master controller on the Orion chips.

+config SPI_LPC32XX
+ tristate "NXP LPC32XX SPI controller"
+ depends on ARCH_LPC32XX
+ help
+ This selects SPI controller found on NXP LPC32XX SoC. There
+ are also ARM AMBA PL022 SSP controllers, but NXP's SPI one
+ has 64 entry FIFOs as opossed to 8 entry in SSP.
+
config SPI_PL022
tristate "ARM AMBA PL022 SSP controller"
depends on ARM_AMBA
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d8cbf65..62a09bd 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -44,6 +44,7 @@ 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_LM70_LLP) += spi-lm70llp.o
+obj-$(CONFIG_SPI_LPC32XX) += spi-lpc32xx.o
obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o
obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o
obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o
diff --git a/drivers/spi/spi-lpc32xx.c b/drivers/spi/spi-lpc32xx.c
new file mode 100644
index 0000000..5c6b1ca
--- /dev/null
+++ b/drivers/spi/spi-lpc32xx.c
@@ -0,0 +1,265 @@
+/*
+ * LPC32XX SPI bus driver
+ *
+ * Copyright (C) 2015 Martin Devera <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_gpio.h>
+#include <linux/spi/spi.h>
+
+#define DRIVER_NAME "spi-lpc32xx"
+
+#define _BIT(n) (1<<(n))
+#define SPI_GLOB_RST _BIT(1) /* SPI interfase sw reset */
+#define SPI_GLOB_ENABLE _BIT(0) /* SPI interface enable */
+
+#define SPI_CON_UNIDIR _BIT(23) /* DATIO pin dir control */
+#define SPI_CON_BHALT _BIT(22) /* Busy halt control */
+#define SPI_CON_BPOL _BIT(21) /* Busy line polarity */
+#define SPI_CON_MSB _BIT(19) /* MSB/LSB control */
+#define SPI_CON_CPOL _BIT(17) /* CPOL control*/
+#define SPI_CON_CPHA _BIT(16) /* CPHA control*/
+#define SPI_CON_MODE00 0 /* mode = 00 */
+#define SPI_CON_MODE01 _BIT(16) /* mode = 01 */
+#define SPI_CON_MODE10 _BIT(17) /* mode = 10 */
+#define SPI_CON_MODE11 _SBF(16,0x3)/* mode = 11 */
+#define SPI_CON_RXTX _BIT(15) /* Tx/Rx control */
+#define SPI_CON_THR _BIT(14) /* FIFO threshold control */
+#define SPI_CON_SHIFT_OFF _BIT(13) /* SPI clock control */
+#define SPI_CON_BITNUM(n) _SBF(9,((n-1)&0xF)) /* number of bits ctr */
+#define SPI_CON_MS _BIT(7) /* Master mode control */
+#define SPI_CON_RATE(n) (n & 0x7F) /* Transfer rate control */
+
+#define SPI_IER_INTEOT _BIT(1) /* End of transfer int en */
+#define SPI_IER_INTTHR _BIT(0) /* FIFO threshold int en */
+
+#define SPI_STAT_INTCLR _BIT(8) /* SPI interrupt clear */
+#define SPI_STAT_EOT _BIT(7) /* SPI End of Transfer flag */
+#define SPI_STAT_BUSYLEV _BIT(6) /* SPI BUSY level */
+#define SPI_STAT_SHIFTACT _BIT(3) /* Shift active flag */
+#define SPI_STAT_BF _BIT(2) /* FIFO full int flag */
+#define SPI_STAT_THR _BIT(1) /* FIFO threshold int flag */
+#define SPI_STAT_BE _BIT(0) /* FIFO empty int flag */
+#define SYNCIO_FRMLEN(x) ((x) << 8)
+#define SYNCIO_TXFRMEN (1 << 14)
+
+#define SPI_GLOBAL(r) (r + 0x00)
+#define SPI_CON(r) (r + 0x04)
+#define SPI_FRM(r) (r + 0x08)
+#define SPI_IER(r) (r + 0x0c)
+#define SPI_STAT(r) (r + 0x10)
+#define SPI_DAT(r) (r + 0x14)
+
+struct spi_lpc32xx_data {
+ void __iomem *syncio;
+ struct clk *spi_clk;
+
+ u8 *buf;
+ unsigned int bpw;
+ unsigned int mode;
+ int len;
+ int is_rd;
+ unsigned long rate; /* master clock rate */
+};
+
+static int spi_lpc32xx_setup(struct spi_device *spi)
+{
+ /* We are expect that SPI-device is not selected */
+ //gpio_direction_output(spi->cs_gpio, !(spi->mode & SPI_CS_HIGH));
+//
+ printk("#### Setup spi\n");
+ return 0;
+}
+
+static int spi_lpc32xx_start(struct spi_lpc32xx_data *hw)
+{
+ int sz,mode;
+ sz = hw->len;
+ if (sz > 64) sz = 64;
+
+ mode = hw->mode;
+ mode |= (hw->bpw-1)<<9;
+// printk("in spi_lpc32xx_start sz=%d buf=%p isrd=%d mode=%X io=%p stat=%X\n",
+// sz,hw->buf,hw->is_rd,mode,hw->syncio,readl(SPI_STAT(hw->syncio)));
+ writel(mode,SPI_CON(hw->syncio));
+ writel(sz,SPI_FRM(hw->syncio));
+ writel(SPI_IER_INTEOT,SPI_IER(hw->syncio));
+ if (hw->is_rd) {
+ readl(SPI_DAT(hw->syncio)); /* start reading */
+ } else {
+ while (sz-- > 0) { /* fill FIFO */
+ writel(*(hw->buf++),SPI_DAT(hw->syncio));
+ }
+ }
+ return 0;
+}
+
+static int spi_lpc32xx_transfer_one(struct spi_master *master,
+ struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ struct spi_lpc32xx_data *hw = spi_master_get_devdata(master);
+ unsigned long div;
+
+ div = 1;
+ if (xfer->speed_hz <= spi->max_speed_hz && xfer->speed_hz > 100)
+ div = hw->rate / xfer->speed_hz;
+ if (div < 1) div = 1;
+ if (div > 0x7f) div = 0x7f;
+
+ hw->mode = ((spi->mode&3)<<16) | SPI_CON_THR | SPI_CON_MS | (div-1);
+
+ if (!(spi->mode & SPI_3WIRE))
+ hw->mode |= SPI_CON_UNIDIR;
+
+ if (spi->mode & SPI_LSB_FIRST)
+ hw->mode |= SPI_CON_MSB;
+
+ hw->len = xfer->len;
+ hw->bpw = xfer->bits_per_word;
+ hw->buf = (u8 *)(xfer->tx_buf ? xfer->tx_buf : xfer->rx_buf);
+ hw->is_rd = xfer->tx_buf ? 0 : 1;
+ if (!hw->is_rd) hw->mode |= SPI_CON_RXTX; else
+ /* only read FIFO, don't cause another shifting */
+ hw->mode |= SPI_CON_SHIFT_OFF;
+
+ spi_lpc32xx_start(hw);
+
+ return 1;
+}
+
+static irqreturn_t spi_lpc32xx_isr(int irq, void *dev_id)
+{
+ struct spi_master *master = dev_id;
+ struct spi_lpc32xx_data *hw = spi_master_get_devdata(master);
+
+ int sz,i;
+ sz = hw->len;
+ if (sz > 64) sz = 64;
+#if 0
+ printk("in spi_lpc32xx_isr sz=%d buf=%p isrd=%d stat=%X\n",
+ sz,hw->buf,hw->is_rd,readl(SPI_STAT(hw->syncio)));
+#endif
+
+ if (hw->is_rd) {
+ writel(0,SPI_FRM(hw->syncio));
+ /* FIFO is full of sz data */
+ for (i=0;i<sz;i++) {
+ *(hw->buf++) = readl(SPI_DAT(hw->syncio));
+ }
+ } else
+ hw->buf += sz; /* data were sent */
+ hw->len -= sz;
+ writel(SPI_STAT_INTCLR,SPI_STAT(hw->syncio));
+
+ if (hw->len > 0)
+ spi_lpc32xx_start(hw);
+ else
+ spi_finalize_current_transfer(master);
+
+ return IRQ_HANDLED;
+}
+
+static int spi_lpc32xx_probe(struct platform_device *pdev)
+{
+ struct spi_lpc32xx_data *hw;
+ struct device_node *np = pdev->dev.of_node;
+ struct spi_master *master;
+ struct resource *res;
+ int irq, ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no DT node defined\n");
+ return -EINVAL;
+ }
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(*hw));
+ if (!master)
+ return -ENOMEM;
+
+ master->bus_num = pdev->id;
+ master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST | SPI_3WIRE;
+ master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8);
+ master->setup = spi_lpc32xx_setup;
+ master->transfer_one = spi_lpc32xx_transfer_one;
+ master->flags = SPI_MASTER_HALF_DUPLEX;
+ master->dev.of_node = pdev->dev.of_node;
+
+ hw = spi_master_get_devdata(master);
+
+ hw->spi_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(hw->spi_clk)) {
+ ret = PTR_ERR(hw->spi_clk);
+ goto err_out;
+ }
+ clk_enable(hw->spi_clk);
+ hw->rate = clk_get_rate(hw->spi_clk)/2;
+ master->max_speed_hz = hw->rate;
+ master->min_speed_hz = hw->rate/128;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hw->syncio = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(hw->syncio)) {
+ ret = PTR_ERR(hw->syncio);
+ goto err_out;
+ }
+
+ /* Clear possible pending interrupt */
+ writel(SPI_GLOB_ENABLE, SPI_GLOBAL(hw->syncio));
+ writel(SPI_GLOB_ENABLE|SPI_GLOB_RST, SPI_GLOBAL(hw->syncio));
+ writel(SPI_GLOB_ENABLE, SPI_GLOBAL(hw->syncio));
+
+ ret = devm_request_irq(&pdev->dev, irq, spi_lpc32xx_isr, 0,
+ dev_name(&pdev->dev), master);
+ if (ret)
+ goto err_out;
+
+ ret = devm_spi_register_master(&pdev->dev, master);
+ if (!ret) {
+ dev_info(&pdev->dev,
+ "SPI bus driver initialized. Master clock %u Hz\n",
+ master->max_speed_hz);
+ return 0;
+ }
+
+ dev_err(&pdev->dev, "Failed to register master\n");
+
+err_out:
+ spi_master_put(master);
+
+ return ret;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id lpc_spi_match[] = {
+ { .compatible = "nxp,lpc3220-spi" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lpc_spi_match);
+#endif
+
+static struct platform_driver lpc32xx_spi_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(lpc_spi_match),
+ },
+ .probe = spi_lpc32xx_probe,
+};
+module_platform_driver(lpc32xx_spi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Martin Devera <[email protected]>");
+MODULE_DESCRIPTION("LPC32XX SPI bus driver");
+MODULE_ALIAS("platform:" DRIVER_NAME);
--
1.7.10.4


2015-07-13 12:19:15

by Paul Bolle

[permalink] [raw]
Subject: Re: [PATCH 1/2] Add NXP LPC32XX SPI driver


(This hit my box with lkml mesages without lkml in the To: header. What
happened here?)

On zo, 2015-07-12 at 11:20 +0200, Martin Devera wrote:
> --- /dev/null
> +++ b/drivers/spi/spi-lpc32xx.c

> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/spi/spi.h>

(I wonder which of these, indirectly, pulls in <linux/module.h>.)

> +#define DRIVER_NAME "spi-lpc32xx"

> +MODULE_ALIAS("platform:" DRIVER_NAME);

This alias seems only useful if there's a corresponding struct
platform_device. Ie, a struct platform_device with a .name of "spi
-lpc32xx", which will fire off a "MODALIAS=platform:spi-lpc32xx" uevent
when it's created.

I couldn't find that platform_device. Did I miss something? Or is there
another way this alias is useful?

Thanks,


Paul Bolle

2015-07-13 12:54:19

by Martin Devera

[permalink] [raw]
Subject: Re: [PATCH 1/2] Add NXP LPC32XX SPI driver

Paul Bolle wrote:
> (This hit my box with lkml mesages without lkml in the To: header. What
> happened here?)

Ahh, have to check my sending script ..

> On zo, 2015-07-12 at 11:20 +0200, Martin Devera wrote:
>> --- /dev/null
>> +++ b/drivers/spi/spi-lpc32xx.c
>
>> +#define DRIVER_NAME "spi-lpc32xx"
>
>> +MODULE_ALIAS("platform:" DRIVER_NAME);
>
> This alias seems only useful if there's a corresponding struct
> platform_device. Ie, a struct platform_device with a .name of "spi
> -lpc32xx", which will fire off a "MODALIAS=platform:spi-lpc32xx" uevent
> when it's created.

My fault, thanks for spotting. It is leftover from driver I used
as template...
In any case, I has other comments from Joachim Eastwood thus I plan
to do more cleanup and resubmit the driver later..

thanks,
Martin