// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.
 */
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <dsp/audio_cal_utils.h>

static int unmap_memory(struct cal_type_data *cal_type,
			struct cal_block_data *cal_block);

size_t get_cal_info_size(int32_t cal_type)
{
	size_t size = 0;
	size_t size1 = 0, size2 = 0;

	switch (cal_type) {
	case CVP_VOC_RX_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_voc_top);
		break;
	case CVP_VOC_TX_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_voc_top);
		break;
	case CVP_VOCPROC_STATIC_CAL_TYPE:
		size = sizeof(struct audio_cal_info_vocproc);
		break;
	case CVP_VOCPROC_DYNAMIC_CAL_TYPE:
		size = sizeof(struct audio_cal_info_vocvol);
		break;
	case CVS_VOCSTRM_STATIC_CAL_TYPE:
		size = 0;
		break;
	case CVP_VOCDEV_CFG_CAL_TYPE:
		size = sizeof(struct audio_cal_info_vocdev_cfg);
		break;
	case CVP_VOCPROC_STATIC_COL_CAL_TYPE:
		size = sizeof(struct audio_cal_info_voc_col);
		break;
	case CVP_VOCPROC_DYNAMIC_COL_CAL_TYPE:
		size = sizeof(struct audio_cal_info_voc_col);
		break;
	case CVS_VOCSTRM_STATIC_COL_CAL_TYPE:
		size = sizeof(struct audio_cal_info_voc_col);
		break;
	case ADM_TOPOLOGY_CAL_TYPE:
	case ADM_LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_adm_top);
		break;
	case ADM_CUST_TOPOLOGY_CAL_TYPE:
	case CORE_CUSTOM_TOPOLOGIES_CAL_TYPE:
		size = 0;
		break;
	case ADM_AUDPROC_CAL_TYPE:
	case ADM_LSM_AUDPROC_CAL_TYPE:
	case ADM_LSM_AUDPROC_PERSISTENT_CAL_TYPE:
		size = sizeof(struct audio_cal_info_audproc);
		break;
	case ADM_AUDVOL_CAL_TYPE:
	case ADM_RTAC_AUDVOL_CAL_TYPE:
		size = sizeof(struct audio_cal_info_audvol);
		break;
	case ASM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_asm_top);
		break;
	case ASM_CUST_TOPOLOGY_CAL_TYPE:
		size = 0;
		break;
	case ASM_AUDSTRM_CAL_TYPE:
		size = sizeof(struct audio_cal_info_audstrm);
		break;
	case AFE_TOPOLOGY_CAL_TYPE:
	case AFE_LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_afe_top);
		break;
	case AFE_CUST_TOPOLOGY_CAL_TYPE:
		size = 0;
		break;
	case AFE_COMMON_RX_CAL_TYPE:
		size = sizeof(struct audio_cal_info_afe);
		break;
	case AFE_COMMON_TX_CAL_TYPE:
	case AFE_LSM_TX_CAL_TYPE:
		size = sizeof(struct audio_cal_info_afe);
		break;
	case AFE_FB_SPKR_PROT_CAL_TYPE:
		size = sizeof(struct audio_cal_info_spk_prot_cfg);
		break;
	case AFE_FB_SPKR_PROT_TH_VI_CAL_TYPE:
		/*
		 * Since get and set parameter structures are different in size
		 * use the maximum size of get and set parameter structure
		 */
		size1 = max(sizeof(struct audio_cal_info_sp_th_vi_ftm_cfg),
			   sizeof(struct audio_cal_info_sp_th_vi_param));
		size2 = max(sizeof(struct audio_cal_info_sp_th_vi_v_vali_cfg),
			   sizeof(struct audio_cal_info_sp_th_vi_v_vali_param));
		size = max(size1, size2);
		break;
	case AFE_FB_SPKR_PROT_EX_VI_CAL_TYPE:
		/*
		 * Since get and set parameter structures are different in size
		 * use the maximum size of get and set parameter structure
		 */
		size = max(sizeof(struct audio_cal_info_sp_ex_vi_ftm_cfg),
			   sizeof(struct audio_cal_info_sp_ex_vi_param));
		break;
	case AFE_ANC_CAL_TYPE:
		size = 0;
		break;
	case AFE_AANC_CAL_TYPE:
		size = sizeof(struct audio_cal_info_aanc);
		break;
	case AFE_HW_DELAY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_hw_delay);
		break;
	case AFE_SIDETONE_CAL_TYPE:
		size = sizeof(struct audio_cal_info_sidetone);
		break;
	case AFE_SIDETONE_IIR_CAL_TYPE:
		size = sizeof(struct audio_cal_info_sidetone_iir);
		break;
	case LSM_CUST_TOPOLOGY_CAL_TYPE:
		size = 0;
		break;
	case LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_info_lsm_top);
		break;
	case ULP_LSM_TOPOLOGY_ID_CAL_TYPE:
		size = sizeof(struct audio_cal_info_lsm_top);
		break;
	case LSM_CAL_TYPE:
		size = sizeof(struct audio_cal_info_lsm);
		break;
	case ADM_RTAC_INFO_CAL_TYPE:
		size = 0;
		break;
	case VOICE_RTAC_INFO_CAL_TYPE:
		size = 0;
		break;
	case ADM_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case ASM_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case VOICE_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case MAD_CAL_TYPE:
		size = 0;
		break;
	case ULP_AFE_CAL_TYPE:
		size = sizeof(struct audio_cal_info_afe);
		break;
	case ULP_LSM_CAL_TYPE:
		size = sizeof(struct audio_cal_info_lsm);
		break;
	case AUDIO_CORE_METAINFO_CAL_TYPE:
		size = sizeof(struct audio_cal_info_metainfo);
		break;
	case SRS_TRUMEDIA_CAL_TYPE:
		size = 0;
		break;
	default:
		pr_err("%s:Invalid cal type %d!",
			__func__, cal_type);
	}
	return size;
}

