/* 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 "ipa_i.h"
#include <linux/ipa_wdi3.h>

#define IPA_HW_WDI3_RX_MBOX_START_INDEX 48
#define IPA_HW_WDI3_TX_MBOX_START_INDEX 50

static int ipa3_send_wdi3_setup_pipe_cmd(
	struct ipa_wdi3_setup_info *info, u8 dir)
{
	int ipa_ep_idx;
	int result = 0;
	struct ipa_mem_buffer cmd;
	struct IpaHwWdi3SetUpCmdData_t *wdi3_params;
	struct IpaHwOffloadSetUpCmdData_t *cmd_data;

	if (info == NULL) {
		IPAERR("invalid input\n");
		return -EINVAL;
	}

	ipa_ep_idx = ipa_get_ep_mapping(info->client);
	if (ipa_ep_idx == -1) {
		IPAERR("fail to get ep idx.\n");
		return -EFAULT;
	}

	IPADBG("client=%d ep=%d\n", info->client, ipa_ep_idx);
	IPADBG("ring_base_pa = 0x%pad\n", &info->transfer_ring_base_pa);
	IPADBG("ring_size = %hu\n", info->transfer_ring_size);
	IPADBG("ring_db_pa = 0x%pad\n", &info->transfer_ring_doorbell_pa);
	IPADBG("evt_ring_base_pa = 0x%pad\n", &info->event_ring_base_pa);
	IPADBG("evt_ring_size = %hu\n", info->event_ring_size);
	IPADBG("evt_ring_db_pa = 0x%pad\n", &info->event_ring_doorbell_pa);
	IPADBG("num_pkt_buffers = %hu\n", info->num_pkt_buffers);
	IPADBG("pkt_offset = %d\n", info->pkt_offset);

	cmd.size = sizeof(*cmd_data);
	cmd.base = dma_alloc_coherent(ipa3_ctx->uc_pdev, cmd.size,
			&cmd.phys_base, GFP_KERNEL);
	if (cmd.base == NULL) {
		IPAERR("fail to get DMA memory.\n");
		return -ENOMEM;
	}

	cmd_data = (struct IpaHwOffloadSetUpCmdData_t *)cmd.base;
	cmd_data->protocol = IPA_HW_FEATURE_WDI3;

	wdi3_params = &cmd_data->SetupCh_params.Wdi3SetupCh_params;
	wdi3_params->transfer_ring_base_pa = (u32)info->transfer_ring_base_pa;
	wdi3_params->transfer_ring_base_pa_hi =
		(u32)((u64)info->transfer_ring_base_pa >> 32);
	wdi3_params->transfer_ring_size = info->transfer_ring_size;
	wdi3_params->transfer_ring_doorbell_pa =
		(u32)info->transfer_ring_doorbell_pa;
	wdi3_params->transfer_ring_doorbell_pa_hi =
		(u32)((u64)info->transfer_ring_doorbell_pa >> 32);
	wdi3_params->event_ring_base_pa = (u32)info->event_ring_base_pa;
	wdi3_params->event_ring_base_pa_hi =
		(u32)((u64)info->event_ring_base_pa >> 32);
	wdi3_params->event_ring_size = info->event_ring_size;
	wdi3_params->event_ring_doorbell_pa =
		(u32)info->event_ring_doorbell_pa;
	wdi3_params->event_ring_doorbell_pa_hi =
		(u32)((u64)info->event_ring_doorbell_pa >> 32);
	wdi3_params->num_pkt_buffers = info->num_pkt_buffers;
	wdi3_params->ipa_pipe_number = ipa_ep_idx;
	wdi3_params->dir = dir;
	wdi3_params->pkt_offset = info->pkt_offset;
	memcpy(wdi3_params->desc_format_template, info->desc_format_template,
		sizeof(wdi3_params->desc_format_template));

	result = ipa3_uc_send_cmd((u32)(cmd.phys_base),
				IPA_CPU_2_HW_CMD_OFFLOAD_CHANNEL_SET_UP,
				IPA_HW_2_CPU_OFFLOAD_CMD_STATUS_SUCCESS,
				false, 10*HZ);
	if (result) {
		IPAERR("uc setup channel cmd failed: %d\n", result);
		result = -EFAULT;
	}

	dma_free_coherent(ipa3_ctx->uc_pdev, cmd.size, cmd.base, cmd.phys_base);
	return result;
}

int ipa3_conn_wdi3_pipes(struct ipa_wdi3_conn_in_params *in,
	struct ipa_wdi3_conn_out_params *out)
{
	struct ipa3_ep_context *ep_rx;
	struct ipa3_ep_context *ep_tx;
	int ipa_ep_idx_rx;
	int ipa_ep_idx_tx;
	int result = 0;

	if (in == NULL || out == NULL) {
		IPAERR("invalid input\n");
		return -EINVAL;
	}

	ipa_ep_idx_rx = ipa_get_ep_mapping(in->rx.client);
	ipa_ep_idx_tx = ipa_get_ep_mapping(in->tx.client);
	if (ipa_ep_idx_rx == -1 || ipa_ep_idx_tx == -1) {
		IPAERR("fail to alloc EP.\n");
		return -EFAULT;
	}
	if (ipa_ep_idx_rx >= IPA3_MAX_NUM_PIPES ||
		ipa_ep_idx_tx >= IPA3_MAX_NUM_PIPES) {
		IPAERR("ep out of range.\n");
		return -EFAULT;
	}

	ep_rx = &ipa3_ctx->ep[ipa_ep_idx_rx];
	ep_tx = &ipa3_ctx->ep[ipa_ep_idx_tx];

	if (ep_rx->valid || ep_tx->valid) {
		IPAERR("EP already allocated.\n");
		return -EFAULT;
	}

	memset(ep_rx, 0, offsetof(struct ipa3_ep_context, sys));
	memset(ep_tx, 0, offsetof(struct ipa3_ep_context, sys));

	IPA_ACTIVE_CLIENTS_INC_SIMPLE();

	/* setup rx ep cfg */
	ep_rx->valid = 1;
	ep_rx->client = in->rx.client;
	result = ipa3_disable_data_path(ipa_ep_idx_rx);
	if (result) {
		IPAERR("disable data path failed res=%d clnt=%d.\n", result,
			ipa_ep_idx_rx);
		IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
		return -EFAULT;
	}
	ep_rx->client_notify = in->notify;
	ep_rx->priv = in->priv;

	memcpy(&ep_rx->cfg, &in->rx.ipa_ep_cfg, sizeof(ep_rx->cfg));

	if (ipa3_cfg_ep(ipa_ep_idx_rx, &ep_rx->cfg)) {
		IPAERR("fail to setup rx pipe cfg\n");
		result = -EFAULT;
		goto fail;
	}

	if (ipa3_send_wdi3_setup_pipe_cmd(&in->rx, IPA_WDI3_RX_DIR)) {
		IPAERR("fail to send cmd to uc for rx pipe\n");
		result = -EFAULT;
		goto fail;
	}
	ipa3_install_dflt_flt_rules(ipa_ep_idx_rx);
	out->rx_uc_db_pa = ipa3_ctx->ipa_wrapper_base +
		ipahal_get_reg_base() +
		ipahal_get_reg_mn_ofst(IPA_UC_MAILBOX_m_n,
		IPA_HW_WDI3_RX_MBOX_START_INDEX/32,
		IPA_HW_WDI3_RX_MBOX_START_INDEX % 32);

	IPADBG("client %d (ep: %d) connected\n", in->rx.client,
		ipa_ep_idx_rx);

	/* setup dl ep cfg */
	ep_tx->valid = 1;
	ep_tx->client = in->tx.client;
	result = ipa3_disable_data_path(ipa_ep_idx_tx);
	if (result) {
		IPAERR("disable data path failed res=%d ep=%d.\n", result,
			ipa_ep_idx_tx);
		result = -EFAULT;
		goto fail;
	}

	memcpy(&ep_tx->cfg, &in->tx.ipa_ep_cfg, sizeof(ep_tx->cfg));

	if (ipa3_cfg_ep(ipa_ep_idx_tx, &ep_tx->cfg)) {
		IPAERR("fail to setup tx pipe cfg\n");
		result = -EFAULT;
		goto fail;
	}

	if (ipa3_send_wdi3_setup_pipe_cmd(&in->tx, IPA_NTN_TX_DIR)) {
		IPAERR("fail to send cmd to uc for tx pipe\n");
		result = -EFAULT;
		goto fail;
	}
	out->tx_uc_db_pa = ipa3_ctx->ipa_wrapper_base +
		ipahal_get_reg_base() +
		ipahal_get_reg_mn_ofst(IPA_UC_MAILBOX_m_n,
		IPA_HW_WDI3_TX_MBOX_START_INDEX/32,
		IPA_HW_WDI3_TX_MBOX_START_INDEX % 32);
	out->tx_uc_db_va = ioremap(out->tx_uc_db_pa, 4);
	if (!out->tx_uc_db_va) {
		IPAERR("fail to ioremap tx uc db\n");
		result = -EFAULT;
		goto fail;
	}
	IPADBG("client %d (ep: %d) connected\n", in->tx.client,
		ipa_ep_idx_tx);

