/*
 * drivers/clk/sunxi/clk_periph.c
 *
 * Copyright (c) 2007-2019 Allwinnertech Co., Ltd.
 * Author: zhengxiaobin <zhengxiaobin@allwinnertech.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that 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<clk/clk_plat.h>
#include<div64.h>
#include"clk_periph.h"
//#include"clk.h"
static u8 sunxi_clk_periph_get_parent(struct clk_hw *hw)
{
	u8 parent;
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(!periph->mux.reg)
		return 0;

	reg = periph_readl(periph,periph->mux.reg);
	parent = GET_BITS(periph->mux.shift, periph->mux.width, reg);
    
	return parent;
}


static int sunxi_clk_periph_set_parent(struct clk_hw *hw, u8 index)
{
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return 0;

	if(!periph->mux.reg)
		return 0;

	reg = periph_readl(periph,periph->mux.reg);
	reg = SET_BITS(periph->mux.shift, periph->mux.width, reg, index);
	periph_writel(periph,reg, periph->mux.reg);

	return 0;
}

static int __sunxi_clk_periph_enable_shared(struct sunxi_clk_periph *periph)
{
	unsigned long reg;
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	if(!periph->com_gate)
		return -1;

	if(!periph->com_gate->val) {
		/* de-assert module */
		if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET)
			&& IS_SHARE_RST_GATE(periph)) {
			reg = periph_readl(periph,gate->reset);
			reg = SET_BITS(gate->rst_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->reset);
		}
		/* enable bus gating */
		if(gate->bus && IS_SHARE_BUS_GATE(periph)) {
			reg = periph_readl(periph,gate->bus);
			reg = SET_BITS(gate->bus_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->bus);
		}

		/* enable module gating */
		if(gate->enable&& IS_SHARE_MOD_GATE(periph)) {
			reg = periph_readl(periph,gate->enable);
			reg = SET_BITS(gate->enb_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->enable);
		}

		/* enable dram gating */
		if(gate->dram&& IS_SHARE_MBUS_GATE(periph)) {
			reg = periph_readl(periph,gate->dram);
			reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
			periph_writel(periph,reg, gate->dram);
		}
	}
	periph->com_gate->val |= 1 << periph->com_gate_off;

	return 0;
}

static int __sunxi_clk_periph_enable(struct clk_hw *hw)
{
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* de-assert module */
	if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET)
		&& !IS_SHARE_RST_GATE(periph)) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 1);
		periph_writel(periph,reg, gate->reset);
	}

/* enable bus gating */
    if(gate->bus && !IS_SHARE_BUS_GATE(periph)) {
    reg = periph_readl(periph,gate->bus);
    reg = SET_BITS(gate->bus_shift, 1, reg, 1);
    periph_writel(periph,reg, gate->bus);
}

/* enable module gating */
    if(gate->enable&& !IS_SHARE_MOD_GATE(periph)) {
        reg = periph_readl(periph,gate->enable);
        if(periph->flags & CLK_REVERT_ENABLE)
            reg = SET_BITS(gate->enb_shift, 1, reg, 0);
        else
            reg = SET_BITS(gate->enb_shift, 1, reg, 1);
        periph_writel(periph,reg, gate->enable);
    }

    /* enable dram gating */
    if(gate->dram&& !IS_SHARE_MBUS_GATE(periph)) {
        reg = periph_readl(periph,gate->dram);
        reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
        periph_writel(periph,reg, gate->dram);

    }

    return 0;
}

static int sunxi_clk_periph_enable(struct clk_hw *hw)
{
	int ret = 0;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return 0;

	if(periph->com_gate)
		ret = __sunxi_clk_periph_enable_shared(periph);

	if (!ret)
		ret = __sunxi_clk_periph_enable(hw);

	return ret;
}

static int __sunxi_clk_periph_is_enabled(struct clk_hw *hw)
{
	int state = 1;
	unsigned long reg;
	struct sunxi_clk_periph *periph = to_clk_periph(hw);
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* enable bus gating */
	if(gate->bus) {
		reg = periph_readl(periph,gate->bus);
		state &= GET_BITS(gate->bus_shift, 1, reg);
	}

	/* enable module gating */
	if(gate->enable) {
		reg = periph_readl(periph,gate->enable);
		state &= GET_BITS(gate->enb_shift, 1, reg);
	}
	/* de-assert module */
	if(gate->reset) {
		reg = periph_readl(periph,gate->reset);
		state &= GET_BITS(gate->rst_shift, 1, reg);
	}

	/* enable dram gating */
	if(gate->dram) {
		reg = periph_readl(periph,gate->dram);
		state &= GET_BITS(gate->ddr_shift, 1, reg);
	}

	return state;
}

