[brcm63xx] add bcm63xx_spi based on reading shared/opensource/flash/spiflash.c from...
authorFlorian Fainelli <florian@openwrt.org>
Sun, 8 Mar 2009 15:27:46 +0000 (15:27 +0000)
committerFlorian Fainelli <florian@openwrt.org>
Sun, 8 Mar 2009 15:27:46 +0000 (15:27 +0000)
SVN-Revision: 14793

target/linux/brcm63xx/config-2.6.27
target/linux/brcm63xx/files/drivers/spi/bcm63xx_spi.c [new file with mode: 0644]
target/linux/brcm63xx/patches-2.6.27/050-spi.patch [new file with mode: 0644]

index 79716aba2906338b3995c25af42716725f59e46e..17fe7abc7707dca656abac0a6208c1085ae768f3 100644 (file)
@@ -58,12 +58,6 @@ CONFIG_CPU_SUPPORTS_HIGHMEM=y
 # CONFIG_CPU_TX49XX is not set
 # CONFIG_CPU_VR41XX is not set
 CONFIG_CRAMFS=y
-CONFIG_CRYPTO_AES=m
-CONFIG_CRYPTO_ALGAPI=m
-CONFIG_CRYPTO_ARC4=m
-CONFIG_CRYPTO_BLKCIPHER=m
-CONFIG_CRYPTO_ECB=m
-CONFIG_CRYPTO_MANAGER=m
 CONFIG_CSRC_R4K=y
 CONFIG_DEFAULT_BIC=y
 CONFIG_DEFAULT_CFQ=y
@@ -235,6 +229,12 @@ CONFIG_SERIAL_BCM63XX_CONSOLE=y
 # CONFIG_SIBYTE_SWARM is not set
 # CONFIG_SPARSEMEM_STATIC is not set
 # CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set
+CONFIG_SPI=y
+CONFIG_SPI_BCM63XX=m
+CONFIG_SPI_BITBANG=m
+# CONFIG_SPI_GPIO is not set
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_SPIDEV=m
 CONFIG_SQUASHFS_EMBEDDED=y
 CONFIG_SQUASHFS_VMALLOC=y
 CONFIG_SSB=y