size_t get_user_cal_type_size(int32_t cal_type)
{
	size_t size = 0;

	switch (cal_type) {
	case CVP_VOC_RX_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_voc_top);
		break;
	case CVP_VOC_TX_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_voc_top);
		break;
	case CVP_VOCPROC_STATIC_CAL_TYPE:
		size = sizeof(struct audio_cal_type_vocproc);
		break;
	case CVP_VOCPROC_DYNAMIC_CAL_TYPE:
		size = sizeof(struct audio_cal_type_vocvol);
		break;
	case CVS_VOCSTRM_STATIC_CAL_TYPE:
		size = sizeof(struct audio_cal_type_basic);
		break;
	case CVP_VOCDEV_CFG_CAL_TYPE:
		size = sizeof(struct audio_cal_type_vocdev_cfg);
		break;
	case CVP_VOCPROC_STATIC_COL_CAL_TYPE:
	case CVP_VOCPROC_DYNAMIC_COL_CAL_TYPE:
	case CVS_VOCSTRM_STATIC_COL_CAL_TYPE:
		size = sizeof(struct audio_cal_type_voc_col);
		break;
	case ADM_TOPOLOGY_CAL_TYPE:
	case ADM_LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_adm_top);
		break;
	case ADM_CUST_TOPOLOGY_CAL_TYPE:
	case CORE_CUSTOM_TOPOLOGIES_CAL_TYPE:
		size = sizeof(struct audio_cal_type_basic);
		break;
	case ADM_AUDPROC_CAL_TYPE:
	case ADM_LSM_AUDPROC_CAL_TYPE:
	case ADM_LSM_AUDPROC_PERSISTENT_CAL_TYPE:
		size = sizeof(struct audio_cal_type_audproc);
		break;
	case ADM_AUDVOL_CAL_TYPE:
	case ADM_RTAC_AUDVOL_CAL_TYPE:
		size = sizeof(struct audio_cal_type_audvol);
		break;
	case ASM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_asm_top);
		break;
	case ASM_CUST_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_basic);
		break;
	case ASM_AUDSTRM_CAL_TYPE:
		size = sizeof(struct audio_cal_type_audstrm);
		break;
	case AFE_TOPOLOGY_CAL_TYPE:
	case AFE_LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_afe_top);
		break;
	case AFE_CUST_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_basic);
		break;
	case AFE_COMMON_RX_CAL_TYPE:
		size = sizeof(struct audio_cal_type_afe);
		break;
	case AFE_COMMON_TX_CAL_TYPE:
	case AFE_LSM_TX_CAL_TYPE:
		size = sizeof(struct audio_cal_type_afe);
		break;
	case AFE_FB_SPKR_PROT_CAL_TYPE:
		size = sizeof(struct audio_cal_type_fb_spk_prot_cfg);
		break;
	case AFE_FB_SPKR_PROT_TH_VI_CAL_TYPE:
		/*
		 * Since get and set parameter structures are different in size
		 * use the maximum size of get and set parameter structure
		 */
		size = max(sizeof(struct audio_cal_type_sp_th_vi_ftm_cfg),
			   sizeof(struct audio_cal_type_sp_th_vi_param));
		break;
	case AFE_FB_SPKR_PROT_EX_VI_CAL_TYPE:
		/*
		 * Since get and set parameter structures are different in size
		 * use the maximum size of get and set parameter structure
		 */
		size = max(sizeof(struct audio_cal_type_sp_ex_vi_ftm_cfg),
			   sizeof(struct audio_cal_type_sp_ex_vi_param));
		break;
	case AFE_ANC_CAL_TYPE:
		size = 0;
		break;
	case AFE_AANC_CAL_TYPE:
		size = sizeof(struct audio_cal_type_aanc);
		break;
	case AFE_HW_DELAY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_hw_delay);
		break;
	case AFE_SIDETONE_CAL_TYPE:
		size = sizeof(struct audio_cal_type_sidetone);
		break;
	case AFE_SIDETONE_IIR_CAL_TYPE:
		size = sizeof(struct audio_cal_type_sidetone_iir);
		break;
	case LSM_CUST_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_basic);
		break;
	case LSM_TOPOLOGY_CAL_TYPE:
		size = sizeof(struct audio_cal_type_lsm_top);
		break;
	case ULP_LSM_TOPOLOGY_ID_CAL_TYPE:
		size = sizeof(struct audio_cal_type_lsm_top);
		break;
	case LSM_CAL_TYPE:
		size = sizeof(struct audio_cal_type_lsm);
		break;
	case ADM_RTAC_INFO_CAL_TYPE:
		size = 0;
		break;
	case VOICE_RTAC_INFO_CAL_TYPE:
		size = 0;
		break;
	case ADM_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case ASM_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case VOICE_RTAC_APR_CAL_TYPE:
		size = 0;
		break;
	case MAD_CAL_TYPE:
		size = 0;
		break;
	case ULP_AFE_CAL_TYPE:
		size = sizeof(struct audio_cal_type_afe);
		break;
	case ULP_LSM_CAL_TYPE:
		size = sizeof(struct audio_cal_type_lsm);
		break;
	case AUDIO_CORE_METAINFO_CAL_TYPE:
		size = sizeof(struct audio_cal_type_metainfo);
		break;
	case SRS_TRUMEDIA_CAL_TYPE:
		size = 0;
		break;
	default:
		pr_err("%s:Invalid cal type %d!",
			__func__, cal_type);
	}
	return size;
}

