// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2013-2014, 2018 Synopsys, Inc. All rights reserved.
 */

#include <common.h>
#include <clock_legacy.h>
#include <init.h>
#include <malloc.h>
#include <vsprintf.h>
#include <asm/arcregs.h>
#include <asm/cache.h>
#include <asm/global_data.h>
#include <linux/bitops.h>

DECLARE_GLOBAL_DATA_PTR;

int arch_cpu_init(void)
{
	timer_init();

	gd->cpu_clk = get_board_sys_clk();
	gd->ram_size = CONFIG_SYS_SDRAM_SIZE;

	cache_init();

	return 0;
}

/* This is a dummy function on arc */
int dram_init(void)
{
	return 0;
}

#ifdef CONFIG_DISPLAY_CPUINFO
const char *arc_700_version(int arcver, char *name, int name_len)
{
	const char *arc_ver;

	switch (arcver) {
	case 0x32:
		arc_ver = "v4.4-4.5";
		break;
	case 0x33:
		arc_ver = "v4.6-v4.9";
		break;
	case 0x34:
		arc_ver = "v4.10";
		break;
	case 0x35:
		arc_ver = "v4.11";
		break;
	default:
		arc_ver = "unknown version";
	}

	snprintf(name, name_len, "ARC 700 %s", arc_ver);

	return name;
}

struct em_template_t {
	const bool cache;
	const bool dsp;
	const bool xymem;
	const char name[8];
};

static const struct em_template_t em_versions[] = {
	{false,	false,	false,	"EM4"},
	{true,	false,	false,	"EM6"},
	{false,	true,	false,	"EM5D"},
	{true,	true,	false,	"EM7D"},
	{false,	true,	true,	"EM9D"},
	{true,	true,	true,	"EM11D"},
};

const char *arc_em_version(int arcver, char *name, int name_len)
{
	const char *arc_name = "EM";
	const char *arc_ver;
	bool cache = ARC_FEATURE_EXISTS(ARC_BCR_IC_BUILD);
	bool dsp = ARC_FEATURE_EXISTS(ARC_AUX_DSP_BUILD);
	bool xymem = ARC_FEATURE_EXISTS(ARC_AUX_XY_BUILD);
	int i;

	for (i = 0; i < sizeof(em_versions) / sizeof(struct em_template_t); i++) {
		if (em_versions[i].cache == cache &&
		    em_versions[i].dsp == dsp &&
		    em_versions[i].xymem == xymem) {
			arc_name = em_versions[i].name;
			break;
		}
	}

	switch (arcver) {
	case 0x41:
		arc_ver = "v1.1a";
		break;
	case 0x42:
		arc_ver = "v3.0";
		break;
	case 0x43:
		arc_ver = "v4.0";
		break;
	case 0x44:
		arc_ver = "v5.0";
		break;
	default:
		arc_ver = "unknown version";
	}

	snprintf(name, name_len, "ARC %s %s", arc_name, arc_ver);

	return name;
}

struct hs_template_t {
	const bool cache;
	const bool mmu;
	const bool dual_issue;
	const bool dsp;
	const char name[8];
};

static const struct hs_template_t hs_versions[] = {
	{false,	false,	false,	false,	"HS34"},
	{true,	false,	false,	false,	"HS36"},
	{true,	true,	false,	false,	"HS38"},
	{false,	false,	true,	false,	"HS44"},
	{true,	false,	true,	false,	"HS46"},
	{true,	true,	true,	false,	"HS48"},
	{false,	false,	true,	true,	"HS45D"},
	{true,	false,	true,	true,	"HS47D"},
};

const char *arc_hs_version(int arcver, char *name, int name_len)
{
	const char *arc_name = "HS";
	const char *arc_ver;
	bool cache = ARC_FEATURE_EXISTS(ARC_BCR_IC_BUILD);
	bool dsp = ARC_FEATURE_EXISTS(ARC_AUX_DSP_BUILD);
	bool mmu = !!read_aux_reg(ARC_AUX_MMU_BCR);
	bool dual_issue = arcver == 0x54 ? true : false;
	int i;

	for (i = 0; i < sizeof(hs_versions) / sizeof(struct hs_template_t); i++) {
		if (hs_versions[i].cache == cache &&
		    hs_versions[i].mmu == mmu &&
		    hs_versions[i].dual_issue == dual_issue &&
		    hs_versions[i].dsp == dsp) {
			arc_name = hs_versions[i].name;
			break;
		}
	}

	switch (arcver) {
	case 0x50:
		arc_ver = "v1.0";
		break;
	case 0x51:
		arc_ver = "v2.0";
		break;
	case 0x52:
		arc_ver = "v2.1c";
		break;
	case 0x53:
		arc_ver = "v3.0";
		break;
	case 0x54:
		arc_ver = "v4.0";
		break;
	default:
		arc_ver = "unknown version";
	}

	snprintf(name, name_len, "ARC %s %s", arc_name, arc_ver);

	return name;
}

const char *decode_identity(void)
{
#define MAX_CPU_NAME_LEN	64

	int arcver = read_aux_reg(ARC_AUX_IDENTITY) & 0xff;
	char *name = malloc(MAX_CPU_NAME_LEN);

	if (arcver >= 0x50)
		return arc_hs_version(arcver, name, MAX_CPU_NAME_LEN);
	else if (arcver >= 0x40)
		return arc_em_version(arcver, name, MAX_CPU_NAME_LEN);
	else if (arcver >= 0x30)
		return arc_700_version(arcver, name, MAX_CPU_NAME_LEN);
	else
		return "Unknown ARC core";
}

const char *decode_subsystem(void)
{
	int subsys_type = read_aux_reg(ARC_AUX_SUBSYS_BUILD) & GENMASK(3, 0);

	switch (subsys_type) {
	case 0: return NULL;
	case 2: return "ARC Sensor & Control IP Subsystem";
	case 3: return "ARC Data Fusion IP Subsystem";
	case 4: return "ARC Secure Subsystem";
	default: return "Unknown subsystem";
	};
}

__weak int print_cpuinfo(void)
{
	const char *subsys_name = decode_subsystem();
	char mhz[8];

	printf("CPU:   %s at %s MHz\n", decode_identity(),
	       strmhz(mhz, gd->cpu_clk));

	if (subsys_name)
		printf("Subsys:%s\n", subsys_name);

	return 0;
}
#endif /* CONFIG_DISPLAY_CPUINFO */
