diff options
author | Gary King <gking@nvidia.com> | 2009-12-09 16:00:52 -0800 |
---|---|---|
committer | Gary King <gking@nvidia.com> | 2009-12-09 19:45:00 -0800 |
commit | 874c3d779eb69fbac28e9e4bfbed51610176ad34 (patch) | |
tree | 1b9c67687c4395a3a7569188d6aa483728ff7f82 | |
parent | 6fad4f8b483a4ba5e5723ecfdf5744f55dca59f2 (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/Kconfig | 8 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 290 | ||||
-rw-r--r-- | include/linux/tegra_devices.h | 10 |
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 |