clk: sunxi-ng: Add MP_MMC clocks that support MMC timing modes switching
All of our MMC clocks are of the MP clock type. A few MMC clocks on some SoCs, such as MMC2 on the A83T, support new/old timing mode switching. >From a clock rate point of view, when the new timing mode is active. the output clock rate is halved. This patch adds a special wrapper class of clocks, MP_MMC, around the generic MP type clocks. The rate related callbacks in ccu_mp_mmc_ops for this class look at the timing mode bit and apply the /2 post-divider when needed, before passing it through to the generic class ops, ccu_mp_ops. Signed-off-by: Chen-Yu Tsai <wens@csie.org> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
committed by
Ulf Hansson
parent
f6f64ed868
commit
dc8797e39f
@@ -172,3 +172,83 @@ const struct clk_ops ccu_mp_ops = {
|
|||||||
.recalc_rate = ccu_mp_recalc_rate,
|
.recalc_rate = ccu_mp_recalc_rate,
|
||||||
.set_rate = ccu_mp_set_rate,
|
.set_rate = ccu_mp_set_rate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for MMC timing mode switching
|
||||||
|
*
|
||||||
|
* The MMC clocks on some SoCs support switching between old and
|
||||||
|
* new timing modes. A platform specific API is provided to query
|
||||||
|
* and set the timing mode on supported SoCs.
|
||||||
|
*
|
||||||
|
* In addition, a special class of ccu_mp_ops is provided, which
|
||||||
|
* takes in to account the timing mode switch. When the new timing
|
||||||
|
* mode is active, the clock output rate is halved. This new class
|
||||||
|
* is a wrapper around the generic ccu_mp_ops. When clock rates
|
||||||
|
* are passed through to ccu_mp_ops callbacks, they are doubled
|
||||||
|
* if the new timing mode bit is set, to account for the post
|
||||||
|
* divider. Conversely, when clock rates are passed back, they
|
||||||
|
* are halved if the mode bit is set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static unsigned long ccu_mp_mmc_recalc_rate(struct clk_hw *hw,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
unsigned long rate = ccu_mp_recalc_rate(hw, parent_rate);
|
||||||
|
struct ccu_common *cm = hw_to_ccu_common(hw);
|
||||||
|
u32 val = readl(cm->base + cm->reg);
|
||||||
|
|
||||||
|
if (val & CCU_MMC_NEW_TIMING_MODE)
|
||||||
|
return rate / 2;
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_mp_mmc_determine_rate(struct clk_hw *hw,
|
||||||
|
struct clk_rate_request *req)
|
||||||
|
{
|
||||||
|
struct ccu_common *cm = hw_to_ccu_common(hw);
|
||||||
|
u32 val = readl(cm->base + cm->reg);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* adjust the requested clock rate */
|
||||||
|
if (val & CCU_MMC_NEW_TIMING_MODE) {
|
||||||
|
req->rate *= 2;
|
||||||
|
req->min_rate *= 2;
|
||||||
|
req->max_rate *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ccu_mp_determine_rate(hw, req);
|
||||||
|
|
||||||
|
/* re-adjust the requested clock rate back */
|
||||||
|
if (val & CCU_MMC_NEW_TIMING_MODE) {
|
||||||
|
req->rate /= 2;
|
||||||
|
req->min_rate /= 2;
|
||||||
|
req->max_rate /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_mp_mmc_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_common *cm = hw_to_ccu_common(hw);
|
||||||
|
u32 val = readl(cm->base + cm->reg);
|
||||||
|
|
||||||
|
if (val & CCU_MMC_NEW_TIMING_MODE)
|
||||||
|
rate *= 2;
|
||||||
|
|
||||||
|
return ccu_mp_set_rate(hw, rate, parent_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct clk_ops ccu_mp_mmc_ops = {
|
||||||
|
.disable = ccu_mp_disable,
|
||||||
|
.enable = ccu_mp_enable,
|
||||||
|
.is_enabled = ccu_mp_is_enabled,
|
||||||
|
|
||||||
|
.get_parent = ccu_mp_get_parent,
|
||||||
|
.set_parent = ccu_mp_set_parent,
|
||||||
|
|
||||||
|
.determine_rate = ccu_mp_mmc_determine_rate,
|
||||||
|
.recalc_rate = ccu_mp_mmc_recalc_rate,
|
||||||
|
.set_rate = ccu_mp_mmc_set_rate,
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#ifndef _CCU_MP_H_
|
#ifndef _CCU_MP_H_
|
||||||
#define _CCU_MP_H_
|
#define _CCU_MP_H_
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
#include <linux/clk-provider.h>
|
#include <linux/clk-provider.h>
|
||||||
|
|
||||||
#include "ccu_common.h"
|
#include "ccu_common.h"
|
||||||
@@ -74,4 +75,33 @@ static inline struct ccu_mp *hw_to_ccu_mp(struct clk_hw *hw)
|
|||||||
|
|
||||||
extern const struct clk_ops ccu_mp_ops;
|
extern const struct clk_ops ccu_mp_ops;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special class of M-P clock that supports MMC timing modes
|
||||||
|
*
|
||||||
|
* Since the MMC clock registers all follow the same layout, we can
|
||||||
|
* simplify the macro for this particular case. In addition, as
|
||||||
|
* switching modes also affects the output clock rate, we need to
|
||||||
|
* have CLK_GET_RATE_NOCACHE for all these types of clocks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SUNXI_CCU_MP_MMC_WITH_MUX_GATE(_struct, _name, _parents, _reg, \
|
||||||
|
_flags) \
|
||||||
|
struct ccu_mp _struct = { \
|
||||||
|
.enable = BIT(31), \
|
||||||
|
.m = _SUNXI_CCU_DIV(0, 4), \
|
||||||
|
.p = _SUNXI_CCU_DIV(16, 2), \
|
||||||
|
.mux = _SUNXI_CCU_MUX(24, 2), \
|
||||||
|
.common = { \
|
||||||
|
.reg = _reg, \
|
||||||
|
.features = CCU_FEATURE_MMC_TIMING_SWITCH, \
|
||||||
|
.hw.init = CLK_HW_INIT_PARENTS(_name, \
|
||||||
|
_parents, \
|
||||||
|
&ccu_mp_mmc_ops, \
|
||||||
|
CLK_GET_RATE_NOCACHE | \
|
||||||
|
_flags), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const struct clk_ops ccu_mp_mmc_ops;
|
||||||
|
|
||||||
#endif /* _CCU_MP_H_ */
|
#endif /* _CCU_MP_H_ */
|
||||||
|
|||||||
Reference in New Issue
Block a user