int32_t cal_utils_get_cal_type_version(void *cal_type_data)
{
	struct audio_cal_type_basic *data = NULL;

	data = (struct audio_cal_type_basic *)cal_type_data;

	return data->cal_hdr.version;
}

static struct cal_type_data *create_cal_type_data(
				struct cal_type_info *info)
{
	struct cal_type_data	*cal_type = NULL;

	if ((info->reg.cal_type < 0) ||
		(info->reg.cal_type >= MAX_CAL_TYPES)) {
		pr_err("%s: cal type %d is Invalid!\n",
			__func__, info->reg.cal_type);
		goto done;
	}

	if (info->cal_util_callbacks.match_block == NULL) {
		pr_err("%s: cal type %d no method to match blocks!\n",
			__func__, info->reg.cal_type);
		goto done;
	}

	cal_type = kmalloc(sizeof(*cal_type), GFP_KERNEL);
	if (cal_type == NULL)
		goto done;

	INIT_LIST_HEAD(&cal_type->cal_blocks);
	mutex_init(&cal_type->lock);
	memcpy(&cal_type->info, info,
		sizeof(cal_type->info));
done:
	return cal_type;
}

/**
 * cal_utils_create_cal_types
 *
 * @num_cal_types: number of types
 * @cal_type: pointer to the cal types pointer
 * @info: pointer to info
 *
 * Returns 0 on success, EINVAL otherwise
 */
