diff options
Diffstat (limited to 'drivers/clk/sunxi-ng')
-rw-r--r-- | drivers/clk/sunxi-ng/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu-sun8i-a83t.c | 10 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_common.h | 4 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_mmc_timing.c | 70 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_mp.c | 80 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_mp.h | 30 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_reset.c | 12 |
7 files changed, 199 insertions, 8 deletions
diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 0c45fa50283d..45a5910379a5 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -1,5 +1,6 @@ # Common objects lib-$(CONFIG_SUNXI_CCU) += ccu_common.o +lib-$(CONFIG_SUNXI_CCU) += ccu_mmc_timing.o lib-$(CONFIG_SUNXI_CCU) += ccu_reset.o # Base clock types diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c index 947f9f6e05d2..e43acebdfbcd 100644 --- a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c +++ b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c @@ -418,14 +418,8 @@ static SUNXI_CCU_PHASE(mmc1_sample_clk, "mmc1-sample", "mmc1", static SUNXI_CCU_PHASE(mmc1_output_clk, "mmc1-output", "mmc1", 0x08c, 8, 3, 0); -/* TODO Support MMC2 clock's new timing mode. */ -static SUNXI_CCU_MP_WITH_MUX_GATE(mmc2_clk, "mmc2", mod0_default_parents, - 0x090, - 0, 4, /* M */ - 16, 2, /* P */ - 24, 2, /* mux */ - BIT(31), /* gate */ - 0); +static SUNXI_CCU_MP_MMC_WITH_MUX_GATE(mmc2_clk, "mmc2", mod0_default_parents, + 0x090, 0); static SUNXI_CCU_PHASE(mmc2_sample_clk, "mmc2-sample", "mmc2", 0x090, 20, 3, 0); diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h index d6fdd7a789aa..cadd1a9f93b6 100644 --- a/drivers/clk/sunxi-ng/ccu_common.h +++ b/drivers/clk/sunxi-ng/ccu_common.h @@ -23,6 +23,10 @@ #define CCU_FEATURE_FIXED_POSTDIV BIT(3) #define CCU_FEATURE_ALL_PREDIV BIT(4) #define CCU_FEATURE_LOCK_REG BIT(5) +#define CCU_FEATURE_MMC_TIMING_SWITCH BIT(6) + +/* MMC timing mode switch bit */ +#define CCU_MMC_NEW_TIMING_MODE BIT(30) struct device_node; diff --git a/drivers/clk/sunxi-ng/ccu_mmc_timing.c b/drivers/clk/sunxi-ng/ccu_mmc_timing.c new file mode 100644 index 000000000000..f9869f7353c0 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mmc_timing.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Chen-Yu Tsai. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/clk/sunxi-ng.h> + +#include "ccu_common.h" + +/** + * sunxi_ccu_set_mmc_timing_mode: Configure the MMC clock timing mode + * @clk: clock to be configured + * @new_mode: true for new timing mode introduced in A83T and later + * + * Returns 0 on success, -ENOTSUPP if the clock does not support + * switching modes. + */ +int sunxi_ccu_set_mmc_timing_mode(struct clk *clk, bool new_mode) +{ + struct clk_hw *hw = __clk_get_hw(clk); + struct ccu_common *cm = hw_to_ccu_common(hw); + unsigned long flags; + u32 val; + + if (!(cm->features & CCU_FEATURE_MMC_TIMING_SWITCH)) + return -ENOTSUPP; + + spin_lock_irqsave(cm->lock, flags); + + val = readl(cm->base + cm->reg); + if (new_mode) + val |= CCU_MMC_NEW_TIMING_MODE; + else + val &= ~CCU_MMC_NEW_TIMING_MODE; + writel(val, cm->base + cm->reg); + + spin_unlock_irqrestore(cm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_ccu_set_mmc_timing_mode); + +/** + * sunxi_ccu_set_mmc_timing_mode: Get the current MMC clock timing mode + * @clk: clock to query + * + * Returns 0 if the clock is in old timing mode, > 0 if it is in + * new timing mode, and -ENOTSUPP if the clock does not support + * this function. + */ +int sunxi_ccu_get_mmc_timing_mode(struct clk *clk) +{ + struct clk_hw *hw = __clk_get_hw(clk); + struct ccu_common *cm = hw_to_ccu_common(hw); + + if (!(cm->features & CCU_FEATURE_MMC_TIMING_SWITCH)) + return -ENOTSUPP; + + return !!(readl(cm->base + cm->reg) & CCU_MMC_NEW_TIMING_MODE); +} +EXPORT_SYMBOL_GPL(sunxi_ccu_get_mmc_timing_mode); diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c index b917ad7a386c..688855e7dc8c 100644 --- a/drivers/clk/sunxi-ng/ccu_mp.c +++ b/drivers/clk/sunxi-ng/ccu_mp.c @@ -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, +}; diff --git a/drivers/clk/sunxi-ng/ccu_mp.h b/drivers/clk/sunxi-ng/ccu_mp.h index 915625e97d98..aaef11d747ea 100644 --- a/drivers/clk/sunxi-ng/ccu_mp.h +++ b/drivers/clk/sunxi-ng/ccu_mp.h @@ -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_ */ diff --git a/drivers/clk/sunxi-ng/ccu_reset.c b/drivers/clk/sunxi-ng/ccu_reset.c index 6c31d48783a7..1dc4e98ea802 100644 --- a/drivers/clk/sunxi-ng/ccu_reset.c +++ b/drivers/clk/sunxi-ng/ccu_reset.c @@ -8,6 +8,7 @@ * the License, or (at your option) any later version. */ +#include <linux/delay.h> #include <linux/io.h> #include <linux/reset-controller.h> @@ -49,7 +50,18 @@ static int ccu_reset_deassert(struct reset_controller_dev *rcdev, return 0; } +static int ccu_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + ccu_reset_assert(rcdev, id); + udelay(10); + ccu_reset_deassert(rcdev, id); + + return 0; +} + const struct reset_control_ops ccu_reset_ops = { .assert = ccu_reset_assert, .deassert = ccu_reset_deassert, + .reset = ccu_reset_reset, }; |