// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/types.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/dev_printk.h>
#include "dsp_client_api.h"

int Mbox_invoke(struct device *dev, u32 dspid, int cmd, void *data, u32 len)
{
	int ret = 0;

	ret = mbox_message_send_data_sync(dev, cmd, data, len, dspid);
	if (ret < 0)
		dev_err(dev, "Mbox invoke write error: %d\n", ret);
	return ret;
}

static u32 pcm_client_format_to_bytes(enum DSP_PCM_FORMAT format)
{
	switch (format) {
	case DSP_PCM_FORMAT_S32_LE:
	case DSP_PCM_FORMAT_S32_BE:
	case DSP_PCM_FORMAT_S24_LE:
	case DSP_PCM_FORMAT_S24_BE:
		return 32 >> 3;		/*4 Bytes*/
	case DSP_PCM_FORMAT_S24_3LE:
	case DSP_PCM_FORMAT_S24_3BE:
		return 24 >> 3;		/*3 Bytes*/
	default:
	case DSP_PCM_FORMAT_S16_LE:
	case DSP_PCM_FORMAT_S16_BE:
		return 16 >> 3;		/*2 Bytes*/
	case DSP_PCM_FORMAT_S8:
		return 8 >> 3;		/*1 Bytes*/
	};
}

u32 pcm_client_frame_to_bytes(void           *hdl, u32 frame)
{
	struct aml_pcm_ctx *p_aml_pcm_ctx = (struct aml_pcm_ctx *)hdl;
	int bytes_per_sample = pcm_client_format_to_bytes(p_aml_pcm_ctx->config.format);

	return (frame * bytes_per_sample * (p_aml_pcm_ctx->config.channels));
}

void *pcm_client_open(u32 card, u32 device, u32 flags, struct rpc_pcm_config *config,
		struct device *dev, u32 dspid)
{
	struct pcm_open_st arg;
	struct aml_pcm_ctx *p_aml_pcm_ctx = kmalloc(sizeof(*p_aml_pcm_ctx), GFP_KERNEL);

	memset(p_aml_pcm_ctx, 0, sizeof(struct aml_pcm_ctx));
	memset(&arg, 0, sizeof(arg));
	arg.card = card;
	arg.device = device;
	arg.flags = flags;
	memcpy(&arg.pcm_config, config, sizeof(struct rpc_pcm_config));
	arg.out_pcm = 0;

	Mbox_invoke(dev, dspid, MBX_TINYALSA_OPEN, &arg, sizeof(arg));
	if (!arg.out_pcm) {
		kfree(p_aml_pcm_ctx);
		return NULL;
	}
	p_aml_pcm_ctx->pcm_srv_hdl = arg.out_pcm;
	memcpy(&p_aml_pcm_ctx->config, config, sizeof(struct rpc_pcm_config));
	return p_aml_pcm_ctx;
}

int pcm_client_close(void        *hdl, struct device *dev, u32 dspid)
{
	struct pcm_close_st arg;
	struct aml_pcm_ctx *p_aml_pcm_ctx = (struct aml_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, MBX_TINYALSA_CLOSE, &arg, sizeof(arg));

	kfree(p_aml_pcm_ctx);
	return arg.out_ret;
}

int pcm_client_writei(void *hdl, const phys_addr_t data, u32 count,
		struct device *dev, u32 dspid)
{
	struct pcm_io_st arg;
	struct aml_pcm_ctx *p_aml_pcm_ctx = (struct aml_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.data = (u64)data;
	arg.count = count;
	arg.out_ret = 0;

	Mbox_invoke(dev, dspid, MBX_TINYALSA_WRITEI, &arg, sizeof(arg));

	return arg.out_ret;
}

int pcm_process_client_writei_to_speaker(void *hdl, const phys_addr_t data, u32 count,
		u32 bypass, struct device *dev, u32 dspid)
{
	struct pcm_process_io_st arg;
	struct aml_pcm_ctx *p_aml_pcm_ctx = (struct aml_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.data = (u64)data;
	arg.count = count;
	arg.out_ret = 0;
	arg.id = bypass;

	Mbox_invoke(dev, dspid, MBX_CMD_APROCESS_WRITE_SPEAKER, &arg, sizeof(arg));

	return arg.out_ret;
}

int pcm_client_readi(void *hdl, const phys_addr_t data, u32 count,
		struct device *dev, u32 dspid)
{
	struct pcm_io_st arg;
	struct aml_pcm_ctx *p_aml_pcm_ctx = (struct aml_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.data = (u64)data;
	arg.count = count;
	arg.out_ret = 0;

	Mbox_invoke(dev, dspid, MBX_TINYALSA_READI, &arg, sizeof(arg));

	return arg.out_ret;
}

void *pcm_process_client_open(u32 card, u32 device, u32 flags,
		struct rpc_pcm_config *config, struct device *dev, u32 dspid)
{
	struct pcm_process_open_st arg;
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = kmalloc(sizeof(*p_aml_pcm_ctx), GFP_KERNEL);

	memset(p_aml_pcm_ctx, 0, sizeof(struct aml_pro_pcm_ctx));
	memset(&arg, 0, sizeof(arg));
	arg.card = card;
	arg.device = device;
	arg.flags = flags;
	memcpy(&arg.pcm_config, config, sizeof(struct rpc_pcm_config));
	arg.out_pcm = 0;

	Mbox_invoke(dev, dspid, MBX_CMD_APROCESS_OPEN, &arg, sizeof(arg));
	if (!arg.out_pcm) {
		kfree(p_aml_pcm_ctx);
		return NULL;
	}
	p_aml_pcm_ctx->pcm_srv_hdl = arg.out_pcm;
	memcpy(&p_aml_pcm_ctx->config, config, sizeof(struct rpc_pcm_config));
	return p_aml_pcm_ctx;
}

int pcm_process_client_close(void        *hdl, struct device *dev, u32 dspid)
{
	struct pcm_process_close_st arg;
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, MBX_CMD_APROCESS_CLOSE, &arg, sizeof(arg));

	kfree(p_aml_pcm_ctx);
	return arg.out_ret;
}

int pcm_process_client_start(void *hdl, struct device *dev, u32 dspid)
{
	struct pcm_process_io_st arg;
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, MBX_CMD_APROCESS_START, &arg, sizeof(arg));

	return arg.out_ret;
}

int pcm_process_client_stop(void *hdl, struct device *dev, u32 dspid)
{
	struct pcm_process_io_st arg;
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;

	memset(&arg, 0, sizeof(arg));
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, MBX_CMD_APROCESS_STOP, &arg, sizeof(arg));

