Commit dc8797e3 authored by Chen-Yu Tsai's avatar Chen-Yu Tsai Committed by Ulf Hansson
Browse files

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: default avatarChen-Yu Tsai <wens@csie.org>
Acked-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent f6f64ed8
Loading
Loading
Loading
Loading
+80 −0
Original line number Diff line number Diff line
@@ -172,3 +172,83 @@ const struct clk_ops ccu_mp_ops = {
	.recalc_rate	= ccu_mp_recalc_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,
};
+30 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#ifndef _CCU_MP_H_
#define _CCU_MP_H_

#include <linux/bitops.h>
#include <linux/clk-provider.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;

/*
 * 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_ */