/* * Copyright (C) 2013 Allwinnertech, kevin.z.m * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Adjustable factor-based clock implementation */ #include #include #include #include #include #include #include #include "clk-sunxi.h" #include "clk-periph.h" #define NEW_RATE_CALULATE 1 static u8 sunxi_clk_periph_get_parent(struct clk_hw *hw) { u8 parent; unsigned long reg, flags = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(!periph->mux.reg) return 0; if(periph->lock) spin_lock_irqsave(periph->lock, flags); reg = periph_readl(periph,periph->mux.reg); parent = GET_BITS(periph->mux.shift, periph->mux.width, reg); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); return parent; } static int sunxi_clk_periph_set_parent(struct clk_hw *hw, u8 index) { unsigned long reg, flags = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(periph->flags & CLK_READONLY) return 0; if(!periph->mux.reg) return 0; if(periph->lock) spin_lock_irqsave(periph->lock, flags); 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); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); 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->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) { unsigned long flags = 0; int ret = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(periph->flags & CLK_READONLY) return 0; if(periph->lock) spin_lock_irqsave(periph->lock, flags); /* if common gate exist, enable it first */ if(periph->com_gate) ret = __sunxi_clk_periph_enable_shared(periph); if(!ret) ret = __sunxi_clk_periph_enable(hw); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); 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; unsigned long flags = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(periph->lock) spin_lock_irqsave(periph->lock, flags); state = __sunxi_clk_periph_is_enabled(hw); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); 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 clk_hw *hw) { unsigned long reg; struct sunxi_clk_periph *periph = to_clk_periph(hw); 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) { unsigned long flags = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(periph->flags & CLK_READONLY) return ; if(periph->lock) spin_lock_irqsave(periph->lock, flags); __sunxi_clk_periph_disable(hw); /* if common gate exist, disable it */ if(periph->com_gate) __sunxi_clk_periph_disable_shared(periph); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); } static unsigned long sunxi_clk_periph_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { unsigned long reg, flags = 0; 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; u64 rate = parent_rate; if(!divider->reg) return parent_rate; if(periph->lock) spin_lock_irqsave(periph->lock, flags); 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<lock) spin_unlock_irqrestore(periph->lock, flags); 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; #ifdef NEW_RATE_CALULATE unsigned long i=0,factor_m=0,factor_n=0,found=0; #endif unsigned long div, div_m = 0, div_n = 0; u64 parent_rate = (*prate+rate/2-1); do_div(parent_rate, rate); div = parent_rate; if(!div) return *prate; parent_rate = *prate; div_m = 1<mwidth; if(divider->nwidth) { div_n = 1<nwidth; div_n = 1<<(div_n-1); } else div_n = 1; #ifndef NEW_RATE_CALULATE if(div <= div_m) { do_div(parent_rate, div); } else if((div <= div_m*2) && (div_n >= 2)) { div &= ~(1<<0); do_div(parent_rate, div); } else if((div <= div_m*4) && (div_n >= 4)) { div &= ~(3<<0); do_div(parent_rate, div); } else if((div <= div_m*8) && (div_n >= 8)) { div &= ~(7<<0); do_div(parent_rate, div); } else if((div <= div_m*16) && (div_n >= 16)) { div &= ~(15<<0); do_div(parent_rate, div); } else if((div <= div_m*32) && (div_n >= 32)) { div &= ~(31<<0); do_div(parent_rate, div); } else if((div <= div_m*64) && (div_n >= 64)) { div &= ~(63<<0); do_div(parent_rate, div); } else if((div <= div_m*128) && (div_n >= 128)) { div &= ~(127<<0); do_div(parent_rate, div); } else { do_div(parent_rate, div_m*div_n); } #else //NEW_RATE_CALULATE while(i < (1<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<nwidth) -1; do_div(parent_rate, (factor_m+1)*(1 << factor_n)); } #endif return parent_rate; } static int __sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { #ifdef NEW_RATE_CALULATE unsigned long i=0,factor_m=0,factor_n=0,found=0; #endif 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; u64 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<mwidth; div_n = (1<nwidth)-1; if( div > (div_m<clk->name , rate ); div = div_m< 0)){ div_n = 1; div_m = div>>1; } else if((div < div_m*4) && (div_n > 1)){ div_n = 2; div_m = div>>2; } else if((div < div_m*8) && (div_n > 2)){ div_n = 3; div_m = div>>3; } else if((div < div_m*16) && (div_n > 3)){ div_n = 4; div_m = div>>4; } else if((div < div_m*32) && (div_n > 4)){ div_n = 5; div_m = div>>5; } else if((div < div_m*64) && (div_n > 5)){ div_n = 6; div_m = div>>6; } else if((div < div_m*128) && (div_n > 6)){ div_n = 7; div_m = div>>7; } else { div_m = (1<mwidth);// - 1; div_n = (1<nwidth) - 1; } if(div_m) div_m--; #else found = 0; while(i < (1<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<nwidth) -1; } div_m = factor_m; div_n = factor_n; #endif } 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) { unsigned long flags = 0; int ret = 0; struct sunxi_clk_periph *periph = to_clk_periph(hw); if(periph->lock) spin_lock_irqsave(periph->lock, flags); ret = __sunxi_clk_periph_set_rate(hw,rate,parent_rate); if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); 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)); } struct clk *sunxi_clk_register_periph(struct periph_init_data *pd, void __iomem *base) { struct clk *clk; struct clk_init_data init; struct sunxi_clk_periph *periph; BUG_ON((pd == NULL) && (pd->periph == NULL)); #ifdef __SUNXI_ALL_CLK_IGNORE_UNUSED__ pd->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 + (unsigned long __force)periph->mux.reg) : NULL; periph->divider.reg = periph->divider.reg ? (base + (unsigned long __force)periph->divider.reg) : NULL; periph->gate.enable = periph->gate.enable ? (base + (unsigned long __force)periph->gate.enable) : NULL; periph->gate.reset = periph->gate.reset ? (base + (unsigned long __force)periph->gate.reset) : NULL; periph->gate.bus = periph->gate.bus ? (base + (unsigned long __force)periph->gate.bus) : NULL; periph->gate.dram = periph->gate.dram ? (base + (unsigned long __force)periph->gate.dram) : NULL; clk = clk_register(NULL, &periph->hw); if (IS_ERR(clk)) return clk; return clk; } int sunxi_periph_reset_deassert(struct clk *c) { struct clk_hw *hw = __clk_get_hw(c); struct sunxi_clk_periph *periph = to_clk_periph(hw); struct sunxi_clk_periph_gate *gate = &periph->gate; unsigned long reg, flag = 0; unsigned long flags = 0; if(periph->flags & CLK_READONLY) return 0; if((periph->com_gate && periph->com_gate->val) && (periph->com_gate->val & periph->com_gate->mask) != (1 << periph->com_gate_off)) return 1; if(periph->lock) spin_lock_irqsave(periph->lock, flags); if(gate->dram) { reg = periph_readl(periph,gate->dram); flag = GET_BITS(gate->ddr_shift, 1, reg); /* disable dram access */ reg = SET_BITS(gate->ddr_shift, 1, reg, 0); periph_writel(periph,reg, gate->dram); } if(gate->reset) { reg = periph_readl(periph,gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 1); periph_writel(periph,reg, gate->reset); } /* enable dram access if it is needed */ if(gate->dram && flag) { reg = periph_readl(periph,gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 1); periph_writel(periph,reg, gate->dram); } if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); return 0; } int sunxi_periph_reset_assert(struct clk *c) { struct clk_hw *hw = __clk_get_hw(c); struct sunxi_clk_periph *periph = to_clk_periph(hw); struct sunxi_clk_periph_gate *gate = &periph->gate; unsigned long reg, flag = 0; unsigned long flags = 0; if(periph->flags & CLK_READONLY) return 0; if((periph->com_gate && periph->com_gate->val) && (periph->com_gate->val & periph->com_gate->mask) != (1 << periph->com_gate_off)) return 1; if(periph->lock) spin_lock_irqsave(periph->lock, flags); /* disable dram access */ if(gate->dram) { reg = periph_readl(periph,gate->dram); flag = GET_BITS(gate->ddr_shift, 1, reg); reg = SET_BITS(gate->ddr_shift, 1, reg, 0); periph_writel(periph,reg, gate->dram); } /* assert reset of periph */ if(gate->reset) { reg = periph_readl(periph,gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 0); periph_writel(periph,reg, gate->reset); } /* enable dram access if it is needed */ if(gate->dram && flag) { reg = periph_readl(periph,gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 1); periph_writel(periph,reg, gate->dram); } if(periph->lock) spin_unlock_irqrestore(periph->lock, flags); return 0; } EXPORT_SYMBOL(sunxi_periph_reset_assert); EXPORT_SYMBOL(sunxi_periph_reset_deassert);