547 lines
12 KiB
C
547 lines
12 KiB
C
|
/*
|
||
|
* drivers/i2c/busses/i2c-ivc-single.c
|
||
|
*
|
||
|
* Copyright (C) 2017 NVIDIA Corporation. All rights reserved.
|
||
|
*
|
||
|
* 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 "i2c-ivc-single.h"
|
||
|
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/mutex.h>
|
||
|
|
||
|
#include <soc/tegra/tegra-ivc-rpc.h>
|
||
|
|
||
|
#include "soc/tegra/camrtc-i2c-common.h"
|
||
|
#include "i2c-rtcpu-common.h"
|
||
|
|
||
|
/*
|
||
|
* I2C IVC Single driver internal data structure
|
||
|
*/
|
||
|
|
||
|
#define MAX_DEVS 4
|
||
|
|
||
|
#define I2C_CAMRTC_RPC_IVC_SINGLE_TIMEOUT_MS 250
|
||
|
|
||
|
struct tegra_i2c_ivc_single_dev {
|
||
|
/* IVC RPC */
|
||
|
struct tegra_ivc_channel *chan;
|
||
|
struct mutex inuse;
|
||
|
|
||
|
bool is_added;
|
||
|
bool is_online;
|
||
|
|
||
|
/* I2C device behind this IVC */
|
||
|
u32 bus_id;
|
||
|
u32 reg_base;
|
||
|
u32 bus_clk_rate;
|
||
|
|
||
|
/* statistics */
|
||
|
struct {
|
||
|
unsigned int xfer_requests;
|
||
|
unsigned int total_bytes;
|
||
|
unsigned int reads, read_bytes;
|
||
|
unsigned int writes, write_bytes;
|
||
|
unsigned int errors;
|
||
|
} stat;
|
||
|
|
||
|
/* RPC call for I2C_REQUEST_SINGLE */
|
||
|
struct tegra_ivc_rpc_call_param rpc_i2c_req;
|
||
|
u8 rpc_i2c_req_buf[CAMRTC_I2C_REQUEST_MAX_LEN];
|
||
|
struct camrtc_rpc_i2c_response rpc_i2c_rsp;
|
||
|
};
|
||
|
|
||
|
static DEFINE_MUTEX(g_i2c_ivc_lock);
|
||
|
static struct tegra_i2c_ivc_single_dev *g_i2c_ivc_devs[MAX_DEVS];
|
||
|
|
||
|
u32 tegra_i2c_get_clk_freq(struct device_node *np)
|
||
|
{
|
||
|
int ret;
|
||
|
u32 bus_clk_rate;
|
||
|
|
||
|
ret = of_property_read_u32(np, "clock-frequency",
|
||
|
&bus_clk_rate);
|
||
|
if (ret)
|
||
|
bus_clk_rate = 100000; /* default clock rate */
|
||
|
|
||
|
return bus_clk_rate;
|
||
|
}
|
||
|
EXPORT_SYMBOL(tegra_i2c_get_clk_freq);
|
||
|
|
||
|
u32 tegra_i2c_get_reg_base(struct device_node *np)
|
||
|
{
|
||
|
int ret;
|
||
|
u32 reg_base;
|
||
|
|
||
|
ret = of_property_read_u32_index(np, "reg", 1, ®_base);
|
||
|
|
||
|
BUG_ON(ret < 0);
|
||
|
|
||
|
return reg_base;
|
||
|
}
|
||
|
EXPORT_SYMBOL(tegra_i2c_get_reg_base);
|
||
|
|
||
|
/*
|
||
|
* I2C interface
|
||
|
*/
|
||
|
static struct tegra_i2c_ivc_single_dev *tegra_i2c_ivc_get_dev(u32 reg_base)
|
||
|
{
|
||
|
size_t i;
|
||
|
struct tegra_i2c_ivc_single_dev *sdev = NULL;
|
||
|
|
||
|
mutex_lock(&g_i2c_ivc_lock);
|
||
|
|
||
|
/* Find an IVC channel corresponding to base */
|
||
|
for (i = 0; i < ARRAY_SIZE(g_i2c_ivc_devs); ++i) {
|
||
|
if (!g_i2c_ivc_devs[i])
|
||
|
continue;
|
||
|
|
||
|
if (g_i2c_ivc_devs[i]->reg_base == reg_base) {
|
||
|
sdev = g_i2c_ivc_devs[i];
|
||
|
mutex_lock(&sdev->inuse);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&g_i2c_ivc_lock);
|
||
|
|
||
|
return sdev;
|
||
|
}
|
||
|
|
||
|
static void tegra_i2c_ivc_put_dev(struct tegra_i2c_ivc_single_dev *sdev)
|
||
|
{
|
||
|
if (sdev)
|
||
|
mutex_unlock(&sdev->inuse);
|
||
|
}
|
||
|
|
||
|
static int tegra_i2c_ivc_register(struct tegra_i2c_ivc_single_dev *sdev)
|
||
|
{
|
||
|
size_t i;
|
||
|
int ret = -EBUSY;
|
||
|
|
||
|
mutex_lock(&g_i2c_ivc_lock);
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(g_i2c_ivc_devs); ++i) {
|
||
|
if (!g_i2c_ivc_devs[i]) {
|
||
|
g_i2c_ivc_devs[i] = sdev;
|
||
|
ret = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&g_i2c_ivc_lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void tegra_i2c_ivc_unregister(struct tegra_i2c_ivc_single_dev *sdev)
|
||
|
{
|
||
|
size_t i;
|
||
|
|
||
|
mutex_lock(&g_i2c_ivc_lock);
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(g_i2c_ivc_devs); ++i) {
|
||
|
if (g_i2c_ivc_devs[i] == sdev) {
|
||
|
g_i2c_ivc_devs[i] = NULL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&g_i2c_ivc_lock);
|
||
|
}
|
||
|
|
||
|
static int tegra_i2c_ivc_add_single(struct tegra_ivc_channel *chan);
|
||
|
|
||
|
static int tegra_i2c_ivc_do_single_xfer(
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev,
|
||
|
const struct i2c_msg *reqs, int num)
|
||
|
{
|
||
|
u8 *pbuf = NULL, *pprev_len = NULL;
|
||
|
const struct i2c_msg *preq, *preq_end;
|
||
|
int ret = 0, len = 0;
|
||
|
u8 *read_ptr = NULL;
|
||
|
int read_len = 0;
|
||
|
|
||
|
if (num == 0)
|
||
|
return 0;
|
||
|
|
||
|
++i2c_ivc_dev->stat.xfer_requests;
|
||
|
|
||
|
/* First byte is bus ID */
|
||
|
|
||
|
pbuf = i2c_ivc_dev->rpc_i2c_req_buf + 1;
|
||
|
len = 1;
|
||
|
|
||
|
preq = reqs;
|
||
|
preq_end = reqs + num;
|
||
|
|
||
|
for (;;) {
|
||
|
bool is_read = (preq->flags & I2C_M_RD);
|
||
|
u32 bytes = 0;
|
||
|
|
||
|
if ((preq == preq_end) || (is_read && read_ptr != NULL) ||
|
||
|
((len + 4 + preq->len) > CAMRTC_I2C_REQUEST_MAX_LEN)) {
|
||
|
struct camrtc_rpc_i2c_response *rpc_rsp;
|
||
|
|
||
|
rpc_rsp = &i2c_ivc_dev->rpc_i2c_rsp;
|
||
|
|
||
|
i2c_ivc_dev->rpc_i2c_req.request_len = len;
|
||
|
i2c_ivc_dev->rpc_i2c_req.callback = NULL;
|
||
|
i2c_ivc_dev->rpc_i2c_req.callback_param =
|
||
|
&i2c_ivc_dev->chan->dev;
|
||
|
ret = tegra_ivc_rpc_call(i2c_ivc_dev->chan,
|
||
|
&i2c_ivc_dev->rpc_i2c_req);
|
||
|
|
||
|
if (ret < 0) {
|
||
|
++i2c_ivc_dev->stat.errors;
|
||
|
dev_err(&i2c_ivc_dev->chan->dev,
|
||
|
"I2C transaction to 0x%x failed: %d\n",
|
||
|
reqs[0].addr, ret);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
if (rpc_rsp->result != 0) {
|
||
|
dev_err(&i2c_ivc_dev->chan->dev,
|
||
|
"I2C transaction response at addr 0x%x failed: %d\n",
|
||
|
reqs[0].addr, rpc_rsp->result);
|
||
|
return -EIO;
|
||
|
}
|
||
|
if (read_ptr) {
|
||
|
memcpy(read_ptr, rpc_rsp->read_data, read_len);
|
||
|
read_ptr = NULL;
|
||
|
}
|
||
|
|
||
|
if (preq == preq_end)
|
||
|
return num;
|
||
|
|
||
|
pbuf = i2c_ivc_dev->rpc_i2c_req_buf + 1;
|
||
|
len = 1;
|
||
|
}
|
||
|
|
||
|
if (!is_read) {
|
||
|
pbuf[0] = 0;
|
||
|
++i2c_ivc_dev->stat.writes;
|
||
|
i2c_ivc_dev->stat.write_bytes += preq->len;
|
||
|
} else {
|
||
|
read_ptr = preq->buf;
|
||
|
read_len = preq->len;
|
||
|
pbuf[0] = CAMRTC_I2C_REQUEST_FLAG_READ;
|
||
|
++i2c_ivc_dev->stat.reads;
|
||
|
i2c_ivc_dev->stat.read_bytes += preq->len;
|
||
|
}
|
||
|
|
||
|
if ((preq->flags & I2C_M_NOSTART) == 0) {
|
||
|
pbuf[1] = preq->addr & 0xff;
|
||
|
if (WARN_ON(preq->flags & I2C_M_TEN)) {
|
||
|
/* This is not yet implemented */
|
||
|
pbuf[0] |= CAMRTC_I2C_REQUEST_FLAG_TEN;
|
||
|
pbuf[2] = (preq->addr >> 8) & 0xff;
|
||
|
bytes = 2;
|
||
|
} else {
|
||
|
pbuf[2] = 0;
|
||
|
bytes = 1;
|
||
|
}
|
||
|
} else {
|
||
|
pbuf[0] |= CAMRTC_I2C_REQUEST_FLAG_NOSTART;
|
||
|
/* slave address is don't care */
|
||
|
pbuf[1] = 0;
|
||
|
pbuf[2] = 0;
|
||
|
}
|
||
|
|
||
|
i2c_ivc_dev->stat.total_bytes += bytes + preq->len;
|
||
|
|
||
|
pbuf[3] = preq->len;
|
||
|
pprev_len = pbuf + 3;
|
||
|
|
||
|
pbuf += 4;
|
||
|
len += 4;
|
||
|
|
||
|
if (is_read) {
|
||
|
preq++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
memcpy(pbuf, preq->buf, preq->len);
|
||
|
pbuf += preq->len;
|
||
|
len += preq->len;
|
||
|
|
||
|
++preq;
|
||
|
|
||
|
/* Merge write requests with NOSTART */
|
||
|
while (preq != preq_end && (preq->flags & I2C_M_NOSTART)) {
|
||
|
u8 *psrc = preq->buf;
|
||
|
|
||
|
if ((len + preq->len) > CAMRTC_I2C_REQUEST_MAX_LEN)
|
||
|
break;
|
||
|
|
||
|
memcpy(pbuf, psrc, preq->len);
|
||
|
|
||
|
pbuf += preq->len;
|
||
|
*pprev_len += preq->len;
|
||
|
len += preq->len;
|
||
|
|
||
|
i2c_ivc_dev->stat.write_bytes += preq->len;
|
||
|
i2c_ivc_dev->stat.total_bytes += preq->len;
|
||
|
|
||
|
++preq;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int tegra_i2c_ivc_single_xfer(u32 i2c_base,
|
||
|
const struct i2c_msg *reqs, int num)
|
||
|
{
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev;
|
||
|
int ret = 0;
|
||
|
|
||
|
i2c_ivc_dev = tegra_i2c_ivc_get_dev(i2c_base);
|
||
|
if (i2c_ivc_dev == NULL)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (num <= 0)
|
||
|
goto unlock;
|
||
|
|
||
|
ret = tegra_ivc_channel_runtime_get(i2c_ivc_dev->chan);
|
||
|
if (ret < 0)
|
||
|
goto unlock;
|
||
|
|
||
|
if (tegra_ivc_rpc_channel_is_suspended(i2c_ivc_dev->chan)) {
|
||
|
ret = -EBUSY;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
if (!i2c_ivc_dev->is_online) {
|
||
|
ret = -ECONNRESET;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
if (!i2c_ivc_dev->is_added) {
|
||
|
ret = tegra_i2c_ivc_add_single(i2c_ivc_dev->chan);
|
||
|
if (ret != 0) {
|
||
|
dev_err(&i2c_ivc_dev->chan->dev,
|
||
|
"I2C device not ready\n");
|
||
|
ret = -EIO;
|
||
|
goto error;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = tegra_i2c_ivc_do_single_xfer(i2c_ivc_dev, reqs, num);
|
||
|
|
||
|
error:
|
||
|
tegra_ivc_channel_runtime_put(i2c_ivc_dev->chan);
|
||
|
unlock:
|
||
|
tegra_i2c_ivc_put_dev(i2c_ivc_dev);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(tegra_i2c_ivc_single_xfer);
|
||
|
|
||
|
/*
|
||
|
* IVC channel Debugfs
|
||
|
*/
|
||
|
|
||
|
#define DEFINE_SEQ_FOPS(_fops_, _show_) \
|
||
|
static int _fops_ ## _open(struct inode *inode, struct file *file) \
|
||
|
{ \
|
||
|
return single_open(file, _show_, inode->i_private); \
|
||
|
} \
|
||
|
static const struct file_operations _fops_ = { \
|
||
|
.open = _fops_ ## _open, \
|
||
|
.read = seq_read, \
|
||
|
.llseek = seq_lseek, \
|
||
|
.release = single_release }
|
||
|
|
||
|
static int tegra_i2c_ivc_single_stat_show(
|
||
|
struct seq_file *file, void *data)
|
||
|
{
|
||
|
struct tegra_ivc_channel *chan = file->private;
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev =
|
||
|
tegra_ivc_channel_get_drvdata(chan);
|
||
|
|
||
|
seq_printf(file, "Xfer requests: %u\n",
|
||
|
i2c_ivc_dev->stat.xfer_requests);
|
||
|
seq_printf(file, "Total bytes: %u\n", i2c_ivc_dev->stat.total_bytes);
|
||
|
seq_printf(file, "Read requests: %u\n", i2c_ivc_dev->stat.reads);
|
||
|
seq_printf(file, "Read bytes: %u\n", i2c_ivc_dev->stat.read_bytes);
|
||
|
seq_printf(file, "Write requests: %u\n", i2c_ivc_dev->stat.writes);
|
||
|
seq_printf(file, "Write bytes: %u\n", i2c_ivc_dev->stat.write_bytes);
|
||
|
seq_printf(file, "Errors: %u\n", i2c_ivc_dev->stat.errors);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DEFINE_SEQ_FOPS(tegra_i2c_ivc_debugfs_stats,
|
||
|
tegra_i2c_ivc_single_stat_show);
|
||
|
|
||
|
static void tegra_i2c_ivc_single_create_debugfs(
|
||
|
struct tegra_ivc_channel *chan,
|
||
|
struct dentry *debugfs_root)
|
||
|
{
|
||
|
debugfs_create_file("stats", S_IRUGO,
|
||
|
debugfs_root, chan,
|
||
|
&tegra_i2c_ivc_debugfs_stats);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* IVC channel driver interface
|
||
|
*/
|
||
|
|
||
|
static int tegra_i2c_ivc_add_single(struct tegra_ivc_channel *chan)
|
||
|
{
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev =
|
||
|
tegra_ivc_channel_get_drvdata(chan);
|
||
|
struct camrtc_rpc_i2c_add_single rpc_add_single;
|
||
|
u32 bus_id;
|
||
|
int ret;
|
||
|
|
||
|
if (i2c_ivc_dev->reg_base == 0) {
|
||
|
dev_err(&chan->dev,
|
||
|
"Invalid I2C device at 0x%08x\n",
|
||
|
i2c_ivc_dev->reg_base);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
rpc_add_single.reg_base = i2c_ivc_dev->reg_base;
|
||
|
rpc_add_single.bus_clk_rate = i2c_ivc_dev->bus_clk_rate;
|
||
|
|
||
|
/* Register an I2C device to CamRTC */
|
||
|
ret = tegra_ivc_rpc_call_pl(chan,
|
||
|
CAMRTC_RPC_REQ_I2C_ADD_SINGLE_DEV,
|
||
|
sizeof(rpc_add_single), &rpc_add_single,
|
||
|
TEGRA_IVC_RPC_RSP_RET_CODE,
|
||
|
sizeof(bus_id), &bus_id,
|
||
|
NULL, NULL, 0);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&chan->dev,
|
||
|
"Failed to register an I2C device at 0x%08x: %d\n",
|
||
|
i2c_ivc_dev->reg_base, ret);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
dev_info(&chan->dev,
|
||
|
"Registered an I2C single device at 0x%08x to bus %d\n",
|
||
|
i2c_ivc_dev->reg_base, bus_id);
|
||
|
|
||
|
i2c_ivc_dev->bus_id = bus_id;
|
||
|
i2c_ivc_dev->rpc_i2c_req_buf[0] = bus_id;
|
||
|
i2c_ivc_dev->rpc_i2c_req.request_id =
|
||
|
CAMRTC_RPC_REQ_I2C_REQUEST_SINGLE;
|
||
|
i2c_ivc_dev->rpc_i2c_req.request = i2c_ivc_dev->rpc_i2c_req_buf;
|
||
|
i2c_ivc_dev->rpc_i2c_req.response_id = CAMRTC_RPC_RSP_I2C_RESPONSE;
|
||
|
i2c_ivc_dev->rpc_i2c_req.response_len =
|
||
|
sizeof(i2c_ivc_dev->rpc_i2c_rsp);
|
||
|
i2c_ivc_dev->rpc_i2c_req.response = &i2c_ivc_dev->rpc_i2c_rsp;
|
||
|
i2c_ivc_dev->rpc_i2c_req.callback = NULL;
|
||
|
i2c_ivc_dev->rpc_i2c_req.callback_param = &i2c_ivc_dev->chan->dev;
|
||
|
i2c_ivc_dev->rpc_i2c_req.timeout_ms =
|
||
|
I2C_CAMRTC_RPC_IVC_SINGLE_TIMEOUT_MS;
|
||
|
i2c_ivc_dev->is_added = true;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void tegra_i2c_ivc_single_ready(
|
||
|
struct tegra_ivc_channel *chan, bool online)
|
||
|
{
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev =
|
||
|
tegra_ivc_channel_get_drvdata(chan);
|
||
|
|
||
|
i2c_ivc_dev->is_online = online;
|
||
|
|
||
|
if (!online)
|
||
|
i2c_ivc_dev->is_added = false;
|
||
|
}
|
||
|
|
||
|
static struct tegra_ivc_rpc_ops tegra_ivc_rpc_user_ops = {
|
||
|
.create_debugfs = tegra_i2c_ivc_single_create_debugfs,
|
||
|
.ready = tegra_i2c_ivc_single_ready,
|
||
|
};
|
||
|
|
||
|
/* Platform device */
|
||
|
static int tegra_ivc_rpc_i2c_single_probe(struct tegra_ivc_channel *chan)
|
||
|
{
|
||
|
int ret;
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev;
|
||
|
struct device_node *i2c_node;
|
||
|
u32 reg_base;
|
||
|
|
||
|
/* Register IVC/RPC channel */
|
||
|
ret = tegra_ivc_rpc_channel_probe(chan, &tegra_ivc_rpc_user_ops);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&chan->dev, "Cannot start IVC/RPC interface");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
i2c_node = of_parse_phandle(chan->dev.of_node, "device", 0);
|
||
|
if (i2c_node == NULL) {
|
||
|
dev_err(&chan->dev, "Cannot get i2c device node");
|
||
|
ret = -ENODEV;
|
||
|
goto remove;
|
||
|
}
|
||
|
|
||
|
reg_base = tegra_i2c_get_reg_base(i2c_node);
|
||
|
if (reg_base == 0) {
|
||
|
ret = -ENODEV;
|
||
|
goto put;
|
||
|
}
|
||
|
|
||
|
i2c_ivc_dev = devm_kzalloc(&chan->dev, sizeof(*i2c_ivc_dev),
|
||
|
GFP_KERNEL);
|
||
|
if (i2c_ivc_dev == NULL) {
|
||
|
ret = -ENOMEM;
|
||
|
goto put;
|
||
|
}
|
||
|
|
||
|
mutex_init(&i2c_ivc_dev->inuse);
|
||
|
|
||
|
i2c_ivc_dev->is_added = false;
|
||
|
i2c_ivc_dev->is_online = false;
|
||
|
i2c_ivc_dev->reg_base = reg_base;
|
||
|
i2c_ivc_dev->bus_clk_rate = tegra_i2c_get_clk_freq(i2c_node);
|
||
|
i2c_ivc_dev->chan = chan;
|
||
|
|
||
|
tegra_ivc_channel_set_drvdata(chan, i2c_ivc_dev);
|
||
|
|
||
|
ret = tegra_i2c_ivc_register(i2c_ivc_dev);
|
||
|
if (ret < 0)
|
||
|
goto put;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
put:
|
||
|
of_node_put(i2c_node);
|
||
|
remove:
|
||
|
tegra_ivc_rpc_channel_remove(chan);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void tegra_ivc_rpc_i2c_single_remove(struct tegra_ivc_channel *chan)
|
||
|
{
|
||
|
struct tegra_i2c_ivc_single_dev *i2c_ivc_dev;
|
||
|
|
||
|
i2c_ivc_dev = tegra_ivc_channel_get_drvdata(chan);
|
||
|
tegra_i2c_ivc_unregister(i2c_ivc_dev);
|
||
|
tegra_ivc_rpc_channel_remove(chan);
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id tegra_ivc_rpc_i2c_single_of_match[] = {
|
||
|
{ .compatible = "nvidia,tegra186-camera-ivc-rpc-i2c-single", },
|
||
|
{ },
|
||
|
};
|
||
|
TEGRA_IVC_RPC_DRIVER_DEFINE(i2c_single, "tegra-ivc-rpc-i2c-single")
|
||
|
|
||
|
MODULE_AUTHOR("Ashish Singh <assingh@nvidia.com>");
|
||
|
MODULE_DESCRIPTION("NVIDIA Tegra CAMRTC I2C IVC driver");
|
||
|
MODULE_LICENSE("GPL");
|