static int sunxi_clk_periph_is_enabled(struct clk_hw *hw)
{
	int state = 0;
	//struct sunxi_clk_periph *periph = to_clk_periph(hw);

	state = __sunxi_clk_periph_is_enabled(hw);
	return state;
}
static void __sunxi_clk_periph_disable_shared(struct sunxi_clk_periph *periph)
{
	unsigned long reg;
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	if(!periph->com_gate->val)
		return ;

	periph->com_gate->val &= ~(1 << periph->com_gate_off);

	if(!periph->com_gate->val) {
		/* disable dram gating */
		if(gate->dram&& IS_SHARE_MBUS_GATE(periph)) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->dram);
        }

        /* disable module gating */
        if(gate->enable&& IS_SHARE_MOD_GATE(periph)) {
		reg = periph_readl(periph,gate->enable);
		reg = SET_BITS(gate->enb_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->enable);
	}

	/* disable bus gating */
	if(gate->bus&& IS_SHARE_BUS_GATE(periph)) {
		reg = periph_readl(periph,gate->bus);
		reg = SET_BITS(gate->bus_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->bus);
        }
        /* assert module */
	if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET)
		&& IS_SHARE_RST_GATE(periph)) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->reset);
        }
    }

}

static void __sunxi_clk_periph_disable(struct sunxi_clk_periph *pperiph)
{
	unsigned long reg;
	struct sunxi_clk_periph *periph = pperiph;
	struct sunxi_clk_periph_gate *gate = &periph->gate;

	/* disable dram gating */
	if(gate->dram&& !IS_SHARE_MBUS_GATE(periph)) {
		reg = periph_readl(periph,gate->dram);
		reg = SET_BITS(gate->ddr_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->dram);
	}

	/* disable module gating */
	if(gate->enable&& !IS_SHARE_MOD_GATE(periph)) {
		reg = periph_readl(periph,gate->enable);
		if(periph->flags & CLK_REVERT_ENABLE)
			reg = SET_BITS(gate->enb_shift, 1, reg, 1);
		else
			reg = SET_BITS(gate->enb_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->enable);
	}

	/* disable bus gating */
	if(gate->bus&& !IS_SHARE_BUS_GATE(periph)) {
		reg = periph_readl(periph,gate->bus);
		reg = SET_BITS(gate->bus_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->bus);
	}

	/* assert module */
	if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET)
		&&!IS_SHARE_RST_GATE(periph)) {
		reg = periph_readl(periph,gate->reset);
		reg = SET_BITS(gate->rst_shift, 1, reg, 0);
		periph_writel(periph,reg, gate->reset);
	}
}

static void sunxi_clk_periph_disable(struct clk_hw *hw)
{
	struct sunxi_clk_periph *periph = to_clk_periph(hw);

	if(periph->flags & CLK_READONLY)
		return;

	__sunxi_clk_periph_disable(periph);

	if(periph->com_gate)
		__sunxi_clk_periph_disable_shared(periph);
}

static unsigned long sunxi_clk_periph_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
    unsigned long reg;
    struct sunxi_clk_periph *periph = to_clk_periph(hw);
    struct sunxi_clk_periph_div *divider = &periph->divider;
    unsigned long div, div_m = 0, div_n = 0;
    unsigned long long rate = parent_rate;

    if(!divider->reg)
        return parent_rate;

    reg = periph_readl(periph,divider->reg);
    if(divider->mwidth)
        div_m = GET_BITS(divider->mshift, divider->mwidth, reg);
    if(divider->nwidth)
        div_n = GET_BITS(divider->nshift, divider->nwidth, reg);
    div = (div_m+1)*(1<<div_n);
    do_div(rate, div);

    return rate;
}


static long sunxi_clk_periph_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
{
    struct sunxi_clk_periph *periph = to_clk_periph(hw);
    struct sunxi_clk_periph_div *divider = &periph->divider;
    unsigned long i=0,factor_m=0,factor_n=0,found=0;
    unsigned long div, div_m = 0, div_n = 0;
    unsigned long long parent_rate = (*prate+rate/2-1);

    do_div(parent_rate, rate);	
	div = parent_rate;
    if(!div)
        return *prate;

    parent_rate = *prate;
    div_m = 1<<divider->mwidth;
    if(divider->nwidth) {
        div_n = 1<<divider->nwidth;
        div_n = 1<<(div_n-1);
    } else
        div_n = 1;

    while(i < (1<<divider->nwidth))
    {
        if(div <= div_m)
        {
            factor_m = div-1;
            factor_n = i;
            do_div(parent_rate, (factor_m+1)*(1 << factor_n));
            found = 1;
            break;
        }
        div = div >>1;
        i++;

         if(!div)
        {
            factor_m = 0;
            factor_n = i;
            do_div(parent_rate, (factor_m+1)*(1 << factor_n));
            found = 1;
            break;
        }
    }
    if(!found)
    {
        factor_m = (div >div_m?div_m:div)-1;
        factor_n = (1<<divider->nwidth) -1;
        do_div(parent_rate, (factor_m+1)*(1 << factor_n));
    }

    return parent_rate;
}

