Files
android_kernel_samsung_sm86…/drivers/cam_cpas/cam_cpas_intf.c
Sokchetra Eung fe2a0160e9 msm: camera: cpas: Modify Client CPAS reg read/write
Remove all instances of cpas camnoc reg read/write by clients.
Move cpas reg base enums to internal cpas file that is only visible
to CPAS usages, and create cpastop reg base macro in CPAS API file.
So, clients can now read and write to cpastop reg only.

CRs-Fixed: 3405131
Change-Id: I1793377a8b6f010bb2c79ac9020cfa3d21e31812
Signed-off-by: Sokchetra Eung <quic_eung@quicinc.com>
2023-03-20 13:46:27 -07:00

1411 lines
34 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/of.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-subdev.h>
#include <media/cam_cpas.h>
#include <media/cam_req_mgr.h>
#include <dt-bindings/msm-camera.h>
#include "cam_subdev.h"
#include "cam_cpas_hw_intf.h"
#include "cam_cpas_soc.h"
#include "camera_main.h"
#include <linux/soc/qcom/llcc-qcom.h>
#define CAM_CPAS_DEV_NAME "cam-cpas"
#define CAM_CPAS_INTF_INITIALIZED() (g_cpas_intf && g_cpas_intf->probe_done)
/**
* struct cam_cpas_intf : CPAS interface
*
* @pdev: Platform device
* @subdev: Subdev info
* @hw_intf: CPAS HW interface
* @hw_caps: CPAS HW capabilities
* @intf_lock: CPAS interface mutex
* @open_cnt: CPAS subdev open count
* @probe_done: Whether CPAS prove completed
*
*/
struct cam_cpas_intf {
struct platform_device *pdev;
struct cam_subdev subdev;
struct cam_hw_intf *hw_intf;
struct cam_cpas_hw_caps hw_caps;
struct mutex intf_lock;
uint32_t open_cnt;
bool probe_done;
};
static struct cam_cpas_intf *g_cpas_intf;
const char *cam_cpas_axi_util_path_type_to_string(
uint32_t path_data_type)
{
switch (path_data_type) {
/* IFE Paths */
case CAM_AXI_PATH_DATA_IFE_LINEAR:
return "IFE_LINEAR";
case CAM_AXI_PATH_DATA_IFE_VID:
return "IFE_VID";
case CAM_AXI_PATH_DATA_IFE_DISP:
return "IFE_DISP";
case CAM_AXI_PATH_DATA_IFE_STATS:
return "IFE_STATS";
case CAM_AXI_PATH_DATA_IFE_RDI0:
return "IFE_RDI0";
case CAM_AXI_PATH_DATA_IFE_RDI1:
return "IFE_RDI1";
case CAM_AXI_PATH_DATA_IFE_RDI2:
return "IFE_RDI2";
case CAM_AXI_PATH_DATA_IFE_RDI3:
return "IFE_RDI3";
case CAM_AXI_PATH_DATA_IFE_PDAF:
return "IFE_PDAF";
case CAM_AXI_PATH_DATA_IFE_PIXEL_RAW:
return "IFE_PIXEL_RAW";
/* IPE Paths */
case CAM_AXI_PATH_DATA_IPE_RD_IN:
return "IPE_RD_IN";
case CAM_AXI_PATH_DATA_IPE_RD_REF:
return "IPE_RD_REF";
case CAM_AXI_PATH_DATA_IPE_WR_VID:
return "IPE_WR_VID";
case CAM_AXI_PATH_DATA_IPE_WR_DISP:
return "IPE_WR_DISP";
case CAM_AXI_PATH_DATA_IPE_WR_REF:
return "IPE_WR_REF";
case CAM_AXI_PATH_DATA_IPE_WR_APP:
return "IPE_WR_APP";
/* OPE Paths */
case CAM_AXI_PATH_DATA_OPE_RD_IN:
return "OPE_RD_IN";
case CAM_AXI_PATH_DATA_OPE_RD_REF:
return "OPE_RD_REF";
case CAM_AXI_PATH_DATA_OPE_WR_VID:
return "OPE_WR_VID";
case CAM_AXI_PATH_DATA_OPE_WR_DISP:
return "OPE_WR_DISP";
case CAM_AXI_PATH_DATA_OPE_WR_REF:
return "OPE_WR_REF";
/* SFE Paths */
case CAM_AXI_PATH_DATA_SFE_NRDI:
return "SFE_NRDI";
case CAM_AXI_PATH_DATA_SFE_RDI0:
return "SFE_RDI0";
case CAM_AXI_PATH_DATA_SFE_RDI1:
return "SFE_RDI1";
case CAM_AXI_PATH_DATA_SFE_RDI2:
return "SFE_RDI2";
case CAM_AXI_PATH_DATA_SFE_RDI3:
return "SFE_RDI3";
case CAM_AXI_PATH_DATA_SFE_RDI4:
return "SFE_RDI4";
case CAM_AXI_PATH_DATA_SFE_STATS:
return "SFE_STATS";
case CAM_AXI_PATH_DATA_CRE_RD_IN:
return "CRE_RD_IN";
case CAM_AXI_PATH_DATA_CRE_WR_OUT:
return "CRE_WR_OUT";
/* OFE Paths */
case CAM_AXI_PATH_DATA_OFE_RD_EXT:
return "OFE_RD_EXT";
case CAM_AXI_PATH_DATA_OFE_RD_INT_PDI:
return "OFE_RD_INT_PDI";
case CAM_AXI_PATH_DATA_OFE_RD_INT_HDR:
return "OFE_RD_INT_HDR";
case CAM_AXI_PATH_DATA_OFE_WR_VID:
return "OFE_WR_VID";
case CAM_AXI_PATH_DATA_OFE_WR_DISP:
return "OFE_WR_DISP";
case CAM_AXI_PATH_DATA_OFE_WR_IR:
return "OFE_WR_IR";
case CAM_AXI_PATH_DATA_OFE_WR_HDR_LTM:
return "OFE_WR_HDR_LTM";
case CAM_AXI_PATH_DATA_OFE_WR_DC4:
return "OFE_WR_DC4";
case CAM_AXI_PATH_DATA_OFE_WR_AI:
return "OFE_WR_AI";
case CAM_AXI_PATH_DATA_OFE_WR_PDI:
return "OFE_WR_PDI";
case CAM_AXI_PATH_DATA_OFE_WR_IDEALRAW:
return "OFE_WR_IDEALRAW";
case CAM_AXI_PATH_DATA_OFE_WR_STATS:
return "OFE_WR_STATS";
/* Common Paths */
case CAM_AXI_PATH_DATA_ALL:
return "DATA_ALL";
default:
return "CPAS_PATH_INVALID";
}
}
EXPORT_SYMBOL(cam_cpas_axi_util_path_type_to_string);
const char *cam_cpas_axi_util_trans_type_to_string(
uint32_t transac_type)
{
switch (transac_type) {
case CAM_AXI_TRANSACTION_READ:
return "TRANSAC_READ";
case CAM_AXI_TRANSACTION_WRITE:
return "TRANSAC_WRITE";
default:
return "TRANSAC_INVALID";
}
}
EXPORT_SYMBOL(cam_cpas_axi_util_trans_type_to_string);
const char *cam_cpas_axi_util_drv_vote_lvl_to_string(
uint32_t vote_lvl)
{
switch (vote_lvl) {
case CAM_CPAS_VOTE_LEVEL_LOW:
return "VOTE_LVL_LOW";
case CAM_CPAS_VOTE_LEVEL_HIGH:
return "VOTE_LVL_HIGH";
default:
return "VOTE_LVL_INVALID";
}
}
EXPORT_SYMBOL(cam_cpas_axi_util_drv_vote_lvl_to_string);
const char *cam_cpas_util_vote_type_to_string(enum cam_cpas_vote_type vote_type)
{
switch (vote_type) {
case CAM_CPAS_VOTE_TYPE_HLOS:
return "VOTE_TYPE_HLOS";
case CAM_CPAS_VOTE_TYPE_DRV:
return "VOTE_TYPE_DRV";
default:
return "VOTE_TYPE_INVALID";
}
}
EXPORT_SYMBOL(cam_cpas_util_vote_type_to_string);
int cam_cpas_query_drv_enable(bool *is_ddr_drv_enabled, bool *is_clk_drv_enabled)
{
struct cam_hw_info *cpas_hw = NULL;
struct cam_cpas_private_soc *soc_private = NULL;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!is_ddr_drv_enabled && !is_clk_drv_enabled) {
CAM_ERR(CAM_CPAS, "invalid input ddr: %pK clk: %pK", is_ddr_drv_enabled,
is_clk_drv_enabled);
return -EINVAL;
}
cpas_hw = (struct cam_hw_info *) g_cpas_intf->hw_intf->hw_priv;
soc_private = (struct cam_cpas_private_soc *) cpas_hw->soc_info.soc_private;
if (is_ddr_drv_enabled)
*is_ddr_drv_enabled = soc_private->enable_cam_ddr_drv;
if (is_clk_drv_enabled)
*is_clk_drv_enabled = soc_private->enable_cam_clk_drv;
return 0;
}
EXPORT_SYMBOL(cam_cpas_query_drv_enable);
int cam_cpas_csid_process_resume(uint32_t csid_idx)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_CSID_PROCESS_RESUME, &csid_idx,
sizeof(uint32_t));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_csid_process_resume);
int cam_cpas_csid_input_core_info_update(int csid_idx, int sfe_idx, bool set_port)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_csid_input_core_info_update core_info_update;
core_info_update.csid_idx = csid_idx;
core_info_update.sfe_idx = sfe_idx;
core_info_update.set_port = set_port;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_CSID_INPUT_CORE_INFO_UPDATE, &core_info_update,
sizeof(core_info_update));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_csid_input_core_info_update);
int cam_cpas_dump_camnoc_buff_fill_info(uint32_t client_handle)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_DUMP_BUFF_FILL_INFO, &client_handle,
sizeof(uint32_t));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_dump_camnoc_buff_fill_info);
bool cam_cpas_is_feature_supported(uint32_t flag, uint32_t hw_map,
uint32_t *fuse_val)
{
struct cam_hw_info *cpas_hw = NULL;
struct cam_cpas_private_soc *soc_private = NULL;
bool supported = true;
int32_t i;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return false;
}
cpas_hw = (struct cam_hw_info *) g_cpas_intf->hw_intf->hw_priv;
soc_private =
(struct cam_cpas_private_soc *)cpas_hw->soc_info.soc_private;
if (flag >= CAM_CPAS_FUSE_FEATURE_MAX) {
CAM_ERR(CAM_CPAS, "Unknown feature flag %x", flag);
return false;
}
for (i = 0; i < soc_private->num_feature_info; i++)
if (soc_private->feature_info[i].feature == flag)
break;
if (i == soc_private->num_feature_info)
goto end;
if (soc_private->feature_info[i].type == CAM_CPAS_FEATURE_TYPE_DISABLE
|| (soc_private->feature_info[i].type ==
CAM_CPAS_FEATURE_TYPE_ENABLE)) {
if ((soc_private->feature_info[i].hw_map & hw_map) == hw_map)
supported = soc_private->feature_info[i].enable;
} else {
if (!fuse_val) {
CAM_ERR(CAM_CPAS,
"Invalid arg fuse_val");
} else {
*fuse_val = soc_private->feature_info[i].value;
}
}
end:
return supported;
}
EXPORT_SYMBOL(cam_cpas_is_feature_supported);
int cam_cpas_get_cpas_hw_version(uint32_t *hw_version)
{
struct cam_hw_info *cpas_hw = NULL;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!hw_version) {
CAM_ERR(CAM_CPAS, "invalid input %pK", hw_version);
return -EINVAL;
}
cpas_hw = (struct cam_hw_info *) g_cpas_intf->hw_intf->hw_priv;
*hw_version = cpas_hw->soc_info.hw_version;
if (*hw_version == CAM_CPAS_TITAN_NONE) {
CAM_DBG(CAM_CPAS, "Didn't find a valid HW Version %d",
*hw_version);
}
return 0;
}
int cam_cpas_get_hw_info(uint32_t *camera_family,
struct cam_hw_version *camera_version,
struct cam_hw_version *cpas_version,
uint32_t *cam_caps,
struct cam_cpas_fuse_info *cam_fuse_info,
struct cam_cpas_domain_id_caps *domain_id_info)
{
struct cam_hw_info *cpas_hw;
struct cam_cpas_private_soc *soc_private;
struct cam_cpas_domain_id_info cpas_domain_id;
int i;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!camera_family || !camera_version || !cpas_version || !cam_caps) {
CAM_ERR(CAM_CPAS, "invalid input %pK %pK %pK %pK",
camera_family, camera_version, cpas_version, cam_caps);
return -EINVAL;
}
cpas_hw = g_cpas_intf->hw_intf->hw_priv;
soc_private = (struct cam_cpas_private_soc *)
cpas_hw->soc_info.soc_private;
*camera_family = g_cpas_intf->hw_caps.camera_family;
*camera_version = g_cpas_intf->hw_caps.camera_version;
*cpas_version = g_cpas_intf->hw_caps.cpas_version;
*cam_caps = g_cpas_intf->hw_caps.camera_capability;
if (cam_fuse_info)
*cam_fuse_info = g_cpas_intf->hw_caps.fuse_info;
if (domain_id_info) {
cpas_domain_id = soc_private->domain_id_info;
if (!soc_private->domain_id_info.domain_id_supported) {
domain_id_info->num_mapping = 0;
domain_id_info->is_supported = 0;
} else {
domain_id_info->is_supported = 1;
domain_id_info->num_mapping =
soc_private->domain_id_info.num_domain_ids;
for (i = 0; i < domain_id_info->num_mapping; i++) {
domain_id_info->entries[i].domain_type =
cpas_domain_id.domain_id_entries[i].domain_type;
domain_id_info->entries[i].mapping_id =
cpas_domain_id.domain_id_entries[i].mapping_id;
}
}
}
CAM_DBG(CAM_CPAS, "Family %d, version %d.%d cam_caps %d, domain_id: %s",
*camera_family, camera_version->major,
camera_version->minor, *cam_caps,
CAM_BOOL_TO_YESNO(soc_private->domain_id_info.domain_id_supported));
return 0;
}
EXPORT_SYMBOL(cam_cpas_get_hw_info);
static inline enum cam_cpas_reg_base __cam_cpas_get_internal_reg_base(
enum cam_cpas_regbase_types reg_base)
{
switch (reg_base) {
case CAM_CPAS_REGBASE_CPASTOP:
return CAM_CPAS_REG_CPASTOP;
default:
return CAM_CPAS_REG_MAX;
}
}
int cam_cpas_reg_write(uint32_t client_handle, enum cam_cpas_regbase_types reg_base,
uint32_t offset, bool mb, uint32_t value)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_reg_read_write cmd_reg_write;
enum cam_cpas_reg_base internal_reg_base;
internal_reg_base = __cam_cpas_get_internal_reg_base(reg_base);
if (internal_reg_base >= CAM_CPAS_REG_MAX) {
CAM_ERR(CAM_CPAS, "Invalid reg base: %d for write ops client: %u",
reg_base, client_handle);
return -EINVAL;
}
cmd_reg_write.client_handle = client_handle;
cmd_reg_write.reg_base = internal_reg_base;
cmd_reg_write.offset = offset;
cmd_reg_write.value = value;
cmd_reg_write.mb = mb;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_REG_WRITE, &cmd_reg_write,
sizeof(struct cam_cpas_hw_cmd_reg_read_write));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_reg_write);
int cam_cpas_reg_read(uint32_t client_handle, enum cam_cpas_regbase_types reg_base,
uint32_t offset, bool mb, uint32_t *value)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!value) {
CAM_ERR(CAM_CPAS, "Invalid arg value");
return -EINVAL;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_reg_read_write cmd_reg_read;
enum cam_cpas_reg_base internal_reg_base;
internal_reg_base = __cam_cpas_get_internal_reg_base(reg_base);
if (internal_reg_base >= CAM_CPAS_REG_MAX) {
CAM_ERR(CAM_CPAS, "Invalid reg base: %d for read ops client: %u",
reg_base, client_handle);
return -EINVAL;
}
cmd_reg_read.client_handle = client_handle;
cmd_reg_read.reg_base = internal_reg_base;
cmd_reg_read.offset = offset;
cmd_reg_read.mb = mb;
cmd_reg_read.value = 0;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_REG_READ, &cmd_reg_read,
sizeof(struct cam_cpas_hw_cmd_reg_read_write));
if (rc) {
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
return rc;
}
*value = cmd_reg_read.value;
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_reg_read);
int cam_cpas_update_axi_vote(uint32_t client_handle,
struct cam_axi_vote *axi_vote)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!axi_vote) {
CAM_ERR(CAM_CPAS, "NULL axi vote");
return -EINVAL;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_axi_vote cmd_axi_vote;
cmd_axi_vote.client_handle = client_handle;
cmd_axi_vote.axi_vote = axi_vote;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_AXI_VOTE, &cmd_axi_vote,
sizeof(struct cam_cpas_hw_cmd_axi_vote));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_update_axi_vote);
int cam_cpas_update_ahb_vote(uint32_t client_handle,
struct cam_ahb_vote *ahb_vote)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_ahb_vote cmd_ahb_vote;
cmd_ahb_vote.client_handle = client_handle;
cmd_ahb_vote.ahb_vote = ahb_vote;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_AHB_VOTE, &cmd_ahb_vote,
sizeof(struct cam_cpas_hw_cmd_ahb_vote));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_update_ahb_vote);
int cam_cpas_stop(uint32_t client_handle)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.stop) {
struct cam_cpas_hw_cmd_stop cmd_hw_stop;
cmd_hw_stop.client_handle = client_handle;
rc = g_cpas_intf->hw_intf->hw_ops.stop(
g_cpas_intf->hw_intf->hw_priv, &cmd_hw_stop,
sizeof(struct cam_cpas_hw_cmd_stop));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in stop, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid stop ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_stop);
int cam_cpas_start(uint32_t client_handle,
struct cam_ahb_vote *ahb_vote, struct cam_axi_vote *axi_vote)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!axi_vote) {
CAM_ERR(CAM_CPAS, "NULL axi vote");
return -EINVAL;
}
if (g_cpas_intf->hw_intf->hw_ops.start) {
struct cam_cpas_hw_cmd_start cmd_hw_start;
cmd_hw_start.client_handle = client_handle;
cmd_hw_start.ahb_vote = ahb_vote;
cmd_hw_start.axi_vote = axi_vote;
rc = g_cpas_intf->hw_intf->hw_ops.start(
g_cpas_intf->hw_intf->hw_priv, &cmd_hw_start,
sizeof(struct cam_cpas_hw_cmd_start));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in start, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid start ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_start);
void cam_cpas_log_votes(bool ddr_only)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_LOG_VOTE, &ddr_only,
sizeof(ddr_only));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
}
}
EXPORT_SYMBOL(cam_cpas_log_votes);
int cam_cpas_select_qos_settings(uint32_t selection_mask)
{
int rc = 0;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -EBADR;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_SELECT_QOS, &selection_mask,
sizeof(selection_mask));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EBADR;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_select_qos_settings);
int cam_cpas_notify_event(const char *identifier_string,
int32_t identifier_value)
{
int rc = 0;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -EBADR;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
struct cam_cpas_hw_cmd_notify_event event = { 0 };
event.identifier_string = identifier_string;
event.identifier_value = identifier_value;
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_LOG_EVENT, &event,
sizeof(event));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EBADR;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_notify_event);
int cam_cpas_unregister_client(uint32_t client_handle)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_UNREGISTER_CLIENT,
&client_handle, sizeof(uint32_t));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_unregister_client);
int cam_cpas_register_client(
struct cam_cpas_register_params *register_params)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_REGISTER_CLIENT, register_params,
sizeof(struct cam_cpas_register_params));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_register_client);
int cam_cpas_get_scid(
enum cam_sys_cache_config_types type)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_GET_SCID, &type,
sizeof(type));
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_get_scid);
int cam_cpas_activate_llcc(
enum cam_sys_cache_config_types type)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_ACTIVATE_LLC, &type,
sizeof(type));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_activate_llcc);
int cam_cpas_deactivate_llcc(
enum cam_sys_cache_config_types type)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_DEACTIVATE_LLC, &type,
sizeof(type));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_deactivate_llcc);
int cam_cpas_configure_staling_llcc(
enum cam_sys_cache_config_types type,
enum cam_sys_cache_llcc_staling_mode mode_param,
enum cam_sys_cache_llcc_staling_op_type operation_type,
uint32_t staling_distance)
{
int rc;
struct cam_hw_info *cpas_hw = NULL;
struct cam_cpas_private_soc *soc_private = NULL;
uint32_t num_caches = 0;
struct cam_sys_cache_local_info sys_cache_info;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
cpas_hw = (struct cam_hw_info *) g_cpas_intf->hw_intf->hw_priv;
soc_private =
(struct cam_cpas_private_soc *)cpas_hw->soc_info.soc_private;
num_caches = soc_private->num_caches;
if (!cam_cpas_is_notif_staling_supported())
return -EOPNOTSUPP;
sys_cache_info.mode = mode_param;
sys_cache_info.op_type = operation_type;
sys_cache_info.staling_distance
= staling_distance;
sys_cache_info.type = type;
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_CONFIGURE_STALING_LLC, &sys_cache_info,
sizeof(struct cam_sys_cache_local_info));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_configure_staling_llcc);
int cam_cpas_notif_increment_staling_counter(
enum cam_sys_cache_config_types type)
{
int rc;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (!cam_cpas_is_notif_staling_supported())
return -EOPNOTSUPP;
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_NOTIF_STALL_INC_LLC, &type,
sizeof(type));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in process_cmd, rc=%d", rc);
} else {
CAM_ERR(CAM_CPAS, "Invalid process_cmd ops");
rc = -EINVAL;
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_notif_increment_staling_counter);
bool cam_cpas_is_notif_staling_supported(void)
{
#if IS_ENABLED(CONFIG_SPECTRA_LLCC_STALING)
return true;
#else
return false;
#endif
}
EXPORT_SYMBOL(cam_cpas_is_notif_staling_supported);
bool cam_cpas_query_domain_id_security_support(void)
{
struct cam_hw_info *cpas_hw = NULL;
struct cam_cpas_private_soc *soc_private = NULL;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return false;
}
cpas_hw = (struct cam_hw_info *) g_cpas_intf->hw_intf->hw_priv;
soc_private =
(struct cam_cpas_private_soc *)cpas_hw->soc_info.soc_private;
return soc_private->domain_id_info.domain_id_supported;
}
EXPORT_SYMBOL(cam_cpas_query_domain_id_security_support);
int cam_cpas_enable_clks_for_domain_id(bool enable)
{
int rc = 0;
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return -ENODEV;
}
if (g_cpas_intf->hw_intf->hw_ops.process_cmd) {
rc = g_cpas_intf->hw_intf->hw_ops.process_cmd(
g_cpas_intf->hw_intf->hw_priv,
CAM_CPAS_HW_CMD_ENABLE_DISABLE_DOMAIN_ID_CLK, &enable,
sizeof(enable));
}
return rc;
}
EXPORT_SYMBOL(cam_cpas_enable_clks_for_domain_id);
int cam_cpas_subdev_cmd(struct cam_cpas_intf *cpas_intf,
struct cam_control *cmd)
{
int rc = 0;
if (!cmd) {
CAM_ERR(CAM_CPAS, "Invalid input cmd");
return -EINVAL;
}
switch (cmd->op_code) {
case CAM_QUERY_CAP: {
struct cam_cpas_query_cap query;
rc = copy_from_user(&query, u64_to_user_ptr(cmd->handle),
sizeof(query));
if (rc) {
CAM_ERR(CAM_CPAS, "Failed in copy from user, rc=%d",
rc);
break;
}
rc = cam_cpas_get_hw_info(&query.camera_family,
&query.camera_version, &query.cpas_version,
&query.reserved, NULL, NULL);
if (rc)
break;
rc = copy_to_user(u64_to_user_ptr(cmd->handle), &query,
sizeof(query));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in copy to user, rc=%d", rc);
break;
}
case CAM_QUERY_CAP_V2: {
struct cam_cpas_query_cap_v2 query;
rc = copy_from_user(&query, u64_to_user_ptr(cmd->handle),
sizeof(query));
if (rc) {
CAM_ERR(CAM_CPAS, "Failed in copy from user, rc=%d",
rc);
break;
}
rc = cam_cpas_get_hw_info(&query.camera_family,
&query.camera_version, &query.cpas_version,
&query.reserved,
&query.fuse_info, NULL);
if (rc)
break;
rc = copy_to_user(u64_to_user_ptr(cmd->handle), &query,
sizeof(query));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in copy to user, rc=%d", rc);
break;
}
case CAM_QUERY_CAP_V3: {
struct cam_cpas_query_cap_v3 query;
rc = copy_from_user(&query, u64_to_user_ptr(cmd->handle),
sizeof(query));
if (rc) {
CAM_ERR(CAM_CPAS, "Failed in copy from user, rc=%d",
rc);
break;
}
rc = cam_cpas_get_hw_info(&query.camera_family,
&query.camera_version, &query.cpas_version,
&query.camera_caps, &query.fuse_info,
&query.domain_id_info);
if (rc)
break;
rc = copy_to_user(u64_to_user_ptr(cmd->handle), &query,
sizeof(query));
if (rc)
CAM_ERR(CAM_CPAS, "Failed in copy to user, rc=%d", rc);
break;
}
case CAM_SD_SHUTDOWN:
break;
default:
CAM_ERR(CAM_CPAS, "Unknown op code %d for CPAS", cmd->op_code);
rc = -EINVAL;
break;
}
return rc;
}
static int cam_cpas_subdev_open(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh)
{
struct cam_cpas_intf *cpas_intf = v4l2_get_subdevdata(sd);
if (!cpas_intf || !cpas_intf->probe_done) {
CAM_ERR(CAM_CPAS, "CPAS not initialized");
return -ENODEV;
}
mutex_lock(&cpas_intf->intf_lock);
cpas_intf->open_cnt++;
CAM_DBG(CAM_CPAS, "CPAS Subdev open count %d", cpas_intf->open_cnt);
mutex_unlock(&cpas_intf->intf_lock);
return 0;
}
static int __cam_cpas_subdev_close(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh)
{
struct cam_cpas_intf *cpas_intf = v4l2_get_subdevdata(sd);
if (!cpas_intf || !cpas_intf->probe_done) {
CAM_ERR(CAM_CPAS, "CPAS not initialized");
return -ENODEV;
}
mutex_lock(&cpas_intf->intf_lock);
if (cpas_intf->open_cnt <= 0) {
CAM_WARN(CAM_CPAS, "device already closed, open_cnt: %d", cpas_intf->open_cnt);
mutex_unlock(&cpas_intf->intf_lock);
return 0;
}
cpas_intf->open_cnt--;
CAM_DBG(CAM_CPAS, "CPAS Subdev close count %d", cpas_intf->open_cnt);
mutex_unlock(&cpas_intf->intf_lock);
return 0;
}
static int cam_cpas_subdev_close(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh)
{
bool crm_active = cam_req_mgr_is_open();
if (crm_active) {
CAM_DBG(CAM_CPAS, "CRM is ACTIVE, close should be from CRM");
return 0;
}
return __cam_cpas_subdev_close(sd, fh);
}
static long cam_cpas_subdev_ioctl(struct v4l2_subdev *sd,
unsigned int cmd, void *arg)
{
int32_t rc;
struct cam_cpas_intf *cpas_intf = v4l2_get_subdevdata(sd);
if (!cpas_intf || !cpas_intf->probe_done) {
CAM_ERR(CAM_CPAS, "CPAS not initialized");
return -ENODEV;
}
switch (cmd) {
case VIDIOC_CAM_CONTROL:
rc = cam_cpas_subdev_cmd(cpas_intf, (struct cam_control *) arg);
break;
case CAM_SD_SHUTDOWN:
rc = __cam_cpas_subdev_close(sd, NULL);
break;
default:
CAM_ERR(CAM_CPAS, "Invalid command %d for CPAS!", cmd);
rc = -EINVAL;
break;
}
return rc;
}
#ifdef CONFIG_COMPAT
static long cam_cpas_subdev_compat_ioctl(struct v4l2_subdev *sd,
unsigned int cmd, unsigned long arg)
{
struct cam_control cmd_data;
int32_t rc;
struct cam_cpas_intf *cpas_intf = v4l2_get_subdevdata(sd);
if (!cpas_intf || !cpas_intf->probe_done) {
CAM_ERR(CAM_CPAS, "CPAS not initialized");
return -ENODEV;
}
if (copy_from_user(&cmd_data, (void __user *)arg,
sizeof(cmd_data))) {
CAM_ERR(CAM_CPAS, "Failed to copy from user_ptr=%pK size=%zu",
(void __user *)arg, sizeof(cmd_data));
return -EFAULT;
}
switch (cmd) {
case VIDIOC_CAM_CONTROL:
rc = cam_cpas_subdev_cmd(cpas_intf, &cmd_data);
break;
default:
CAM_ERR(CAM_CPAS, "Invalid command %d for CPAS!", cmd);
rc = -EINVAL;
break;
}
if (!rc) {
if (copy_to_user((void __user *)arg, &cmd_data,
sizeof(cmd_data))) {
CAM_ERR(CAM_CPAS,
"Failed to copy to user_ptr=%pK size=%zu",
(void __user *)arg, sizeof(cmd_data));
rc = -EFAULT;
}
}
return rc;
}
#endif
static struct v4l2_subdev_core_ops cpas_subdev_core_ops = {
.ioctl = cam_cpas_subdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = cam_cpas_subdev_compat_ioctl,
#endif
};
static const struct v4l2_subdev_ops cpas_subdev_ops = {
.core = &cpas_subdev_core_ops,
};
static const struct v4l2_subdev_internal_ops cpas_subdev_intern_ops = {
.open = cam_cpas_subdev_open,
.close = cam_cpas_subdev_close,
};
static int cam_cpas_subdev_register(struct platform_device *pdev)
{
int rc;
struct cam_subdev *subdev;
if (!g_cpas_intf)
return -EINVAL;
subdev = &g_cpas_intf->subdev;
subdev->name = CAM_CPAS_DEV_NAME;
subdev->pdev = pdev;
subdev->ops = &cpas_subdev_ops;
subdev->internal_ops = &cpas_subdev_intern_ops;
subdev->token = g_cpas_intf;
subdev->sd_flags =
V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
subdev->ent_function = CAM_CPAS_DEVICE_TYPE;
subdev->close_seq_prior = CAM_SD_CLOSE_LOW_PRIORITY;
rc = cam_register_subdev(subdev);
if (rc) {
CAM_ERR(CAM_CPAS, "failed register subdev: %s!",
CAM_CPAS_DEV_NAME);
return rc;
}
platform_set_drvdata(g_cpas_intf->pdev, g_cpas_intf);
return rc;
}
static int cam_cpas_dev_component_bind(struct device *dev,
struct device *master_dev, void *data)
{
struct cam_cpas_hw_caps *hw_caps;
struct cam_hw_intf *hw_intf;
int rc;
struct platform_device *pdev = to_platform_device(dev);
if (g_cpas_intf) {
CAM_ERR(CAM_CPAS, "cpas component already binded");
return -EALREADY;
}
g_cpas_intf = kzalloc(sizeof(*g_cpas_intf), GFP_KERNEL);
if (!g_cpas_intf)
return -ENOMEM;
mutex_init(&g_cpas_intf->intf_lock);
g_cpas_intf->pdev = pdev;
rc = cam_cpas_hw_probe(pdev, &g_cpas_intf->hw_intf);
if (rc || (g_cpas_intf->hw_intf == NULL)) {
CAM_ERR(CAM_CPAS, "Failed in hw probe, rc=%d", rc);
goto error_destroy_mem;
}
hw_intf = g_cpas_intf->hw_intf;
hw_caps = &g_cpas_intf->hw_caps;
if (hw_intf->hw_ops.get_hw_caps) {
rc = hw_intf->hw_ops.get_hw_caps(hw_intf->hw_priv,
hw_caps, sizeof(struct cam_cpas_hw_caps));
if (rc) {
CAM_ERR(CAM_CPAS, "Failed in get_hw_caps, rc=%d", rc);
goto error_hw_remove;
}
} else {
CAM_ERR(CAM_CPAS, "Invalid get_hw_caps ops");
goto error_hw_remove;
}
rc = cam_cpas_subdev_register(pdev);
if (rc)
goto error_hw_remove;
g_cpas_intf->probe_done = true;
CAM_DBG(CAM_CPAS,
"Component bound successfully %d, %d.%d.%d, %d.%d.%d, 0x%x",
hw_caps->camera_family, hw_caps->camera_version.major,
hw_caps->camera_version.minor, hw_caps->camera_version.incr,
hw_caps->cpas_version.major, hw_caps->cpas_version.minor,
hw_caps->cpas_version.incr, hw_caps->camera_capability);
return rc;
error_hw_remove:
cam_cpas_hw_remove(g_cpas_intf->hw_intf);
error_destroy_mem:
mutex_destroy(&g_cpas_intf->intf_lock);
kfree(g_cpas_intf);
g_cpas_intf = NULL;
CAM_ERR(CAM_CPAS, "CPAS component bind failed");
return rc;
}
static void cam_cpas_dev_component_unbind(struct device *dev,
struct device *master_dev, void *data)
{
if (!CAM_CPAS_INTF_INITIALIZED()) {
CAM_ERR(CAM_CPAS, "cpas intf not initialized");
return;
}
mutex_lock(&g_cpas_intf->intf_lock);
g_cpas_intf->probe_done = false;
cam_unregister_subdev(&g_cpas_intf->subdev);
cam_cpas_hw_remove(g_cpas_intf->hw_intf);
mutex_unlock(&g_cpas_intf->intf_lock);
mutex_destroy(&g_cpas_intf->intf_lock);
kfree(g_cpas_intf);
g_cpas_intf = NULL;
}
const static struct component_ops cam_cpas_dev_component_ops = {
.bind = cam_cpas_dev_component_bind,
.unbind = cam_cpas_dev_component_unbind,
};
static int cam_cpas_dev_probe(struct platform_device *pdev)
{
int rc = 0;
CAM_DBG(CAM_CPAS, "Adding CPAS INTF component");
rc = component_add(&pdev->dev, &cam_cpas_dev_component_ops);
if (rc)
CAM_ERR(CAM_CPAS, "failed to add component rc: %d", rc);
return rc;
}
static int cam_cpas_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &cam_cpas_dev_component_ops);
return 0;
}
static const struct of_device_id cam_cpas_dt_match[] = {
{.compatible = "qcom,cam-cpas"},
{}
};
struct platform_driver cam_cpas_driver = {
.probe = cam_cpas_dev_probe,
.remove = cam_cpas_dev_remove,
.driver = {
.name = CAM_CPAS_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = cam_cpas_dt_match,
.suppress_bind_attrs = true,
},
};
int cam_cpas_dev_init_module(void)
{
return platform_driver_register(&cam_cpas_driver);
}
void cam_cpas_dev_exit_module(void)
{
platform_driver_unregister(&cam_cpas_driver);
}
MODULE_DESCRIPTION("MSM CPAS driver");
MODULE_LICENSE("GPL v2");