// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
 */
#include <linux/iopoll.h>
#include "dsi_ctrl_hw.h"
#include "dsi_ctrl_reg.h"
#include "dsi_hw.h"
#include "dsi_ctrl.h"
#include "dsi_catalog.h"

#define DISP_CC_MISC_CMD_REG_OFF 0x00

/* register to configure DMA scheduling */
#define DSI_DMA_SCHEDULE_CTRL 0x100
#define DSI_DMA_SCHEDULE_CTRL2 0x0104

/* offset addresses of MDP INTF base register, to be mapped for debug feature */
#define MDP_INTF_TEAR_OFFSET 0x280
#define MDP_INTF_TEAR_LINE_COUNT_OFFSET 0x30
#define MDP_INTF_LINE_COUNT_OFFSET 0xB0

#define DSI_MDP_MISR_CTRL 0x364
#define DSI_MDP_MISR_SIGNATURE 0x368

void dsi_ctrl_hw_22_setup_lane_map(struct dsi_ctrl_hw *ctrl,
		       struct dsi_lane_map *lane_map)
{
	u32 reg_value = lane_map->lane_map_v2[DSI_LOGICAL_LANE_0] |
			(lane_map->lane_map_v2[DSI_LOGICAL_LANE_1] << 4) |
			(lane_map->lane_map_v2[DSI_LOGICAL_LANE_2] << 8) |
			(lane_map->lane_map_v2[DSI_LOGICAL_LANE_3] << 12);

	DSI_W32(ctrl, DSI_LANE_SWAP_CTRL, reg_value);

	DSI_CTRL_HW_DBG(ctrl, "[DSI_%d] Lane swap setup complete\n",
			ctrl->index);
}

int dsi_ctrl_hw_22_wait_for_lane_idle(struct dsi_ctrl_hw *ctrl,
		u32 lanes)
{
	int rc = 0, val = 0;
	u32 fifo_empty_mask = 0;
	u32 const sleep_us = 10;
	u32 const timeout_us = 100;

	if (lanes & DSI_DATA_LANE_0)
		fifo_empty_mask |= (BIT(12) | BIT(16));

	if (lanes & DSI_DATA_LANE_1)
		fifo_empty_mask |= BIT(20);

	if (lanes & DSI_DATA_LANE_2)
		fifo_empty_mask |= BIT(24);

	if (lanes & DSI_DATA_LANE_3)
		fifo_empty_mask |= BIT(28);

	DSI_CTRL_HW_DBG(ctrl, "%s: polling for fifo empty, mask=0x%08x\n",
		__func__, fifo_empty_mask);
	rc = DSI_READ_POLL_TIMEOUT(ctrl, DSI_FIFO_STATUS, val,
			(val & fifo_empty_mask), sleep_us, timeout_us);
	if (rc) {
		DSI_CTRL_HW_ERR(ctrl,
				"%s: fifo not empty, FIFO_STATUS=0x%08x\n",
				__func__, val);
		goto error;
	}
error:
	return rc;
}

ssize_t dsi_ctrl_hw_22_reg_dump_to_buffer(struct dsi_ctrl_hw *ctrl,
					  char *buf,
					  u32 size)
{
	return size;
}

/**
 * dsi_ctrl_hw_22_phy_reset_config() - to configure clamp control during ulps
 * @ctrl:          Pointer to the controller host hardware.
 * @enable:      boolean to specify enable/disable.
 */
void dsi_ctrl_hw_22_phy_reset_config(struct dsi_ctrl_hw *ctrl,
		bool enable)
{
	u32 reg = 0;

	reg = DSI_DISP_CC_R32(ctrl, DISP_CC_MISC_CMD_REG_OFF);

	/* Mask/unmask disable PHY reset bit */
	if (enable)
		reg &= ~BIT(ctrl->index);
	else
		reg |= BIT(ctrl->index);
	DSI_DISP_CC_W32(ctrl, DISP_CC_MISC_CMD_REG_OFF, reg);
}

/**
 * dsi_ctrl_hw_22_schedule_dma_cmd() - to schedule DMA command transfer
 * @ctrl:         Pointer to the controller host hardware.
 * @line_no:      Line number at which command needs to be sent.
 */
void dsi_ctrl_hw_22_schedule_dma_cmd(struct dsi_ctrl_hw *ctrl, int line_no)
{
	u32 reg = 0;

	reg = DSI_R32(ctrl, DSI_DMA_SCHEDULE_CTRL);
	reg |= BIT(28);
	reg |= (line_no & 0xffff);

	DSI_W32(ctrl, DSI_DMA_SCHEDULE_CTRL, reg);
	ctrl->reset_trig_ctrl = true;
}