static int __sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
    unsigned long i=0,factor_m=0,factor_n=0,found=0;
    unsigned long reg;
    struct sunxi_clk_periph *periph = to_clk_periph(hw);
    struct sunxi_clk_periph_div *divider = &periph->divider;
    unsigned long div, div_m = 0, div_n = 0;
    unsigned long long tmp_rate = parent_rate;

	if(periph->flags & CLK_READONLY)
        return 0;

	if(!divider->reg)
		return 0;

    do_div(tmp_rate, rate);
    div = tmp_rate;
    if(!div)
        div_m = div_n =0;
    else {
        div_m = 1<<divider->mwidth;
        div_n = (1<<divider->nwidth)-1;
		
		if( div > (div_m<<div_n) )
		{
			//WARN(1, "clk %s rate is too large : %lu\n",hw->clk->name , rate );
			div = div_m<<div_n;
		}
    found = 0;
    while(i < (1<<divider->nwidth))
    {
        if(div <= div_m)
        {
            factor_m = div-1;
            factor_n = i;
            found = 1;
            break;
        }
        div = div >>1;
        i++;
         if(!div)
        {
            factor_m = 0;
            factor_n = i;
            found = 1;
            break;
        }
    }
    if(!found)
    {
        factor_m = (div >div_m?div_m:div)-1;
        factor_n = (1<<divider->nwidth) -1;
    }
    div_m = factor_m;
    div_n = factor_n;
    }

    reg = periph_readl(periph,divider->reg);
    if(divider->mwidth)
        reg = SET_BITS(divider->mshift, divider->mwidth, reg, div_m);
    if(divider->nwidth)
        reg = SET_BITS(divider->nshift, divider->nwidth, reg, div_n);
    periph_writel(periph,reg, divider->reg);

    return 0;
}
static int sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
    int ret = 0;
    //struct sunxi_clk_periph *periph = to_clk_periph(hw);

    ret = __sunxi_clk_periph_set_rate(hw,rate,parent_rate);
    return ret;
}

static const struct clk_ops sunxi_clk_periph_ops = {

    .get_parent = sunxi_clk_periph_get_parent,
    .set_parent = sunxi_clk_periph_set_parent,

    .recalc_rate = sunxi_clk_periph_recalc_rate,
    .round_rate = sunxi_clk_periph_round_rate,
    .set_rate = sunxi_clk_periph_set_rate,

    .is_enabled = sunxi_clk_periph_is_enabled,
    .enable = sunxi_clk_periph_enable,
    .disable = sunxi_clk_periph_disable,
};
void sunxi_clk_get_periph_ops(struct clk_ops* ops)
{
    memcpy(ops,&sunxi_clk_periph_ops,sizeof(sunxi_clk_periph_ops));
}

int sunxi_clk_register_periph(struct periph_init_data *pd, void *base)
{
	struct clk_init_data init;
	struct clk *clk;
	struct sunxi_clk_periph *periph;

#ifdef __SUNXI_ALL_CLK_IGNORE_UNUSED__
		flags |= CLK_IGNORE_UNUSED;
#endif
	periph = pd->periph;

	init.name = pd->name;
	init.ops = periph->priv_clkops?periph->priv_clkops:&sunxi_clk_periph_ops;
	init.flags = pd->flags;
	init.parent_names = pd->parent_names;
	init.num_parents = pd->num_parents;

	/* Data in .init is copied by clk_register(), so stack variable OK */
	periph->hw.init = &init;
	periph->flags = init.flags;

	/* fix registers */
	periph->mux.reg = periph->mux.reg ? (base + (ulong)periph->mux.reg) : NULL;
	periph->divider.reg = periph->divider.reg ?
				(base + (ulong)periph->divider.reg) : NULL;

	periph->gate.enable = periph->gate.enable ?
				(base + (ulong)periph->gate.enable) : NULL;
	periph->gate.reset = periph->gate.reset ?
				(base + (ulong)periph->gate.reset) : NULL;
	periph->gate.bus = periph->gate.bus ?
				(base + (ulong)periph->gate.bus) : NULL;
	periph->gate.dram = periph->gate.dram ?
				(base + (ulong)periph->gate.dram) : NULL;

	clk = clk_register(&periph->hw);
	if (NULL == clk)
		return -1;

	return 0;
}

