tegrakernel/kernel/nvidia/drivers/video/tegra/host/nvcsi/nvcsi.c

441 lines
10 KiB
C

/*
* NVCSI driver for T186
*
* Copyright (c) 2014-2019, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/fs.h>
#include <asm/ioctls.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <uapi/linux/nvhost_nvcsi_ioctl.h>
#include <linux/uaccess.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <media/mc_common.h>
#include <media/csi.h>
#include <media/tegra_camera_platform.h>
#include "dev.h"
#include "bus_client.h"
#include "nvhost_acm.h"
#include "t186/t186.h"
#include "nvcsi.h"
#include "camera/csi/csi4_fops.h"
#include "deskew.h"
#define PG_CLK_RATE 102000000
/* width of interface between VI and CSI */
#define CSI_BUS_WIDTH 64
/* number of lanes per brick */
#define NUM_LANES 4
#define CSIA (1 << 20)
#define CSIF (1 << 25)
struct nvcsi {
struct platform_device *pdev;
struct regulator *regulator;
struct tegra_csi_device csi;
struct dentry *dir;
};
static long long input_stats;
static struct tegra_csi_device *mc_csi;
static struct tegra_csi_data t18_nvcsi_data = {
.info = (struct nvhost_device_data *)&t18_nvcsi_info,
.csi_fops = &csi4_fops,
};
static const struct of_device_id tegra_nvcsi_of_match[] = {
{
.compatible = "nvidia,tegra186-nvcsi",
.data = &t18_nvcsi_data
},
{ },
};
struct nvcsi_private {
struct platform_device *pdev;
struct nvcsi_deskew_context deskew_ctx;
};
static int nvcsi_deskew_debugfs_init(struct nvcsi *nvcsi);
static void nvcsi_deskew_debugfs_remove(struct nvcsi *nvcsi);
int nvcsi_finalize_poweron(struct platform_device *pdev)
{
struct nvcsi *nvcsi = nvhost_get_private_data(pdev);
int ret;
if (nvcsi->regulator) {
ret = regulator_enable(nvcsi->regulator);
if (ret) {
dev_err(&pdev->dev, "failed to enable csi regulator failed.");
return ret;
}
}
return tegra_csi_mipi_calibrate(&nvcsi->csi, true);
}
int nvcsi_prepare_poweroff(struct platform_device *pdev)
{
struct nvcsi *nvcsi = nvhost_get_private_data(pdev);
int ret;
ret = tegra_csi_mipi_calibrate(&nvcsi->csi, false);
if (ret)
dev_err(&pdev->dev, "disable mipi calibraiton failed\n");
if (nvcsi->regulator) {
ret = regulator_disable(nvcsi->regulator);
if (ret)
dev_err(&pdev->dev, "failed to disabled csi regulator failed.");
}
return 0;
}
static int nvcsi_probe_regulator(struct nvcsi *nvcsi)
{
struct device *dev = &nvcsi->pdev->dev;
struct regulator *regulator;
const char *regulator_name;
int err;
err = of_property_read_string(dev->of_node, "nvidia,csi_regulator",
&regulator_name);
if (err)
return err;
regulator = devm_regulator_get(dev, regulator_name);
if (IS_ERR(regulator))
return PTR_ERR(regulator);
nvcsi->regulator = regulator;
return 0;
}
int nvcsi_cil_sw_reset(int lanes, int enable)
{
unsigned int phy_num = 0U;
unsigned int val = enable ? (SW_RESET1_EN | SW_RESET0_EN) : 0U;
unsigned int addr, i;
for (i = CSIA; i < CSIF; i = i << 2U) {
if (lanes & i) {
addr = CSI4_BASE_ADDRESS + NVCSI_CIL_A_SW_RESET +
(CSI4_PHY_OFFSET * phy_num);
host1x_writel(mc_csi->pdev, addr, val);
}
if (lanes & (i << 1U)) {
addr = CSI4_BASE_ADDRESS + NVCSI_CIL_B_SW_RESET +
(CSI4_PHY_OFFSET * phy_num);
host1x_writel(mc_csi->pdev, addr, val);
}
phy_num++;
}
return 0;
}
EXPORT_SYMBOL_GPL(nvcsi_cil_sw_reset);
static int nvcsi_probe(struct platform_device *dev)
{
int err = 0;
struct nvhost_device_data *pdata = NULL;
struct nvcsi *nvcsi = NULL;
struct tegra_csi_data *data = NULL;
struct tegra_camera_dev_info csi_info;
memset(&csi_info, 0, sizeof(csi_info));
if (dev->dev.of_node) {
const struct of_device_id *match;
match = of_match_device(tegra_nvcsi_of_match, &dev->dev);
if (match) {
data = (struct tegra_csi_data *) match->data;
pdata = (struct nvhost_device_data *)data->info;
}
} else {
pdata = (struct nvhost_device_data *)dev->dev.platform_data;
}
WARN_ON(!pdata);
if (!pdata) {
dev_info(&dev->dev, "no platform data\n");
err = -ENODATA;
goto err_get_pdata;
}
nvcsi = devm_kzalloc(&dev->dev, sizeof(*nvcsi), GFP_KERNEL);
if (!nvcsi) {
err = -ENOMEM;
goto err_alloc_nvcsi;
}
nvcsi->pdev = dev;
pdata->pdev = dev;
mutex_init(&pdata->lock);
platform_set_drvdata(dev, pdata);
pdata->private_data = nvcsi;
mc_csi = &nvcsi->csi;
err = nvcsi_probe_regulator(nvcsi);
if (err)
dev_info(&dev->dev, "failed to get regulator (%d)\n", err);
err = nvhost_client_device_get_resources(dev);
if (err)
goto err_get_resources;
err = nvhost_module_init(dev);
if (err)
goto err_module_init;
err = nvhost_client_device_init(dev);
if (err)
goto err_client_device_init;
if (data)
nvcsi->csi.fops = data->csi_fops;
err = tegra_csi_media_controller_init(&nvcsi->csi, dev);
nvcsi_deskew_platform_setup(&nvcsi->csi, false);
if (err < 0)
goto err_mediacontroller_init;
nvcsi_deskew_debugfs_init(nvcsi);
csi_info.pdev = dev;
csi_info.hw_type = HWTYPE_CSI;
csi_info.use_max = true;
csi_info.bus_width = CSI_BUS_WIDTH;
csi_info.lane_num = NUM_LANES;
csi_info.pg_clk_rate = PG_CLK_RATE;
err = tegra_camera_device_register(&csi_info, nvcsi);
if (err)
goto err_module_init;
return 0;
err_mediacontroller_init:
err_client_device_init:
nvhost_module_deinit(dev);
err_module_init:
err_get_resources:
err_alloc_nvcsi:
err_get_pdata:
return err;
}
static int __exit nvcsi_remove(struct platform_device *dev)
{
struct nvhost_device_data *pdata = platform_get_drvdata(dev);
struct nvcsi *nvcsi = (struct nvcsi *)pdata->private_data;
tegra_camera_device_unregister(nvcsi);
mc_csi = NULL;
nvcsi_deskew_debugfs_remove(nvcsi);
tegra_csi_media_controller_remove(&nvcsi->csi);
return 0;
}
static struct platform_driver nvcsi_driver = {
.probe = nvcsi_probe,
.remove = __exit_p(nvcsi_remove),
.driver = {
.owner = THIS_MODULE,
.name = "nvcsi",
#ifdef CONFIG_OF
.of_match_table = tegra_nvcsi_of_match,
#endif
#ifdef CONFIG_PM
.pm = &nvhost_module_pm_ops,
#endif
},
};
#ifdef CONFIG_PM_GENERIC_DOMAINS
static struct of_device_id tegra_nvcsi_domain_match[] = {
{.compatible = "nvidia,tegra186-ve-pd",
.data = (struct nvhost_device_data *)&t18_nvcsi_info},
{},
};
#endif
static int dbgfs_deskew_stats(struct seq_file *s, void *data)
{
deskew_dbgfs_deskew_stats(s);
return 0;
}
static int dbgfs_open(struct inode *inode, struct file *file)
{
return single_open(file, dbgfs_deskew_stats, inode->i_private);
}
static const struct file_operations dbg_show_ops = {
.open = dbgfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static int dbgfs_calc_bound(struct seq_file *s, void *data)
{
deskew_dbgfs_calc_bound(s, input_stats);
return 0;
}
static int dbg_calc_open(struct inode *inode, struct file *file)
{
return single_open(file, dbgfs_calc_bound, inode->i_private);
}
static const struct file_operations dbg_calc_ops = {
.open = dbg_calc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static void nvcsi_deskew_debugfs_remove(struct nvcsi *nvcsi)
{
debugfs_remove_recursive(nvcsi->dir);
}
static int nvcsi_deskew_debugfs_init(struct nvcsi *nvcsi)
{
struct dentry *val;
nvcsi->dir = debugfs_create_dir("deskew", NULL);
if (!nvcsi->dir)
return -ENOMEM;
val = debugfs_create_file("stats", S_IRUGO, nvcsi->dir, mc_csi,
&dbg_show_ops);
if (!val)
goto err_debugfs;
val = debugfs_create_x64("input_status", S_IRUGO | S_IWUSR,
nvcsi->dir, &input_stats);
if (!val)
goto err_debugfs;
val = debugfs_create_file("calc_bound", S_IRUGO | S_IWUSR,
nvcsi->dir, mc_csi, &dbg_calc_ops);
if (!val)
goto err_debugfs;
return 0;
err_debugfs:
dev_err(mc_csi->dev, "Fail to create debugfs\n");
debugfs_remove_recursive(nvcsi->dir);
return -ENOMEM;
}
static long nvcsi_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct nvcsi_private *priv = file->private_data;
int ret;
switch (cmd) {
// sensor must be turned on before calling this ioctl, and streaming
// should be started shortly after.
case NVHOST_NVCSI_IOCTL_DESKEW_SETUP: {
unsigned int active_lanes;
dev_dbg(mc_csi->dev, "ioctl: deskew_setup\n");
priv->deskew_ctx.deskew_lanes = get_user(active_lanes,
(long __user *)arg);
ret = nvcsi_deskew_setup(&priv->deskew_ctx);
return ret;
}
case NVHOST_NVCSI_IOCTL_DESKEW_APPLY: {
dev_dbg(mc_csi->dev, "ioctl: deskew_apply\n");
ret = nvcsi_deskew_apply_check(&priv->deskew_ctx);
return ret;
}
}
return -ENOIOCTLCMD;
}
static int nvcsi_open(struct inode *inode, struct file *file)
{
struct nvhost_device_data *pdata = container_of(inode->i_cdev,
struct nvhost_device_data, ctrl_cdev);
struct platform_device *pdev = pdata->pdev;
struct nvcsi_private *priv;
priv = kmalloc(sizeof(*priv), GFP_KERNEL);
if (unlikely(priv == NULL))
return -ENOMEM;
priv->pdev = pdev;
file->private_data = priv;
return nonseekable_open(inode, file);
}
static int nvcsi_release(struct inode *inode, struct file *file)
{
struct nvcsi_private *priv = file->private_data;
kfree(priv);
return 0;
}
const struct file_operations tegra_nvcsi_ctrl_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = nvcsi_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = nvcsi_ioctl,
#endif
.open = nvcsi_open,
.release = nvcsi_release,
};
static int __init nvcsi_init(void)
{
#ifdef CONFIG_PM_GENERIC_DOMAINS
int ret;
ret = nvhost_domain_init(tegra_nvcsi_domain_match);
if (ret)
return ret;
#endif
return platform_driver_register(&nvcsi_driver);
}
static void __exit nvcsi_exit(void)
{
platform_driver_unregister(&nvcsi_driver);
}
late_initcall(nvcsi_init);
module_exit(nvcsi_exit);