/*
 * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/bug.h>

#include <soc/tegra/fuse.h>

#include "fuse.h"

#define CPU_PROCESS_CORNERS	2
#define GPU_PROCESS_CORNERS	2
#define CORE_PROCESS_CORNERS	2

#define FUSE_CPU_SPEEDO_0	0x14
#define FUSE_CPU_SPEEDO_1	0x2c
#define FUSE_CPU_SPEEDO_2	0x30
#define FUSE_SOC_SPEEDO_0	0x34
#define FUSE_SOC_SPEEDO_1	0x38
#define FUSE_SOC_SPEEDO_2	0x3c
#define FUSE_CPU_IDDQ		0x18
#define FUSE_SOC_IDDQ		0x40
#define FUSE_GPU_IDDQ		0x128
#define FUSE_FT_REV		0x28

enum {
	THRESHOLD_INDEX_0,
	THRESHOLD_INDEX_1,
	THRESHOLD_INDEX_COUNT,
};

static const u32 __initconst cpu_process_speedos[][CPU_PROCESS_CORNERS] = {
	{2190,	UINT_MAX},
	{0,	UINT_MAX},
};

static const u32 __initconst gpu_process_speedos[][GPU_PROCESS_CORNERS] = {
	{1965,	UINT_MAX},
	{0,	UINT_MAX},
};

static const u32 __initconst core_process_speedos[][CORE_PROCESS_CORNERS] = {
	{2101,	UINT_MAX},
	{0,	UINT_MAX},
};

static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
					 int *threshold)
{
	int sku = sku_info->sku_id;

	/* Assign to default */
	sku_info->cpu_speedo_id = 0;
	sku_info->soc_speedo_id = 0;
	sku_info->gpu_speedo_id = 0;
	*threshold = THRESHOLD_INDEX_0;

	switch (sku) {
	case 0x00: /* Eng sku */
	case 0x0F:
	case 0x23:
		/* Using the default */
		break;
	case 0x83:
		sku_info->cpu_speedo_id = 2;
		break;

	case 0x1F:
	case 0x87:
	case 0x27:
		sku_info->cpu_speedo_id = 2;
		sku_info->soc_speedo_id = 0;
		sku_info->gpu_speedo_id = 1;
		*threshold = THRESHOLD_INDEX_0;
		break;
	case 0x81:
	case 0x21:
	case 0x07:
		sku_info->cpu_speedo_id = 1;
		sku_info->soc_speedo_id = 1;
		sku_info->gpu_speedo_id = 1;
		*threshold = THRESHOLD_INDEX_1;
		break;
	case 0x49:
	case 0x4A:
	case 0x48:
		sku_info->cpu_speedo_id = 4;
		sku_info->soc_speedo_id = 2;
		sku_info->gpu_speedo_id = 3;
		*threshold = THRESHOLD_INDEX_1;
		break;
	default:
		pr_err("Tegra Unknown SKU %d\n", sku);
		/* Using the default for the error case */
		break;
	}
}

void __init tegra124_init_speedo_data(struct tegra_sku_info *sku_info)
{
	int i, threshold, cpu_speedo_0_value, soc_speedo_0_value;
	int cpu_iddq_value, gpu_iddq_value, soc_iddq_value;

	BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
			THRESHOLD_INDEX_COUNT);
	BUILD_BUG_ON(ARRAY_SIZE(gpu_process_speedos) !=
			THRESHOLD_INDEX_COUNT);
	BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
			THRESHOLD_INDEX_COUNT);

	cpu_speedo_0_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_0);

	/* GPU Speedo is stored in CPU_SPEEDO_2 */
	sku_info->gpu_speedo_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_2);

	soc_speedo_0_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_0);

	cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);
	soc_iddq_value = tegra30_fuse_readl(FUSE_SOC_IDDQ);
	gpu_iddq_value = tegra30_fuse_readl(FUSE_GPU_IDDQ);

	sku_info->cpu_speedo_value = cpu_speedo_0_value;

	if (sku_info->cpu_speedo_value == 0) {
		pr_warn("Tegra Warning: Speedo value not fused.\n");
		WARN_ON(1);
		return;
	}

	rev_sku_to_speedo_ids(sku_info, &threshold);

	sku_info->cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);

	for (i = 0; i < GPU_PROCESS_CORNERS; i++)
		if (sku_info->gpu_speedo_value <
			gpu_process_speedos[threshold][i])
			break;
	sku_info->gpu_process_id = i;

	for (i = 0; i < CPU_PROCESS_CORNERS; i++)
		if (sku_info->cpu_speedo_value <
			cpu_process_speedos[threshold][i])
				break;
	sku_info->cpu_process_id = i;

	for (i = 0; i < CORE_PROCESS_CORNERS; i++)
		if (soc_speedo_0_value <
			core_process_speedos[threshold][i])
			break;
	sku_info->core_process_id = i;

	pr_debug("Tegra GPU Speedo ID=%d, Speedo Value=%d\n",
		 sku_info->gpu_speedo_id, sku_info->gpu_speedo_value);
}