blob: 6d699cf965eb2c1253c16abb8737e43a50916a42 [file] [log] [blame]
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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 <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/ion.h>
#include <linux/iommu.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include "cam_soc_util.h"
#include "cam_smmu_api.h"
#include "cam_io_util.h"
#include "cam_cdm_intf_api.h"
#include "cam_cdm.h"
#include "cam_cdm_soc.h"
#include "cam_cdm_core_common.h"
static void cam_cdm_get_client_refcount(struct cam_cdm_client *client)
{
mutex_lock(&client->lock);
CAM_DBG(CAM_CDM, "CDM client get refcount=%d",
client->refcount);
client->refcount++;
mutex_unlock(&client->lock);
}
static void cam_cdm_put_client_refcount(struct cam_cdm_client *client)
{
mutex_lock(&client->lock);
CAM_DBG(CAM_CDM, "CDM client put refcount=%d",
client->refcount);
if (client->refcount > 0) {
client->refcount--;
} else {
CAM_ERR(CAM_CDM, "Refcount put when zero");
WARN_ON(1);
}
mutex_unlock(&client->lock);
}
bool cam_cdm_set_cam_hw_version(
uint32_t ver, struct cam_hw_version *cam_version)
{
switch (ver) {
case CAM_CDM170_VERSION:
cam_version->major = (ver & 0xF0000000);
cam_version->minor = (ver & 0xFFF0000);
cam_version->incr = (ver & 0xFFFF);
cam_version->reserved = 0;
return true;
default:
CAM_ERR(CAM_CDM, "CDM Version=%x not supported in util", ver);
break;
}
return false;
}
bool cam_cdm_cpas_cb(uint32_t client_handle, void *userdata,
struct cam_cpas_irq_data *irq_data)
{
if (!irq_data)
return false;
CAM_DBG(CAM_CDM, "CPAS error callback type=%d", irq_data->irq_type);
return false;
}
struct cam_cdm_utils_ops *cam_cdm_get_ops(
uint32_t ver, struct cam_hw_version *cam_version, bool by_cam_version)
{
if (by_cam_version == false) {
switch (ver) {
case CAM_CDM170_VERSION:
return &CDM170_ops;
default:
CAM_ERR(CAM_CDM, "CDM Version=%x not supported in util",
ver);
}
} else if (cam_version) {
if ((cam_version->major == 1) && (cam_version->minor == 0) &&
(cam_version->incr == 0))
return &CDM170_ops;
CAM_ERR(CAM_CDM, "cam_hw_version=%x:%x:%x not supported",
cam_version->major, cam_version->minor,
cam_version->incr);
}
return NULL;
}
struct cam_cdm_bl_cb_request_entry *cam_cdm_find_request_by_bl_tag(
uint32_t tag, struct list_head *bl_list)
{
struct cam_cdm_bl_cb_request_entry *node;
list_for_each_entry(node, bl_list, entry) {
if (node->bl_tag == tag)
return node;
}
CAM_ERR(CAM_CDM, "Could not find the bl request for tag=%x", tag);
return NULL;
}
int cam_cdm_get_caps(void *hw_priv,
void *get_hw_cap_args, uint32_t arg_size)
{
struct cam_hw_info *cdm_hw = hw_priv;
struct cam_cdm *cdm_core;
if ((cdm_hw) && (cdm_hw->core_info) && (get_hw_cap_args) &&
(sizeof(struct cam_iommu_handle) == arg_size)) {
cdm_core = (struct cam_cdm *)cdm_hw->core_info;
*((struct cam_iommu_handle *)get_hw_cap_args) =
cdm_core->iommu_hdl;
return 0;
}
return -EINVAL;
}
int cam_cdm_find_free_client_slot(struct cam_cdm *hw)
{
int i;
for (i = 0; i < CAM_PER_CDM_MAX_REGISTERED_CLIENTS; i++) {
if (hw->clients[i] == NULL) {
CAM_DBG(CAM_CDM, "Found client slot %d", i);
return i;
}
}
CAM_ERR(CAM_CDM, "No more client slots");
return -EBUSY;
}
void cam_cdm_notify_clients(struct cam_hw_info *cdm_hw,
enum cam_cdm_cb_status status, void *data)
{
int i;
struct cam_cdm *core = NULL;
struct cam_cdm_client *client = NULL;
if (!cdm_hw) {
CAM_ERR(CAM_CDM, "CDM Notify called with NULL hw info");
return;
}
core = (struct cam_cdm *)cdm_hw->core_info;
if (status == CAM_CDM_CB_STATUS_BL_SUCCESS) {
int client_idx;
struct cam_cdm_bl_cb_request_entry *node =
(struct cam_cdm_bl_cb_request_entry *)data;
client_idx = CAM_CDM_GET_CLIENT_IDX(node->client_hdl);
client = core->clients[client_idx];
if ((!client) || (client->handle != node->client_hdl)) {
CAM_ERR(CAM_CDM, "Invalid client %pK hdl=%x", client,
node->client_hdl);
return;
}
cam_cdm_get_client_refcount(client);
if (client->data.cam_cdm_callback) {
CAM_DBG(CAM_CDM, "Calling client=%s cb cookie=%d",
client->data.identifier, node->cookie);
client->data.cam_cdm_callback(node->client_hdl,
node->userdata, CAM_CDM_CB_STATUS_BL_SUCCESS,
node->cookie);
CAM_DBG(CAM_CDM, "Exit client cb cookie=%d",
node->cookie);
} else {
CAM_ERR(CAM_CDM, "No cb registered for client hdl=%x",
node->client_hdl);
}
cam_cdm_put_client_refcount(client);
return;
}
for (i = 0; i < CAM_PER_CDM_MAX_REGISTERED_CLIENTS; i++) {
if (core->clients[i] != NULL) {
client = core->clients[i];
mutex_lock(&client->lock);
CAM_DBG(CAM_CDM, "Found client slot %d", i);
if (client->data.cam_cdm_callback) {
if (status == CAM_CDM_CB_STATUS_PAGEFAULT) {
unsigned long iova =
(unsigned long)data;
client->data.cam_cdm_callback(
client->handle,
client->data.userdata,
CAM_CDM_CB_STATUS_PAGEFAULT,
(iova & 0xFFFFFFFF));
}
} else {
CAM_ERR(CAM_CDM,
"No cb registered for client hdl=%x",
client->handle);
}
mutex_unlock(&client->lock);
}
}
}
int cam_cdm_stream_ops_internal(void *hw_priv,
void *start_args, bool operation)
{
struct cam_hw_info *cdm_hw = hw_priv;
struct cam_cdm *core = NULL;
int rc = -EPERM;
int client_idx;
struct cam_cdm_client *client;
uint32_t *handle = start_args;
if (!hw_priv)
return -EINVAL;
core = (struct cam_cdm *)cdm_hw->core_info;
client_idx = CAM_CDM_GET_CLIENT_IDX(*handle);
client = core->clients[client_idx];
if (!client) {
CAM_ERR(CAM_CDM, "Invalid client %pK hdl=%x", client, *handle);
return -EINVAL;
}
cam_cdm_get_client_refcount(client);
if (*handle != client->handle) {
CAM_ERR(CAM_CDM, "client id given handle=%x invalid", *handle);
cam_cdm_put_client_refcount(client);
return -EINVAL;
}
if (operation == true) {
if (true == client->stream_on) {
CAM_ERR(CAM_CDM,
"Invalid CDM client is already streamed ON");
cam_cdm_put_client_refcount(client);
return rc;
}
} else {
if (client->stream_on == false) {
CAM_ERR(CAM_CDM,
"Invalid CDM client is already streamed Off");
cam_cdm_put_client_refcount(client);
return rc;
}
}
mutex_lock(&cdm_hw->hw_mutex);
if (operation == true) {
if (!cdm_hw->open_count) {
struct cam_ahb_vote ahb_vote;
struct cam_axi_vote axi_vote;
ahb_vote.type = CAM_VOTE_ABSOLUTE;
ahb_vote.vote.level = CAM_SVS_VOTE;
axi_vote.compressed_bw = CAM_CPAS_DEFAULT_AXI_BW;
axi_vote.uncompressed_bw = CAM_CPAS_DEFAULT_AXI_BW;
rc = cam_cpas_start(core->cpas_handle,
&ahb_vote, &axi_vote);
if (rc != 0) {
CAM_ERR(CAM_CDM, "CPAS start failed");
goto end;
}
CAM_DBG(CAM_CDM, "CDM init first time");
if (core->id == CAM_CDM_VIRTUAL) {
CAM_DBG(CAM_CDM,
"Virtual CDM HW init first time");
rc = 0;
} else {
CAM_DBG(CAM_CDM, "CDM HW init first time");
rc = cam_hw_cdm_init(hw_priv, NULL, 0);
if (rc == 0) {
rc = cam_hw_cdm_alloc_genirq_mem(
hw_priv);
if (rc != 0) {
CAM_ERR(CAM_CDM,
"Genirqalloc failed");
cam_hw_cdm_deinit(hw_priv,
NULL, 0);
}
} else {
CAM_ERR(CAM_CDM, "CDM HW init failed");
}
}
if (rc == 0) {
cdm_hw->open_count++;
client->stream_on = true;
} else {
if (cam_cpas_stop(core->cpas_handle))
CAM_ERR(CAM_CDM, "CPAS stop failed");
}
} else {
cdm_hw->open_count++;
CAM_DBG(CAM_CDM, "CDM HW already ON count=%d",
cdm_hw->open_count);
rc = 0;
client->stream_on = true;
}
} else {
if (cdm_hw->open_count) {
cdm_hw->open_count--;
CAM_DBG(CAM_CDM, "stream OFF CDM %d",
cdm_hw->open_count);
if (!cdm_hw->open_count) {
CAM_DBG(CAM_CDM, "CDM Deinit now");
if (core->id == CAM_CDM_VIRTUAL) {
CAM_DBG(CAM_CDM,
"Virtual CDM HW Deinit");
rc = 0;
} else {
CAM_DBG(CAM_CDM, "CDM HW Deinit now");
rc = cam_hw_cdm_deinit(
hw_priv, NULL, 0);
if (cam_hw_cdm_release_genirq_mem(
hw_priv))
CAM_ERR(CAM_CDM,
"Genirq release fail");
}
if (rc) {
CAM_ERR(CAM_CDM,
"Deinit failed in streamoff");
} else {
client->stream_on = false;
rc = cam_cpas_stop(core->cpas_handle);
if (rc)
CAM_ERR(CAM_CDM,
"CPAS stop failed");
}
} else {
client->stream_on = false;
rc = 0;
CAM_DBG(CAM_CDM,
"Client stream off success =%d",
cdm_hw->open_count);
}
} else {
CAM_DBG(CAM_CDM, "stream OFF CDM Invalid %d",
cdm_hw->open_count);
rc = -ENXIO;
}
}
end:
cam_cdm_put_client_refcount(client);
mutex_unlock(&cdm_hw->hw_mutex);
return rc;
}
int cam_cdm_stream_start(void *hw_priv,
void *start_args, uint32_t size)
{
int rc = 0;
if (!hw_priv)
return -EINVAL;
rc = cam_cdm_stream_ops_internal(hw_priv, start_args, true);
return rc;
}
int cam_cdm_stream_stop(void *hw_priv,
void *start_args, uint32_t size)
{
int rc = 0;
if (!hw_priv)
return -EINVAL;
rc = cam_cdm_stream_ops_internal(hw_priv, start_args, false);
return rc;
}
int cam_cdm_process_cmd(void *hw_priv,
uint32_t cmd, void *cmd_args, uint32_t arg_size)
{
struct cam_hw_info *cdm_hw = hw_priv;
struct cam_hw_soc_info *soc_data = NULL;
struct cam_cdm *core = NULL;
int rc = -EINVAL;
if ((!hw_priv) || (!cmd_args) ||
(cmd >= CAM_CDM_HW_INTF_CMD_INVALID))
return rc;
soc_data = &cdm_hw->soc_info;
core = (struct cam_cdm *)cdm_hw->core_info;
switch (cmd) {
case CAM_CDM_HW_INTF_CMD_SUBMIT_BL: {
struct cam_cdm_hw_intf_cmd_submit_bl *req;
int idx;
struct cam_cdm_client *client;
if (sizeof(struct cam_cdm_hw_intf_cmd_submit_bl) != arg_size) {
CAM_ERR(CAM_CDM, "Invalid CDM cmd %d arg size=%x", cmd,
arg_size);
break;
}
req = (struct cam_cdm_hw_intf_cmd_submit_bl *)cmd_args;
if ((req->data->type < 0) ||
(req->data->type > CAM_CDM_BL_CMD_TYPE_KERNEL_IOVA)) {
CAM_ERR(CAM_CDM, "Invalid req bl cmd addr type=%d",
req->data->type);
break;
}
idx = CAM_CDM_GET_CLIENT_IDX(req->handle);
client = core->clients[idx];
if ((!client) || (req->handle != client->handle)) {
CAM_ERR(CAM_CDM, "Invalid client %pK hdl=%x", client,
req->handle);
break;
}
cam_cdm_get_client_refcount(client);
if ((req->data->flag == true) &&
(!client->data.cam_cdm_callback)) {
CAM_ERR(CAM_CDM,
"CDM request cb without registering cb");
cam_cdm_put_client_refcount(client);
break;
}
if (client->stream_on != true) {
CAM_ERR(CAM_CDM,
"Invalid CDM needs to be streamed ON first");
cam_cdm_put_client_refcount(client);
break;
}
if (core->id == CAM_CDM_VIRTUAL)
rc = cam_virtual_cdm_submit_bl(cdm_hw, req, client);
else
rc = cam_hw_cdm_submit_bl(cdm_hw, req, client);
cam_cdm_put_client_refcount(client);
break;
}
case CAM_CDM_HW_INTF_CMD_ACQUIRE: {
struct cam_cdm_acquire_data *data;
int idx;
struct cam_cdm_client *client;
if (sizeof(struct cam_cdm_acquire_data) != arg_size) {
CAM_ERR(CAM_CDM, "Invalid CDM cmd %d arg size=%x", cmd,
arg_size);
break;
}
mutex_lock(&cdm_hw->hw_mutex);
data = (struct cam_cdm_acquire_data *)cmd_args;
CAM_DBG(CAM_CDM, "Trying to acquire client=%s in hw idx=%d",
data->identifier, core->index);
idx = cam_cdm_find_free_client_slot(core);
if ((idx < 0) || (core->clients[idx])) {
mutex_unlock(&cdm_hw->hw_mutex);
CAM_ERR(CAM_CDM,
"Fail to client slots, client=%s in hw idx=%d",
data->identifier, core->index);
break;
}
core->clients[idx] = kzalloc(sizeof(struct cam_cdm_client),
GFP_KERNEL);
if (!core->clients[idx]) {
mutex_unlock(&cdm_hw->hw_mutex);
rc = -ENOMEM;
break;
}
mutex_unlock(&cdm_hw->hw_mutex);
client = core->clients[idx];
mutex_init(&client->lock);
data->ops = core->ops;
if (core->id == CAM_CDM_VIRTUAL) {
data->cdm_version.major = 1;
data->cdm_version.minor = 0;
data->cdm_version.incr = 0;
data->cdm_version.reserved = 0;
data->ops = cam_cdm_get_ops(0,
&data->cdm_version, true);
if (!data->ops) {
mutex_destroy(&client->lock);
mutex_lock(&cdm_hw->hw_mutex);
kfree(core->clients[idx]);
core->clients[idx] = NULL;
mutex_unlock(
&cdm_hw->hw_mutex);
rc = -EPERM;
CAM_ERR(CAM_CDM, "Invalid ops for virtual cdm");
break;
}
} else {
data->cdm_version = core->version;
}
cam_cdm_get_client_refcount(client);
mutex_lock(&client->lock);
memcpy(&client->data, data,
sizeof(struct cam_cdm_acquire_data));
client->handle = CAM_CDM_CREATE_CLIENT_HANDLE(
core->index,
idx);
client->stream_on = false;
data->handle = client->handle;
CAM_DBG(CAM_CDM, "Acquired client=%s in hwidx=%d",
data->identifier, core->index);
mutex_unlock(&client->lock);
rc = 0;
break;
}
case CAM_CDM_HW_INTF_CMD_RELEASE: {
uint32_t *handle = cmd_args;
int idx;
struct cam_cdm_client *client;
if (sizeof(uint32_t) != arg_size) {
CAM_ERR(CAM_CDM,
"Invalid CDM cmd %d size=%x for handle=%x",
cmd, arg_size, *handle);
return -EINVAL;
}
idx = CAM_CDM_GET_CLIENT_IDX(*handle);
mutex_lock(&cdm_hw->hw_mutex);
client = core->clients[idx];
if ((!client) || (*handle != client->handle)) {
CAM_ERR(CAM_CDM, "Invalid client %pK hdl=%x",
client, *handle);
mutex_unlock(&cdm_hw->hw_mutex);
break;
}
cam_cdm_put_client_refcount(client);
mutex_lock(&client->lock);
if (client->refcount != 0) {
CAM_ERR(CAM_CDM, "CDM Client refcount not zero %d",
client->refcount);
rc = -EPERM;
mutex_unlock(&client->lock);
mutex_unlock(&cdm_hw->hw_mutex);
break;
}
core->clients[idx] = NULL;
mutex_unlock(&client->lock);
mutex_destroy(&client->lock);
kfree(client);
mutex_unlock(&cdm_hw->hw_mutex);
rc = 0;
break;
}
case CAM_CDM_HW_INTF_CMD_RESET_HW: {
CAM_ERR(CAM_CDM, "CDM HW reset not supported for handle =%x",
*((uint32_t *)cmd_args));
break;
}
default:
CAM_ERR(CAM_CDM, "CDM HW intf command not valid =%d", cmd);
break;
}
return rc;
}