summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSheshagiri Shenoy <sshenoy@nvidia.com>2011-03-17 17:35:49 -0700
committerVarun Colbert <vcolbert@nvidia.com>2011-04-14 21:22:52 -0700
commit8de40995f4ee35073c99c7f07b631450bc8824d6 (patch)
tree16e1d97723c3f6fc126f91c18c3249324cbe6ef9
parentd6ad1d95125bf21ed6c99cc8a633f0a23779d18a (diff)
net: caif: added tegra specific caif for rainbow modem.
- added the tegra dependent layer of the caif protocol. - integrated with the open source rainbow caif. - verified the functionality using latest rainbow RIL. bug 785523 Change-Id: I75be8d2ef6e5562facf902a3f963f34d241bb6c3 Reviewed-on: http://git-master/r/23421 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--drivers/net/caif/Kconfig9
-rw-r--r--drivers/net/caif/Makefile4
-rw-r--r--drivers/net/caif/tegra_caif_sspi.c426
-rw-r--r--include/linux/tegra_caif.h34
4 files changed, 473 insertions, 0 deletions
diff --git a/drivers/net/caif/Kconfig b/drivers/net/caif/Kconfig
index 75bfc3a9d95f..2f1f24d1407e 100644
--- a/drivers/net/caif/Kconfig
+++ b/drivers/net/caif/Kconfig
@@ -31,3 +31,12 @@ config CAIF_SPI_SYNC
Putting the next command and length in the start of the frame can
help to synchronize to the next transfer in case of over or under-runs.
This option also needs to be enabled on the modem.
+
+config TEGRA_SPI_CAIF
+ tristate "TEGRA specific CAIF SPI transport driver for slave interface"
+ depends on CAIF_SPI_SLAVE && TEGRA_SPI_SLAVE
+ default n
+ ---help---
+ The CAIF Link layer SPI Protocol driver for Tegra Slave SPI interface.
+ This driver implements a platform driver to accommodate for a
+ Tegra Slave SPI device.
diff --git a/drivers/net/caif/Makefile b/drivers/net/caif/Makefile
index 3a11d619452b..998959c4724e 100644
--- a/drivers/net/caif/Makefile
+++ b/drivers/net/caif/Makefile
@@ -8,3 +8,7 @@ obj-$(CONFIG_CAIF_TTY) += caif_serial.o
# SPI slave physical interfaces module
cfspi_slave-objs := caif_spi.o caif_spi_slave.o
obj-$(CONFIG_CAIF_SPI_SLAVE) += cfspi_slave.o
+
+# Tegra specific SPI slave physical interfaces module
+tegra_cfspi_slave-objs := tegra_caif_sspi.o
+obj-$(CONFIG_TEGRA_SPI_CAIF) += tegra_cfspi_slave.o
diff --git a/drivers/net/caif/tegra_caif_sspi.c b/drivers/net/caif/tegra_caif_sspi.c
new file mode 100644
index 000000000000..c2c15c18f2e4
--- /dev/null
+++ b/drivers/net/caif/tegra_caif_sspi.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2011, 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/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/tegra_caif.h>
+#include <mach/spi.h>
+#include <net/caif/caif_spi.h>
+
+MODULE_LICENSE("GPL");
+
+#define SPI_CAIF_PAD_TRANSACTION_SIZE(x) \
+ (((x) > 4) ? ((((x) + 15) / 16) * 16) : (x))
+
+struct sspi_struct {
+ struct cfspi_dev sdev;
+ struct cfspi_xfer *xfer;
+};
+
+static struct sspi_struct slave;
+static struct platform_device slave_device;
+static struct spi_device *tegra_caif_spi_slave_device;
+int tegra_caif_sspi_gpio_spi_int;
+int tegra_caif_sspi_gpio_spi_ss;
+int tegra_caif_sspi_gpio_reset;
+int tegra_caif_sspi_gpio_power;
+int tegra_caif_sspi_gpio_awr;
+int tegra_caif_sspi_gpio_cwr;
+
+
+static int __devinit tegra_caif_spi_slave_probe(struct spi_device *spi);
+
+static int tegra_caif_spi_slave_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_caif_spi_slave_suspend(struct spi_device *spi
+ , pm_message_t mesg)
+{
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PM
+static int tegra_caif_spi_slave_resume(struct spi_device *spi)
+{
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct spi_driver tegra_caif_spi_slave_driver = {
+ .driver = {
+ .name = "baseband_spi_slave0.0",
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_caif_spi_slave_probe,
+ .remove = __devexit_p(tegra_caif_spi_slave_remove),
+#ifdef CONFIG_PM
+ .suspend = tegra_caif_spi_slave_suspend,
+ .resume = tegra_caif_spi_slave_resume,
+#endif /* CONFIG_PM */
+};
+
+void tegra_caif_modem_power(int on)
+{
+ static int u3xx_on;
+ int err;
+ int cnt = 0;
+ int val = 0;
+
+ if (u3xx_on == on)
+ return;
+ u3xx_on = on;
+
+ if (u3xx_on) {
+ /* turn on u3xx modem */
+ err = gpio_request(tegra_caif_sspi_gpio_reset
+ , "caif_sspi_reset");
+ if (err < 0)
+ goto err1;
+
+ err = gpio_request(tegra_caif_sspi_gpio_power
+ , "caif_sspi_power");
+ if (err < 0)
+ goto err2;
+
+ err = gpio_request(tegra_caif_sspi_gpio_awr
+ , "caif_sspi_awr");
+ if (err < 0)
+ goto err3;
+
+ err = gpio_request(tegra_caif_sspi_gpio_cwr
+ , "caif_sspi_cwr");
+ if (err < 0)
+ goto err4;
+
+ err = gpio_direction_output(tegra_caif_sspi_gpio_reset
+ , 0 /* asserted */);
+ if (err < 0)
+ goto err5;
+
+ err = gpio_direction_output(tegra_caif_sspi_gpio_power
+ , 0 /* off */);
+ if (err < 0)
+ goto err6;
+
+ err = gpio_direction_output(tegra_caif_sspi_gpio_awr
+ , 0);
+ if (err < 0)
+ goto err7;
+
+ err = gpio_direction_input(tegra_caif_sspi_gpio_cwr);
+ if (err < 0)
+ goto err8;
+
+ gpio_set_value(tegra_caif_sspi_gpio_power, 0);
+ gpio_set_value(tegra_caif_sspi_gpio_reset, 0);
+
+ msleep(800);
+
+ /* pulse modem power on for 300 ms */
+ gpio_set_value(tegra_caif_sspi_gpio_reset
+ , 1 /* deasserted */);
+ msleep(300);
+ gpio_set_value(tegra_caif_sspi_gpio_power, 1);
+ msleep(300);
+ gpio_set_value(tegra_caif_sspi_gpio_power, 0);
+ msleep(100);
+
+ /* set awr high */
+ gpio_set_value(tegra_caif_sspi_gpio_awr, 1);
+ val = gpio_get_value(tegra_caif_sspi_gpio_cwr);
+ while (!val) {
+ /* wait for cwr to go high */
+ val = gpio_get_value(tegra_caif_sspi_gpio_cwr);
+ pr_info(".");
+ msleep(100);
+ cnt++;
+ if (cnt > 200) {
+ pr_err("\nWaiting for CWR timed out - ERROR\n");
+ break;
+ }
+ }
+ }
+ return;
+err8:
+err7:
+err6:
+err5:
+ gpio_free(tegra_caif_sspi_gpio_cwr);
+err4:
+ gpio_free(tegra_caif_sspi_gpio_awr);
+err3:
+ gpio_free(tegra_caif_sspi_gpio_power);
+err2:
+ gpio_free(tegra_caif_sspi_gpio_reset);
+err1:
+ return;
+}
+
+static irqreturn_t sspi_irq(int irq, void *arg)
+{
+ /* You only need to trigger on an edge to the active state of the
+ * SS signal. Once a edge is detected, the ss_cb() function should
+ * be called with the parameter assert set to true. It is OK
+ * (and even advised) to call the ss_cb() function in IRQ context
+ * in order not to add any delay.
+ */
+ int val;
+ struct cfspi_dev *sdev = (struct cfspi_dev *)arg;
+ val = gpio_get_value(tegra_caif_sspi_gpio_spi_ss);
+ if (val)
+ return IRQ_HANDLED;
+ sdev->ifc->ss_cb(true, sdev->ifc);
+ return IRQ_HANDLED;
+}
+
+static int sspi_callback(void *arg)
+{
+ /* for each spi_sync() call
+ * - sspi_callback() called before spi transfer
+ * - sspi_complete() called after spi transfer
+ */
+
+ /* set master interrupt gpio pin active (tells master to
+ * start spi clock)
+ */
+ udelay(MIN_TRANSITION_TIME_USEC);
+ gpio_set_value(tegra_caif_sspi_gpio_spi_int, 1);
+ return 0;
+}
+
+static void sspi_complete(void *context)
+{
+ /* Normally the DMA or the SPI framework will call you back
+ * in something similar to this. The only thing you need to
+ * do is to call the xfer_done_cb() function, providing the pointer
+ * to the CAIF SPI interface. It is OK to call this function
+ * from IRQ context.
+ */
+
+ struct cfspi_dev *sdev = (struct cfspi_dev *)context;
+ sdev->ifc->xfer_done_cb(sdev->ifc);
+}
+
+static void swap_byte(unsigned char *buf, unsigned int bufsiz)
+{
+ unsigned int i;
+ unsigned char tmp;
+ for (i = 0; i < bufsiz; i += 2) {
+ tmp = buf[i];
+ buf[i] = buf[i+1];
+ buf[i+1] = tmp;
+ }
+}
+
+static int sspi_init_xfer(struct cfspi_xfer *xfer, struct cfspi_dev *dev)
+{
+ /* Store transfer info. For a normal implementation you should
+ * set up your DMA here and make sure that you are ready to
+ * receive the data from the master SPI.
+ */
+
+ struct sspi_struct *sspi = (struct sspi_struct *)dev->priv;
+ struct spi_message m;
+ struct spi_transfer t;
+ int err;
+
+ sspi->xfer = xfer;
+
+ if (!tegra_caif_spi_slave_device)
+ return -ENODEV;
+
+ err = spi_tegra_register_callback(tegra_caif_spi_slave_device,
+ sspi_callback, sspi);
+ if (err < 0) {
+ pr_err("\nspi_tegra_register_callback() failed\n");
+ return -ENODEV;
+ }
+ memset(&t, 0, sizeof(t));
+ t.tx_buf = xfer->va_tx;
+ swap_byte(xfer->va_tx, xfer->tx_dma_len);
+ t.rx_buf = xfer->va_rx;
+ t.len = max(xfer->tx_dma_len, xfer->rx_dma_len);
+ t.len = SPI_CAIF_PAD_TRANSACTION_SIZE(t.len);
+ t.bits_per_word = 16;
+ /* SPI controller clock should be 4 times the spi_clk */
+ t.speed_hz = (SPI_MASTER_CLK_MHZ * 4 * 1000000);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ dmb();
+ err = spi_sync(tegra_caif_spi_slave_device, &m);
+ dmb();
+ swap_byte(xfer->va_tx, xfer->tx_dma_len);
+ swap_byte(xfer->va_rx, xfer->rx_dma_len);
+ sspi_complete(&sspi->sdev);
+ if (err < 0) {
+ pr_err("spi_init_xfer - spi_sync() err %d\n", err);
+ return err;
+ }
+ return 0;
+}
+
+void sspi_sig_xfer(bool xfer, struct cfspi_dev *dev)
+{
+ /* If xfer is true then you should assert the SPI_INT to indicate to
+ * the master that you are ready to recieve the data from the master
+ * SPI. If xfer is false then you should de-assert SPI_INT to indicate
+ * that the transfer is done.
+ */
+ if (xfer)
+ gpio_set_value(tegra_caif_sspi_gpio_spi_int, 1);
+ else
+ gpio_set_value(tegra_caif_sspi_gpio_spi_int, 0);
+}
+
+static void sspi_release(struct device *dev)
+{
+ /*
+ * Here you should release your SPI device resources.
+ */
+}
+
+static int __init sspi_init(void)
+{
+ /* Here you should initialize your SPI device by providing the
+ * necessary functions, clock speed, name and private data. Once
+ * done, you can register your device with the
+ * platform_device_register() function. This function will return
+ * with the CAIF SPI interface initialized. This is probably also
+ * the place where you should set up your GPIOs, interrupts and SPI
+ * resources.
+ */
+
+ int res = 0;
+
+ /* Register Tegra SPI protocol driver. */
+ res = spi_register_driver(&tegra_caif_spi_slave_driver);
+ if (res < 0)
+ return res;
+
+ /* Initialize slave device. */
+ slave.sdev.init_xfer = sspi_init_xfer;
+ slave.sdev.sig_xfer = sspi_sig_xfer;
+ slave.sdev.clk_mhz = SPI_MASTER_CLK_MHZ;
+ slave.sdev.priv = &slave;
+ slave.sdev.name = "spi_sspi";
+ slave_device.dev.release = sspi_release;
+
+ /* Initialize platform device. */
+ slave_device.name = "cfspi_sspi";
+ slave_device.dev.platform_data = &slave.sdev;
+
+ /* Register platform device. */
+ res = platform_device_register(&slave_device);
+ if (res)
+ return -ENODEV;
+
+ return res;
+}
+
+static void __exit sspi_exit(void)
+{
+ /* Delete platfrom device. */
+ platform_device_del(&slave_device);
+
+ /* Free Tegra SPI protocol driver. */
+ spi_unregister_driver(&tegra_caif_spi_slave_driver);
+
+ /* Free Tegra GPIO interrupts. */
+ disable_irq(gpio_to_irq(tegra_caif_sspi_gpio_spi_ss));
+ free_irq(gpio_to_irq(tegra_caif_sspi_gpio_spi_ss), &slave_device);
+
+ /* Free Tegra GPIOs. */
+ gpio_free(tegra_caif_sspi_gpio_spi_ss);
+ gpio_free(tegra_caif_sspi_gpio_spi_int);
+}
+
+static int __devinit tegra_caif_spi_slave_probe(struct spi_device *spi)
+{
+ struct tegra_caif_platform_data *pdata;
+ int res;
+
+ if (!spi)
+ return -ENODEV;
+
+ pdata = spi->dev.platform_data;
+ if (!pdata)
+ return -ENODEV;
+
+ tegra_caif_sspi_gpio_spi_int = pdata->spi_int;
+ tegra_caif_sspi_gpio_spi_ss = pdata->spi_ss;
+ tegra_caif_sspi_gpio_reset = pdata->reset;
+ tegra_caif_sspi_gpio_power = pdata->power;
+ tegra_caif_sspi_gpio_awr = pdata->awr;
+ tegra_caif_sspi_gpio_cwr = pdata->cwr;
+
+ tegra_caif_spi_slave_device = spi;
+
+ /* Initialize Tegra GPIOs. */
+ res = gpio_request(tegra_caif_sspi_gpio_spi_int, "caif_sspi_spi_int");
+ if (res < 0)
+ goto err1;
+
+ res = gpio_request(tegra_caif_sspi_gpio_spi_ss, "caif_sspi_ss");
+ if (res < 0)
+ goto err2;
+
+ res = gpio_direction_output(tegra_caif_sspi_gpio_spi_int, 0);
+ if (res < 0)
+ goto err3;
+
+ res = gpio_direction_input(tegra_caif_sspi_gpio_spi_ss);
+ if (res < 0)
+ goto err4;
+
+ tegra_caif_modem_power(1);
+ msleep(2000);
+
+ /* Initialize Tegra GPIO interrupts. */
+ res = request_irq(gpio_to_irq(tegra_caif_sspi_gpio_spi_ss),
+ sspi_irq, IRQF_TRIGGER_FALLING, "caif_sspi_ss_irq",
+ &slave.sdev);
+ if (res < 0)
+ goto err5;
+
+ return 0;
+err5:
+ free_irq(gpio_to_irq(tegra_caif_sspi_gpio_spi_ss), &slave_device);
+err4:
+err3:
+ gpio_free(tegra_caif_sspi_gpio_spi_ss);
+err2:
+ gpio_free(tegra_caif_sspi_gpio_spi_int);
+err1:
+ return res;
+}
+
+module_init(sspi_init);
+module_exit(sspi_exit);
diff --git a/include/linux/tegra_caif.h b/include/linux/tegra_caif.h
new file mode 100644
index 000000000000..fed67499defc
--- /dev/null
+++ b/include/linux/tegra_caif.h
@@ -0,0 +1,34 @@
+/* include/linux/tegra_caif.h
+ *
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef _TEGRA_CAIF_H_
+#define _TEGRA_CAIF_H_
+
+/* The GPIO details needed by the rainbow caif */
+struct tegra_caif_platform_data {
+ int reset;
+ int power;
+ int awr;
+ int cwr;
+ int spi_int;
+ int spi_ss;
+};
+
+#endif /* _TEGRA_CAIF_H_ */
+