/*
 * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <mmio.h>
#include <utils_def.h>

#include "uniphier.h"

#define UNIPHIER_ROM_RSV3		0x5980120c

#define UNIPHIER_STMBE2COM		0x5f800030
#define UNIPHIER_STMTOBEIRQ		0x5f800060
#define UNIPHIER_BETOSTMIRQ0PT		0x5f800070
#define UNIPHIER_BEIRQCLRPT		0x5f800072

#define UNIPHIER_SCP_READY_MAGIC	0x0000b6a5

#define UNIPHIER_SCP_PACKET_START	0xA0
#define UNIPHIER_SCP_PACKET_END		0xA5
#define UNIPHIER_SCP_PACKET_ESC		0xA6
#define UNIPHIER_SCP_IS_CTRL_CODE(c)	(0xA0 <= (c) && (c) <= 0xA6)

int uniphier_scp_is_running(void)
{
	return mmio_read_32(UNIPHIER_STMBE2COM) == UNIPHIER_SCP_READY_MAGIC;
}

void uniphier_scp_start(void)
{
	uint32_t tmp;

	mmio_write_32(UNIPHIER_STMBE2COM + 4, UNIPHIER_SCP_BASE);
	mmio_write_32(UNIPHIER_STMBE2COM, UNIPHIER_SCP_READY_MAGIC);

	do {
		tmp = mmio_read_32(UNIPHIER_ROM_RSV3);
	} while (!(tmp & BIT(8)));

	mmio_write_32(UNIPHIER_ROM_RSV3, tmp | BIT(9));
}

static void uniphier_scp_send_packet(const uint8_t *packet, int packet_len)
{
	uintptr_t reg = UNIPHIER_STMBE2COM;
	uint32_t word;
	int len, i;

	while (packet_len) {
		len = MIN(packet_len, 4);
		word = 0;

		for (i = 0; i < len; i++)
			word |= *packet++ << (8 * i);

		mmio_write_32(reg, word);
		reg += 4;
		packet_len -= len;
	}

	mmio_write_8(UNIPHIER_BETOSTMIRQ0PT, 0x55);

	while (!(mmio_read_32(UNIPHIER_STMTOBEIRQ) & BIT(1)))
		;
	mmio_write_8(UNIPHIER_BEIRQCLRPT, BIT(1) | BIT(0));
}

static void uniphier_scp_send_cmd(const uint8_t *cmd, int cmd_len)
{
	uint8_t packet[32];	/* long enough */
	uint8_t *p = packet;
	uint8_t c;
	int i;

	*p++ = UNIPHIER_SCP_PACKET_START;
	*p++ = cmd_len;

	for (i = 0; i < cmd_len; i++) {
		c = *cmd++;
		if (UNIPHIER_SCP_IS_CTRL_CODE(c)) {
			*p++ = UNIPHIER_SCP_PACKET_ESC;
			*p++ = c ^ BIT(7);
		} else {
			*p++ = c;
		}
	}

	*p++ = UNIPHIER_SCP_PACKET_END;

	uniphier_scp_send_packet(packet, p - packet);
}

#define UNIPHIER_SCP_CMD(name, ...)					\
static const uint8_t __uniphier_scp_##name##_cmd[] = {			\
	__VA_ARGS__							\
};									\
void uniphier_scp_##name(void)						\
{									\
	uniphier_scp_send_cmd(__uniphier_scp_##name##_cmd,		\
			      ARRAY_SIZE(__uniphier_scp_##name##_cmd));	\
}

UNIPHIER_SCP_CMD(open_com, 0x00, 0x00, 0x05)
UNIPHIER_SCP_CMD(system_off, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x01)
UNIPHIER_SCP_CMD(system_reset, 0x00, 0x02, 0x00)