/*
 * dsi_ctrl_hw_kickoff_non_embedded_mode()-Kickoff cmd  in non-embedded mode
 * @ctrl:                  - Pointer to the controller host hardware.
 * @dsi_ctrl_cmd_dma_info: - command buffer information.
 * @flags:		   - DSI CTRL Flags.
 */
void dsi_ctrl_hw_kickoff_non_embedded_mode(struct dsi_ctrl_hw *ctrl,
				    struct dsi_ctrl_cmd_dma_info *cmd,
				    u32 flags)
{
	u32 reg = 0;

	reg = DSI_R32(ctrl, DSI_COMMAND_MODE_DMA_CTRL);

	reg &= ~BIT(31);/* disable broadcast */
	reg &= ~BIT(30);

	if (cmd->use_lpm)
		reg |= BIT(26);
	else
		reg &= ~BIT(26);

	/* Select non EMBEDDED_MODE, pick the packet header from register */
	reg &= ~BIT(28);
	reg |= BIT(24);/* long packet */
	reg |= BIT(29);/* wc_sel = 1 */
	reg |= (((cmd->datatype) & 0x03f) << 16);/* data type */
	DSI_W32(ctrl, DSI_COMMAND_MODE_DMA_CTRL, reg);

	/* Enable WRITE_WATERMARK_DISABLE and READ_WATERMARK_DISABLE bits */
	reg = DSI_R32(ctrl, DSI_DMA_FIFO_CTRL);
	reg |= BIT(20);
	reg |= BIT(16);
	reg |= 0x33;/* Set READ and WRITE watermark levels to maximum */
	DSI_W32(ctrl, DSI_DMA_FIFO_CTRL, reg);

	DSI_W32(ctrl, DSI_DMA_CMD_OFFSET, cmd->offset);
	DSI_W32(ctrl, DSI_DMA_CMD_LENGTH, ((cmd->length) & 0xFFFFFF));

	/* wait for writes to complete before kick off */
	wmb();

	if (!(flags & DSI_CTRL_HW_CMD_WAIT_FOR_TRIGGER))
		DSI_W32(ctrl, DSI_CMD_MODE_DMA_SW_TRIGGER, 0x1);
}

/*
 * dsi_ctrl_hw_22_config_clk_gating() - enable/disable clk gating on DSI PHY
 * @ctrl:          Pointer to the controller host hardware.
 * @enable:        bool to notify enable/disable.
 * @clk_selection:        clock to enable/disable clock gating.
 *
 */
void dsi_ctrl_hw_22_config_clk_gating(struct dsi_ctrl_hw *ctrl, bool enable,
				enum dsi_clk_gate_type clk_selection)
{
	u32 reg = 0;
	u32 enable_select = 0;

	reg = DSI_DISP_CC_R32(ctrl, DISP_CC_MISC_CMD_REG_OFF);

	if (clk_selection & PIXEL_CLK)
		enable_select |= ctrl->index ? BIT(6) : BIT(5);

	if (clk_selection & BYTE_CLK)
		enable_select |= ctrl->index ? BIT(8) : BIT(7);

	if (clk_selection & DSI_PHY)
		enable_select |= ctrl->index ? BIT(10) : BIT(9);

	if (enable)
		reg |= enable_select;
	else
		reg &= ~enable_select;

	DSI_DISP_CC_W32(ctrl, DISP_CC_MISC_CMD_REG_OFF, reg);
}

/**
 * dsi_ctrl_hw_22_configure_cmddma_window() - configure DMA window for CMD TX
 * @ctrl:	Pointer to the controller host hardware.
 * @cmd:	Pointer to the DSI DMA command info.
 * @line_no:	Line number at which the CMD needs to be triggered.
 * @window:	Width of the DMA CMD window.
 */
void dsi_ctrl_hw_22_configure_cmddma_window(struct dsi_ctrl_hw *ctrl,
		struct dsi_ctrl_cmd_dma_info *cmd,
		u32 line_no, u32 window)
{
	u32 reg = 0;

	if (cmd->en_broadcast) {
		reg = DSI_R32(ctrl, DSI_TRIG_CTRL);
		if (cmd->is_master) {
			reg &= ~0xF;
			reg |= 0xc;
		} else {
			reg &= ~0xF;
			reg |= BIT(16);
		}

		DSI_W32(ctrl, DSI_TRIG_CTRL, reg);

		if (cmd->is_master) {
			reg = 0;
			reg |= line_no;
			reg |= window << 16;
			DSI_W32(ctrl, DSI_DMA_SCHEDULE_CTRL2, reg);
		}
	} else {
		reg = DSI_R32(ctrl, DSI_TRIG_CTRL);
		reg &= ~0xF;
		reg |= 0xc;
		DSI_W32(ctrl, DSI_TRIG_CTRL, reg);

		reg = 0;
		reg |= line_no;
		reg |= window << 16;
		DSI_W32(ctrl, DSI_DMA_SCHEDULE_CTRL2, reg);
	}
	ctrl->reset_trig_ctrl = true;
}