fail:
	IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
	return result;
}

static int ipa3_send_wdi3_common_ch_cmd(int ipa_ep_idx, int command)
{
	struct ipa_mem_buffer cmd;
	struct IpaHwOffloadCommonChCmdData_t *cmd_data;
	union IpaHwWdi3CommonChCmdData_t *wdi3;
	int result = 0;

	cmd.size = sizeof(*cmd_data);
	cmd.base = dma_alloc_coherent(ipa3_ctx->uc_pdev, cmd.size,
		&cmd.phys_base, GFP_KERNEL);
	if (cmd.base == NULL) {
		IPAERR("fail to get DMA memory.\n");
		return -ENOMEM;
	}

	IPA_ACTIVE_CLIENTS_INC_SIMPLE();
	/* enable the TX pipe */
	cmd_data = (struct IpaHwOffloadCommonChCmdData_t *)cmd.base;
	cmd_data->protocol = IPA_HW_FEATURE_WDI3;

	wdi3 = &cmd_data->CommonCh_params.Wdi3CommonCh_params;
	wdi3->params.ipa_pipe_number = ipa_ep_idx;
	result = ipa3_uc_send_cmd((u32)(cmd.phys_base), command,
				IPA_HW_2_CPU_OFFLOAD_CMD_STATUS_SUCCESS,
				false, 10*HZ);
	if (result) {
		result = -EFAULT;
		goto fail;
	}

fail:
	dma_free_coherent(ipa3_ctx->uc_pdev, cmd.size, cmd.base, cmd.phys_base);
	IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
	return result;
}

