summaryrefslogtreecommitdiff
path: root/drivers/spi
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2009-12-10 17:31:06 -0800
committerGary King <gking@nvidia.com>2009-12-11 15:03:15 -0800
commit244a6732bde71a879ec29ce07a8a0ec12a98c337 (patch)
tree0e64291f6672516ed39c52f67e2b46a078dfbf9c /drivers/spi
parentb86cbafbb7328f276b6540c1cec4491d4c2d342a (diff)
SPI: add NvRm-based SPI driver for NVIDIA Tegra SoCs
Adds a SPI bus driver for NVIDIA Tegra SoCs, using NVIDIA's NvRm and NvOs APIs Change-Id: I1e7f5f9e692079b15b1e5e9b253ca25cb047e861
Diffstat (limited to 'drivers/spi')
-rw-r--r--drivers/spi/Kconfig8
-rw-r--r--drivers/spi/Makefile2
-rw-r--r--drivers/spi/tegra_spi.c320
3 files changed, 330 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 83a185d52961..d20d356f06fe 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -165,6 +165,14 @@ config SPI_OMAP24XX
SPI master controller for OMAP24xx/OMAP34xx Multichannel SPI
(McSPI) modules.
+config SPI_TEGRA
+ boolean "SPI driver for NVIDIA Tegra SoCs"
+ depends on ARCH_TEGRA
+ help
+ Enables the integrated SPI controllers on NVIDIA Tegra SoCs
+
+
+
config SPI_ORION
tristate "Orion SPI master (EXPERIMENTAL)"
depends on PLAT_ORION && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5d0451936d86..e41c02e7be31 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -30,6 +30,8 @@ obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o
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_TEGRA) += tegra_spi.o
+
# ... add above this line ...
# SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/tegra_spi.c b/drivers/spi/tegra_spi.c
new file mode 100644
index 000000000000..f881a22c7fe5
--- /dev/null
+++ b/drivers/spi/tegra_spi.c
@@ -0,0 +1,320 @@
+/*
+ * drivers/spi/tegra_spi.c
+ *
+ * SPI bus driver for NVIDIA Tegra SoCs, based on NvRm APIs
+ *
+ * Copyright (c) 2009, NVIDIA Corporation.
+ *
+ * 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, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/workqueue.h>
+
+
+#include "mach/nvrm_linux.h"
+#include "mach/io.h"
+#include "nvrm_spi.h"
+#include "nvodm_query.h"
+
+/* FIXME, don't include the Rm private API */
+#include "io/ap15/rm_spi_slink.h"
+
+/* Cannot use spinlocks as the NvRm SPI apis uses mutextes and one cannot use
+ * mutextes inside a spinlock.
+ */
+#define USE_SPINLOCK 0
+#if USE_SPINLOCK
+#define LOCK_T spinlock_t
+#define CREATELOCK(_l) spin_lock_init(&(_l))
+#define DELETELOCK(_l)
+#define LOCK(_l) spin_lock(&(_l))
+#define UNLOCK(_l) spin_unlock(&(_l))
+#define ATOMIC(_l,_f) spin_lock_irqsave(&(_l),(_f))
+#define UNATOMIC(_l,_f) spin_unlock_irqrestore(&(_l),(_f))
+#else
+#define LOCK_T struct mutex
+#define CREATELOCK(_l) mutex_init(&(_l))
+#define DELETELOCK(_l)
+#define LOCK(_l) mutex_lock(&(_l))
+#define UNLOCK(_l) mutex_unlock(&(_l))
+#define ATOMIC(_l,_f) local_irq_save((_f))
+#define UNATOMIC(_l,_f) local_irq_restore((_f))
+#endif
+
+struct NvSpiShim {
+ NvRmSpiHandle hSpi;
+ NvU32 IoModuleID;
+ NvU32 Instance;
+ NvU32 ChipSelect;
+ NvU32 PinMuxConfig;
+
+ NvU32 Mode;
+ NvU32 BitsPerWord;
+ NvU32 ClockInKHz;
+
+ struct list_head msg_queue;
+ LOCK_T lock;
+ struct work_struct work;
+ struct workqueue_struct *WorkQueue;
+};
+
+/* Only these signaling mode are supported */
+#define NV_SUPPORTED_MODE_BITS (SPI_CPOL | SPI_CPHA)
+
+static int tegra_spi_setup(struct spi_device *pSpiDevice)
+{
+ struct NvSpiShim *pShimSpi;
+
+ pShimSpi = spi_master_get_devdata(pSpiDevice->master);
+
+ if (pSpiDevice->mode & ~NV_SUPPORTED_MODE_BITS) {
+ dev_dbg(&pSpiDevice->dev, "setup: unsupported mode bits 0x%x\n",
+ pSpiDevice->mode & ~NV_SUPPORTED_MODE_BITS);
+ }
+
+ pShimSpi->Mode = pSpiDevice->mode & ~NV_SUPPORTED_MODE_BITS;
+ switch (pShimSpi->Mode) {
+ case SPI_MODE_0:
+ pShimSpi->Mode = NvOdmQuerySpiSignalMode_0;
+ break;
+ case SPI_MODE_1:
+ pShimSpi->Mode = NvOdmQuerySpiSignalMode_1;
+ break;
+ case SPI_MODE_2:
+ pShimSpi->Mode = NvOdmQuerySpiSignalMode_2;
+ break;
+ case SPI_MODE_3:
+ pShimSpi->Mode = NvOdmQuerySpiSignalMode_3;
+ break;
+ }
+
+ if (pSpiDevice->bits_per_word == 0)
+ pSpiDevice->bits_per_word = 8;
+
+ pShimSpi->BitsPerWord = pSpiDevice->bits_per_word;
+ pShimSpi->ClockInKHz = pSpiDevice->max_speed_hz / 1000;
+ pShimSpi->ChipSelect = pSpiDevice->chip_select;
+
+ NvRmSpiSetSignalMode(pShimSpi->hSpi, pShimSpi->ChipSelect,
+ pShimSpi->Mode);
+ return 0;
+}
+
+static int tegra_spi_transfer(struct spi_device *pSpiDevice,
+ struct spi_message *msg)
+{
+ struct NvSpiShim *pShimSpi;
+
+ if (unlikely(list_empty(&msg->transfers) ||
+ !pSpiDevice->max_speed_hz))
+ return -EINVAL;
+
+ /* FIXME validate the msg */
+
+ pShimSpi = spi_master_get_devdata(pSpiDevice->master);
+
+ /* Add the message to the queue and signal the worker thread */
+ LOCK(pShimSpi->lock);
+ list_add_tail(&msg->queue, &pShimSpi->msg_queue);
+ queue_work(pShimSpi->WorkQueue, &pShimSpi->work);
+ UNLOCK(pShimSpi->lock);
+
+ return 0;
+}
+
+static void tegra_spi_cleanup(struct spi_device *pSpiDevice)
+{
+ /* Nothing to do. NvRmSpi API is stateless.
+ * Isn't that bueatifull?
+ */
+ return;
+}
+
+static void tegra_spi_workerthread(struct work_struct *w)
+{
+ struct NvSpiShim *pShimSpi;
+
+ pShimSpi = container_of(w, struct NvSpiShim, work);
+
+ LOCK(pShimSpi->lock);
+
+ while (!list_empty(&pShimSpi->msg_queue)) {
+ struct spi_message *m;
+ struct spi_transfer *t = NULL;
+ int status = 0;
+ NvRmSpiTransactionInfo trans[64];
+ NvU32 i= 0;
+ NvU32 actual_length = 0;
+
+ m = container_of(pShimSpi->msg_queue.next,
+ struct spi_message, queue);
+
+ list_del_init(&m->queue);
+
+ UNLOCK(pShimSpi->lock);
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+ if (i==64) {
+ status = -EINVAL;
+ break;
+ }
+ if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
+ status = -EINVAL;
+ break;
+ }
+ if (t->len) {
+ trans[i].rxBuffer = t->rx_buf;
+ trans[i].txBuffer = (NvU8 *)t->tx_buf;
+ trans[i].len = t->len;
+ actual_length += t->len;
+ }
+ if (t->cs_change) {
+ /* What should we do here? */
+ }
+ i++;
+ }
+
+ if (!status && i) {
+ NvRmSpiOptimizedMultipleTransactions(pShimSpi->hSpi,
+ pShimSpi->PinMuxConfig, pShimSpi->ChipSelect,
+ pShimSpi->ClockInKHz, pShimSpi->BitsPerWord,
+ trans, i);
+
+ m->actual_length += actual_length;
+ }
+
+ m->status = status;
+ m->complete(m->context);
+
+ LOCK(pShimSpi->lock);
+ }
+
+ UNLOCK(pShimSpi->lock);
+}
+
+static int __init tegra_spi_probe(struct platform_device *pdev)
+{
+ struct spi_master *pSpi;
+ struct NvSpiShim *pShimSpi;
+ struct tegra_spi_pdata *pdata = pdev->dev.platform_data;
+ int status= 0;
+ NvError err;
+ char name[64];
+
+ pSpi = spi_alloc_master(&pdev->dev, sizeof *pShimSpi);
+ if (pSpi == NULL) {
+ dev_dbg(&pdev->dev, "master allocation failed\n");
+ return -ENOMEM;
+ }
+
+ pSpi->setup = tegra_spi_setup;
+ pSpi->transfer = tegra_spi_transfer;
+ pSpi->cleanup = tegra_spi_cleanup;
+ pSpi->num_chipselect = 4;
+ pSpi->bus_num = pdev->id;
+
+ dev_set_drvdata(&pdev->dev, pSpi);
+ pShimSpi = spi_master_get_devdata(pSpi);
+ pShimSpi->IoModuleID = pdata->IoModuleID;
+ pShimSpi->Instance = pdata->Instance;
+ pShimSpi->PinMuxConfig = pdata->PinMuxConfig;
+
+ err = NvRmSpiOpen(s_hRmGlobal, pShimSpi->IoModuleID,
+ pShimSpi->Instance, NV_TRUE, &pShimSpi->hSpi);
+ if (err != NvSuccess) {
+ dev_dbg(&pdev->dev, "NvRmSpiOpen failed (%d)\n", err);
+ goto spi_open_failed;
+ }
+
+ /* FIXME: Do we need one workqueue for each instance or one
+ * workqueue for all instances will do?
+ */
+ snprintf(name, sizeof(name), "%s_%d", "tegra_spi_wq",
+ pdata->IoModuleID || pdata->Instance << 16);
+ pShimSpi->WorkQueue = create_singlethread_workqueue(name);
+ if (pShimSpi->WorkQueue == NULL) {
+ dev_err(&pdev->dev, "Failed to create work queue\n");
+ goto workQueueCreate_failed;
+ }
+
+ INIT_WORK(&pShimSpi->work, tegra_spi_workerthread);
+
+ CREATELOCK(pShimSpi->lock);
+ INIT_LIST_HEAD(&pShimSpi->msg_queue);
+
+ status = spi_register_master(pSpi);
+ if (status < 0) {
+ dev_err(&pdev->dev, "spi_register_master failed %d\n", status);
+ goto spi_register_failed;
+ }
+
+ dev_info(&pdev->dev,
+ "Registered spi device for mod,inst,mux (%d,%d,%d)\n",
+ pShimSpi->IoModuleID, pShimSpi->Instance,
+ pShimSpi->PinMuxConfig);
+
+ return status;
+
+spi_register_failed:
+ destroy_workqueue(pShimSpi->WorkQueue);
+workQueueCreate_failed:
+ NvRmSpiClose(pShimSpi->hSpi);
+spi_open_failed:
+ spi_master_put(pSpi);
+ return status;
+}
+
+static int __exit tegra_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *pSpi;
+ struct NvSpiShim *pShimSpi;
+
+ pSpi = dev_get_drvdata(&pdev->dev);
+ pShimSpi = spi_master_get_devdata(pSpi);
+
+ spi_unregister_master(pSpi);
+ NvRmSpiClose(pShimSpi->hSpi);
+ destroy_workqueue(pShimSpi->WorkQueue);
+
+ return 0;
+}
+
+static struct platform_driver tegra_spi_driver = {
+ .driver = {
+ .name = "tegra_spi",
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(tegra_spi_remove),
+};
+
+static int __init tegra_spi_init(void)
+{
+ return platform_driver_probe(&tegra_spi_driver, tegra_spi_probe);
+}
+module_init(tegra_spi_init);
+
+static void __exit tegra_spi_exit(void)
+{
+ platform_driver_unregister(&tegra_spi_driver);
+}
+module_exit(tegra_spi_exit);
+
+