summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2009-12-09 16:00:52 -0800
committerGary King <gking@nvidia.com>2009-12-09 19:45:00 -0800
commit874c3d779eb69fbac28e9e4bfbed51610176ad34 (patch)
tree1b9c67687c4395a3a7569188d6aa483728ff7f82
parent6fad4f8b483a4ba5e5723ecfdf5744f55dca59f2 (diff)
tegra: add I2C adapter-class driver
Driver implementation uses NvRm, NvDdk and NvOdm APIs to implement standard I2C bus interface. Change-Id: I29fbd39bd7fae95d70f1a1b9b552e34cc045371c
-rw-r--r--drivers/i2c/busses/Kconfig8
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-tegra.c290
-rw-r--r--include/linux/tegra_devices.h10
4 files changed, 309 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7f95905bbb9d..0602f0b01689 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -180,6 +180,14 @@ config I2C_SIS630
This driver can also be built as a module. If so, the module
will be called i2c-sis630.
+config I2C_TEGRA
+ boolean "NVIDIA Tegra internal I2C controller"
+ depends on ARCH_TEGRA
+ help
+ If you say yes to this option, support will be included for the
+ I2C controller embedded in NVIDIA Tegra SOCs
+
+
config I2C_SIS96X
tristate "SiS 96x"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 0c2c4b26cdf1..104b63198dee 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o
obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o
+obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o
# External I2C/SMBus adapter drivers
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
new file mode 100644
index 000000000000..723b8e4a797d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -0,0 +1,290 @@
+/*
+ * drivers/i2c/busses/i2c-tegra.c
+ *
+ * I2C bus driver for internal I2C controllers in NVIDIA Tegra SoCs
+ *
+ * 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/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/tegra_devices.h>
+#include <asm/uaccess.h>
+
+#include <mach/nvrm_linux.h>
+#include <nvrm_module.h>
+#include <nvos.h>
+#include <nvodm_query_discovery.h>
+
+#define I2C_SMBUS_TRASNSPORT_GUID NV_ODM_GUID('I','2','c','S','m','B','u','s')
+
+struct tegra_i2c_dev
+{
+ struct device *dev;
+ struct i2c_adapter adapter;
+ NvRmI2cHandle I2cHandle;
+ int clock_in_khz;
+ NvOdmI2cPinMap pin_map;
+};
+
+static int tegra_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ struct tegra_i2c_dev *dev = i2c_get_adapdata(adap);
+ NvRmI2cTransactionInfo *tInfo=NULL;
+ NvU32 i;
+ NvU32 len=0;
+ int err = 0;
+ int nverror = NvSuccess;
+ NvU8 *tempBuffer,*buffer=NULL;
+
+ tInfo = kzalloc(sizeof(NvRmI2cTransactionInfo)*num,GFP_KERNEL);
+ if(!tInfo) {
+ printk(KERN_ERR "tegra_i2c_xfer: Could not allocate memory\n");
+ err = -ENOMEM;
+ goto end;
+ }
+
+ for(i = 0 ; i < num ; i++) {
+ len += msgs[i].len;
+ }
+
+ buffer = kzalloc(len,GFP_KERNEL);
+ if(!buffer) {
+ printk(KERN_ERR "tegra_i2c_xfer: Could not allocate memory\n");
+ err = -ENOMEM;
+ goto end;
+ }
+
+ /*
+ * NvRmI2cTransactions accepts all the messages at once. We can not
+ * send 1 message at a time. Hence clubbing all messages and
+ * representing them in a form that NvRmI2cTransactions understands
+ * */
+
+ tempBuffer = buffer;
+
+ for (i = 0 ; i < num ; i++) {
+ if (msgs[i].flags & I2C_M_NOSTART) {
+ tInfo[i].Flags |= NVRM_I2C_NOSTOP;
+ }
+ if (msgs[i].flags & I2C_M_IGNORE_NAK) {
+ tInfo[i].Flags |= NVRM_I2C_NOACK;
+ }
+ if (msgs[i].flags & I2C_M_RD) {
+ tInfo[i].Flags |= NVRM_I2C_READ;
+ }
+ else {
+ tInfo[i].Flags |= NVRM_I2C_WRITE;
+ memcpy(tempBuffer,msgs[i].buf,msgs[i].len);
+ }
+ tempBuffer += msgs[i].len;
+ tInfo[i].NumBytes = msgs[i].len;
+ tInfo[i].Address = msgs[i].addr << 1;
+ tInfo[i].Is10BitAddress = (NvBool)(msgs[i].flags & I2C_M_TEN);
+ }
+
+ nverror = NvRmI2cTransaction(dev->I2cHandle, dev->pin_map, 1000,
+ dev->clock_in_khz, buffer, len, tInfo, num);
+
+ if (nverror != NvSuccess) {
+ switch (nverror) {
+ case NvError_I2cDeviceNotFound:
+ printk(KERN_ERR "NvRmI2cTransaction failed: No I2C "
+ "device claimed the address 0x%x on "
+ "adapter %d\n", msgs[i].addr,adap->nr);
+ err = -ENXIO;
+ break;
+ case NvError_I2cReadFailed:
+ printk(KERN_ERR "NvRmI2c Read failed %x\n", err);
+ err = -EIO;
+ break;
+ case NvError_I2cWriteFailed:
+ printk(KERN_ERR "NvRmI2c Write failed %x\n", err);
+ err = -EIO;
+ break;
+ case NvError_Timeout:
+ printk(KERN_ERR "NvRmI2c Timeout %x\n", err);
+ err = -ETIMEDOUT;
+ break;
+ default:
+ printk(KERN_ERR "NvRmI2c Transaction Error %x\n",err);
+ err = -EIO;
+ break;
+ }
+ goto end;
+ }
+
+ tempBuffer = buffer;
+
+ for (i = 0 ; i < num ; i++) {
+ if (tInfo[i].Flags & NVRM_I2C_READ) {
+ memcpy(msgs[i].buf,tempBuffer,tInfo[i].NumBytes);
+ }
+ tempBuffer += tInfo[i].NumBytes;
+ }
+
+end:
+ if(tInfo != NULL)
+ kfree(tInfo);
+
+ if(buffer != NULL)
+ kfree(buffer);
+
+ if (!err)
+ return num;
+ else
+ return err;
+}
+
+static u32 tegra_i2c_func(struct i2c_adapter *adap)
+{
+ /* FIXME: For now keep it simple and don't support protocol mangling
+ features */
+ return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm tegra_i2c_algo = {
+ .master_xfer = tegra_i2c_xfer,
+ .functionality = tegra_i2c_func,
+};
+
+static int tegra_i2c_probe(struct platform_device *pdev)
+{
+ struct tegra_i2c_dev *dev;
+ struct tegra_i2c_platform_data *pdata = pdev->dev.platform_data;
+ int ret;
+ NvError err;
+ const NvOdmPeripheralConnectivity *pConn = NULL;
+ const NvOdmIoAddress *addr_list;
+ int i = 0;
+
+ printk(KERN_INFO "tegra_i2c_probe\n");
+
+ if (pdata == NULL)
+ return -ENODEV;
+
+ // Check if the I2C instance is dedicated for NvEc communication.
+ // If yes, then don't allow it to register as platform driver.
+ pConn = NvOdmPeripheralGetGuid(I2C_SMBUS_TRASNSPORT_GUID);
+ if (pConn) {
+ const NvOdmIoAddress *addr_list = pConn->AddressList;
+ for (i = 0; i < pConn->NumAddress; i++, addr_list++) {
+ if (addr_list->Interface == NvOdmIoModule_I2c) &&
+ addr_list->Instance == pData->Instance) {
+ return -ENODEV;
+ }
+ }
+ }
+
+ dev = kzalloc(sizeof(struct tegra_i2c_dev), GFP_KERNEL);
+ if (!dev)
+ {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ err = NvRmI2cOpen(s_hRmGlobal, pdata->IoModuleID,
+ pdata->Instance, &dev->I2cHandle);
+ if (err)
+ {
+ ret = -ENODEV;
+ printk("Failed to open NvRmI2cOpen - returned %d\n", err);
+ goto err_rmapi_failed;
+ }
+ dev->clock_in_khz = pdata->ClockInKHz;
+ dev->pin_map = pdata->PinMuxConfig;
+
+ dev->dev = &pdev->dev;
+ platform_set_drvdata(pdev, dev);
+
+ i2c_set_adapdata(&dev->adapter, dev);
+ dev->adapter.algo = &tegra_i2c_algo;
+ strncpy(dev->adapter.name, "Tegra I2C adapter",
+ sizeof(dev->adapter.name));
+
+ dev->adapter.nr = pdev->id;
+ ret = i2c_add_numbered_adapter(&dev->adapter);
+ if (ret)
+ {
+ dev_err(&pdev->dev, "i2c_add_adapter failed\n");
+ goto err_i2c_add_adapter_failed;
+ }
+
+ return 0;
+
+err_rmapi_failed:
+err_i2c_add_adapter_failed:
+ kfree(dev);
+ return ret;
+}
+
+static int
+tegra_i2c_remove(struct platform_device *pdev)
+{
+ struct tegra_i2c_dev *dev = platform_get_drvdata(pdev);
+
+ NvRmI2cClose(dev->I2cHandle);
+ platform_set_drvdata(pdev, NULL);
+ i2c_del_adapter(&dev->adapter);
+ kfree(dev);
+ return 0;
+}
+
+static int tegra_i2c_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ /* FIXME to be implemented */
+ return 0;
+}
+
+static int tegra_i2c_resume(struct platform_device *pdev)
+{
+ /* FIXME to be implemented */
+ return 0;
+}
+
+static struct platform_driver tegra_i2c_driver = {
+ .probe = tegra_i2c_probe,
+ .remove = tegra_i2c_remove,
+ .suspend = tegra_i2c_suspend,
+ .resume = tegra_i2c_resume,
+ .driver =
+ {
+ .name = "tegra_i2c",
+ .owner = THIS_MODULE,
+ },
+};
+
+/* I2C may be needed to bring up other drivers */
+static int __init
+tegra_i2c_init_driver(void)
+{
+ return platform_driver_register(&tegra_i2c_driver);
+}
+module_init(tegra_i2c_init_driver);
+
+static void __exit tegra_i2c_exit_driver(void)
+{
+ platform_driver_unregister(&tegra_i2c_driver);
+}
+module_exit(tegra_i2c_exit_driver);
diff --git a/include/linux/tegra_devices.h b/include/linux/tegra_devices.h
index d3fd49fa0684..a890e0276a79 100644
--- a/include/linux/tegra_devices.h
+++ b/include/linux/tegra_devices.h
@@ -27,7 +27,9 @@
#include "nvrm_gpio.h"
#include "nvddk_usbphy.h"
#include "nvodm_query.h"
+#include "nvodm_query_pinmux.h"
+/* Platform data for EHCI HCD driver */
struct tegra_hcd_platform_data {
NvU32 instance;
NvRmGpioPinHandle hGpioIDpin;
@@ -42,4 +44,12 @@ struct tegra_hcd_platform_data {
NvDdkUsbPhyHandle hUsbPhy;
};
+/* Platfrom data for I2C bus driver */
+struct tegra_i2c_platform_data {
+ NvU32 IoModuleID;
+ NvU32 Instance;
+ NvU32 ClockInKHz;
+ NvOdmI2cPinMap PinMuxConfig;
+};
+
#endif