/*
 * Joshua Henderson <joshua.henderson@microchip.com>
 * Copyright (C) 2015 Microchip Technology Inc.  All rights reserved.
 *
 *  This program is free software; you can distribute it and/or modify it
 *  under the terms 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.
 */
#include <asm/mach-pic32/pic32.h>

#include "pic32mzda.h"

/* Oscillators, PLL & clocks */
#define ICLK_MASK	0x00000080
#define PLLDIV_MASK	0x00000007
#define CUROSC_MASK	0x00000007
#define PLLMUL_MASK	0x0000007F
#define PB_MASK		0x00000007
#define FRC1		0
#define FRC2		7
#define SPLL		1
#define POSC		2
#define FRC_CLK		8000000

#define PIC32_POSC_FREQ	24000000

#define OSCCON		0x0000
#define SPLLCON		0x0020
#define PB1DIV		0x0140

u32 pic32_get_sysclk(void)
{
	u32 osc_freq = 0;
	u32 pllclk;
	u32 frcdivn;
	u32 osccon;
	u32 spllcon;
	int curr_osc;

	u32 plliclk;
	u32 pllidiv;
	u32 pllodiv;
	u32 pllmult;
	u32 frcdiv;

	void __iomem *osc_base = ioremap(PIC32_BASE_OSC, 0x200);

	osccon = __raw_readl(osc_base + OSCCON);
	spllcon = __raw_readl(osc_base + SPLLCON);

	plliclk = (spllcon & ICLK_MASK);
	pllidiv = ((spllcon >> 8) & PLLDIV_MASK) + 1;
	pllodiv = ((spllcon >> 24) & PLLDIV_MASK);
	pllmult = ((spllcon >> 16) & PLLMUL_MASK) + 1;
	frcdiv = ((osccon >> 24) & PLLDIV_MASK);

	pllclk = plliclk ? FRC_CLK : PIC32_POSC_FREQ;
	frcdivn = ((1 << frcdiv) + 1) + (128 * (frcdiv == 7));

	if (pllodiv < 2)
		pllodiv = 2;
	else if (pllodiv < 5)
		pllodiv = (1 << pllodiv);
	else
		pllodiv = 32;

	curr_osc = (int)((osccon >> 12) & CUROSC_MASK);

	switch (curr_osc) {
	case FRC1:
	case FRC2:
		osc_freq = FRC_CLK / frcdivn;
		break;
	case SPLL:
		osc_freq = ((pllclk / pllidiv) * pllmult) / pllodiv;
		break;
	case POSC:
		osc_freq = PIC32_POSC_FREQ;
		break;
	default:
		break;
	}

	iounmap(osc_base);

	return osc_freq;
}

u32 pic32_get_pbclk(int bus)
{
	u32 clk_freq;
	void __iomem *osc_base = ioremap(PIC32_BASE_OSC, 0x200);
	u32 pbxdiv = PB1DIV + ((bus - 1) * 0x10);
	u32 pbdiv = (__raw_readl(osc_base + pbxdiv) & PB_MASK) + 1;

	iounmap(osc_base);

	clk_freq = pic32_get_sysclk();

	return clk_freq / pbdiv;
}