/**
 * dsi_ctrl_hw_22_reset_trigger_controls() - reset dsi trigger configurations
 * @ctrl:             Pointer to the controller host hardware.
 * @cfg:              DSI host configuration that is common to both video and
 *                    command modes.
 */
void dsi_ctrl_hw_22_reset_trigger_controls(struct dsi_ctrl_hw *ctrl,
				       struct dsi_host_common_cfg *cfg)
{
	u32 reg = 0;
	const u8 trigger_map[DSI_TRIGGER_MAX] = {
		0x0, 0x2, 0x1, 0x4, 0x5, 0x6 };

	reg |= (cfg->te_mode == DSI_TE_ON_EXT_PIN) ? BIT(31) : 0;
	reg |= (trigger_map[cfg->dma_cmd_trigger] & 0x7);
	reg |= (trigger_map[cfg->mdp_cmd_trigger] & 0x7) << 4;
	DSI_W32(ctrl, DSI_TRIG_CTRL, reg);
	DSI_W32(ctrl, DSI_DMA_SCHEDULE_CTRL2, 0x0);
	DSI_W32(ctrl, DSI_DMA_SCHEDULE_CTRL, 0x0);
	ctrl->reset_trig_ctrl = false;
}

/**
 * dsi_ctrl_hw_22_log_line_count() - reads the MDP interface line count
 *				     registers.
 * @ctrl:	Pointer to the controller host hardware.
 * @cmd_mode:	Boolean to indicate command mode operation.
 *
 * Return: INTF register value.
 */
u32 dsi_ctrl_hw_22_log_line_count(struct dsi_ctrl_hw *ctrl, bool cmd_mode)
{

	u32 reg = 0;

	if (IS_ERR_OR_NULL(ctrl->mdp_intf_base))
		return reg;

	if (cmd_mode)
		reg = DSI_MDP_INTF_R32(ctrl, MDP_INTF_TEAR_OFFSET
					+ MDP_INTF_TEAR_LINE_COUNT_OFFSET);
	else
		reg = DSI_MDP_INTF_R32(ctrl, MDP_INTF_LINE_COUNT_OFFSET);
	return reg;
}

void dsi_ctrl_hw_22_configure_splitlink(struct dsi_ctrl_hw *ctrl,
		struct dsi_host_common_cfg *common_cfg, u32 flags)
{
	u32 reg = 0;

	reg = DSI_R32(ctrl, DSI_SPLIT_LINK);

	/* DMA_LINK_SEL */
	reg &= ~(0x7 << 12);

	/* Send command to both sublinks unless specified */
	if (flags & DSI_CTRL_CMD_SUBLINK0)
		reg |= BIT(12);
	else if (flags & DSI_CTRL_CMD_SUBLINK1)
		reg |= BIT(13);
	else
		reg |= (BIT(12) | BIT(13));

	/**
	 * Avoid dma trigger on sublink1 for read commands. This can be
	 * enabled in future if panel supports sending read command on sublink1.
	 */
	if (flags & DSI_CTRL_CMD_READ) {
		reg = reg & ~BIT(13);
	}

	DSI_W32(ctrl, DSI_SPLIT_LINK, reg);

	/* Make sure the split link config is updated */
	wmb();
}

void dsi_ctrl_hw_22_setup_misr(struct dsi_ctrl_hw *ctrl, enum dsi_op_mode panel_mode,
			bool enable, u32 frame_count)
{
	u32 config = 0;

	DSI_W32(ctrl, DSI_MDP_MISR_CTRL, config);
	wmb(); /* clear misr data */

	if (enable) {
		config = (frame_count & 0xffff);
		config |= BIT(8) | BIT(24) | BIT(31); /* enable, panel data-only, free run mode */
	}

	DSI_CTRL_HW_DBG(ctrl, "MISR enable:%d, frame_count:%d, config:0x%x\n",
			enable, frame_count, config);
	DSI_W32(ctrl, DSI_MDP_MISR_CTRL, config);
	wmb(); /* make sure MISR is configured */
}

u32 dsi_ctrl_hw_22_collect_misr(struct dsi_ctrl_hw *ctrl, enum dsi_op_mode panel_mode)
{
	u32 enabled;
	u32 misr = 0;

	enabled = DSI_R32(ctrl, DSI_MDP_MISR_CTRL) & BIT(8);
	if (enabled)
		misr = DSI_R32(ctrl, DSI_MDP_MISR_SIGNATURE);

	DSI_CTRL_HW_DBG(ctrl, "MISR enabled:%d value:0x%x\n", enabled, misr);
	return misr;
}
