#include <linux/string.h>
#include <linux/module.h>
#include "cmucal.h"

#define get_node(list, idx)		(GET_IDX(list[idx].id) == idx ? \
					&list[idx] : NULL)
#define get_clk_node(list, idx)		(GET_IDX(list[idx].clk.id) == idx ? \
					&list[idx] : NULL)

extern struct cmucal_clk_fixed_rate cmucal_fixed_rate_list[];
extern struct cmucal_clk_fixed_factor cmucal_fixed_factor_list[];
extern struct cmucal_pll cmucal_pll_list[];
extern struct cmucal_mux cmucal_mux_list[];
extern struct cmucal_div cmucal_div_list[];
extern struct cmucal_gate cmucal_gate_list[];
extern struct cmucal_qch cmucal_qch_list[];
extern struct cmucal_option cmucal_option_list[];
extern struct sfr_block cmucal_sfr_block_list[];
extern struct sfr cmucal_sfr_list[];
extern struct sfr_access cmucal_sfr_access_list[];
extern struct vclk cmucal_vclk_list[];
extern struct vclk acpm_vclk_list[];
extern struct cmucal_clkout cmucal_clkout_list[];

extern unsigned int cmucal_fixed_rate_size;
extern unsigned int cmucal_fixed_factor_size;
extern unsigned int cmucal_pll_size;
extern unsigned int cmucal_mux_size;
extern unsigned int cmucal_div_size;
extern unsigned int cmucal_gate_size;
extern unsigned int cmucal_qch_size;
extern unsigned int cmucal_option_size;
extern unsigned int cmucal_sfr_block_size;
extern unsigned int cmucal_sfr_size;
extern unsigned int cmucal_sfr_access_size;
extern unsigned int cmucal_vclk_size;
extern unsigned int acpm_vclk_size;
extern unsigned int cmucal_clkout_size;

unsigned int cmucal_get_list_size(unsigned int type)
{
	switch (type) {
	case FIXED_RATE_TYPE:
		return cmucal_fixed_rate_size;
	case FIXED_FACTOR_TYPE:
		return cmucal_fixed_factor_size;
	case PLL_TYPE:
		return cmucal_pll_size;
	case MUX_TYPE:
		return cmucal_mux_size;
	case DIV_TYPE:
		return cmucal_div_size;
	case GATE_TYPE:
		return cmucal_gate_size;
	case QCH_TYPE:
		return cmucal_qch_size;
	case OPTION_TYPE:
		return cmucal_option_size;
	case SFR_BLOCK_TYPE:
		return cmucal_sfr_block_size;
	case SFR_TYPE:
		return cmucal_sfr_size;
	case SFR_ACCESS_TYPE:
		return cmucal_sfr_access_size;
	case VCLK_TYPE:
		return cmucal_vclk_size;
	case CLKOUT_TYPE:
		return cmucal_clkout_size;
	case ACPM_VCLK_TYPE:
		return acpm_vclk_size;
	default:
		pr_info("unsupport cmucal type %x\n", type);
	}

	return 0;
}
EXPORT_SYMBOL_GPL(cmucal_get_list_size);

void *cmucal_get_node(unsigned int id)
{
	unsigned int type = GET_TYPE(id);
	unsigned short idx = GET_IDX(id);
	void *node = NULL;

	switch (type) {
	case FIXED_RATE_TYPE:
		node = get_clk_node(cmucal_fixed_rate_list, idx);
		break;
	case FIXED_FACTOR_TYPE:
		node = get_clk_node(cmucal_fixed_factor_list, idx);
		break;
	case PLL_TYPE:
		node = get_clk_node(cmucal_pll_list, idx);
		break;
	case MUX_TYPE:
		node = get_clk_node(cmucal_mux_list, idx);
		break;
	case DIV_TYPE:
		node = get_clk_node(cmucal_div_list, idx);
		break;
	case GATE_TYPE:
		node = get_clk_node(cmucal_gate_list, idx);
		break;
	case QCH_TYPE:
		node = get_clk_node(cmucal_qch_list, idx);
		break;
	case OPTION_TYPE:
		node = get_clk_node(cmucal_option_list, idx);
		break;
	case CLKOUT_TYPE:
		node = get_clk_node(cmucal_clkout_list, idx);
		break;
	case VCLK_TYPE:
		if (IS_ACPM_VCLK(id))
			node = get_node(acpm_vclk_list, idx);
		else
			node = get_node(cmucal_vclk_list, idx);
		break;
	case ACPM_VCLK_TYPE:
		node = get_node(acpm_vclk_list, idx);
		break;
	default:
		pr_info("unsupport cmucal node %x\n", id);
	}

	return node;
}
EXPORT_SYMBOL_GPL(cmucal_get_node);

