diff options
author | Gary King <gking@nvidia.com> | 2009-12-10 17:31:06 -0800 |
---|---|---|
committer | Gary King <gking@nvidia.com> | 2009-12-11 15:03:15 -0800 |
commit | 244a6732bde71a879ec29ce07a8a0ec12a98c337 (patch) | |
tree | 0e64291f6672516ed39c52f67e2b46a078dfbf9c /drivers/spi | |
parent | b86cbafbb7328f276b6540c1cec4491d4c2d342a (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/Kconfig | 8 | ||||
-rw-r--r-- | drivers/spi/Makefile | 2 | ||||
-rw-r--r-- | drivers/spi/tegra_spi.c | 320 |
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); + + |