int cal_utils_create_cal_types(int num_cal_types,
			struct cal_type_data **cal_type,
			struct cal_type_info *info)
{
	int ret = 0;
	int i;

	pr_debug("%s\n", __func__);

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n", __func__);
		ret = -EINVAL;
		goto done;
	} else if (info == NULL) {
		pr_err("%s: info is NULL!\n", __func__);
		ret = -EINVAL;
		goto done;
	} else if ((num_cal_types <= 0) ||
		(num_cal_types > MAX_CAL_TYPES)) {
		pr_err("%s: num_cal_types of %d is Invalid!\n",
			__func__, num_cal_types);
		ret = -EINVAL;
		goto done;
	}

	for (i = 0; i < num_cal_types; i++) {
		if ((info[i].reg.cal_type < 0) ||
			(info[i].reg.cal_type >= MAX_CAL_TYPES)) {
			pr_err("%s: cal type %d at index %d is Invalid!\n",
				__func__, info[i].reg.cal_type, i);
			ret = -EINVAL;
			goto done;
		}

		cal_type[i] = create_cal_type_data(&info[i]);
		if (cal_type[i] == NULL) {
			pr_err("%s: Could not allocate cal_type of index %d!\n",
				__func__, i);
			ret = -EINVAL;
			goto done;
		}

		ret = audio_cal_register(1, &info[i].reg);
		if (ret < 0) {
			pr_err("%s: audio_cal_register failed, ret = %d!\n",
				__func__, ret);
			ret = -EINVAL;
			goto done;
		}
		pr_debug("%s: cal type %d at index %d!\n",
			__func__, info[i].reg.cal_type, i);
	}
done:
	return ret;
}
EXPORT_SYMBOL(cal_utils_create_cal_types);

static void delete_cal_block(struct cal_block_data *cal_block)
{
	pr_debug("%s\n", __func__);

	if (cal_block == NULL)
		goto done;

	list_del(&cal_block->list);
	kfree(cal_block->client_info);
	cal_block->client_info = NULL;
	kfree(cal_block->cal_info);
	cal_block->cal_info = NULL;
	if (cal_block->map_data.dma_buf  != NULL) {
		msm_audio_ion_free(cal_block->map_data.dma_buf);
		cal_block->map_data.dma_buf = NULL;
	}
	kfree(cal_block);
done:
	return;
}

static void destroy_all_cal_blocks(struct cal_type_data *cal_type)
{
	int ret = 0;
	struct list_head *ptr, *next;
	struct cal_block_data *cal_block;

	list_for_each_safe(ptr, next,
		&cal_type->cal_blocks) {

		cal_block = list_entry(ptr,
			struct cal_block_data, list);

		ret = unmap_memory(cal_type, cal_block);
		if (ret < 0) {
			pr_err("%s: unmap_memory failed, cal type %d, ret = %d!\n",
				__func__,
			       cal_type->info.reg.cal_type,
				ret);
		}
		delete_cal_block(cal_block);
		cal_block = NULL;
	}
}

static void destroy_cal_type_data(struct cal_type_data *cal_type)
{
	if (cal_type == NULL)
		goto done;

	destroy_all_cal_blocks(cal_type);
	list_del(&cal_type->cal_blocks);
	kfree(cal_type);
done:
	return;
}

/**
 * cal_utils_destroy_cal_types -
 *        Destroys cal types and deregister from cal info
 *
 * @num_cal_types: number of cal types
 * @cal_type: cal type pointer with cal info
 *
 */
void cal_utils_destroy_cal_types(int num_cal_types,
			struct cal_type_data **cal_type)
{
	int i;