diff --git a/target/linux/brcm63xx/files/drivers/spi/bcm63xx_spi.c b/target/linux/brcm63xx/files/drivers/spi/bcm63xx_spi.c
new file mode 100644 (file)
index 0000000..449b1d5
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * Broadcom BCM63xx SPI controller support
+ *
+ * Copyright (C) 2009 Florian Fainelli <florian@openwrt.org>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/gpio.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+
+#include <bcm63xx_io.h>
+#include <bcm63xx_regs.h>
+#include <bcm63xx_dev_spi.h>
+
+#define PFX            KBUILD_MODNAME
+#define DRV_VER                "0.1.0"
+
+struct bcm63xx_spi {
+       /* bitbang has to be first */
+        struct spi_bitbang     bitbang;
+        struct completion      done;
+
+        void __iomem           *regs;
+        int                    irq;
+
+       /* Platform data */
+        u32                    speed_hz;
+       unsigned                msg_fifo_size;
+       unsigned                rx_fifo_size;
+
+       /* Data buffers */
+       const unsigned char     *tx_ptr;
+       unsigned char           *rx_ptr;
+       int                     remaining_bytes;
+
+       struct clk              *clk;
+       struct resource         *ioarea;
+       struct platform_device  *pdev;
+};
+
+static void bcm63xx_spi_chipselect(struct spi_device *spi, int is_on)
+{
+       u16 val;
+
+       val = bcm_spi_readw(SPI_CMD);
+       if (is_on == BITBANG_CS_INACTIVE)
+               val |= SPI_CMD_NOOP;
+       else if (is_on == BITBANG_CS_ACTIVE)
+               val |= (1 << spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT);
+       bcm_spi_writew(val, SPI_CMD);
+}
+
+static int bcm63xx_spi_setup_transfer(struct spi_device *spi,
+                                       struct spi_transfer *t)
+{
+       u8 bits_per_word;
+       u8 clk_cfg;
+       u32 hz;
+       unsigned int div;
+
+       struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
+
+       bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word;
+       hz = (t) ? t->speed_hz : spi->max_speed_hz;
+       if (bits_per_word != 8) {
+               dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n",
+                       __func__, bits_per_word);
+               return -EINVAL;
+        }
+
+       if (spi->chip_select > spi->master->num_chipselect) {
+               dev_err(&spi->dev, "%s, unsupported slave %d\n",
+                       __func__, spi->chip_select);
+               return -EINVAL;
+       }
+
+       /* Check clock setting */
+       div = (bs->speed_hz / hz);
+       switch (div) {
+       case 2:
+               clk_cfg = SPI_CLK_25MHZ;
+               break;
+       case 4:
+               clk_cfg = SPI_CLK_12_50MHZ;
+               break;
+       case 8:
+               clk_cfg = SPI_CLK_6_250MHZ;
+               break;
+       case 16:
+               clk_cfg = SPI_CLK_3_125MHZ;
+               break;
+       case 32:
+               clk_cfg = SPI_CLK_1_563MHZ;
+               break;
+       case 128:
+               clk_cfg = SPI_CLK_0_781MHZ;
+               break;
+       case 64:
+       default:
+               /* Set to slowest mode for compatibility */
+               clk_cfg = SPI_CLK_0_781MHZ;
+               break;
+       }
+
+       bcm_spi_writeb(clk_cfg, SPI_CLK_CFG);
+       dev_dbg(&spi->dev, "Setting clock register to %d (hz %d, cmd %02x)\n",
+                                                               div, hz, clk_cfg);
+       
+       return 0;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA)
+
+static int bcm63xx_spi_setup(struct spi_device *spi)
+{
+       struct spi_bitbang *bitbang;
+       struct bcm63xx_spi *bs;
+       int retval;
+
+       bs = spi_master_get_devdata(spi->master);
+       bitbang = &bs->bitbang;
+
+       if (!spi->bits_per_word)
+               spi->bits_per_word = 8;
+
+       if (spi->mode & ~MODEBITS) {
+               dev_err(&spi->dev, "%s, unsupported mode bits %x\n",
+                       __func__, spi->mode & ~MODEBITS);
+               return -EINVAL;
+       }
+
+       retval = bcm63xx_spi_setup_transfer(spi, NULL);
+       if (retval < 0) {
+               dev_err(&spi->dev, "setup: unsupported mode bits %x\n",
+                       spi->mode & ~MODEBITS);
+               return retval;
+       }
+
+       dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n",
+               __func__, spi->mode & MODEBITS, spi->bits_per_word, 0);
+
+       return 0;
+}
+
+/* Fill the TX FIFO with as many bytes as possible */
+static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs)
+{
+        u8 tail;
+
+        /* Fill the Tx FIFO with as many bytes as possible */
+       tail = bcm_spi_readb(SPI_MSG_TAIL);
+        while ((tail < bs->msg_fifo_size) && (bs->remaining_bytes > 0)) {
+                if (bs->tx_ptr)
+                        bcm_spi_writeb(*bs->tx_ptr++, SPI_MSG_DATA);
+               else
+                       bcm_spi_writeb(0, SPI_MSG_DATA); 
+                bs->remaining_bytes--;
+               tail = bcm_spi_readb(SPI_MSG_TAIL);
+        }
+}
+
+static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+       struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
+       u8 msg_ctl;
+       u16 cmd;
+
+       dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
+                               t->tx_buf, t->rx_buf, t->len);
+
+       /* Transmitter is inhibited */
+       bs->tx_ptr = t->tx_buf;
+       bs->rx_ptr = t->rx_buf;
+       bs->remaining_bytes = t->len;
+       init_completion(&bs->done);
+
+       bcm63xx_spi_fill_tx_fifo(bs);
+
+       /* Enable the command done interrupt which
+        * we use to determine completion of a command */
+       bcm_writeb(SPI_INTR_CMD_DONE, SPI_INT_MASK);
+       
+       /* Fill in the Message control register */
+       msg_ctl = bcm_spi_readb(SPI_MSG_CTL);
+       msg_ctl |= (t->len << SPI_BYTE_CNT_SHIFT);
+       msg_ctl |= (SPI_HD_R << SPI_MSG_TYPE_SHIFT);
+       bcm_spi_writeb(msg_ctl, SPI_MSG_CTL);
+       
+       /* Issue the transfer */
+       cmd = bcm_spi_readb(SPI_CMD);
+       cmd |= SPI_CMD_START_IMMEDIATE;
+       cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
+       bcm_spi_writeb(cmd, SPI_CMD);
+
+       wait_for_completion(&bs->done); 
+
+       /* Disable the CMD_DONE interrupt */
+       bcm_spi_writeb(~(SPI_INTR_CMD_DONE), SPI_INT_MASK);
+
+       return t->len - bs->remaining_bytes;
+}
+
+/* This driver supports single master mode only. Hence 
+ * CMD_DONE is the only interrupt we care about
+ */
+static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id)
+{
+       struct spi_master *master = (struct spi_master *)dev_id;
+       struct bcm63xx_spi *bs = spi_master_get_devdata(master);
+       u8 intr;
+       u16 cmd;
+
+       /* Read interupts and clear them immediately */
+       intr = bcm_spi_readb(SPI_INT_STATUS);
+       bcm_writeb(SPI_INTR_CLEAR_ALL, SPI_INT_STATUS);
+
+       /* A tansfer completed */
+       if (intr & SPI_INTR_CMD_DONE) {
+               u8 rx_empty;
+       
+               rx_empty = bcm_spi_readb(SPI_ST);
+               /* Read out all the data */
+               while ((rx_empty & SPI_RX_EMPTY) == 0) {
+                       u8 data;
+               
+                       data = bcm_spi_readb(SPI_RX_DATA);
+                       if (bs->rx_ptr)
+                               *bs->rx_ptr++ = data;
+
+                       rx_empty = bcm_spi_readb(SPI_RX_EMPTY);
+               }
+
+               /* See if there is more data to send */
+               if (bs->remaining_bytes > 0) {
+                       bcm63xx_spi_fill_tx_fifo(bs);
+
+                       /* Start the transfer */
+                       cmd = bcm_spi_readb(SPI_CMD);
+                       cmd |= SPI_CMD_START_IMMEDIATE;
+                       cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
+                       bcm_spi_writeb(cmd, SPI_CMD);
+               } else
+                       complete(&bs->done);
+       }
+
+       return IRQ_HANDLED;
+}
+
+
+static int __init bcm63xx_spi_probe(struct platform_device *pdev)
+{
+       struct resource *r;
+       struct bcm63xx_spi_pdata *pdata = pdev->dev.platform_data;
+       int irq;
+       struct spi_master *master;
+       struct clk *clk;
+       struct bcm63xx_spi *bs;
+       int ret;
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               ret = -ENXIO;
+               goto out;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               ret = -ENXIO;
+               goto out;
+       }
+
+       clk = clk_get(&pdev->dev, "spi");
+       if (IS_ERR(clk)) {
+               dev_err(&pdev->dev, "No clock for device\n");
+               ret = -ENODEV;
+               goto out;
+       }
+       
+       master = spi_alloc_master(&pdev->dev, sizeof(struct bcm63xx_spi));
+       if (!master) {
+               ret = -ENOMEM;
+               goto out_free;
+       }
+
+       bs = spi_master_get_devdata(master);
+       bs->bitbang.master = spi_master_get(master);
+       bs->bitbang.chipselect = bcm63xx_spi_chipselect;
+       bs->bitbang.setup_transfer = bcm63xx_spi_setup_transfer;
+       bs->bitbang.txrx_bufs = bcm63xx_txrx_bufs;
+       bs->bitbang.master->setup = bcm63xx_spi_setup;
+       init_completion(&bs->done);
+       
+       platform_set_drvdata(pdev, master);
+        bs->pdev = pdev;
+
+        bs->regs = ioremap_nocache(r->start, r->end - r->start);
+       if (!bs->regs) {
+               printk(KERN_ERR PFX " unable to ioremap regs\n");
+               ret = -ENOMEM;
+               goto out_free;
+       }
+       bs->irq = irq;
+       bs->clk = clk;
+       bs->msg_fifo_size = pdata->msg_fifo_size;
+       bs->rx_fifo_size = pdata->rx_fifo_size;
+
+       ret = request_irq(irq, bcm63xx_spi_interrupt, 0,
+                               pdev->dev.bus_id, master);
+       if (ret) {
+               printk(KERN_ERR PFX " unable to request irq\n");
+               goto out_unmap;
+       }
+
+       master->bus_num = pdata->bus_num;
+       master->num_chipselect = pdata->num_chipselect;
+       bs->speed_hz = pdata->speed_hz;
+       
+       /* Initialize hardware */
+       clk_enable(bs->clk);
+       bcm_spi_writew(SPI_CMD_HARD_RESET, SPI_CMD);
+       bcm_spi_writeb(SPI_INTR_CLEAR_ALL, SPI_INT_MASK);
+       
+       dev_info(&pdev->dev, PFX " at 0x%08x (irq %d) %s\n",
+                               r->start, irq, DRV_VER);
+
+       ret = spi_bitbang_start(&bs->bitbang);
+       if (ret) {
+               dev_err(&pdev->dev, "spi_bitbang_start FAILED\n");
+               goto out_reset_hw;
+       }
+
+       return ret;
+
+out_reset_hw:
+       clk_disable(clk);
+       free_irq(irq, master);
+out_unmap:
+       iounmap(bs->regs);
+out_free:
+       clk_put(clk);
+       spi_master_put(master);
+out:
+       return ret;
+}
+
+static int __exit bcm63xx_spi_remove(struct platform_device *pdev)
+{
+       struct spi_master       *master = platform_get_drvdata(pdev);
+       struct bcm63xx_spi      *bs = spi_master_get_devdata(master);
+
+       spi_bitbang_stop(&bs->bitbang);
+       clk_disable(bs->clk);
+       clk_put(bs->clk);
+       free_irq(bs->irq, master);
+       iounmap(bs->regs);
+       platform_set_drvdata(pdev, 0);
+       spi_master_put(bs->bitbang.master);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int bcm63xx_spi_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+       struct spi_master       *master = platform_get_drvdata(pdev);
+       struct bcm63xx_spi      *bs = spi_master_get_devdata(master);
+
+        clk_disable(bs->clk);
+        
+       return 0;
+}
+
+static int bcm63xx_spi_resume(struct platform_device *pdev)
+{
+       struct bcm63xx_spi      *bs = spi_master_get_devdata(master);
+       struct bcm63xx_spi      *bs = spi_master_get_devdata(master);
+
+       clk_enable(bs->clk);
+
+       return 0;
+}
+#else
+#define bcm63xx_spi_suspend    NULL
+#define bcm63xx_spi_resume     NULL
+#endif
+
+static struct platform_driver bcm63xx_spi_driver = {
+       .driver = {
+               .name   = "bcm63xx-spi",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = bcm63xx_spi_probe,
+       .remove         = bcm63xx_spi_remove,
+       .suspend        = bcm63xx_spi_suspend,
+       .resume         = bcm63xx_spi_resume,
+};
+
+
+static int __init bcm63xx_spi_init(void)
+{
+       return platform_driver_register(&bcm63xx_spi_driver);
+}
+
+static void __exit bcm63xx_spi_exit(void)
+{
+       platform_driver_unregister(&bcm63xx_spi_driver);
+}
+
+module_init(bcm63xx_spi_init);
+module_exit(bcm63xx_spi_exit);
+
+MODULE_ALIAS("platform:bcm63xx_spi");
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("Broadcom BCM63xx SPI Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/brcm63xx/patches-2.6.27/050-spi.patch b/target/linux/brcm63xx/patches-2.6.27/050-spi.patch
new file mode 100644 (file)
index 0000000..7dea317
--- /dev/null
@@ -0,0 +1,26 @@
+--- a/drivers/spi/Kconfig      2009-03-06 02:24:35.000000000 +0100
++++ b/drivers/spi/Kconfig      2009-03-06 02:25:01.000000000 +0100
+@@ -217,6 +217,13 @@
+         See the "OPB Serial Peripheral Interface (SPI) (v1.00e)"
+         Product Specification document (DS464) for hardware details.
++config SPI_BCM63XX
++      tristate "Broadcom BCM63xx SPI controller"
++      depends on BCM63XX
++      select SPI_BITBANG
++      help
++        SPI driver for the Broadcom BCM63xx SPI controller.
++
+ #
+ # Add new SPI master controllers in alphabetical order above this line
+ #
+--- a/drivers/spi/Makefile     2009-03-06 02:25:07.000000000 +0100
++++ b/drivers/spi/Makefile     2009-03-06 02:24:13.000000000 +0100
+@@ -30,6 +30,7 @@
+ obj-$(CONFIG_SPI_TXX9)                        += spi_txx9.o
+ obj-$(CONFIG_SPI_XILINX)              += xilinx_spi.o
+ obj-$(CONFIG_SPI_SH_SCI)              += spi_sh_sci.o
++obj-$(CONFIG_SPI_BCM63XX)             += bcm63xx_spi.o
+ #     ... add above this line ...
+ # SPI protocol drivers (device/link on bus)