/* * 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 "clk-sunxi.h" #include "clk-periph.h" #define NEW_RATE_CALCULATE 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_CALCULATE 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); if (!rate) return -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_CALCULATE 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_CALCULATE */ 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<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_CALCULATE 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<init->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 << divider->mwidth); div_n = (1 << divider->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 << divider->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; } EXPORT_SYMBOL(sunxi_periph_reset_deassert); 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);