	pr_debug("%s\n", __func__);

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n", __func__);
		goto done;
	} else if ((num_cal_types <= 0) ||
		(num_cal_types > MAX_CAL_TYPES)) {
		pr_err("%s: num_cal_types of %d is Invalid!\n",
			__func__, num_cal_types);
		goto done;
	}

	for (i = 0; i < num_cal_types; i++) {
		audio_cal_deregister(1, &cal_type[i]->info.reg);
		destroy_cal_type_data(cal_type[i]);
		cal_type[i] = NULL;
	}
done:
	return;
}
EXPORT_SYMBOL(cal_utils_destroy_cal_types);

/**
 * cal_utils_get_only_cal_block
 *
 * @cal_type: pointer to the cal type
 *
 * Returns cal_block structure
 */
struct cal_block_data *cal_utils_get_only_cal_block(
			struct cal_type_data *cal_type)
{
	struct list_head		*ptr, *next;
	struct cal_block_data		*cal_block = NULL;

	if (cal_type == NULL)
		goto done;

	list_for_each_safe(ptr, next,
		&cal_type->cal_blocks) {

		cal_block = list_entry(ptr,
			struct cal_block_data, list);
		break;
	}
done:
	return cal_block;
}
EXPORT_SYMBOL(cal_utils_get_only_cal_block);

/**
 * cal_utils_get_only_cal_block
 *
 * @cal_block: pointer to cal block struct
 * @user_data: pointer to user data
 *
 * Returns true on match
 */
bool cal_utils_match_buf_num(struct cal_block_data *cal_block,
					void *user_data)
{
	bool ret = false;
	struct audio_cal_type_basic	*data = user_data;

	if (cal_block->buffer_number == data->cal_hdr.buffer_number)
		ret = true;

	return ret;
}
EXPORT_SYMBOL(cal_utils_match_buf_num);

static struct cal_block_data *get_matching_cal_block(
					struct cal_type_data *cal_type,
					void *data)
{
	struct list_head		*ptr, *next;
	struct cal_block_data		*cal_block = NULL;

	list_for_each_safe(ptr, next,
		&cal_type->cal_blocks) {

		cal_block = list_entry(ptr,
			struct cal_block_data, list);

		if (cal_type->info.cal_util_callbacks.
			match_block(cal_block, data))
			return cal_block;
	}

	return NULL;
}

static int cal_block_ion_alloc(struct cal_block_data *cal_block)
{
	int	ret = 0;

	if (cal_block == NULL) {
		pr_err("%s: cal_block is NULL!\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	ret = msm_audio_ion_import(&cal_block->map_data.dma_buf,
		cal_block->map_data.ion_map_handle,
		NULL, 0,
		&cal_block->cal_data.paddr,
		&cal_block->map_data.map_size,
		&cal_block->cal_data.kvaddr);
	if (ret) {
		pr_err("%s: audio ION import failed, rc = %d\n",
			__func__, ret);
		ret = -ENOMEM;
		goto done;
	}
done:
	return ret;
}

static struct cal_block_data *create_cal_block(struct cal_type_data *cal_type,
				struct audio_cal_type_basic *basic_cal,
				size_t client_info_size, void *client_info)
{
	struct cal_block_data	*cal_block = NULL;

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n", __func__);
		goto done;
	} else if (basic_cal == NULL) {
		pr_err("%s: basic_cal is NULL!\n", __func__);
		goto done;
	}

	cal_block = kzalloc(sizeof(*cal_block),
		GFP_KERNEL);
	if (cal_block == NULL)
		goto done;

	INIT_LIST_HEAD(&cal_block->list);

	cal_block->map_data.ion_map_handle = basic_cal->cal_data.mem_handle;
	if (basic_cal->cal_data.mem_handle > 0) {
		if (cal_block_ion_alloc(cal_block)) {
			pr_err("%s: cal_block_ion_alloc failed!\n",
				__func__);
			goto err;
		}
	}
	if (client_info_size > 0) {
		cal_block->client_info_size = client_info_size;
		cal_block->client_info = kmalloc(client_info_size, GFP_KERNEL);
		if (cal_block->client_info == NULL) {
			pr_err("%s: could not allocats client_info!\n",
				__func__);
			goto err;
		}
		if (client_info != NULL)
			memcpy(cal_block->client_info, client_info,
				client_info_size);
	}

	cal_block->cal_info = kzalloc(
		get_cal_info_size(cal_type->info.reg.cal_type),
		GFP_KERNEL);
	if (cal_block->cal_info == NULL) {
		pr_err("%s: could not allocats cal_info!\n",
			__func__);
		goto err;
	}
	cal_block->buffer_number = basic_cal->cal_hdr.buffer_number;
	list_add_tail(&cal_block->list, &cal_type->cal_blocks);
	pr_debug("%s: created block for cal type %d, buf num %d, map handle %d, map size %zd paddr 0x%pK!\n",
		__func__, cal_type->info.reg.cal_type,
		cal_block->buffer_number,
		cal_block->map_data.ion_map_handle,
		cal_block->map_data.map_size,
		&cal_block->cal_data.paddr);
done:
	return cal_block;
err:
	kfree(cal_block->cal_info);
	cal_block->cal_info = NULL;
	kfree(cal_block->client_info);
	cal_block->client_info = NULL;
	kfree(cal_block);
	cal_block = NULL;
	return cal_block;
}