int ipa3_disconn_wdi3_pipes(int ipa_ep_idx_tx, int ipa_ep_idx_rx)
{
	struct ipa3_ep_context *ep_tx, *ep_rx;
	int result = 0;

	IPADBG("ep_tx = %d\n", ipa_ep_idx_tx);
	IPADBG("ep_rx = %d\n", ipa_ep_idx_rx);

	if (ipa_ep_idx_tx < 0 || ipa_ep_idx_tx >= IPA3_MAX_NUM_PIPES ||
		ipa_ep_idx_rx < 0 || ipa_ep_idx_rx >= IPA3_MAX_NUM_PIPES) {
		IPAERR("invalid ipa ep index\n");
		return -EINVAL;
	}

	ep_tx = &ipa3_ctx->ep[ipa_ep_idx_tx];
	ep_rx = &ipa3_ctx->ep[ipa_ep_idx_rx];

	/* tear down tx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_tx,
		IPA_CPU_2_HW_CMD_OFFLOAD_TEAR_DOWN)) {
		IPAERR("fail to tear down tx pipe\n");
		result = -EFAULT;
		goto fail;
	}
	ipa3_disable_data_path(ipa_ep_idx_tx);
	memset(ep_tx, 0, sizeof(struct ipa3_ep_context));
	IPADBG("tx client (ep: %d) disconnected\n", ipa_ep_idx_tx);

	/* tear down rx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_rx,
		IPA_CPU_2_HW_CMD_OFFLOAD_TEAR_DOWN)) {
		IPAERR("fail to tear down rx pipe\n");
		result = -EFAULT;
		goto fail;
	}
	ipa3_disable_data_path(ipa_ep_idx_rx);
	ipa3_delete_dflt_flt_rules(ipa_ep_idx_rx);
	memset(ep_rx, 0, sizeof(struct ipa3_ep_context));
	IPADBG("rx client (ep: %d) disconnected\n", ipa_ep_idx_rx);

fail:
	return result;
}

int ipa3_enable_wdi3_pipes(int ipa_ep_idx_tx, int ipa_ep_idx_rx)
{
	struct ipa3_ep_context *ep_tx, *ep_rx;
	int result = 0;

	IPADBG("ep_tx = %d\n", ipa_ep_idx_tx);
	IPADBG("ep_rx = %d\n", ipa_ep_idx_rx);

	ep_tx = &ipa3_ctx->ep[ipa_ep_idx_tx];
	ep_rx = &ipa3_ctx->ep[ipa_ep_idx_rx];

	/* enable tx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_tx,
		IPA_CPU_2_HW_CMD_OFFLOAD_ENABLE)) {
		IPAERR("fail to enable tx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* resume tx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_tx,
		IPA_CPU_2_HW_CMD_OFFLOAD_RESUME)) {
		IPAERR("fail to resume tx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* enable rx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_rx,
		IPA_CPU_2_HW_CMD_OFFLOAD_ENABLE)) {
		IPAERR("fail to enable rx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* resume rx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_rx,
		IPA_CPU_2_HW_CMD_OFFLOAD_RESUME)) {
		IPAERR("fail to resume rx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	IPA_ACTIVE_CLIENTS_INC_SIMPLE();

	/* enable data path */
	result = ipa3_enable_data_path(ipa_ep_idx_rx);
	if (result) {
		IPAERR("enable data path failed res=%d clnt=%d.\n", result,
			ipa_ep_idx_rx);
		IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
		return -EFAULT;
	}

	result = ipa3_enable_data_path(ipa_ep_idx_tx);
	if (result) {
		IPAERR("enable data path failed res=%d clnt=%d.\n", result,
			ipa_ep_idx_tx);
		IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
		return -EFAULT;
	}

	IPA_ACTIVE_CLIENTS_DEC_SIMPLE();

fail:
	return result;
}

