[PATCH 1/2] clk: hisilicon: add hi3620_mmc_clks
Seungwon Jeon
tgih.jun at samsung.com
Tue Jan 14 04:47:11 EST 2014
On Mon, January 13, 2014, Zhangfei Gao wrote:
> Suggest by Arnd: abstract mmc tuning as clock behavior,
> also because different soc have different tuning method and registers.
> hi3620_mmc_clks is added to handle mmc clock specifically on hi3620.
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao at linaro.org>
> Acked-by: Arnd Bergmann <arnd at arndb.de>
> Acked-by: Jaehoon Chung <jh80.chung at samsung.com>
> ---
> .../bindings/arm/hisilicon/hisilicon.txt | 14 +
> .../devicetree/bindings/clock/hi3620-clock.txt | 1 +
> drivers/clk/hisilicon/clk-hi3620.c | 267 ++++++++++++++++++++
> include/dt-bindings/clock/hi3620-clock.h | 5 +
> 4 files changed, 287 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> index 8c7a4653508d..df0a452b8526 100644
> --- a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> @@ -30,3 +30,17 @@ Example:
> resume-offset = <0x308>;
> reboot-offset = <0x4>;
> };
> +
> +PCTRL: Peripheral misc control register
> +
> +Required Properties:
> +- compatible: "hisilicon,pctrl"
> +- reg: Address and size of pctrl.
> +
> +Example:
> +
> + /* for Hi3620 */
> + pctrl: pctrl at fca09000 {
> + compatible = "hisilicon,pctrl";
> + reg = <0xfca09000 0x1000>;
> + };
> diff --git a/Documentation/devicetree/bindings/clock/hi3620-clock.txt
> b/Documentation/devicetree/bindings/clock/hi3620-clock.txt
> index 4b71ab41be53..dad6269f52c5 100644
> --- a/Documentation/devicetree/bindings/clock/hi3620-clock.txt
> +++ b/Documentation/devicetree/bindings/clock/hi3620-clock.txt
> @@ -7,6 +7,7 @@ Required Properties:
>
> - compatible: should be one of the following.
> - "hisilicon,hi3620-clock" - controller compatible with Hi3620 SoC.
> + - "hisilicon,hi3620-mmc-clock" - controller specific for Hi3620 mmc.
>
> - reg: physical base address of the controller and length of memory mapped
> region.
> diff --git a/drivers/clk/hisilicon/clk-hi3620.c b/drivers/clk/hisilicon/clk-hi3620.c
> index f24ad6a3a797..54cc3475ec36 100644
> --- a/drivers/clk/hisilicon/clk-hi3620.c
> +++ b/drivers/clk/hisilicon/clk-hi3620.c
> @@ -240,3 +240,270 @@ static void __init hi3620_clk_init(struct device_node *np)
> base);
> }
> CLK_OF_DECLARE(hi3620_clk, "hisilicon,hi3620-clock", hi3620_clk_init);
> +
> +struct hisi_mmc_clock {
> + unsigned int id;
> + const char *name;
> + const char *parent_name;
> + unsigned long flags;
> + u32 clken_reg;
> + u32 clken_bit;
> + u32 div_reg;
> + u32 div_off;
> + u32 div_bits;
> + u32 drv_reg;
> + u32 drv_off;
> + u32 drv_bits;
> + u32 sam_reg;
> + u32 sam_off;
> + u32 sam_bits;
> +};
> +
> +struct clk_mmc {
> + struct clk_hw hw;
> + u32 id;
> + void __iomem *clken_reg;
> + u32 clken_bit;
> + void __iomem *div_reg;
> + u32 div_off;
> + u32 div_bits;
> + void __iomem *drv_reg;
> + u32 drv_off;
> + u32 drv_bits;
> + void __iomem *sam_reg;
> + u32 sam_off;
> + u32 sam_bits;
> +};
> +
> +#define to_mmc(_hw) container_of(_hw, struct clk_mmc, hw)
> +
> +static struct hisi_mmc_clock hi3620_mmc_clks[] __initdata = {
> + { HI3620_SD_CIUCLK, "sd_bclk1", "sd_clk", CLK_SET_RATE_PARENT, 0x1f8, 0, 0x1f8, 1, 3, 0x1f8, 4,
> 4, 0x1f8, 8, 4},
> + { HI3620_MMC_CIUCLK1, "mmc_bclk1", "mmc_clk1", CLK_SET_RATE_PARENT, 0x1f8, 12, 0x1f8, 13, 3,
> 0x1f8, 16, 4, 0x1f8, 20, 4},
> + { HI3620_MMC_CIUCLK2, "mmc_bclk2", "mmc_clk2", CLK_SET_RATE_PARENT, 0x1f8, 24, 0x1f8, 25, 3,
> 0x1f8, 28, 4, 0x1fc, 0, 4},
> + { HI3620_MMC_CIUCLK3, "mmc_bclk3", "mmc_clk3", CLK_SET_RATE_PARENT, 0x1fc, 4, 0x1fc, 5, 3,
> 0x1fc, 8, 4, 0x1fc, 12, 4},
> +};
> +
> +static unsigned long mmc_clk_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + switch (parent_rate) {
> + case 26000000:
> + return 13000000;
> + case 180000000:
> + return 25000000;
> + case 360000000:
> + return 50000000;
> + case 720000000:
> + return 100000000;
> + default:
> + return parent_rate;
> + }
> +}
> +
> +static long mmc_clk_determine_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *best_parent_rate,
> + struct clk **best_parent_p)
> +{
> + struct clk_mmc *mclk = to_mmc(hw);
> + unsigned long best = 0;
> +
> + if ((rate <= 13000000) && (mclk->id == HI3620_MMC_CIUCLK1)) {
No need to check HI3620_MMC_CIUCLK2 and HI3620_MMC_CIUCLK3?
> + rate = 13000000;
> + best = 26000000;
> + } else if (rate <= 26000000) {
> + rate = 25000000;
> + best = 180000000;
> + } else if (rate <= 52000000) {
> + rate = 50000000;
> + best = 360000000;
> + } else if (rate <= 100000000) {
> + rate = 100000000;
> + best = 720000000;
> + } else {
> + /* max is 180M */
> + rate = 180000000;
> + best = 1440000000;
> + }
> + *best_parent_rate = best;
> + return rate;
> +}
> +
> +static u32 mmc_clk_delay(u32 val, u32 para, u32 off, u32 len)
> +{
> + u32 i;
> +
> + if (para >= 0) {
> + for (i = 0; i < len; i++) {
> + if (para % 2)
> + val |= 1 << (off + i);
> + else
> + val &= ~(1 << (off + i));
> + para = para >> 1;
> + }
> + }
> + return val;
> +}
> +
> +static int mmc_clk_set_timing(struct clk_hw *hw, unsigned long rate)
> +{
> + struct clk_mmc *mclk = to_mmc(hw);
> + unsigned long flags;
> + u32 sam, drv, div, val;
> + static DEFINE_SPINLOCK(mmc_clk_lock);
> +
> + switch (rate) {
> + case 13000000:
> + sam = 3;
> + drv = 1;
> + div = 1;
> + break;
> + case 25000000:
> + sam = 13;
> + drv = 6;
> + div = 6;
> + break;
> + case 50000000:
> + sam = 3;
> + drv = 6;
> + div = 6;
> + break;
> + case 100000000:
> + sam = 6;
> + drv = 4;
> + div = 6;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + spin_lock_irqsave(&mmc_clk_lock, flags);
> +
> + val = readl_relaxed(mclk->clken_reg);
> + val &= ~(1 << mclk->clken_bit);
> + writel_relaxed(val, mclk->clken_reg);
> +
> + val = readl_relaxed(mclk->sam_reg);
> + val = mmc_clk_delay(val, sam, mclk->sam_off, mclk->sam_bits);
> + writel_relaxed(val, mclk->sam_reg);
> +
> + val = readl_relaxed(mclk->drv_reg);
> + val = mmc_clk_delay(val, drv, mclk->drv_off, mclk->drv_bits);
> + writel_relaxed(val, mclk->drv_reg);
> +
> + val = readl_relaxed(mclk->div_reg);
> + val = mmc_clk_delay(val, div, mclk->div_off, mclk->div_bits);
> + writel_relaxed(val, mclk->div_reg);
> +
> + val = readl_relaxed(mclk->clken_reg);
> + val |= 1 << mclk->clken_bit;
> + writel_relaxed(val, mclk->clken_reg);
> +
> + spin_unlock_irqrestore(&mmc_clk_lock, flags);
> +
> + return 0;
> +}
> +
> +static int mmc_clk_prepare(struct clk_hw *hw)
> +{
> + struct clk_mmc *mclk = to_mmc(hw);
> + unsigned long rate;
> +
> + if (mclk->id == HI3620_MMC_CIUCLK1)
HI3620_SD_CIUCLK was used in previous version.
Is it fixed with HI3620_MMC_CIUCLK1?
And, please clarify in case of HI3620_MMC_CIUCLK2 and HI3620_MMC_CIUCLK3 as well.
Thanks,
Seungwon Jeon
> + rate = 13000000;
> + else
> + rate = 25000000;
> +
> + return mmc_clk_set_timing(hw, rate);
> +}
> +
> +static int mmc_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + return mmc_clk_set_timing(hw, rate);
> +}
> +
> +static struct clk_ops clk_mmc_ops = {
> + .prepare = mmc_clk_prepare,
> + .determine_rate = mmc_clk_determine_rate,
> + .set_rate = mmc_clk_set_rate,
> + .recalc_rate = mmc_clk_recalc_rate,
> +};
> +
> +static struct clk *hisi_register_clk_mmc(struct hisi_mmc_clock *mmc_clk,
> + void __iomem *base, struct device_node *np)
> +{
> + struct clk_mmc *mclk;
> + struct clk *clk;
> + struct clk_init_data init;
> +
> + mclk = kzalloc(sizeof(*mclk), GFP_KERNEL);
> + if (!mclk) {
> + pr_err("%s: fail to allocate mmc clk\n", __func__);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + init.name = mmc_clk->name;
> + init.ops = &clk_mmc_ops;
> + init.flags = mmc_clk->flags | CLK_IS_BASIC;
> + init.parent_names = (mmc_clk->parent_name ? &mmc_clk->parent_name : NULL);
> + init.num_parents = (mmc_clk->parent_name ? 1 : 0);
> + mclk->hw.init = &init;
> +
> + mclk->id = mmc_clk->id;
> + mclk->clken_reg = base + mmc_clk->clken_reg;
> + mclk->clken_bit = mmc_clk->clken_bit;
> + mclk->div_reg = base + mmc_clk->div_reg;
> + mclk->div_off = mmc_clk->div_off;
> + mclk->div_bits = mmc_clk->div_bits;
> + mclk->drv_reg = base + mmc_clk->drv_reg;
> + mclk->drv_off = mmc_clk->drv_off;
> + mclk->drv_bits = mmc_clk->drv_bits;
> + mclk->sam_reg = base + mmc_clk->sam_reg;
> + mclk->sam_off = mmc_clk->sam_off;
> + mclk->sam_bits = mmc_clk->sam_bits;
> +
> + clk = clk_register(NULL, &mclk->hw);
> + if (WARN_ON(IS_ERR(clk)))
> + kfree(mclk);
> + return clk;
> +}
> +
> +static void __init hi3620_mmc_clk_init(struct device_node *node)
> +{
> + void __iomem *base;
> + int i, num = ARRAY_SIZE(hi3620_mmc_clks);
> + struct clk_onecell_data *clk_data;
> +
> + if (!node) {
> + pr_err("failed to find pctrl node in DTS\n");
> + return;
> + }
> +
> + base = of_iomap(node, 0);
> + if (!base) {
> + pr_err("failed to map pctrl\n");
> + return;
> + }
> +
> + clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
> + if (WARN_ON(!clk_data))
> + return;
> +
> + clk_data->clks = kzalloc(sizeof(struct clk *) * num, GFP_KERNEL);
> + if (!clk_data->clks) {
> + pr_err("%s: fail to allocate mmc clk\n", __func__);
> + return;
> + }
> +
> + for (i = 0; i < num; i++) {
> + struct hisi_mmc_clock *mmc_clk = &hi3620_mmc_clks[i];
> + clk_data->clks[mmc_clk->id] =
> + hisi_register_clk_mmc(mmc_clk, base, node);
> + }
> +
> + clk_data->clk_num = num;
> + of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> +}
> +
> +CLK_OF_DECLARE(hi3620_mmc_clk, "hisilicon,hi3620-mmc-clock", hi3620_mmc_clk_init);
> diff --git a/include/dt-bindings/clock/hi3620-clock.h b/include/dt-bindings/clock/hi3620-clock.h
> index 6eaa6a45e110..21b9d0e2eb0c 100644
> --- a/include/dt-bindings/clock/hi3620-clock.h
> +++ b/include/dt-bindings/clock/hi3620-clock.h
> @@ -147,6 +147,11 @@
> #define HI3620_MMC_CLK3 217
> #define HI3620_MCU_CLK 218
>
> +#define HI3620_SD_CIUCLK 0
> +#define HI3620_MMC_CIUCLK1 1
> +#define HI3620_MMC_CIUCLK2 2
> +#define HI3620_MMC_CIUCLK3 3
> +
> #define HI3620_NR_CLKS 219
>
> #endif /* __DTS_HI3620_CLOCK_H */
> --
> 1.7.9.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
More information about the linux-arm-kernel
mailing list