Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751449AbbGLKyj (ORCPT ); Sun, 12 Jul 2015 06:54:39 -0400 Received: from ms1.cdi.cz ([93.90.167.38]:37368 "EHLO ms1.cdi.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751060AbbGLKyi (ORCPT ); Sun, 12 Jul 2015 06:54:38 -0400 Message-Id: From: Martin Devera Date: Sun, 12 Jul 2015 11:20:49 +0200 Subject: [PATCH 1/2] Add NXP LPC32XX SPI driver X-CDI-Malware-Check: OK X-Spam-Score: -1.6 (-) To: unlisted-recipients:; (no To-header on input) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 11197 Lines: 363 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 = 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 = ; + 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 + * + * 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 +#include +#include +#include +#include +#include + +#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;ibuf++) = 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 "); +MODULE_DESCRIPTION("LPC32XX SPI bus driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/