void cal_utils_clear_cal_block_q6maps(int num_cal_types,
					struct cal_type_data **cal_type)
{
	int i = 0;
	struct list_head *ptr, *next;
	struct cal_block_data *cal_block;

	pr_debug("%s\n", __func__);

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n", __func__);
		goto done;
	} else if ((num_cal_types <= 0) ||
		(num_cal_types > MAX_CAL_TYPES)) {
		pr_err("%s: num_cal_types of %d is Invalid!\n",
			__func__, num_cal_types);
		goto done;
	}

	for (; i < num_cal_types; i++) {
		if (cal_type[i] == NULL)
			continue;

		mutex_lock(&cal_type[i]->lock);
		list_for_each_safe(ptr, next,
			&cal_type[i]->cal_blocks) {

			cal_block = list_entry(ptr,
				struct cal_block_data, list);

			cal_block->map_data.q6map_handle = 0;
		}
		mutex_unlock(&cal_type[i]->lock);
	}
done:
	return;
}



static int realloc_memory(struct cal_block_data *cal_block)
{
	int ret = 0;

	msm_audio_ion_free(cal_block->map_data.dma_buf);
	cal_block->map_data.dma_buf = NULL;
	cal_block->cal_data.size = 0;

	ret = cal_block_ion_alloc(cal_block);
	if (ret < 0)
		pr_err("%s: realloc_memory failed!\n",
			__func__);
	return ret;
}

static int map_memory(struct cal_type_data *cal_type,
			struct cal_block_data *cal_block)
{
	int ret = 0;


	if (cal_type->info.cal_util_callbacks.map_cal != NULL) {
		if ((cal_block->map_data.ion_map_handle < 0) ||
			(cal_block->map_data.map_size <= 0) ||
			(cal_block->map_data.q6map_handle != 0)) {
			goto done;
		}

		pr_debug("%s: cal type %d call map\n",
			__func__, cal_type->info.reg.cal_type);
		ret = cal_type->info.cal_util_callbacks.
			map_cal(cal_type->info.reg.cal_type, cal_block);
		if (ret < 0) {
			pr_err("%s: map_cal failed, cal type %d, ret = %d!\n",
				__func__, cal_type->info.reg.cal_type,
				ret);
			goto done;
		}
	}
done:
	return ret;
}

static int unmap_memory(struct cal_type_data *cal_type,
			struct cal_block_data *cal_block)
{
	int ret = 0;

	if (cal_type->info.cal_util_callbacks.unmap_cal != NULL) {
		if ((cal_block->map_data.ion_map_handle < 0) ||
			(cal_block->map_data.map_size <= 0) ||
			(cal_block->map_data.q6map_handle == 0)) {
			goto done;
		}
		pr_debug("%s: cal type %d call unmap\n",
			__func__, cal_type->info.reg.cal_type);
		ret = cal_type->info.cal_util_callbacks.
			unmap_cal(cal_type->info.reg.cal_type, cal_block);
		if (ret < 0) {
			pr_err("%s: unmap_cal failed, cal type %d, ret = %d!\n",
				__func__, cal_type->info.reg.cal_type,
				ret);
			goto done;
		}
	}
done:
	return ret;
}

