mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-24 18:21:35 +03:00
CONFIG_TEGRA_BWMGR is a downstream solution in the kernel before K5.15, and the implementation now is completed moved to BPMP side. Since it is not supported anymore, remove the legacy code for maintainability. Bug 4955427 Change-Id: If639871f286ba84177e31a351757e25ce621905b Signed-off-by: Johnny Liu <johnliu@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3248364 Reviewed-by: Jon Hunter <jonathanh@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: svcacv <svcacv@nvidia.com>
834 lines
19 KiB
C
834 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2015-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
*/
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#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);
|
|
|
|
ret = clk_set_rate(info->iso_emc, total_khz * 1000);
|
|
if (ret)
|
|
dev_err(info->dev, "%s:Failed to set iso bw\n",
|
|
__func__);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (cdev->hw_type == HWTYPE_NONE)
|
|
return 0;
|
|
|
|
if ((cdev->hw_type == HWTYPE_CSI)
|
|
&& (info->sensor_type == SENSORTYPE_SLVSEC))
|
|
return 0;
|
|
|
|
if ((cdev->hw_type == HWTYPE_SLVSEC)
|
|
&& (info->sensor_type != SENSORTYPE_SLVSEC))
|
|
return 0;
|
|
|
|
if (!cdev->ops || !cdev->ops->set_rate)
|
|
return 0;
|
|
|
|
switch (cdev->hw_type) {
|
|
case HWTYPE_CSI:
|
|
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:
|
|
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;
|
|
}
|
|
|
|
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
|
|
static void tegra_camera_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
tegra_camera_remove(pdev);
|
|
}
|
|
#else
|
|
static int tegra_camera_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
return tegra_camera_remove(pdev);
|
|
}
|
|
#endif
|
|
|
|
static struct platform_driver tegra_camera_driver = {
|
|
.probe = tegra_camera_probe,
|
|
.remove = tegra_camera_remove_wrapper,
|
|
.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");
|