int ipa3_disable_wdi3_pipes(int ipa_ep_idx_tx, int ipa_ep_idx_rx)
{
	struct ipa3_ep_context *ep_tx, *ep_rx;
	int result = 0;

	IPADBG("ep_tx = %d\n", ipa_ep_idx_tx);
	IPADBG("ep_rx = %d\n", ipa_ep_idx_rx);

	ep_tx = &ipa3_ctx->ep[ipa_ep_idx_tx];
	ep_rx = &ipa3_ctx->ep[ipa_ep_idx_rx];

	/* suspend tx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_tx,
		IPA_CPU_2_HW_CMD_OFFLOAD_SUSPEND)) {
		IPAERR("fail to suspend tx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* disable tx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_tx,
		IPA_CPU_2_HW_CMD_OFFLOAD_DISABLE)) {
		IPAERR("fail to disable tx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* suspend rx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_rx,
		IPA_CPU_2_HW_CMD_OFFLOAD_SUSPEND)) {
		IPAERR("fail to suspend rx pipe\n");
		result = -EFAULT;
		goto fail;
	}

	/* disable rx pipe */
	if (ipa3_send_wdi3_common_ch_cmd(ipa_ep_idx_rx,
		IPA_CPU_2_HW_CMD_OFFLOAD_DISABLE)) {
		IPAERR("fail to disable rx pipe\n");
		result = -EFAULT;
		goto fail;
	}

fail:
	return result;
}
