407 lines
10 KiB
C
407 lines
10 KiB
C
/*
|
|
* drivers/i2c/busses/i2c-tegra-hv.c
|
|
*
|
|
* Copyright (C) 2015-2017 NVIDIA Corporation. All rights reserved.
|
|
* Author: Arnab Basu <abasu@nvidia.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-tegra-hv.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/device.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/ioport.h>
|
|
|
|
#include "i2c-tegra-hv-common.h"
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000))
|
|
#define TEGRA_I2C_RETRIES 3
|
|
|
|
#define I2C_NO_ERROR 0
|
|
|
|
/**
|
|
* struct tegra_hv_i2c_dev - per device i2c context
|
|
* @dev: device reference
|
|
* @adapter: core i2c layer adapter information
|
|
* @base: i2c controller base
|
|
* @comm_chan: context for the logical channel over which this
|
|
* device communicates with the i2c server
|
|
* @msg_complete: transfer completion notifier
|
|
* @max_payload_size: maximum packet size supported
|
|
* by this i2c device
|
|
* @completion_timeout: time to wait for reply from server
|
|
* @bus_clk_rate: current i2c bus clock rate (this is currently a dummy value)
|
|
*/
|
|
struct tegra_hv_i2c_dev {
|
|
struct device *dev;
|
|
struct i2c_adapter adapter;
|
|
phys_addr_t base;
|
|
void *comm_chan;
|
|
struct completion msg_complete;
|
|
u32 max_payload_size;
|
|
u32 completion_timeout;
|
|
u32 bus_clk_rate;
|
|
};
|
|
|
|
static void tegra_hv_i2c_isr(void *dev_id)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = dev_id;
|
|
complete(&i2c_dev->msg_complete);
|
|
}
|
|
|
|
static int tegra_hv_i2c_xfer_msg(struct tegra_hv_i2c_dev *i2c_dev,
|
|
struct i2c_msg *msg, int sno, bool more_msgs)
|
|
{
|
|
int ret;
|
|
int msg_err;
|
|
int msg_read;
|
|
int rv;
|
|
int j = 0;
|
|
uint32_t flags = 0;
|
|
|
|
if (msg->len == 0)
|
|
return -EINVAL;
|
|
|
|
if (msg->len > i2c_dev->max_payload_size)
|
|
return -E2BIG;
|
|
|
|
msg_err = I2C_NO_ERROR;
|
|
msg_read = (msg->flags & I2C_M_RD);
|
|
reinit_completion(&i2c_dev->msg_complete);
|
|
|
|
if (more_msgs)
|
|
flags |= HV_I2C_FLAGS_REPEAT_START;
|
|
|
|
if (msg->flags & I2C_M_TEN)
|
|
flags |= HV_I2C_FLAGS_10BIT_ADDR;
|
|
|
|
ret = hv_i2c_transfer(i2c_dev->comm_chan, i2c_dev->base, msg->addr,
|
|
msg_read, msg->buf, msg->len, &msg_err, sno, flags);
|
|
if (ret < 0) {
|
|
dev_err(i2c_dev->dev, "unable to send message (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
|
i2c_dev->completion_timeout);
|
|
|
|
if (ret == 0) {
|
|
dev_err(i2c_dev->dev,
|
|
"i2c transfer timed out, addr 0x%04x, data 0x%02x\n",
|
|
msg->addr, msg->buf[0]);
|
|
rv = -EBUSY;
|
|
goto error;
|
|
}
|
|
|
|
dev_dbg(i2c_dev->dev, "transfer complete: %d %d %d\n",
|
|
ret, completion_done(&i2c_dev->msg_complete), msg_err);
|
|
|
|
if (likely(msg_err == I2C_NO_ERROR))
|
|
return 0;
|
|
|
|
dev_dbg(i2c_dev->dev, "received error code %d\n", msg_err);
|
|
rv = -EIO;
|
|
error:
|
|
reinit_completion(&i2c_dev->msg_complete);
|
|
ret = hv_i2c_comm_chan_cleanup(i2c_dev->comm_chan, i2c_dev->base);
|
|
|
|
if (ret < 0) {
|
|
dev_err(i2c_dev->dev, "Failed to send cleanup message\n");
|
|
}
|
|
|
|
while ((ret = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
|
i2c_dev->completion_timeout * 2)) == 0) {
|
|
dev_err(i2c_dev->dev, "Cleanup failed after timeout (%d tries)\n",
|
|
j++);
|
|
if (j >= 5)
|
|
break;
|
|
/* Skipping INIT_COMPLETION on purpose, if completion gets
|
|
* signalled in the time between 2 calls to wait_for_completion
|
|
* we don't want to overwrite that
|
|
*/
|
|
}
|
|
tegra_hv_i2c_poll_cleanup(i2c_dev->comm_chan);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int tegra_hv_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
|
|
int num)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
|
|
int i;
|
|
int ret = 0;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
ret = tegra_hv_i2c_xfer_msg(i2c_dev, &msgs[i], i,
|
|
(i < (num - 1)));
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
return ret ? ret : i;
|
|
}
|
|
|
|
static u32 tegra_hv_i2c_func(struct i2c_adapter *adap)
|
|
{
|
|
u32 ret = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct i2c_algorithm tegra_hv_i2c_algo = {
|
|
.master_xfer = tegra_hv_i2c_xfer,
|
|
.functionality = tegra_hv_i2c_func,
|
|
};
|
|
|
|
static struct tegra_hv_i2c_platform_data *parse_i2c_tegra_dt(
|
|
struct platform_device *pdev)
|
|
{
|
|
struct tegra_hv_i2c_platform_data *pdata;
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return pdata;
|
|
}
|
|
|
|
static void tegra_i2c_hv_parse_dt(struct tegra_hv_i2c_dev *i2c_dev)
|
|
{
|
|
struct device_node *np = i2c_dev->dev->of_node;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(np, "clock-frequency",
|
|
&i2c_dev->bus_clk_rate);
|
|
if (ret)
|
|
i2c_dev->bus_clk_rate = 100000; /* default clock rate */
|
|
}
|
|
|
|
/* Match table for of_platform binding */
|
|
static const struct of_device_id tegra_hv_i2c_of_match[] = {
|
|
{ .compatible = "nvidia,tegra124-i2c-hv", .data = NULL},
|
|
{ .compatible = "nvidia,tegra210-i2c-hv", .data = NULL},
|
|
{ .compatible = "nvidia,tegra186-i2c-hv", .data = NULL},
|
|
{ .compatible = "nvidia,tegra194-i2c-hv", .data = NULL},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_hv_i2c_of_match);
|
|
|
|
static int tegra_hv_i2c_probe(struct platform_device *pdev)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev;
|
|
struct tegra_hv_i2c_platform_data *pdata = pdev->dev.platform_data;
|
|
int ret = 0;
|
|
const struct of_device_id *match;
|
|
int bus_num = -1;
|
|
void *chan;
|
|
int err;
|
|
struct resource *res;
|
|
|
|
if (pdev->dev.of_node) {
|
|
match = of_match_device(of_match_ptr(tegra_hv_i2c_of_match),
|
|
&pdev->dev);
|
|
if (!match) {
|
|
dev_err(&pdev->dev, "Device Not matching\n");
|
|
return -ENODEV;
|
|
}
|
|
if (!pdata)
|
|
pdata = parse_i2c_tegra_dt(pdev);
|
|
} else {
|
|
WARN(1, "Only device tree based init is supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "no mem resource\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
|
|
if (!i2c_dev) {
|
|
dev_err(&pdev->dev, "Could not allocate struct tegra_hv_i2c_dev");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chan = hv_i2c_comm_init(&pdev->dev, tegra_hv_i2c_isr, i2c_dev);
|
|
if (IS_ERR(chan))
|
|
return PTR_ERR(chan);
|
|
|
|
i2c_dev->dev = &pdev->dev;
|
|
i2c_dev->comm_chan = chan;
|
|
|
|
tegra_i2c_hv_parse_dt(i2c_dev);
|
|
|
|
platform_set_drvdata(pdev, i2c_dev);
|
|
|
|
i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
|
|
i2c_dev->adapter.owner = THIS_MODULE;
|
|
i2c_dev->adapter.class = I2C_CLASS_HWMON;
|
|
strlcpy(i2c_dev->adapter.name, "Tegra I2C HV adapter",
|
|
sizeof(i2c_dev->adapter.name));
|
|
i2c_dev->adapter.algo = &tegra_hv_i2c_algo;
|
|
i2c_dev->adapter.dev.parent = &pdev->dev;
|
|
i2c_dev->adapter.nr = bus_num;
|
|
i2c_dev->adapter.dev.of_node = pdev->dev.of_node;
|
|
i2c_dev->adapter.bus_clk_rate = i2c_dev->bus_clk_rate;
|
|
|
|
if (pdata->retries)
|
|
i2c_dev->adapter.retries = pdata->retries;
|
|
else
|
|
i2c_dev->adapter.retries = TEGRA_I2C_RETRIES;
|
|
|
|
if (pdata->timeout)
|
|
i2c_dev->adapter.timeout = pdata->timeout;
|
|
|
|
if (i2c_dev->adapter.timeout)
|
|
i2c_dev->completion_timeout = i2c_dev->adapter.timeout;
|
|
else
|
|
i2c_dev->completion_timeout = TEGRA_I2C_TIMEOUT;
|
|
|
|
i2c_dev->base = res->start;
|
|
init_completion(&i2c_dev->msg_complete);
|
|
|
|
/* Send a cleanup message in case this is a reboot and we had a
|
|
* transaction in progress
|
|
*/
|
|
ret = hv_i2c_comm_chan_cleanup(i2c_dev->comm_chan, i2c_dev->base);
|
|
if (ret < 0) {
|
|
dev_warn(&pdev->dev, "Cleanup after (re)boot failed\n");
|
|
} else {
|
|
ret = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
|
i2c_dev->completion_timeout);
|
|
if (ret == 0)
|
|
dev_warn(&pdev->dev, "Timed out sending cleanup after (re)boot\n");
|
|
}
|
|
|
|
reinit_completion(&i2c_dev->msg_complete);
|
|
|
|
ret = hv_i2c_get_max_payload(i2c_dev->comm_chan, i2c_dev->base,
|
|
&(i2c_dev->max_payload_size), &err);
|
|
if (ret < 0) {
|
|
dev_warn(&pdev->dev, "Could not get max payload, defaulting to 4096\n");
|
|
i2c_dev->max_payload_size = 4096;
|
|
} else {
|
|
ret = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
|
i2c_dev->completion_timeout);
|
|
if (ret == 0) {
|
|
dev_warn(&pdev->dev, "Timed out getting max payload, defaulting to 4096\n");
|
|
i2c_dev->max_payload_size = 4096;
|
|
} else if (err != I2C_NO_ERROR) {
|
|
dev_warn(&pdev->dev, "Error getting max payload, defaulting to 4096\n");
|
|
i2c_dev->max_payload_size = 4096;
|
|
}
|
|
}
|
|
|
|
ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to add I2C adapter\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_hv_i2c_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
|
|
|
|
hv_i2c_comm_chan_free(i2c_dev->comm_chan);
|
|
i2c_dev->comm_chan = NULL;
|
|
i2c_del_adapter(&i2c_dev->adapter);
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_hv_i2c_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
|
|
|
|
dev_info(i2c_dev->dev, "Bus is shutdown down..\n");
|
|
i2c_shutdown_adapter(&i2c_dev->adapter);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int tegra_hv_i2c_suspend(struct device *dev)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = dev_get_drvdata(dev);
|
|
|
|
i2c_shutdown_adapter(&i2c_dev->adapter);
|
|
hv_i2c_comm_suspend(i2c_dev->comm_chan);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_hv_i2c_resume(struct device *dev)
|
|
{
|
|
struct tegra_hv_i2c_dev *i2c_dev = dev_get_drvdata(dev);
|
|
|
|
hv_i2c_comm_resume(i2c_dev->comm_chan);
|
|
i2c_shutdown_clear_adapter(&i2c_dev->adapter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops tegra_hv_i2c_pm_ops = {
|
|
.suspend_noirq = tegra_hv_i2c_suspend,
|
|
.resume_noirq = tegra_hv_i2c_resume,
|
|
};
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static struct platform_device_id tegra_hv_i2c_devtype[] = {
|
|
{
|
|
.name = "tegra12-hv-i2c",
|
|
.driver_data = 0,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver tegra_hv_i2c_driver = {
|
|
.probe = tegra_hv_i2c_probe,
|
|
.remove = tegra_hv_i2c_remove,
|
|
.late_shutdown = tegra_hv_i2c_shutdown,
|
|
.id_table = tegra_hv_i2c_devtype,
|
|
.driver = {
|
|
.name = "tegra-hv-i2c",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(tegra_hv_i2c_of_match),
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &tegra_hv_i2c_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static int __init tegra_hv_i2c_init_driver(void)
|
|
{
|
|
return platform_driver_register(&tegra_hv_i2c_driver);
|
|
}
|
|
|
|
static void __exit tegra_hv_i2c_exit_driver(void)
|
|
{
|
|
platform_driver_unregister(&tegra_hv_i2c_driver);
|
|
}
|
|
|
|
subsys_initcall(tegra_hv_i2c_init_driver);
|
|
module_exit(tegra_hv_i2c_exit_driver);
|
|
|
|
MODULE_DESCRIPTION("nVidia Tegra Hypervisor I2C Bus Controller driver");
|
|
MODULE_AUTHOR("Arnab Basu");
|
|
MODULE_LICENSE("GPL v2");
|