/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * MIPI DSI Bus
 *
 * Andrzej Hajda <a.hajda@samsung.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <console/console.h>
#include <string.h>
#include <soc/addressmap.h>
#include <soc/clock.h>
#include <device/device.h>
#include <soc/nvidia/tegra/types.h>
#include <soc/display.h>
#include <soc/mipi_dsi.h>
#include <soc/mipi_display.h>
#include <soc/tegra_dsi.h>
#include <types.h>

struct mipi_dsi_device mipi_dsi_device_data[NUM_DSI] = {
	{
		.master = NULL,
		.slave = &mipi_dsi_device_data[DSI_B],
	},
	{
		.master = &mipi_dsi_device_data[DSI_A],
		.slave = NULL,
	},
};

static struct mipi_dsi_device *
mipi_dsi_device_alloc(struct mipi_dsi_host *host)
{
	static int index = 0;
	struct mipi_dsi_device *dsi;

	if (index >= NUM_DSI)
		return (void *)-EPTR;

	dsi = &mipi_dsi_device_data[index++];
	dsi->host = host;
	return dsi;
}

static struct mipi_dsi_device *
of_mipi_dsi_device_add(struct mipi_dsi_host *host)
{
	struct mipi_dsi_device *dsi;
	u32 reg = 0;

	dsi = mipi_dsi_device_alloc(host);
	if (IS_ERR_PTR(dsi)) {
		printk(BIOS_ERR, "failed to allocate DSI device\n");
		return dsi;
	}

	dsi->channel = reg;
	host->dev = (void *)dsi;

	return dsi;
}

int mipi_dsi_host_register(struct mipi_dsi_host *host)
{
	of_mipi_dsi_device_add(host);
	return 0;
}

/**
 * mipi_dsi_attach - attach a DSI device to its DSI host
 * @param dsi: DSI peripheral
 */
int mipi_dsi_attach(struct mipi_dsi_device *dsi)
{
	const struct mipi_dsi_host_ops *ops = dsi->host->ops;

	if (!ops || !ops->attach)
		return -ENOSYS;

	return ops->attach(dsi->host, dsi);
}

/**
 * mipi_dsi_detach - detach a DSI device from its DSI host
 * @param dsi: DSI peripheral
 */
int mipi_dsi_detach(struct mipi_dsi_device *dsi)
{
	const struct mipi_dsi_host_ops *ops = dsi->host->ops;

	if (!ops || !ops->detach)
		return -ENOSYS;

	return ops->detach(dsi->host, dsi);
}

/**
 * mipi_dsi_enslave() - use a MIPI DSI peripheral as slave for dual-channel
 *    operation
 * @param master: master DSI peripheral device
 * @param slave: slave DSI peripheral device
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_enslave(struct mipi_dsi_device *master,
		     struct mipi_dsi_device *slave)
{
	int err = 0;

	slave->master = master;
	master->slave = slave;

	if (master->ops && master->ops->enslave)
		err = master->ops->enslave(master, slave);

	return err;
}

/**
 * mipi_dsi_liberate() - stop using a MIPI DSI peripheral as slave for dual-
 *    channel operation
 * @param master: master DSI peripheral device
 * @param slave: slave DSI peripheral device
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_liberate(struct mipi_dsi_device *master,
		      struct mipi_dsi_device *slave)
{
	int err = 0;

	if (master->ops && master->ops->liberate)
		err = master->ops->liberate(master, slave);

	master->slave = NULL;
	slave->master = NULL;

	return err;
}

/**
 * mipi_dsi_dcs_write() - send DCS write command
 * @param dsi: DSI peripheral device
 * @param cmd: DCS command
 * @param data: buffer containing the command payload
 * @param len: command payload length
 *
 * This function will automatically choose the right data type depending on
 * the command payload length.
 *
 * @return The number of bytes successfully transmitted or a negative error code on failure.
 */
ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
			   const void *data, size_t len)
{
	struct mipi_dsi_msg msg;
	ssize_t err;
	size_t size;

	u8 buffer[MAX_DSI_HOST_FIFO_DEPTH + 4];
	u8 *tx = buffer;

	if (len > MAX_DSI_HOST_FIFO_DEPTH) {
		printk(BIOS_ERR, "%s: Error: too large payload length: %zu\n",
			__func__, len);

		return -EINVAL;
	}

	if (len > 0) {
		unsigned int offset = 0;

		/*
		 * DCS long write packets contain the word count in the header
		 * bytes 1 and 2 and have a payload containing the DCS command
		 * byte folowed by word count minus one bytes.
		 *
		 * DCS short write packets encode the DCS command and up to
		 * one parameter in header bytes 1 and 2.
		 */
		if (len > 1)
			size = 3 + len;
		else
			size = 1 + len;

		/* write word count to header for DCS long write packets */
		if (len > 1) {
			tx[offset++] = ((1 + len) >> 0) & 0xff;
			tx[offset++] = ((1 + len) >> 8) & 0xff;
		}

		/* write the DCS command byte followed by the payload */
		tx[offset++] = cmd;
		memcpy(tx + offset, data, len);
	} else {
		tx = &cmd;
		size = 1;
	}

	memset(&msg, 0, sizeof(msg));
	msg.flags = MIPI_DSI_MSG_USE_LPM;
	msg.channel = dsi->channel;
	msg.tx_len = size;
	msg.tx_buf = tx;

	switch (len) {
	case 0:
		msg.type = MIPI_DSI_DCS_SHORT_WRITE;
		break;
	case 1:
		msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
		break;
	default:
		msg.type = MIPI_DSI_DCS_LONG_WRITE;
		break;
	}

	err = dsi->host->ops->transfer(dsi->host, &msg);

	return err;
}

