/* Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved. * * Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in *the the People's Republic of China and other countries. * All Allwinner Technology Co.,Ltd. trademarks are used with permission. * * DISCLAIMER * THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT. * IF YOU NEED TO INTEGRATE THIRD PARTY’S TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.) * IN ALLWINNERS’SDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN * ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES. * ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS * COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE. * YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTY’S TECHNOLOGY. * * * THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT * PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND, * WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING * THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE * OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sunxi_hal_common.h" #include "clk_periph.h" #define NEW_RATE_CALCULATE 1 hal_clk_status_t sunxi_clk_periph_get_parent(clk_periph_pt clk, u8 *parent_index) { u32 reg = 0; struct sunxi_clk_periph *periph = clk->config; CCMU_TRACE(); if (!periph->mux.reg) { *parent_index = 0; return HAL_CLK_STATUS_OK; } //if (periph->lock) // spin_lock_irqsave(periph->lock, flags); reg = readl(periph->mux.reg); *parent_index = GET_BITS(periph->mux.shift, periph->mux.width, reg); //if (periph->lock) // spin_unlock_irqrestore(periph->lock, flags); CCMU_DBG("parent index %d \n", (*parent_index)); return HAL_CLK_STATUS_OK; } hal_clk_status_t sunxi_clk_periph_set_parent(clk_periph_pt clk, u8 index) { unsigned long reg, flags = 0; struct sunxi_clk_periph *periph = clk->config; //if (periph->flags & CLK_READONLY) // return 0; if (!periph->mux.reg) { return HAL_CLK_STATUS_OK; } //if (periph->lock) // spin_lock_irqsave(periph->lock, flags); reg = readl(periph->mux.reg); reg = SET_BITS(periph->mux.shift, periph->mux.width, reg, index); writel(reg, periph->mux.reg); //if (periph->lock) // spin_unlock_irqrestore(periph->lock, flags); return HAL_CLK_STATUS_OK; } hal_clk_status_t __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 && IS_SHARE_RST_GATE(periph)) { reg = readl(gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 1); writel(reg, gate->reset); } /* enable bus gating */ if (gate->bus && IS_SHARE_BUS_GATE(periph)) { reg = readl(gate->bus); reg = SET_BITS(gate->bus_shift, 1, reg, 1); writel(reg, gate->bus); } /* enable module gating */ if (gate->enable && IS_SHARE_MOD_GATE(periph)) { reg = readl(gate->enable); reg = SET_BITS(gate->enb_shift, 1, reg, 1); writel(reg, gate->enable); } /* enable dram gating */ if (gate->dram && IS_SHARE_MBUS_GATE(periph)) { reg = readl(gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 1); writel(reg, gate->dram); } } periph->com_gate->val |= 1 << periph->com_gate_off; return HAL_CLK_STATUS_OK; } hal_clk_status_t __sunxi_clk_periph_enable(struct sunxi_clk_periph *periph) { u32 reg; struct sunxi_clk_periph_gate *gate = &periph->gate; /* de-assert module */ if (gate->reset && !IS_SHARE_RST_GATE(periph)) { reg = readl(gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 1); writel(reg, gate->reset); } /* enable bus gating */ if (gate->bus && !IS_SHARE_BUS_GATE(periph)) { reg = readl(gate->bus); reg = SET_BITS(gate->bus_shift, 1, reg, 1); writel(reg, gate->bus); } /* enable module gating */ if (gate->enable && !IS_SHARE_MOD_GATE(periph)) { reg = readl(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); writel(reg, gate->enable); } /* enable dram gating */ if (gate->dram && !IS_SHARE_MBUS_GATE(periph)) { reg = readl(gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 1); writel(reg, gate->dram); } return HAL_CLK_STATUS_OK; } hal_clk_status_t sunxi_clk_periph_enable(clk_periph_pt clk) { //unsigned long flags = 0; hal_clk_status_t ret = 0; struct sunxi_clk_periph *periph = NULL; //if (periph->flags & CLK_READONLY) // return 0; periph = clk->config; //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(periph); } //if (periph->lock) // spin_unlock_irqrestore(periph->lock, flags); return ret == HAL_CLK_STATUS_OK ? \ HAL_CLK_STATUS_ENABLED : HAL_CLK_STATUS_ERROR_CLK_ENABLED_FAILED; } hal_clk_status_t sunxi_clk_periph_is_enabled(clk_periph_pt clk) { u32 state = 0, reg = 0; //unsigned long flags = 0; struct sunxi_clk_periph *periph = NULL; struct sunxi_clk_periph_gate *gate = NULL; periph = clk->config; gate = &periph->gate; //if (periph->lock) // spin_lock_irqsave(periph->lock, flags); /* enable bus gating */ if (gate->bus) { reg = readl(gate->bus); state &= GET_BITS(gate->bus_shift, 1, reg); } /* enable module gating */ if (gate->enable) { reg = readl(gate->enable); state &= GET_BITS(gate->enb_shift, 1, reg); } /* de-assert module */ if (gate->reset) { reg = readl(gate->reset); state &= GET_BITS(gate->rst_shift, 1, reg); } /* enable dram gating */ if (gate->dram) { reg = readl(gate->dram); state &= GET_BITS(gate->ddr_shift, 1, reg); } //if (periph->lock) // spin_unlock_irqrestore(periph->lock, flags); return state ? HAL_CLK_STATUS_ENABLED : HAL_CLK_STATUS_DISABLED; } 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 = readl(gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 0); writel(reg, gate->dram); } /* disable module gating */ if (gate->enable && IS_SHARE_MOD_GATE(periph)) { reg = readl(gate->enable); reg = SET_BITS(gate->enb_shift, 1, reg, 0); writel(reg, gate->enable); } /* disable bus gating */ if (gate->bus && IS_SHARE_BUS_GATE(periph)) { reg = readl(gate->bus); reg = SET_BITS(gate->bus_shift, 1, reg, 0); writel(reg, gate->bus); } /* assert module */ if (gate->reset && IS_SHARE_RST_GATE(periph)) { reg = readl(gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 0); writel(reg, gate->reset); } } } static void __sunxi_clk_periph_disable(struct sunxi_clk_periph *periph) { u32 reg; struct sunxi_clk_periph_gate *gate = &periph->gate; /* disable dram gating */ if (gate->dram && !IS_SHARE_MBUS_GATE(periph)) { reg = readl(gate->dram); reg = SET_BITS(gate->ddr_shift, 1, reg, 0); writel(reg, gate->dram); } /* disable module gating */ if (gate->enable && !IS_SHARE_MOD_GATE(periph)) { reg = readl(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); writel(reg, gate->enable); } /* disable bus gating */ if (gate->bus && !IS_SHARE_BUS_GATE(periph)) { reg = readl(gate->bus); reg = SET_BITS(gate->bus_shift, 1, reg, 0); writel(reg, gate->bus); } /* assert module */ if (gate->reset && !IS_SHARE_RST_GATE(periph)) { reg = readl(gate->reset); reg = SET_BITS(gate->rst_shift, 1, reg, 0); writel(reg, gate->reset); } } hal_clk_status_t sunxi_clk_periph_disable(clk_periph_pt clk) { //unsigned long flags = 0; struct sunxi_clk_periph *periph = NULL; //if (periph->flags & CLK_READONLY) // return 0; periph = clk->config; //if (periph->lock) // spin_lock_irqsave(periph->lock, flags); __sunxi_clk_periph_disable(periph); /* 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); return HAL_CLK_STATUS_DISABLED; } hal_clk_status_t sunxi_clk_periph_recalc_rate(clk_periph_pt clk, u32 *rate) { u32 reg = 0, parent_rate = 0; u64 div, div_m = 0, div_n = 0, clk_rate = 0; hal_clk_status_t ret = HAL_CLK_STATUS_OK; struct sunxi_clk_periph *periph = NULL; struct sunxi_clk_periph_div *divider = NULL; periph = clk->config; divider = &periph->divider; parent_rate = clk->clk_core.parent_rate; clk_rate = (u64)parent_rate; if (!divider->reg) { *rate = parent_rate; return ret; } //if (periph->lock) // spin_lock_irqsave(periph->lock, flags); reg = readl(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(clk_rate, div); //if (periph->lock) // spin_unlock_irqrestore(periph->lock, flags); *rate = (u32)clk_rate; return ret; } u32 sunxi_clk_periph_round_rate(clk_periph_pt clk, u32 rate, u32 prate) { struct sunxi_clk_periph *periph = NULL; struct sunxi_clk_periph_div *divider = NULL; u32 i = 0, round_rate = 0; u64 div, div_m = 0, div_n = 0; u64 factor_m = 0, factor_n = 0, found = 0; CCMU_TRACE(); periph = clk->config; divider = &periph->divider; round_rate = (prate + rate / 2 - 1); if (!rate) { return 0; } do_div(round_rate, rate); div = round_rate; if (!div) { return prate; } round_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; } /* NEW_RATE_CALCULATE */ while (i < (1 << divider->nwidth)) { if (div <= div_m) { factor_m = div - 1; factor_n = i; do_div(round_rate, (factor_m + 1) * (1 << factor_n)); found = 1; break; } div = div >> 1; i++; if (!div) { factor_m = 0; factor_n = i; do_div(round_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(round_rate, (factor_m + 1) * (1 << factor_n)); } CCMU_DBG("parent rate %dHZ, target rate %dHZ, round rate %dHZ\n", prate, rate, round_rate); CCMU_TRACE(); return round_rate; } hal_clk_status_t sunxi_clk_periph_set_rate(clk_periph_pt clk, u32 rate) { u32 i = 0, factor_m = 0, factor_n = 0, found = 0, parent_rate = 0; u32 reg = 0; struct sunxi_clk_periph *periph = NULL; struct sunxi_clk_periph_div *divider = NULL; u32 div, div_m = 0, div_n = 0; CCMU_TRACE(); periph = clk->config; divider = &periph->divider; parent_rate = clk->clk_core.parent_rate; CCMU_DBG("parent rate %dHZ, set rate %dHZ\n", parent_rate, rate); u64 tmp_rate = parent_rate; 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->init->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; } CCMU_DBG("divider->reg %d divider->mwidth %d divider->nshift %d \n", divider->reg, divider->mwidth, divider->nshift); if (!divider->reg) { return HAL_CLK_STATUS_OK; } reg = readl(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); } writel(reg, divider->reg); CCMU_TRACE(); return HAL_CLK_STATUS_OK; }