void * cmucal_get_sfr_node(unsigned int id)
{
	unsigned int type = GET_TYPE(id);
	unsigned short idx = GET_IDX(id);
	void *node = NULL;

	switch (type) {
	case SFR_BLOCK_TYPE:
		node = get_node(cmucal_sfr_block_list, idx);
		break;
	case SFR_TYPE:
		node = get_node(cmucal_sfr_list, idx);
		break;
	case SFR_ACCESS_TYPE:
		node = get_node(cmucal_sfr_access_list, idx);
		break;
	default:
		pr_info("unsupport cmucal sfr node %x\n", id);
	}

	return node;
}
EXPORT_SYMBOL_GPL(cmucal_get_sfr_node);

unsigned int cmucal_get_id(char *name)
{
	unsigned int id = INVALID_CLK_ID;
	int i;

	if (strstr(name, "MUX") || strstr(name, "MOUT")) {
		for (i = 0; i < cmucal_mux_size; i++)
			if (!strcmp(name, cmucal_mux_list[i].clk.name)) {
				id = cmucal_mux_list[i].clk.id;
				return id;
			}
	}

	if (strstr(name, "GATE") || strstr(name, "GOUT") || strstr(name, "CLK_BLK")) {
		for (i = 0; i < cmucal_gate_size; i++)
			if (!strcmp(name, cmucal_gate_list[i].clk.name)) {
				id = cmucal_gate_list[i].clk.id;
				return id;
			}
	}

	if (strstr(name, "DIV") || strstr(name, "DOUT") || strstr(name, "CLKCMU")) {
		for (i = 0; i < cmucal_div_size; i++)
			if (!strcmp(name, cmucal_div_list[i].clk.name)) {
				id = cmucal_div_list[i].clk.id;
				return id;
			}
		for (i = 0; i < cmucal_fixed_factor_size; i++)
			if (!strcmp(name, cmucal_fixed_factor_list[i].clk.name)) {
				id = cmucal_fixed_factor_list[i].clk.id;
				return id;
			}
	}

	if (strstr(name, "VCLK") || strstr(name, "dvfs")) {
		for (i = 0; i < cmucal_vclk_size; i++)
			if (!strcmp(name, cmucal_vclk_list[i].name)) {
				id = cmucal_vclk_list[i].id;
				return id;
			}
	}

	if (strstr(name, "PLL")) {
		for (i = 0; i < cmucal_pll_size; i++)
			if (!strcmp(name, cmucal_pll_list[i].clk.name)) {
				id = cmucal_pll_list[i].clk.id;
				return id;
			}
	}

	if (strstr(name, "IO") || strstr(name, "OSC")) {
		for (i = 0; i < cmucal_pll_size; i++)
			if (!strcmp(name, cmucal_fixed_rate_list[i].clk.name)) {
				id = cmucal_fixed_rate_list[i].clk.id;
				return id;
			}
	}

	return id;
}
EXPORT_SYMBOL_GPL(cmucal_get_id);

unsigned int cmucal_get_id_by_addr(unsigned int addr)
{
	unsigned int id = INVALID_CLK_ID;
	unsigned int type;
	int i;

	type = addr & 0x3F00;

	if (type == 0x1800 || type == 0x1900) {
		/* DIV */
		for (i = 0; i < cmucal_div_size; i++)
			if (addr == cmucal_div_list[i].clk.paddr) {
				id = cmucal_div_list[i].clk.id;
				return id;
			}
	} else if (type == 0x1000 || type == 0x1100) {
		/* MUX */
		for (i = 0; i < cmucal_mux_size; i++)
			if (addr == cmucal_mux_list[i].clk.paddr) {
				id = cmucal_mux_list[i].clk.id;
				return id;
			}
	} else if (type == 0x2000 || type == 0x2100) {
		/* GATE */
		for (i = 0; i < cmucal_gate_size; i++)
			if (addr == cmucal_gate_list[i].clk.paddr) {
				id = cmucal_gate_list[i].clk.id;
				return id;
			}
	} else {
		/* PLL */
		for (i = 0; i < cmucal_pll_size; i++)
			if (addr == cmucal_pll_list[i].clk.paddr) {
				id = cmucal_pll_list[i].clk.id;
				return id;
			}

		/* USER_MUX */
		for (i = 0; i < cmucal_mux_size; i++)
			if (addr == cmucal_mux_list[i].clk.paddr) {
				id = cmucal_mux_list[i].clk.id;
				return id;
			}
	}

	return id;
}
EXPORT_SYMBOL_GPL(cmucal_get_id_by_addr);

MODULE_LICENSE("GPL");