/**
 * mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
 *    module
 * @param dsi: DSI peripheral device
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi)
{
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_display_on() - start displaying the image data on the
 *    display device
 * @param dsi: DSI peripheral device
 *
 * @return 0 on success or a negative error code on failure
 */
int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi)
{
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_column_address() - define the column extent of the frame
 *    memory accessed by the host processor
 * @param dsi: DSI peripheral device
 * @param start: first column of frame memory
 * @param end: last column of frame memory
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
				    u16 end)
{
	u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, payload,
				 sizeof(payload));
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_page_address() - define the page extent of the frame
 *    memory accessed by the host processor
 * @param dsi: DSI peripheral device
 * @param start: first page of frame memory
 * @param end: last page of frame memory
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
				  u16 end)
{
	u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PAGE_ADDRESS, payload,
				 sizeof(payload));
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
 *    output signal on the TE signal line.
 * @param dsi: DSI peripheral device
 * @param mode: the Tearing Effect Output Line mode
 *
 * @return 0 on success or a negative error code on failure
 */
int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
			     enum mipi_dsi_dcs_tear_mode mode)
{
	u8 value = mode;
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_ON, &value,
				 sizeof(value));
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
 *    data used by the interface
 * @param dsi: DSI peripheral device
 * @param format: pixel format
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format)
{
	ssize_t err;

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PIXEL_FORMAT, &format,
				 sizeof(format));
	if (err < 0)
		return err;

	return 0;
}

/**
 * mipi_dsi_dcs_set_address_mode() - sets the data order for forward transfers
 *    from the host to the peripheral
 * @param dsi: DSI peripheral device
 * @param reverse_page_address: reverses the page addressing to bottom->top
 * @param reverse_col_address: reverses the column addressing to right->left
 * @param reverse_page_col_address: reverses the page/column addressing order
 * @param refresh_from_bottom: refresh the display bottom to top
 * @param reverse_rgb: send pixel data bgr instead of rgb
 * @param latch_right_to_left: latch the incoming display data right to left
 * @param flip_horizontal: flip the image horizontally, left to right
 * @param flip_vertical: flip the image vertically, top to bottom
 *
 * @return 0 on success or a negative error code on failure.
 */
int mipi_dsi_dcs_set_address_mode(struct mipi_dsi_device *dsi,
			bool reverse_page_address,
			bool reverse_col_address,
			bool reverse_page_col_address,
			bool refresh_from_bottom,
			bool reverse_rgb,
			bool latch_right_to_left,
			bool flip_horizontal,
			bool flip_vertical)
{
	ssize_t err;
	u8 data;

	data = ((flip_vertical ? 1 : 0) << 0) |
		((flip_horizontal ? 1 : 0) << 1) |
		((latch_right_to_left ? 1 : 0) << 2) |
		((reverse_rgb ? 1 : 0) << 3) |
		((refresh_from_bottom ? 1 : 0) << 4) |
		((reverse_page_col_address ? 1 : 0) << 5) |
		((reverse_col_address ? 1 : 0) << 6) |
		((reverse_page_address ? 1 : 0) << 7);

	err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_ADDRESS_MODE, &data, 1);
	if (err < 0)
		return err;

	return 0;
}
