diff -drupN a/drivers/clk/sunxi/clk-periph.c b/drivers/clk/sunxi/clk-periph.c --- a/drivers/clk/sunxi/clk-periph.c 1970-01-01 03:00:00.000000000 +0300 +++ b/drivers/clk/sunxi/clk-periph.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,763 @@ +/* + * 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)) { +#if defined(CONFIG_ARCH_SUN8IW18P1) + if (!strcmp(periph->com_gate->name, "codec")) { + pr_debug("reset audio codec, assert and then de-assert!!\n"); + reg = periph_readl(periph, gate->reset); + reg = SET_BITS(gate->rst_shift, 1, reg, 0); + periph_writel(periph, reg, gate->reset); + } + reg = periph_readl(periph, gate->reset); + reg = SET_BITS(gate->rst_shift, 1, reg, 1); + periph_writel(periph, reg, gate->reset); +#else + reg = periph_readl(periph, gate->reset); + reg = SET_BITS(gate->rst_shift, 1, reg, 1); + periph_writel(periph, reg, gate->reset); +#endif + } + /* 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)) { +#if defined(CONFIG_ARCH_SUN8IW18P1) + if (!strcmp(periph->com_gate->name, "codec")) { + pr_debug("don't reset audio codec, keep de-assert\n"); + } else { + reg = periph_readl(periph, gate->reset); + reg = SET_BITS(gate->rst_shift, 1, reg, 0); + periph_writel(periph, reg, gate->reset); + } +#else + reg = periph_readl(periph, gate->reset); + reg = SET_BITS(gate->rst_shift, 1, reg, 0); + periph_writel(periph, reg, gate->reset); +#endif + } + } + +} + +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; +} + +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; +#ifdef CONFIG_PM_SLEEP + struct sunxi_periph_clk_reg_cache *periph_clk_reg; +#endif + + BUG_ON((pd == NULL) && (pd->periph == NULL)); + +#ifdef CONFIG_PM_SLEEP + periph_clk_reg = kzalloc(sizeof(struct sunxi_periph_clk_reg_cache), GFP_KERNEL); + if (!periph_clk_reg) { + pr_err("%s: could not allocate periph clk reg\n", __func__); + return ERR_PTR(-ENOMEM); + } +#endif + +#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; + +#ifdef CONFIG_PM_SLEEP + if (!strcmp(init.name, "cpu") || !strcmp(init.name, "axi") || + !strcmp(init.name, "cpuapb") || !strcmp(init.name, "ahb1") || + !strcmp(init.name, "ahb2")) { + kfree(periph_clk_reg); + } else { + periph_clk_reg->mux_reg = periph->mux.reg ? periph->mux.reg : NULL; + periph_clk_reg->divider_reg = periph->divider.reg ? periph->divider.reg : NULL; + periph_clk_reg->gate_enable_reg = periph->gate.enable ? periph->gate.enable : NULL; + periph_clk_reg->gate_reset_reg = periph->gate.reset ? periph->gate.reset : NULL; + periph_clk_reg->gate_bus_reg = periph->gate.bus ? periph->gate.bus : NULL; + periph_clk_reg->gate_dram_reg = periph->gate.dram ? periph->gate.dram : NULL; + list_add_tail(&periph_clk_reg->node, &clk_periph_reg_cache_list); + } +#endif + 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);