/**
 * cal_utils_alloc_cal
 *
 * @data_size: size of data to allocate
 * @data: data pointer
 * @cal_type: pointer to the cal type
 * @client_info_size: client info size
 * @client_info: pointer to client info
 *
 * Returns 0 on success, appropriate error code otherwise
 */
int cal_utils_alloc_cal(size_t data_size, void *data,
			struct cal_type_data *cal_type,
			size_t client_info_size, void *client_info)
{
	int ret = 0;
	struct cal_block_data *cal_block;
	struct audio_cal_type_alloc *alloc_data = data;

	pr_debug("%s\n", __func__);

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n",
			__func__);
		ret = -EINVAL;
		goto done;
	}
	if (data_size < sizeof(struct audio_cal_type_alloc)) {
		pr_err("%s: data_size of %zd does not equal alloc struct size of %zd!\n",
			__func__, data_size,
		       sizeof(struct audio_cal_type_alloc));
		ret = -EINVAL;
		goto done;
	}
	if ((client_info_size > 0) && (client_info == NULL)) {
		pr_err("%s: User info pointer is NULL but size is %zd!\n",
			__func__, client_info_size);
		ret = -EINVAL;
		goto done;
	}

	if (alloc_data->cal_data.mem_handle < 0) {
		pr_err("%s: mem_handle %d invalid!\n",
			__func__, alloc_data->cal_data.mem_handle);
		ret = -EINVAL;
		goto done;
	}

	mutex_lock(&cal_type->lock);

	cal_block = get_matching_cal_block(cal_type,
		data);
	if (cal_block != NULL) {
		ret = unmap_memory(cal_type, cal_block);
		if (ret < 0)
			goto err;
		ret = realloc_memory(cal_block);
		if (ret < 0)
			goto err;
	} else {
		cal_block = create_cal_block(cal_type,
			(struct audio_cal_type_basic *)alloc_data,
			client_info_size, client_info);
		if (cal_block == NULL) {
			pr_err("%s: create_cal_block failed for %d!\n",
				__func__, alloc_data->cal_data.mem_handle);
			ret = -EINVAL;
			goto err;
		}
	}

	ret = map_memory(cal_type, cal_block);
	if (ret < 0)
		goto err;
err:
	mutex_unlock(&cal_type->lock);
done:
	return ret;
}
EXPORT_SYMBOL(cal_utils_alloc_cal);

/**
 * cal_utils_dealloc_cal
 *
 * @data_size: size of data to allocate
 * @data: data pointer
 * @cal_type: pointer to the cal type
 *
 * Returns 0 on success, appropriate error code otherwise
 */
int cal_utils_dealloc_cal(size_t data_size, void *data,
			struct cal_type_data *cal_type)
{
	int ret = 0;
	struct cal_block_data *cal_block;
	struct audio_cal_type_dealloc *dealloc_data = data;

	pr_debug("%s\n", __func__);


	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n",
			__func__);
		ret = -EINVAL;
		goto done;
	}

	if (data_size < sizeof(struct audio_cal_type_dealloc)) {
		pr_err("%s: data_size of %zd does not equal struct size of %zd!\n",
			__func__, data_size,
			sizeof(struct audio_cal_type_dealloc));
		ret = -EINVAL;
		goto done;
	}

	if ((dealloc_data->cal_data.mem_handle == -1) &&
		(dealloc_data->cal_hdr.buffer_number == ALL_CAL_BLOCKS)) {
		destroy_all_cal_blocks(cal_type);
		goto done;
	}

	if (dealloc_data->cal_data.mem_handle < 0) {
		pr_err("%s: mem_handle %d invalid!\n",
			__func__, dealloc_data->cal_data.mem_handle);
		ret = -EINVAL;
		goto done;
	}

	mutex_lock(&cal_type->lock);
	cal_block = get_matching_cal_block(
		cal_type,
		data);
	if (cal_block == NULL) {
		pr_err("%s: allocation does not exist for %d!\n",
			__func__, dealloc_data->cal_data.mem_handle);
		ret = -EINVAL;
		goto err;
	}

	ret = unmap_memory(cal_type, cal_block);
	if (ret < 0)
		goto err;

	delete_cal_block(cal_block);
