summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2010-05-27 20:13:26 -0700
committerGary King <gking@nvidia.com>2010-05-27 21:41:03 -0700
commit6b72a2f24eb2dfecb1115659bd17e3c3c29b70ff (patch)
tree1b9b6df0eb05c5497c048aaa95f8e13094e5496e /drivers
parentf6caae835d7b2aaec4bdae6075049fd432bebdb1 (diff)
spi: add spi master driver for NVIDIA Tegra SoCs
Change-Id: I8d2834f5b7e73cd6a1eb6584715bdd707e23e830
Diffstat (limited to 'drivers')
-rw-r--r--drivers/spi/Kconfig6
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/tegra_spi.c283
3 files changed, 290 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 4b6f7cba3b3d..54883a8d0e95 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -150,6 +150,12 @@ config SPI_MPC8xxx
This driver uses a simple set of shift registers for data (opposed
to the CPM based descriptor model).
+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_OMAP_UWIRE
tristate "OMAP1 MicroWire"
depends on ARCH_OMAP1
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 21a118269cac..1918ce509369 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_ORION) += orion_spi.o
obj-$(CONFIG_SPI_PL022) += amba-pl022.o
obj-$(CONFIG_SPI_MPC52xx_PSC) += mpc52xx_psc_spi.o
obj-$(CONFIG_SPI_MPC8xxx) += spi_mpc8xxx.o
+obj-$(CONFIG_SPI_TEGRA) += tegra_spi.o
obj-$(CONFIG_SPI_PPC4xx) += spi_ppc4xx.o
obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o
obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o
diff --git a/drivers/spi/tegra_spi.c b/drivers/spi/tegra_spi.c
new file mode 100644
index 000000000000..416ab1fce40b
--- /dev/null
+++ b/drivers/spi/tegra_spi.c
@@ -0,0 +1,283 @@
+/*
+ * drivers/spi/tegra_spi.c
+ *
+ * SPI bus driver for NVIDIA Tegra SoCs, based on NvRm APIs
+ *
+ * Copyright (c) 2009-2010, 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.
+ */
+
+#define NV_DEBUG 0
+
+#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/err.h>
+#include <linux/workqueue.h>
+
+
+#include <mach/spi.h>
+#include <mach/nvrm_linux.h>
+#include <nvrm_spi.h>
+#include <nvodm_query.h>
+
+#include <rm_spi_slink.h>
+
+struct tegra_spi {
+ NvRmSpiHandle rm_spi;
+ NvU32 pinmux;
+ NvU32 Mode;
+ struct list_head msg_queue;
+ spinlock_t lock;
+ struct work_struct work;
+ struct workqueue_struct *queue;
+};
+
+/* Only these signaling mode are supported */
+#define NV_SUPPORTED_MODE_BITS (SPI_CPOL | SPI_CPHA)
+
+static int tegra_spi_setup(struct spi_device *device)
+{
+ struct tegra_spi *spi;
+
+ spi = spi_master_get_devdata(device->master);
+
+ if (device->mode & ~NV_SUPPORTED_MODE_BITS) {
+ dev_dbg(&device->dev, "setup: unsupported mode bits 0x%x\n",
+ device->mode & ~NV_SUPPORTED_MODE_BITS);
+ }
+
+ spi->Mode = device->mode & NV_SUPPORTED_MODE_BITS;
+ switch (spi->Mode) {
+ case SPI_MODE_0:
+ spi->Mode = NvOdmQuerySpiSignalMode_0;
+ break;
+ case SPI_MODE_1:
+ spi->Mode = NvOdmQuerySpiSignalMode_1;
+ break;
+ case SPI_MODE_2:
+ spi->Mode = NvOdmQuerySpiSignalMode_2;
+ break;
+ case SPI_MODE_3:
+ spi->Mode = NvOdmQuerySpiSignalMode_3;
+ break;
+ }
+
+ if (device->bits_per_word == 0)
+ device->bits_per_word = 8;
+
+ NvRmSpiSetSignalMode(spi->rm_spi, device->chip_select, spi->Mode);
+ return 0;
+}
+
+static int tegra_spi_transfer(struct spi_device *device,
+ struct spi_message *msg)
+{
+ struct tegra_spi *spi;
+
+ if (unlikely(list_empty(&msg->transfers) || !device->max_speed_hz))
+ return -EINVAL;
+
+ /* FIXME validate the msg */
+
+ spi = spi_master_get_devdata(device->master);
+
+ /* Add the message to the queue and signal the worker thread */
+ spin_lock(&spi->lock);
+ list_add_tail(&msg->queue, &spi->msg_queue);
+ queue_work(spi->queue, &spi->work);
+ spin_unlock(&spi->lock);
+
+ return 0;
+}
+
+static void tegra_spi_cleanup(struct spi_device *device)
+{
+ return;
+}
+
+static int tegra_spi_do_message(struct tegra_spi *spi, struct spi_message *m)
+{
+ NvRmSpiTransactionInfo trans[64];
+ struct spi_transfer *t;
+ unsigned int len = 0;
+ int i = 0;
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+ if (i==ARRAY_SIZE(trans))
+ return -EIO;
+
+ if (t->len && !t->tx_buf && !t->rx_buf)
+ return -EINVAL;
+
+ if (t->cs_change) {
+ WARN_ON_ONCE(1);
+ return -EIO;
+ }
+
+ if (t->len) {
+ trans[i].rxBuffer = t->rx_buf;
+ trans[i].txBuffer = (NvU8*)t->tx_buf;
+ trans[i].len = t->len;
+ len += t->len;
+ }
+
+ i++;
+ }
+
+ if (!i)
+ return 0;
+
+ m->actual_length += len;
+ NvRmSpiMultipleTransactions(spi->rm_spi, spi->pinmux,
+ m->spi->chip_select, m->spi->max_speed_hz / 1000,
+ m->spi->bits_per_word, trans, i);
+
+ return 0;
+}
+
+static void tegra_spi_workerthread(struct work_struct *w)
+{
+ struct tegra_spi *spi;
+
+ spi = container_of(w, struct tegra_spi, work);
+
+ spin_lock(&spi->lock);
+
+ while (!list_empty(&spi->msg_queue)) {
+ struct spi_message *m;
+
+ m = container_of(spi->msg_queue.next, struct spi_message, queue);
+ list_del_init(&m->queue);
+ spin_unlock(&spi->lock);
+
+ if (!m->spi) {
+ WARN_ON(1);
+ return;
+ }
+ m->status = tegra_spi_do_message(spi, m);
+ m->complete(m->context);
+
+ spin_lock(&spi->lock);
+ }
+
+ spin_unlock(&spi->lock);
+}
+
+static int __init tegra_spi_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct tegra_spi *spi;
+ struct tegra_spi_platform_data *plat = pdev->dev.platform_data;
+ int status= 0;
+ NvError e;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(*spi));
+ if (IS_ERR_OR_NULL(master)) {
+ dev_err(&pdev->dev, "master allocation failed\n");
+ return -ENOMEM;
+ }
+
+ master->setup = tegra_spi_setup;
+ master->transfer = tegra_spi_transfer;
+ master->cleanup = tegra_spi_cleanup;
+ master->num_chipselect = 4;
+ master->bus_num = pdev->id;
+
+ dev_set_drvdata(&pdev->dev, master);
+ spi = spi_master_get_devdata(master);
+
+ spi->pinmux = plat->pinmux;
+
+ if (plat->is_slink) {
+ e = NvRmSpiOpen(s_hRmGlobal, NvOdmIoModule_Spi,
+ pdev->id, NV_TRUE, &spi->rm_spi);
+ } else {
+ e = NvRmSpiOpen(s_hRmGlobal, NvOdmIoModule_Sflash,
+ 0, NV_TRUE, &spi->rm_spi);
+ }
+ if (e != NvSuccess) {
+ dev_err(&pdev->dev, "NvRmSpiOpen returned 0x%x\n", e);
+ status = -ENODEV;
+ goto spi_open_failed;
+ }
+
+ spi->queue = create_singlethread_workqueue(dev_name(&pdev->dev));
+ if (!spi->queue) {
+ dev_err(&pdev->dev, "Failed to create work queue\n");
+ goto workQueueCreate_failed;
+ }
+
+ INIT_WORK(&spi->work, tegra_spi_workerthread);
+
+ spin_lock_init(&spi->lock);
+ INIT_LIST_HEAD(&spi->msg_queue);
+
+ status = spi_register_master(master);
+ if (status < 0) {
+ dev_err(&pdev->dev, "spi_register_master failed %d\n", status);
+ goto spi_register_failed;
+ }
+
+ return status;
+
+spi_register_failed:
+ destroy_workqueue(spi->queue);
+workQueueCreate_failed:
+ NvRmSpiClose(spi->rm_spi);
+spi_open_failed:
+ spi_master_put(master);
+ return status;
+}
+
+static int tegra_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct tegra_spi *spi;
+
+ master = dev_get_drvdata(&pdev->dev);
+ spi = spi_master_get_devdata(master);
+
+ spi_unregister_master(master);
+ NvRmSpiClose(spi->rm_spi);
+ destroy_workqueue(spi->queue);
+
+ return 0;
+}
+
+static struct platform_driver tegra_spi_driver = {
+ .probe = tegra_spi_probe,
+ .remove = tegra_spi_remove,
+ .driver = {
+ .name = "tegra_spi",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init tegra_spi_init(void)
+{
+ return platform_driver_register(&tegra_spi_driver);
+}
+module_init(tegra_spi_init);
+
+static void __exit tegra_spi_exit(void)
+{
+ platform_driver_unregister(&tegra_spi_driver);
+}
+module_exit(tegra_spi_exit);