716 lines
18 KiB
C
716 lines
18 KiB
C
|
/*
|
||
|
* Copyright (C) 2013 Allwinnertech, kevin.z.m <kevin@allwinnertech.com>
|
||
|
*
|
||
|
* 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 <linux/clk.h>
|
||
|
#include <linux/clk-provider.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/module.h>
|
||
|
#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<<div_n);
|
||
|
do_div(rate, div);
|
||
|
|
||
|
if (periph->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<<divider->mwidth;
|
||
|
if (divider->nwidth) {
|
||
|
div_n = 1<<divider->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<<divider->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<<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;
|
||
|
}
|
||
|
#ifndef NEW_RATE_CALCULATE
|
||
|
if (div < div_m) {
|
||
|
div_m = div;
|
||
|
div_n = 0;
|
||
|
} else if ((div < div_m*2) && (div_n > 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<<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;
|
||
|
#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);
|