err:
	mutex_unlock(&cal_type->lock);
done:
	return ret;
}
EXPORT_SYMBOL(cal_utils_dealloc_cal);

/**
 * cal_utils_set_cal
 *
 * @data_size: size of data to allocate
 * @data: data pointer
 * @cal_type: pointer to the cal type
 * @client_info_size: client info size
 * @client_info: pointer to client info
 *
 * Returns 0 on success, appropriate error code otherwise
 */
int cal_utils_set_cal(size_t data_size, void *data,
			struct cal_type_data *cal_type,
			size_t client_info_size, void *client_info)
{
	int ret = 0;
	struct cal_block_data *cal_block;
	struct audio_cal_type_basic *basic_data = data;

	pr_debug("%s\n", __func__);

	if (cal_type == NULL) {
		pr_err("%s: cal_type is NULL!\n",
			__func__);
		ret = -EINVAL;
		goto done;
	}

	if ((client_info_size > 0) && (client_info == NULL)) {
		pr_err("%s: User info pointer is NULL but size is %zd!\n",
			__func__, client_info_size);
		ret = -EINVAL;
		goto done;
	}

	if ((data_size > get_user_cal_type_size(
		cal_type->info.reg.cal_type)) || (data_size < 0)) {
		pr_err("%s: cal_type %d, data_size of %zd is invalid, expecting %zd!\n",
			__func__, cal_type->info.reg.cal_type, data_size,
			get_user_cal_type_size(cal_type->info.reg.cal_type));
		ret = -EINVAL;
		goto done;
	}

	mutex_lock(&cal_type->lock);
	cal_block = get_matching_cal_block(
		cal_type,
		data);
	if (cal_block == NULL) {
		if (basic_data->cal_data.mem_handle > 0) {
			pr_err("%s: allocation does not exist for %d!\n",
				__func__, basic_data->cal_data.mem_handle);
			ret = -EINVAL;
			goto err;
		} else {
			cal_block = create_cal_block(
				cal_type,
				basic_data,
				client_info_size, client_info);
			if (cal_block == NULL) {
				pr_err("%s: create_cal_block failed for cal type %d!\n",
					__func__,
				       cal_type->info.reg.cal_type);
				ret = -EINVAL;
				goto err;
			}
		}
	}

	ret = map_memory(cal_type, cal_block);
	if (ret < 0)
		goto err;

	cal_block->cal_data.size = basic_data->cal_data.cal_size;

	if (client_info_size > 0) {
		memcpy(cal_block->client_info,
			client_info,
			client_info_size);
	}

	memcpy(cal_block->cal_info,
		((uint8_t *)data + sizeof(struct audio_cal_type_basic)),
		data_size - sizeof(struct audio_cal_type_basic));

	/* reset buffer stale flag */
	cal_block->cal_stale = false;

err:
	mutex_unlock(&cal_type->lock);
done:
	return ret;
}
EXPORT_SYMBOL(cal_utils_set_cal);

/**
 * cal_utils_mark_cal_used
 *
 * @cal_block: pointer to cal block
 */
void cal_utils_mark_cal_used(struct cal_block_data *cal_block)
{
	if (cal_block)
		cal_block->cal_stale = true;
}
EXPORT_SYMBOL(cal_utils_mark_cal_used);

/**
 * cal_utils_is_cal_stale
 *
 * @cal_block: pointer to cal block
 *
 * Returns true if cal block is stale, false otherwise
 */
bool cal_utils_is_cal_stale(struct cal_block_data *cal_block)
{
	if ((cal_block) && (cal_block->cal_stale))
		return true;

	return false;
}
EXPORT_SYMBOL(cal_utils_is_cal_stale);