mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
The Nvhost-related APIs have been deprecated and are no longer
supported since our migration from K510 to K515. Therefore, the camera
platform driver should refrain from using "nvhost_*" functions to set
the clock rate.
Integrate the common operation callback interface into the
tegra_camera_dev_info structure. This modification allows individual
drivers to achieve clock set rate operations by registering the
callback and completing the implementation.
Bug 4374126
Signed-off-by: Johnny Liu <johnliu@nvidia.com>
Change-Id: Iebed7cc974f5dbdabddded3c84d0925de3a6c4f9
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3020535
(cherry picked from commit 57a82628aa)
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3023111
Reviewed-by: svcacv <svcacv@nvidia.com>
Reviewed-by: Bibek Basu <bbasu@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
817 lines
19 KiB
C
817 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* SPDX-FileCopyrightText: Copyright (C) 2015-2023 NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/module.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform/tegra/bwmgr_mc.h>
|
|
#include <linux/platform/tegra/latency_allowance.h>
|
|
#include <linux/platform/tegra/isomgr.h>
|
|
#include <linux/platform/tegra/mc_utils.h>
|
|
#include <linux/list.h>
|
|
#include <linux/nvhost.h>
|
|
|
|
#include <media/vi.h>
|
|
#include <media/tegra_camera_platform.h>
|
|
#include <soc/tegra/fuse.h>
|
|
#define CAMDEV_NAME "tegra_camera_ctrl"
|
|
|
|
/* Peak BPP for any of the YUV/Bayer formats */
|
|
#define CAMERA_PEAK_BPP 2
|
|
|
|
#define LANE_SPEED_1_GBPS 1000000000
|
|
#define LANE_SPEED_1_5_GBPS 1500000000
|
|
|
|
struct tegra_camera_info {
|
|
char devname[64];
|
|
atomic_t in_use;
|
|
struct device *dev;
|
|
struct clk *emc;
|
|
struct clk *iso_emc;
|
|
#if defined(CONFIG_INTERCONNECT)
|
|
int icc_iso_id;
|
|
struct icc_path *icc_iso_path_handle;
|
|
int icc_noniso_id;
|
|
struct icc_path *icc_noniso_path_handle;
|
|
struct mutex icc_noniso_path_handle_lock;
|
|
#endif
|
|
struct mutex update_bw_lock;
|
|
u64 vi_mode_isobw;
|
|
u64 bypass_mode_isobw;
|
|
/* set max bw by default */
|
|
bool en_max_bw;
|
|
|
|
u64 phy_pixel_rate;
|
|
u64 active_pixel_rate;
|
|
u64 active_iso_bw;
|
|
u32 max_pixel_depth;
|
|
u32 ppc_divider;
|
|
u32 num_active_streams;
|
|
u32 num_device_lanes;
|
|
u32 sensor_type;
|
|
u32 memory_latency;
|
|
bool pg_mode;
|
|
struct list_head device_list;
|
|
struct mutex device_list_mutex;
|
|
};
|
|
|
|
static const struct of_device_id tegra_camera_of_ids[] = {
|
|
{ .compatible = "nvidia, tegra-camera-platform" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_camera_of_ids);
|
|
|
|
static struct miscdevice tegra_camera_misc;
|
|
|
|
static int tegra_camera_isomgr_register(struct tegra_camera_info *info,
|
|
struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_camera_isomgr_unregister(struct tegra_camera_info *info)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_camera_isomgr_request(
|
|
struct tegra_camera_info *info, uint iso_bw, uint lt)
|
|
{
|
|
dev_dbg(info->dev,
|
|
"%s++ bw=%u, lt=%u\n", __func__, iso_bw, lt);
|
|
return 0;
|
|
}
|
|
|
|
int tegra_camera_emc_clk_enable(void)
|
|
{
|
|
struct tegra_camera_info *info;
|
|
int ret = 0;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
ret = clk_prepare_enable(info->emc);
|
|
if (ret) {
|
|
dev_err(info->dev, "Cannot enable camera.emc\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(info->iso_emc);
|
|
if (ret) {
|
|
dev_err(info->dev, "Cannot enable camera_iso.emc\n");
|
|
goto err_iso_emc;
|
|
}
|
|
|
|
return 0;
|
|
err_iso_emc:
|
|
clk_disable_unprepare(info->emc);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_emc_clk_enable);
|
|
|
|
int tegra_camera_emc_clk_disable(void)
|
|
{
|
|
struct tegra_camera_info *info;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
clk_disable_unprepare(info->emc);
|
|
clk_disable_unprepare(info->iso_emc);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_emc_clk_disable);
|
|
|
|
static int tegra_camera_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct tegra_camera_info *info;
|
|
struct miscdevice *mdev;
|
|
|
|
mdev = file->private_data;
|
|
info = dev_get_drvdata(mdev->parent);
|
|
file->private_data = info;
|
|
|
|
return tegra_camera_emc_clk_enable();
|
|
}
|
|
|
|
static int tegra_camera_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct tegra_camera_info *info;
|
|
|
|
info = file->private_data;
|
|
tegra_camera_emc_clk_disable();
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static u64 vi_mode_d;
|
|
static u64 bypass_mode_d;
|
|
|
|
static int dbgfs_tegra_camera_init(void)
|
|
{
|
|
struct dentry *dir;
|
|
|
|
dir = debugfs_create_dir("tegra_camera_platform", NULL);
|
|
if (!dir)
|
|
return -ENOMEM;
|
|
|
|
debugfs_create_u64("vi", S_IRUGO, dir, &vi_mode_d);
|
|
debugfs_create_u64("scf", S_IRUGO, dir, &bypass_mode_d);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* submits total aggregated iso bw request to isomgr.
|
|
*/
|
|
int tegra_camera_update_isobw(void)
|
|
{
|
|
struct tegra_camera_info *info;
|
|
unsigned long total_khz;
|
|
unsigned long bw;
|
|
#ifdef CONFIG_NV_TEGRA_MC
|
|
unsigned long bw_mbps;
|
|
#endif
|
|
int ret = 0;
|
|
|
|
if (tegra_camera_misc.parent == NULL) {
|
|
pr_info("driver not enabled, cannot update bw\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -ENODEV;
|
|
mutex_lock(&info->update_bw_lock);
|
|
|
|
bw = info->active_iso_bw;
|
|
if (info->bypass_mode_isobw > info->active_iso_bw)
|
|
bw = info->bypass_mode_isobw;
|
|
|
|
if (info->bypass_mode_isobw > 0)
|
|
info->num_active_streams++;
|
|
|
|
if (info->num_active_streams == 0)
|
|
bw = 0;
|
|
|
|
#ifdef CONFIG_NV_TEGRA_MC
|
|
/*
|
|
* Different chip versions use different APIs to set LA for VI.
|
|
* If one fails, try another, and fail if both of them don't work.
|
|
* Convert bw from kbps to mbps, and round up to the next mbps to
|
|
* guarantee it's larger than the requested for LA/PTSA setting.
|
|
*/
|
|
bw_mbps = (bw / 1000U) + 1;
|
|
ret = tegra_set_camera_ptsa(TEGRA_LA_VI_W, bw_mbps, 1);
|
|
if (ret) {
|
|
ret = tegra_set_latency_allowance(TEGRA_LA_VI_W, bw_mbps);
|
|
if (ret) {
|
|
dev_err(info->dev, "%s: set la failed: %d\n",
|
|
__func__, ret);
|
|
mutex_unlock(&info->update_bw_lock);
|
|
return ret;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Use Khz to prevent overflow */
|
|
total_khz = emc_bw_to_freq(bw);
|
|
total_khz = min(ULONG_MAX / 1000, total_khz);
|
|
|
|
dev_dbg(info->dev, "%s:Set iso bw %lu kbyteps at %lu KHz\n",
|
|
__func__, bw, total_khz);
|
|
#if !defined(CONFIG_TEGRA_BWMGR)
|
|
ret = clk_set_rate(info->iso_emc, total_khz * 1000);
|
|
if (ret)
|
|
dev_err(info->dev, "%s:Failed to set iso bw\n",
|
|
__func__);
|
|
#endif
|
|
/*
|
|
* Request to ISOMGR or ICC depending on chip version.
|
|
*/
|
|
ret = tegra_camera_isomgr_request(info, bw, info->memory_latency);
|
|
if (ret) {
|
|
dev_err(info->dev,
|
|
"%s: failed to reserve %lu KBps with isomgr\n",
|
|
__func__, bw);
|
|
mutex_unlock(&info->update_bw_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
info->vi_mode_isobw = bw;
|
|
#ifdef CONFIG_DEBUG_FS
|
|
vi_mode_d = bw;
|
|
bypass_mode_d = info->bypass_mode_isobw;
|
|
#endif
|
|
|
|
if (info->bypass_mode_isobw > 0)
|
|
info->num_active_streams--;
|
|
|
|
mutex_unlock(&info->update_bw_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_update_isobw);
|
|
|
|
static long tegra_camera_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct tegra_camera_info *info;
|
|
|
|
info = file->private_data;
|
|
|
|
switch (_IOC_NR(cmd)) {
|
|
case _IOC_NR(TEGRA_CAMERA_IOCTL_SET_BW):
|
|
{
|
|
struct bw_info kcopy;
|
|
unsigned long mc_khz = 0;
|
|
|
|
memset(&kcopy, 0, sizeof(kcopy));
|
|
|
|
if (copy_from_user(&kcopy, (const void __user *)arg,
|
|
sizeof(struct bw_info))) {
|
|
dev_err(info->dev, "%s:Failed to get data from user\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Use Khz to prevent overflow */
|
|
mc_khz = emc_bw_to_freq(kcopy.bw);
|
|
mc_khz = min(ULONG_MAX / 1000, mc_khz);
|
|
|
|
if (kcopy.is_iso) {
|
|
info->bypass_mode_isobw = kcopy.bw;
|
|
ret = tegra_camera_update_isobw();
|
|
} else {
|
|
dev_dbg(info->dev, "%s:Set bw %llu at %lu KHz\n",
|
|
__func__, kcopy.bw, mc_khz);
|
|
ret = clk_set_rate(info->emc, mc_khz * 1000);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case _IOC_NR(TEGRA_CAMERA_IOCTL_GET_BW):
|
|
{
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case _IOC_NR(TEGRA_CAMERA_IOCTL_GET_CURR_REQ_ISO_BW):
|
|
{
|
|
struct tegra_camera_info *state;
|
|
u64 bw;
|
|
|
|
state = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!state)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&state->update_bw_lock);
|
|
bw = state->vi_mode_isobw;
|
|
mutex_unlock(&state->update_bw_lock);
|
|
|
|
if (copy_to_user((void __user *)arg, (const void *)&bw,
|
|
sizeof(bw))) {
|
|
dev_err(info->dev,
|
|
"%s:Failed to copy data to user\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations tegra_camera_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tegra_camera_open,
|
|
.unlocked_ioctl = tegra_camera_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = tegra_camera_ioctl,
|
|
#endif
|
|
.release = tegra_camera_release,
|
|
};
|
|
|
|
static bool is_isomgr_up(struct device *dev)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static int tegra_camera_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct tegra_camera_info *info;
|
|
|
|
/* Defer the probe till isomgr is initialized */
|
|
if (!is_isomgr_up(&pdev->dev)) {
|
|
dev_dbg(&pdev->dev,
|
|
"%s:camera_platform_driver probe deferred as isomgr not up\n",
|
|
__func__);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
dev_dbg(&pdev->dev,
|
|
"%s:tegra_camera_platform driver probe\n", __func__);
|
|
|
|
tegra_camera_misc.minor = MISC_DYNAMIC_MINOR;
|
|
tegra_camera_misc.name = CAMDEV_NAME;
|
|
tegra_camera_misc.fops = &tegra_camera_ops;
|
|
tegra_camera_misc.parent = &pdev->dev;
|
|
|
|
ret = misc_register(&tegra_camera_misc);
|
|
if (ret) {
|
|
dev_err(tegra_camera_misc.this_device,
|
|
"register failed for %s\n", tegra_camera_misc.name);
|
|
return ret;
|
|
}
|
|
|
|
info = devm_kzalloc(tegra_camera_misc.this_device,
|
|
sizeof(struct tegra_camera_info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
strcpy(info->devname, tegra_camera_misc.name);
|
|
info->dev = tegra_camera_misc.this_device;
|
|
|
|
mutex_init(&info->update_bw_lock);
|
|
/* Register Camera as isomgr client. */
|
|
ret = tegra_camera_isomgr_register(info, &pdev->dev);
|
|
if (ret) {
|
|
dev_err(info->dev,
|
|
"%s: failed to register CAMERA as isomgr client\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
info->en_max_bw = of_property_read_bool(pdev->dev.of_node,
|
|
"default-max-bw");
|
|
info->phy_pixel_rate = 0;
|
|
info->active_pixel_rate = 0;
|
|
info->active_iso_bw = 0;
|
|
info->max_pixel_depth = 0;
|
|
info->ppc_divider = 1;
|
|
info->num_active_streams = 0;
|
|
info->num_device_lanes = 0;
|
|
info->sensor_type = 0;
|
|
info->memory_latency = 0;
|
|
info->pg_mode = false;
|
|
mutex_init(&info->device_list_mutex);
|
|
INIT_LIST_HEAD(&info->device_list);
|
|
|
|
platform_set_drvdata(pdev, info);
|
|
#ifdef CONFIG_DEBUG_FS
|
|
if (debugfs_initialized()) {
|
|
ret = dbgfs_tegra_camera_init();
|
|
if (ret)
|
|
dev_err(info->dev, "Fail to create debugfs");
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void update_platform_data(struct tegra_camera_dev_info *cdev,
|
|
struct tegra_camera_info *info, bool dev_registered)
|
|
{
|
|
/* TPG: handled differently based on
|
|
* throughput calculations.
|
|
*/
|
|
static u64 phy_pixel_rate_aggregated;
|
|
|
|
if (cdev->sensor_type == SENSORTYPE_VIRTUAL)
|
|
info->pg_mode = dev_registered;
|
|
|
|
if (cdev->sensor_type != SENSORTYPE_NONE)
|
|
info->sensor_type = cdev->sensor_type;
|
|
|
|
if (cdev->hw_type != HWTYPE_NONE) {
|
|
if (info->num_device_lanes < cdev->lane_num)
|
|
info->num_device_lanes = cdev->lane_num;
|
|
}
|
|
|
|
if (dev_registered) {
|
|
/* if bytes per pixel is greater than 2, then num_ppc
|
|
* is 4 for VI. Set divider for this case.
|
|
*/
|
|
if (cdev->bpp > 2)
|
|
info->ppc_divider = 2;
|
|
if (cdev->lane_num < info->num_device_lanes) {
|
|
// temp variable to store aggregated rate
|
|
phy_pixel_rate_aggregated += cdev->pixel_rate;
|
|
} else if (cdev->lane_num == info->num_device_lanes) {
|
|
if (info->phy_pixel_rate < cdev->pixel_rate)
|
|
info->phy_pixel_rate = cdev->pixel_rate;
|
|
}
|
|
|
|
if (info->phy_pixel_rate < phy_pixel_rate_aggregated)
|
|
info->phy_pixel_rate = phy_pixel_rate_aggregated;
|
|
|
|
if (info->max_pixel_depth < cdev->pixel_bit_depth)
|
|
info->max_pixel_depth = cdev->pixel_bit_depth;
|
|
if (info->memory_latency < cdev->memory_latency)
|
|
info->memory_latency = cdev->memory_latency;
|
|
}
|
|
}
|
|
|
|
static int add_nvhost_client(struct tegra_camera_dev_info *cdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (cdev->hw_type == HWTYPE_NONE)
|
|
return 0;
|
|
|
|
ret = nvhost_module_add_client(cdev->pdev, &cdev->hw_type);
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int remove_nvhost_client(struct tegra_camera_dev_info *cdev)
|
|
{
|
|
if (cdev->hw_type == HWTYPE_NONE)
|
|
return 0;
|
|
|
|
nvhost_module_remove_client(cdev->pdev, &cdev->hw_type);
|
|
return 0;
|
|
}
|
|
|
|
int tegra_camera_device_register(struct tegra_camera_dev_info *cdev_info,
|
|
void *priv)
|
|
{
|
|
int err = 0;
|
|
struct tegra_camera_dev_info *cdev;
|
|
struct tegra_camera_info *info;
|
|
|
|
/*
|
|
* If tegra_camera_platform is not enabled, devices
|
|
* cannot be registered, but the HW engines will still be probed.
|
|
* So just return without registering.
|
|
*/
|
|
if (tegra_camera_misc.parent == NULL) {
|
|
pr_info("driver not enabled, cannot register any devices\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!cdev_info || !priv)
|
|
return -EINVAL;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
cdev = kzalloc(sizeof(struct tegra_camera_dev_info), GFP_KERNEL);
|
|
if (!cdev)
|
|
return -ENOMEM;
|
|
|
|
*cdev = *cdev_info;
|
|
|
|
INIT_LIST_HEAD(&cdev->device_node);
|
|
cdev->priv = priv;
|
|
|
|
mutex_lock(&info->device_list_mutex);
|
|
list_add(&cdev->device_node, &info->device_list);
|
|
err = add_nvhost_client(cdev);
|
|
if (err) {
|
|
mutex_unlock(&info->device_list_mutex);
|
|
dev_err(info->dev, "%s could not add %d to nvhost\n",
|
|
__func__, cdev->hw_type);
|
|
return err;
|
|
}
|
|
update_platform_data(cdev, info, true);
|
|
mutex_unlock(&info->device_list_mutex);
|
|
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_device_register);
|
|
|
|
int tegra_camera_device_unregister(void *priv)
|
|
{
|
|
struct tegra_camera_dev_info *cdev, *tmp;
|
|
int found = 0;
|
|
struct tegra_camera_info *info;
|
|
|
|
/*
|
|
* If tegra_camera_platform is not enabled, devices
|
|
* were not registered, so return here.
|
|
*/
|
|
if (tegra_camera_misc.parent == NULL) {
|
|
pr_info("driver not enabled, no devices were registered\n");
|
|
return 0;
|
|
}
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&info->device_list_mutex);
|
|
list_for_each_entry_safe(cdev, tmp, &info->device_list, device_node) {
|
|
if (priv == cdev->priv) {
|
|
list_del(&cdev->device_node);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
remove_nvhost_client(cdev);
|
|
update_platform_data(cdev, info, false);
|
|
kfree(cdev);
|
|
}
|
|
mutex_unlock(&info->device_list_mutex);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_device_unregister);
|
|
|
|
int tegra_camera_get_device_list_entry(const u32 hw_type, const void *priv,
|
|
struct tegra_camera_dev_info *cdev_info)
|
|
{
|
|
struct tegra_camera_dev_info *cdev;
|
|
struct tegra_camera_info *info;
|
|
int found = 0;
|
|
int ret = 0;
|
|
|
|
if (tegra_camera_misc.parent == NULL)
|
|
return -EINVAL;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&info->device_list_mutex);
|
|
list_for_each_entry(cdev, &info->device_list, device_node) {
|
|
if (hw_type == cdev->hw_type) {
|
|
/*
|
|
* If priv is NULL yet the hw_type is set to that of a
|
|
* sensor the first sensor in the device list will be
|
|
* returned. Otherwise, a NULL priv and a matching
|
|
* hw_type will return the hw unit (VI, CSI etc.).
|
|
*
|
|
* Otherwise, a non NULL priv is used as an additional
|
|
* constraint to return specific devices who may or
|
|
* may not share common hw_types.
|
|
*/
|
|
if (!priv) {
|
|
found = 1;
|
|
break;
|
|
} else if (priv == cdev->priv) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found && (cdev_info != NULL))
|
|
*cdev_info = *cdev;
|
|
|
|
mutex_unlock(&info->device_list_mutex);
|
|
|
|
if (!found)
|
|
return -ENOENT;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_get_device_list_entry);
|
|
|
|
int tegra_camera_get_device_list_stats(u32 *n_sensors, u32 *n_hwtypes)
|
|
{
|
|
struct tegra_camera_dev_info *cdev;
|
|
struct tegra_camera_info *info;
|
|
int ret = 0;
|
|
|
|
if (!n_sensors || !n_hwtypes)
|
|
return -EINVAL;
|
|
|
|
if (tegra_camera_misc.parent == NULL)
|
|
return -EINVAL;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
*n_sensors = 0;
|
|
*n_hwtypes = 0;
|
|
|
|
mutex_lock(&info->device_list_mutex);
|
|
list_for_each_entry(cdev, &info->device_list, device_node) {
|
|
if (cdev->hw_type == HWTYPE_NONE)
|
|
(*n_sensors)++;
|
|
else
|
|
(*n_hwtypes)++;
|
|
}
|
|
mutex_unlock(&info->device_list_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_get_device_list_stats);
|
|
|
|
static int calculate_and_set_device_clock(struct tegra_camera_info *info,
|
|
struct tegra_camera_dev_info *cdev)
|
|
{
|
|
u64 active_pr = info->active_pixel_rate;
|
|
u64 phy_pr = info->phy_pixel_rate;
|
|
u32 overhead = cdev->overhead + 100;
|
|
u32 max_depth = info->max_pixel_depth;
|
|
u32 bus_width = cdev->bus_width;
|
|
u32 lane_num = cdev->lane_num;
|
|
u64 lane_speed = cdev->lane_speed;
|
|
u32 ppc = (cdev->ppc) ? cdev->ppc : 1;
|
|
u32 ppc_divider = (ppc > 1) ? info->ppc_divider : 1;
|
|
u64 nr = 0;
|
|
u64 dr = 0;
|
|
u64 clk_rate = 0;
|
|
u64 final_pr = (cdev->use_max) ? phy_pr : active_pr;
|
|
bool set_clk = true;
|
|
|
|
if (cdev->hw_type == HWTYPE_NONE)
|
|
return 0;
|
|
|
|
if (!cdev->ops->set_rate)
|
|
return -EOPNOTSUPP;
|
|
|
|
switch (cdev->hw_type) {
|
|
case HWTYPE_CSI:
|
|
if (info->sensor_type == SENSORTYPE_SLVSEC)
|
|
set_clk = false;
|
|
nr = max_depth * final_pr * overhead;
|
|
dr = bus_width * 100;
|
|
if (dr == 0)
|
|
return -EINVAL;
|
|
break;
|
|
case HWTYPE_VI:
|
|
nr = final_pr * overhead;
|
|
dr = 100 * (ppc / ppc_divider);
|
|
break;
|
|
case HWTYPE_ISPA:
|
|
case HWTYPE_ISPB:
|
|
nr = final_pr * overhead;
|
|
dr = 100 * ppc;
|
|
break;
|
|
case HWTYPE_SLVSEC:
|
|
if (info->sensor_type != SENSORTYPE_SLVSEC)
|
|
set_clk = false;
|
|
nr = lane_speed * lane_num * overhead;
|
|
dr = bus_width * 100;
|
|
if (dr == 0)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* avoid rounding errors by adding dr to nr */
|
|
clk_rate = (nr + dr) / dr;
|
|
|
|
/* Use special rates based on throughput
|
|
* for TPG.
|
|
*/
|
|
if (info->pg_mode) {
|
|
clk_rate = (cdev->pg_clk_rate) ?
|
|
cdev->pg_clk_rate : DEFAULT_PG_CLK_RATE;
|
|
}
|
|
|
|
/* no stream active, set to 0 */
|
|
if (info->num_active_streams == 0)
|
|
clk_rate = 0;
|
|
|
|
return cdev->ops->set_rate(cdev, clk_rate);
|
|
}
|
|
|
|
int tegra_camera_update_clknbw(void *priv, bool stream_on)
|
|
{
|
|
struct tegra_camera_dev_info *cdev;
|
|
struct tegra_camera_info *info;
|
|
int ret = 0;
|
|
|
|
info = dev_get_drvdata(tegra_camera_misc.parent);
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&info->device_list_mutex);
|
|
/* Need to traverse the list twice, first to make sure that
|
|
* stream on is set for the active stream and then to
|
|
* update clocks and BW.
|
|
* Needed as devices could have been added in any order in the list.
|
|
*/
|
|
list_for_each_entry(cdev, &info->device_list, device_node) {
|
|
if (priv == cdev->priv) {
|
|
/* set stream on */
|
|
cdev->stream_on = stream_on;
|
|
if (stream_on) {
|
|
info->active_pixel_rate += cdev->pixel_rate;
|
|
info->active_iso_bw += cdev->bw;
|
|
info->num_active_streams++;
|
|
} else {
|
|
info->active_pixel_rate -= cdev->pixel_rate;
|
|
info->active_iso_bw -= cdev->bw;
|
|
info->num_active_streams--;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* update clocks */
|
|
list_for_each_entry(cdev, &info->device_list, device_node) {
|
|
ret = calculate_and_set_device_clock(info, cdev);
|
|
if (ret) {
|
|
mutex_unlock(&info->device_list_mutex);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
mutex_unlock(&info->device_list_mutex);
|
|
|
|
/* set BW */
|
|
tegra_camera_update_isobw();
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_camera_update_clknbw);
|
|
|
|
static int tegra_camera_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra_camera_info *info = platform_get_drvdata(pdev);
|
|
|
|
dev_info(&pdev->dev, "%s:camera_platform_driver remove\n", __func__);
|
|
|
|
/* deallocate isomgr bw */
|
|
if (info->en_max_bw)
|
|
tegra_camera_isomgr_request(info, 0, info->memory_latency);
|
|
|
|
tegra_camera_isomgr_unregister(info);
|
|
misc_deregister(&tegra_camera_misc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver tegra_camera_driver = {
|
|
.probe = tegra_camera_probe,
|
|
.remove = tegra_camera_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "tegra_camera_platform",
|
|
.of_match_table = tegra_camera_of_ids
|
|
}
|
|
};
|
|
static int __init tegra_camera_init(void)
|
|
{
|
|
return platform_driver_register(&tegra_camera_driver);
|
|
}
|
|
static void __exit tegra_camera_exit(void)
|
|
{
|
|
platform_driver_unregister(&tegra_camera_driver);
|
|
}
|
|
|
|
module_init(tegra_camera_init);
|
|
module_exit(tegra_camera_exit);
|
|
MODULE_LICENSE("GPL");
|