	return arg.out_ret;
}

int pcm_process_client_dqbuf(void *hdl, struct buf_info *buf, struct buf_info *release_buf,
		u32 type, struct device *dev, u32 dspid)
{
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;
	struct pcm_process_buf_st arg;

	if (type == PROCESSBUF)
		arg.id = 0;
	else
		arg.id = 1;

	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.buf_handle = 0;
	arg.data = 0;
	arg.count = 0;
	arg.out_ret = -EINVAL;
	if (release_buf && release_buf->handle &&
		release_buf->phyaddr && release_buf->viraddr &&
		release_buf->size &&
		release_buf->viraddr == __va((phys_addr_t)release_buf->phyaddr))
		arg.release_buf_handle = release_buf->handle;
	else
		arg.release_buf_handle = 0;

	Mbox_invoke(dev, dspid, CMD_APROCESS_DQBUF, &arg, sizeof(arg));

	buf->handle = arg.buf_handle;
	buf->phyaddr = arg.data;
	buf->size = arg.count;

	if (arg.buf_handle)
		buf->viraddr = __va((phys_addr_t)buf->phyaddr);

	return arg.out_ret;
}

int pcm_process_client_qbuf(void *hdl, struct buf_info *buf, u32 type,
		struct device *dev, u32 dspid)
{
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;
	struct pcm_process_buf_st arg;

	if (!buf->handle)
		return 0;
	if (type == PROCESSBUF)
		arg.id = 0;
	else
		arg.id = 1;
	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.buf_handle = buf->handle;
	arg.data = buf->phyaddr;
	arg.count = 0;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, CMD_APROCESS_QBUF, &arg, sizeof(arg));

	return arg.out_ret;
}

int pcm_process_client_get_volume_gain(void *hdl, int *gain,
		int is_out, struct device *dev, u32 dspid)
{
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;
	struct pcm_process_gain_st arg;

	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.gain = 0;
	arg.is_out = is_out;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, CMD_APROCESS_GET_GAIN, &arg, sizeof(arg));

	if (!arg.out_ret)
		*gain = arg.gain;
	return arg.out_ret;
}

int pcm_process_client_set_volume_gain(void *hdl, int gain,
		int is_out, struct device *dev, u32 dspid)
{
	struct aml_pro_pcm_ctx *p_aml_pcm_ctx = (struct aml_pro_pcm_ctx *)hdl;
	struct pcm_process_gain_st arg;

	arg.pcm = p_aml_pcm_ctx->pcm_srv_hdl;
	arg.gain = gain;
	arg.is_out = is_out;
	arg.out_ret = -EINVAL;

	Mbox_invoke(dev, dspid, CMD_APROCESS_SET_GAIN, &arg, sizeof(arg));

	return arg.out_ret;
}

void *aml_dsp_mem_allocate(phys_addr_t *phy, size_t size,
		struct device *dev, u32 dspid)
{
	struct acodec_shm_alloc_st arg;

	arg.size = size;
	arg.pid = 0;
	arg.phy = 0;

	Mbox_invoke(dev, dspid, MBX_CMD_SHM_ALLOC, &arg, sizeof(arg));
	if (!arg.phy)
		return NULL;
	*phy = (phys_addr_t)arg.phy;
	return __va((phys_addr_t)arg.phy);
}

void aml_dsp_mem_free(phys_addr_t phy, struct device *dev, u32 dspid)
{
	struct acodec_shm_free_st arg;

	arg.phy = (u64)phy;

	Mbox_invoke(dev, dspid, MBX_CMD_SHM_FREE, &arg, sizeof(arg));
}

void *audio_device_open(u32 card, u32 device, u32 flags,
		struct rpc_pcm_config *config, struct device *dev, u32 dspid)
{
	void *phandle = NULL;

	if (flags & PCM_IN) {
		config->period_count = 4;
		config->start_threshold = 0;
		config->silence_threshold = 0;
		config->stop_threshold = 0;
		phandle = pcm_process_client_open(card, device, PCM_IN, config, dev, dspid);
		if (!phandle) {
			pr_err("pcm client open fail\n");
			return NULL;
		}
	} else {
		config->period_count = 4;
		config->start_threshold = 0;
		config->silence_threshold = 0;
		config->stop_threshold = 0;
		phandle = pcm_client_open(card, device, PCM_OUT, config, dev, dspid);
		if (!phandle) {
			pr_err("pcm client open fail\n");
			return NULL;
		}
	}
	return phandle;
}
