From patchwork Wed Mar 13 05:05:39 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Haojian Zhuang X-Patchwork-Id: 15318 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 93F3523E2E for ; Wed, 13 Mar 2013 05:07:38 +0000 (UTC) Received: from mail-vc0-f173.google.com (mail-vc0-f173.google.com [209.85.220.173]) by fiordland.canonical.com (Postfix) with ESMTP id 1B731A19283 for ; Wed, 13 Mar 2013 05:07:38 +0000 (UTC) Received: by mail-vc0-f173.google.com with SMTP id fl15so318379vcb.4 for ; Tue, 12 Mar 2013 22:07:37 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-received:x-forwarded-to:x-forwarded-for:delivered-to:x-received :received-spf:x-received:from:to:cc:subject:date:message-id:x-mailer :in-reply-to:references:x-gm-message-state; bh=J7jzFw2upsT6tBXuNTBMuswUrfBmIRYjWgRdO0TjLeM=; b=DipLfMq1Gbgs1aGDNSrmiKhU80dFb+rLXz1hP8y1GeVgSpSmCqD83n4fcZJJErVntB 1ZfHCCuf7psD9dzYC8y2IlMQRtpqcdSkjZTjXlqt/bJzwM+syFcc3WUK3nXTomT5xkWD sNAhDWxTYGWIGsKbKh+iyIr7xQdlgCfdZOywpjPywESYt8648ht0mkD05tf7HAuqLao+ ZFZhqKvTGF9TvByosLcHHlBuOUzmmhYYc05czA8oxVN7CDVRzEOMR5WsE2FXNa2pNkOF rM08Wg6JwiPXLys/URtVu2POjoPGgywKDvK1erpAknkhcwLlxpgSSWpM4ZNv7PPjDyHz K4gA== X-Received: by 10.220.201.132 with SMTP id fa4mr3505125vcb.14.1363151257530; Tue, 12 Mar 2013 22:07:37 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.58.127.98 with SMTP id nf2csp1639veb; Tue, 12 Mar 2013 22:07:36 -0700 (PDT) X-Received: by 10.68.217.202 with SMTP id pa10mr36320240pbc.11.1363151256476; Tue, 12 Mar 2013 22:07:36 -0700 (PDT) Received: from mail-pb0-f54.google.com (mail-pb0-f54.google.com [209.85.160.54]) by mx.google.com with ESMTPS id qx5si33528083pbc.294.2013.03.12.22.07.36 (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 12 Mar 2013 22:07:36 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.160.54 is neither permitted nor denied by best guess record for domain of haojian.zhuang@linaro.org) client-ip=209.85.160.54; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.160.54 is neither permitted nor denied by best guess record for domain of haojian.zhuang@linaro.org) smtp.mail=haojian.zhuang@linaro.org Received: by mail-pb0-f54.google.com with SMTP id rr4so617311pbb.13 for ; Tue, 12 Mar 2013 22:07:35 -0700 (PDT) X-Received: by 10.68.197.41 with SMTP id ir9mr43224896pbc.87.1363151255427; Tue, 12 Mar 2013 22:07:35 -0700 (PDT) Received: from localhost.localdomain ([67.198.145.34]) by mx.google.com with ESMTPS id ab1sm27947630pbd.37.2013.03.12.22.07.30 (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 12 Mar 2013 22:07:34 -0700 (PDT) From: Haojian Zhuang To: linux@arm.linux.org.uk, linus.walleij@linaro.org, arnd@arndb.de, olof@lixom.net, rob.herring@calxeda.com, linux-arm-kernel@lists.infradead.org, pawel.moll@arm.com, swarren@nvidia.com, john.stultz@linaro.org, tglx@linutronix.de, mturquette@linaro.org Cc: patches@linaro.org, Haojian Zhuang Subject: [PATCH v3 08/11] clk: hi3xxx: add clock support Date: Wed, 13 Mar 2013 13:05:39 +0800 Message-Id: <1363151142-32162-9-git-send-email-haojian.zhuang@linaro.org> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1363151142-32162-1-git-send-email-haojian.zhuang@linaro.org> References: <1363151142-32162-1-git-send-email-haojian.zhuang@linaro.org> X-Gm-Message-State: ALoCoQkwmoIU/kEDSyYuhTtYlZKbGvTQoDPt6MgIXN3X5KvBL1nc95ZPN5y6ka7ZlD7Z20cKrmSY Add clock support with device tree on Hisilicon SoC. Signed-off-by: Haojian Zhuang --- .../devicetree/bindings/clock/hisilicon.txt | 73 +++ drivers/clk/Makefile | 1 + drivers/clk/clk-hi3xxx.c | 643 ++++++++++++++++++++ 3 files changed, 717 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/hisilicon.txt create mode 100644 drivers/clk/clk-hi3xxx.c diff --git a/Documentation/devicetree/bindings/clock/hisilicon.txt b/Documentation/devicetree/bindings/clock/hisilicon.txt new file mode 100644 index 0000000..be8db9d --- /dev/null +++ b/Documentation/devicetree/bindings/clock/hisilicon.txt @@ -0,0 +1,73 @@ +Device Tree Clock bindings for arch-vt8500 + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties for mux clocks: + - compatible : Shall be "hisilicon,hi3620-clk-mux". + - clocks : shall be the input parent clock phandle for the clock. This should + be the reference clock. + - clock-output-names : shall be reference name. + - #clock-cells : from common clock binding; shall be set to 0. + - hisilicon,hi3620-mux : array of mux register offset & mask bits + +Required properties for Hi3620 gate clocks: + - compatible : Shall be "hisilicon,hi3620-clk-gate". + - clocks : shall be the input parent clock phandle for the clock. This should + be the reference clock. + - clock-output-names : shall be reference name. + - #clock-cells : from common clock binding; shall be set to 0. + - hisilicon,hi3620-clkgate : array of enable register offset & enable bits + - hisilicon,hi3620-clkreset : array of reset register offset & enable bits + +Required properties for clock divider: + - compatible : Shall be "hisilicon,hi3620-clk-div". + - clocks : shall be the input parent clock phandle for the clock. This should + be the reference clock. + - clock-output-names : shall be reference name. + - #clock-cells : from common clock binding; shall be set to 0. + - #hisilicon,clkdiv-table-cells : the number of parameters after phandle in + hisilicon,clkdiv-table property. + - hisilicon,clkdiv-table : list of value that are used to configure clock + divider. They're value of phandle, index & divider value. + - hisilicon,clkdiv : array of divider register offset & mask bits. + +Required properties for gate clocks: + - compatible : Shall be "hisilicon,clk-gate". + - clocks : shall be the input parent clock phandle for the clock. This should + be the reference clock. + - clock-output-names : shall be reference name. + - #clock-cells : from common clock binding; shall be set to 0. + - hisilicon,clkgate-inverted : bool value. True means that set-to-disable. + +Required properties for clock fixed factor divider: + - compatible : Shall be "hisilicon,fixed-factor". + - clocks : shall be the input parent clock phandle for the clock. This should + be the reference clock. + - clock-output-names : shall be reference name. + - #clock-cells : from common clock binding; shall be set to 0. + - hisilicon,fixed-factor : array of multiplier & divider. + +For example: + timclk1: clkgate@38 { + compatible = "hisilicon,clk-gate"; + #clock-cells = <0>; + clocks = <&refclk_timer1>; + clock-output-names = "timclk1"; + hisilicon,clkgate-inverted; + hisilicon,clkgate = <0 18>; + }; + + dtable: clkdiv@0 { + #hisilicon,clkdiv-table-cells = <2>; + }; + + div_cfgaxi: clkdiv@2 { + compatible = "hisilicon,hi3620-clk-div"; + #clock-cells = <0>; + clocks = <&div_shareaxi>; + clock-output-names = "cfgAXI_div"; + hisilicon,clkdiv-table = <&dtable 0x01 2>; + hisilicon,clkdiv = <0x100 0x60>; + }; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 0147022..b065e76 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-mux.o # SoCs specific obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o +obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3xxx.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_ARCH_MXS) += mxs/ obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/ diff --git a/drivers/clk/clk-hi3xxx.c b/drivers/clk/clk-hi3xxx.c new file mode 100644 index 0000000..4499e57 --- /dev/null +++ b/drivers/clk/clk-hi3xxx.c @@ -0,0 +1,643 @@ +/* + * Hisilicon clock driver + * + * Copyright (c) 2012-2013 Hisilicon Limited. + * Copyright (c) 2012-2013 Linaro Limited. + * + * Author: Haojian Zhuang + * Xin Li + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HI3620_DISABLE_OFF 0x4 +#define HI3620_STATUS_OFF 0x8 + +#define WIDTH_TO_MASK(width) ((1 << (width)) - 1) + +/* + * The reverse of DIV_ROUND_UP: The maximum number which + * divided by m is r + */ +#define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1) + +struct hi3620_periclk { + struct clk_hw hw; + void __iomem *enable; /* enable register */ + void __iomem *reset; /* reset register */ + u32 ebits; /* bits in enable/disable register */ + u32 rbits; /* bits in reset/unreset register */ + spinlock_t *lock; +}; + +struct hi3620_muxclk { + struct clk_hw hw; + void __iomem *reg; /* mux register */ + u8 shift; + u8 width; + u32 mbits; /* mask bits in mux register */ + spinlock_t *lock; +}; + +struct hi3620_divclk { + struct clk_hw hw; + void __iomem *reg; /* divider register */ + u8 shift; + u8 width; + u32 mbits; /* mask bits in divider register */ + const struct clk_div_table *table; + spinlock_t *lock; +}; + +struct hs_clk { + void __iomem *pmctrl; + void __iomem *sctrl; + spinlock_t lock; +}; + +static struct hs_clk hs_clk; + +static void __init hs_init_clocks(void); + +static int hi3620_clkgate_prepare(struct clk_hw *hw) +{ + struct hi3620_periclk *pclk; + unsigned long flags = 0; + + pclk = container_of(hw, struct hi3620_periclk, hw); + + if (pclk->lock) + spin_lock_irqsave(pclk->lock, flags); + writel_relaxed(pclk->ebits, pclk->enable + HI3620_DISABLE_OFF); + writel_relaxed(pclk->rbits, pclk->reset + HI3620_DISABLE_OFF); + readl_relaxed(pclk->reset + HI3620_STATUS_OFF); + if (pclk->lock) + spin_unlock_irqrestore(pclk->lock, flags); + return 0; +} + +static int hi3620_clkgate_enable(struct clk_hw *hw) +{ + struct hi3620_periclk *pclk; + unsigned long flags = 0; + + pclk = container_of(hw, struct hi3620_periclk, hw); + if (pclk->lock) + spin_lock_irqsave(pclk->lock, flags); + writel_relaxed(pclk->ebits, pclk->enable); + readl_relaxed(pclk->enable + HI3620_STATUS_OFF); + if (pclk->lock) + spin_unlock_irqrestore(pclk->lock, flags); + return 0; +} + +static void hi3620_clkgate_disable(struct clk_hw *hw) +{ + struct hi3620_periclk *pclk; + unsigned long flags = 0; + + pclk = container_of(hw, struct hi3620_periclk, hw); + if (pclk->lock) + spin_lock_irqsave(pclk->lock, flags); + writel_relaxed(pclk->ebits, pclk->enable + HI3620_DISABLE_OFF); + readl_relaxed(pclk->enable + HI3620_STATUS_OFF); + if (pclk->lock) + spin_unlock_irqrestore(pclk->lock, flags); +} + +static struct clk_ops hi3620_clkgate_ops = { + .prepare = hi3620_clkgate_prepare, + .enable = hi3620_clkgate_enable, + .disable = hi3620_clkgate_disable, +}; + +static void __init hi3620_clkgate_setup(struct device_node *np) +{ + struct hi3620_periclk *pclk; + struct clk_init_data *init; + struct clk *clk; + const char *clk_name, *name, **parent_names; + u32 rdata[2], gdata[2]; + + if (!hs_clk.sctrl) + return; + + if (of_property_read_string(np, "clock-output-names", &clk_name)) + return; + if (of_property_read_u32_array(np, "hisilicon,hi3620-clkreset", + &rdata[0], 2)) + return; + if (of_property_read_u32_array(np, "hisilicon,hi3620-clkgate", + &gdata[0], 2)) + return; + + /* gate only has the fixed parent */ + parent_names = kzalloc(sizeof(char *), GFP_KERNEL); + if (!parent_names) + return; + parent_names[0] = of_clk_get_parent_name(np, 0); + + pclk = kzalloc(sizeof(*pclk), GFP_KERNEL); + if (!pclk) + goto err_pclk; + + init = kzalloc(sizeof(*init), GFP_KERNEL); + if (!init) + goto err_init; + init->name = kstrdup(clk_name, GFP_KERNEL); + init->ops = &hi3620_clkgate_ops; + init->flags = CLK_SET_RATE_PARENT; + init->parent_names = parent_names; + init->num_parents = 1; + + pclk->reset = hs_clk.sctrl + rdata[0]; + pclk->rbits = rdata[1]; + pclk->enable = hs_clk.sctrl + gdata[0]; + pclk->ebits = gdata[1]; + pclk->lock = &hs_clk.lock; + pclk->hw.init = init; + + clk = clk_register(NULL, &pclk->hw); + if (IS_ERR(clk)) + goto err_clk; + if (!of_property_read_string(np, "clock-names", &name)) + clk_register_clkdev(clk, name, NULL); + of_clk_add_provider(np, of_clk_src_simple_get, clk); + return; +err_clk: + kfree(init); +err_init: + kfree(pclk); +err_pclk: + kfree(parent_names); +} + +static u8 hi3620_clk_get_parent(struct clk_hw *hw) +{ + struct hi3620_muxclk *mclk; + u32 data; + unsigned long flags = 0; + + mclk = container_of(hw, struct hi3620_muxclk, hw); + + if (mclk->lock) + spin_lock_irqsave(mclk->lock, flags); + + data = readl_relaxed(mclk->reg) >> mclk->shift; + data &= WIDTH_TO_MASK(mclk->width); + + if (mclk->lock) + spin_unlock_irqrestore(mclk->lock, flags); + + if (data >= __clk_get_num_parents(hw->clk)) + return -EINVAL; + + return (u8)data; +} + +static int hi3620_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct hi3620_muxclk *mclk; + u32 data; + unsigned long flags = 0; + + mclk = container_of(hw, struct hi3620_muxclk, hw); + + if (mclk->lock) + spin_lock_irqsave(mclk->lock, flags); + + data = readl_relaxed(mclk->reg); + data &= ~(WIDTH_TO_MASK(mclk->width) << mclk->shift); + data |= index << mclk->shift; + writel_relaxed(data, mclk->reg); + /* set mask enable bits */ + data |= mclk->mbits; + writel_relaxed(data, mclk->reg); + + if (mclk->lock) + spin_unlock_irqrestore(mclk->lock, flags); + + return 0; +} + +static struct clk_ops hi3620_clkmux_ops = { + .get_parent = hi3620_clk_get_parent, + .set_parent = hi3620_clk_set_parent, +}; + +static void __init hi3620_clkmux_setup(struct device_node *np) +{ + struct hi3620_muxclk *mclk; + struct clk_init_data *init; + struct clk *clk; + const char *clk_name, **parent_names; + u32 rdata[2]; + u8 num_parents; + int i; + + hs_init_clocks(); + if (!hs_clk.sctrl) + return; + + if (of_property_read_string(np, "clock-output-names", &clk_name)) + return; + if (of_property_read_u32_array(np, "hisilicon,hi3620-clkmux", + &rdata[0], 2)) + return; + /* get the count of items in mux */ + for (i = 0; ; i++) { + /* parent's #clock-cells property is always 0 */ + if (!of_parse_phandle(np, "clocks", i)) + break; + } + parent_names = kzalloc(sizeof(char *) * i, GFP_KERNEL); + if (!parent_names) + return; + + for (num_parents = i, i = 0; i < num_parents; i++) + parent_names[i] = of_clk_get_parent_name(np, i); + + mclk = kzalloc(sizeof(*mclk), GFP_KERNEL); + if (!mclk) + goto err_mclk; + init = kzalloc(sizeof(*init), GFP_KERNEL); + if (!init) + goto err_init; + init->name = kstrdup(clk_name, GFP_KERNEL); + init->ops = &hi3620_clkmux_ops; + init->flags = CLK_SET_RATE_PARENT; + init->parent_names = parent_names; + init->num_parents = num_parents; + + mclk->reg = hs_clk.sctrl + rdata[0]; + /* enable_mask bits are in higher 16bits */ + mclk->mbits = rdata[1] << 16; + mclk->shift = ffs(rdata[1]) - 1; + mclk->width = fls(rdata[1]) - ffs(rdata[1]) + 1; + mclk->lock = &hs_clk.lock; + mclk->hw.init = init; + + clk = clk_register(NULL, &mclk->hw); + if (IS_ERR(clk)) + goto err_clk; + of_clk_add_provider(np, of_clk_src_simple_get, clk); + + return; +err_clk: + kfree(init); +err_init: + kfree(mclk); +err_mclk: + kfree(parent_names); +} + +static void __init hs_clkgate_setup(struct device_node *np) +{ + struct clk *clk; + const char *clk_name, **parent_names, *name; + unsigned long flags = 0; + u32 data[2]; + + hs_init_clocks(); + if (!hs_clk.sctrl) + return; + if (of_property_read_string(np, "clock-output-names", &clk_name)) + return; + if (of_property_read_u32_array(np, "hisilicon,clkgate", + &data[0], 2)) + return; + if (of_property_read_bool(np, "hisilicon,clkgate-inverted")) + flags = CLK_GATE_SET_TO_DISABLE; + /* gate only has the fixed parent */ + parent_names = kzalloc(sizeof(char *), GFP_KERNEL); + if (!parent_names) + return; + parent_names[0] = of_clk_get_parent_name(np, 0); + + clk = clk_register_gate(NULL, clk_name, parent_names[0], 0, + hs_clk.sctrl + data[0], (u8)data[1], flags, + &hs_clk.lock); + if (IS_ERR(clk)) + goto err; + if (!of_property_read_string(np, "clock-names", &name)) + clk_register_clkdev(clk, name, NULL); + of_clk_add_provider(np, of_clk_src_simple_get, clk); + return; +err: + kfree(parent_names); +} + +void __init hs_fixed_factor_setup(struct device_node *np) +{ + struct clk *clk; + const char *clk_name, **parent_names; + u32 data[2]; + + if (of_property_read_string(np, "clock-output-names", &clk_name)) + return; + if (of_property_read_u32_array(np, "hisilicon,fixed-factor", + data, 2)) + return ; + /* gate only has the fixed parent */ + parent_names = kzalloc(sizeof(char *), GFP_KERNEL); + if (!parent_names) + return; + parent_names[0] = of_clk_get_parent_name(np, 0); + + clk = clk_register_fixed_factor(NULL, clk_name, parent_names[0], 0, + data[0], data[1]); + if (IS_ERR(clk)) + goto err; + of_clk_add_provider(np, of_clk_src_simple_get, clk); + return; +err: + kfree(parent_names); +} + +static unsigned int hi3620_get_table_maxdiv(const struct clk_div_table *table) +{ + unsigned int maxdiv = 0; + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div > maxdiv) + maxdiv = clkt->div; + return maxdiv; +} + +static unsigned int hi3620_get_table_div(const struct clk_div_table *table, + unsigned int val) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->val == val) + return clkt->div; + return 0; +} + +static unsigned int hi3620_get_table_val(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return clkt->val; + return 0; +} + +static unsigned long hi3620_clkdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct hi3620_divclk *dclk = container_of(hw, struct hi3620_divclk, hw); + unsigned int div, val; + + val = readl_relaxed(dclk->reg) >> dclk->shift; + val &= WIDTH_TO_MASK(dclk->width); + + div = hi3620_get_table_div(dclk->table, val); + if (!div) { + pr_warning("%s: Invalid divisor for clock %s\n", __func__, + __clk_get_name(hw->clk)); + return parent_rate; + } + + return parent_rate / div; +} + +static bool hi3620_is_valid_table_div(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return true; + return false; +} + +static int hi3620_clkdiv_bestdiv(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate) +{ + struct hi3620_divclk *dclk = container_of(hw, struct hi3620_divclk, hw); + struct clk *clk_parent = __clk_get_parent(hw->clk); + int i, bestdiv = 0; + unsigned long parent_rate, best = 0, now, maxdiv; + + maxdiv = hi3620_get_table_maxdiv(dclk->table); + + if (!(__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT)) { + parent_rate = *best_parent_rate; + bestdiv = DIV_ROUND_UP(parent_rate, rate); + bestdiv = bestdiv == 0 ? 1 : bestdiv; + bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv; + return bestdiv; + } + + /* + * The maximum divider we can use without overflowing + * unsigned long in rate * i below + */ + maxdiv = min(ULONG_MAX / rate, maxdiv); + + for (i = 1; i <= maxdiv; i++) { + if (!hi3620_is_valid_table_div(dclk->table, i)) + continue; + parent_rate = __clk_round_rate(clk_parent, + MULT_ROUND_UP(rate, i)); + now = parent_rate / i; + if (now <= rate && now > best) { + bestdiv = i; + best = now; + *best_parent_rate = parent_rate; + } + } + + if (!bestdiv) { + bestdiv = hi3620_get_table_maxdiv(dclk->table); + *best_parent_rate = __clk_round_rate(clk_parent, 1); + } + + return bestdiv; +} + +static long hi3620_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + int div; + + if (!rate) + rate = 1; + div = hi3620_clkdiv_bestdiv(hw, rate, prate); + + return *prate / div; +} + +static int hi3620_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct hi3620_divclk *dclk = container_of(hw, struct hi3620_divclk, hw); + unsigned int div, value; + unsigned long flags = 0; + u32 data; + + div = parent_rate / rate; + value = hi3620_get_table_val(dclk->table, div); + + if (value > WIDTH_TO_MASK(dclk->width)) + value = WIDTH_TO_MASK(dclk->width); + + if (dclk->lock) + spin_lock_irqsave(dclk->lock, flags); + + data = readl_relaxed(dclk->reg); + data &= ~(WIDTH_TO_MASK(dclk->width) << dclk->shift); + data |= value << dclk->shift; + data |= dclk->mbits; + writel_relaxed(data, dclk->reg); + + if (dclk->lock) + spin_unlock_irqrestore(dclk->lock, flags); + + return 0; +} + +static struct clk_ops hi3620_clkdiv_ops = { + .recalc_rate = hi3620_clkdiv_recalc_rate, + .round_rate = hi3620_clkdiv_round_rate, + .set_rate = hi3620_clkdiv_set_rate, +}; + +void __init hi3620_clkdiv_setup(struct device_node *np) +{ + struct clk *clk; + const char *clk_name, **parent_names; + struct clk_init_data *init; + struct clk_div_table *table; + struct hi3620_divclk *dclk; + unsigned int table_num; + int i; + u32 data[2]; + const char *propname = "hisilicon,clkdiv-table"; + const char *cellname = "#hisilicon,clkdiv-table-cells"; + struct of_phandle_args div_table; + + hs_init_clocks(); + if (!hs_clk.sctrl) + return; + + if (of_property_read_string(np, "clock-output-names", &clk_name)) + return; + if (of_property_read_u32_array(np, "hisilicon,clkdiv", + &data[0], 2)) + return; + + /*process the div_table*/ + for (i = 0; ; i++) { + if (of_parse_phandle_with_args(np, propname, cellname, + i, &div_table)) + break; + } + + /*table ends with <0, 0>, so plus one to table_num*/ + table_num = i + 1; + + table = kzalloc(sizeof(struct clk_div_table) * table_num, GFP_KERNEL); + if (!table) + return ; + + for (i = 0; ; i++) { + if (of_parse_phandle_with_args(np, propname, cellname, + i, &div_table)) + break; + + table[i].val = div_table.args[0]; + table[i].div = div_table.args[1]; + } + + /* gate only has the fixed parent */ + parent_names = kzalloc(sizeof(char *), GFP_KERNEL); + if (!parent_names) + goto err_par; + parent_names[0] = of_clk_get_parent_name(np, 0); + + dclk = kzalloc(sizeof(*dclk), GFP_KERNEL); + if (!dclk) + goto err_dclk; + init = kzalloc(sizeof(*init), GFP_KERNEL); + if (!init) + goto err_init; + init->name = kstrdup(clk_name, GFP_KERNEL); + init->ops = &hi3620_clkdiv_ops; + init->parent_names = parent_names; + init->num_parents = 1; + + dclk->reg = hs_clk.sctrl + data[0]; + dclk->shift = ffs(data[1]) - 1; + dclk->width = fls(data[1]) - ffs(data[1]) + 1; + dclk->mbits = data[1] << 16; + dclk->lock = &hs_clk.lock; + dclk->hw.init = init; + dclk->table = table; + clk = clk_register(NULL, &dclk->hw); + if (IS_ERR(clk)) + goto err_clk; + of_clk_add_provider(np, of_clk_src_simple_get, clk); + return; +err_clk: + kfree(init); +err_init: + kfree(dclk); +err_dclk: + kfree(parent_names); +err_par: + kfree(table); +} +CLK_OF_DECLARE(hi3620_mux, "hisilicon,hi3620-clk-mux", hi3620_clkmux_setup) +CLK_OF_DECLARE(hi3620_gate, "hisilicon,hi3620-clk-gate", hi3620_clkgate_setup) +CLK_OF_DECLARE(hi3620_div, "hisilicon,hi3620-clk-div", hi3620_clkdiv_setup) +CLK_OF_DECLARE(hs_gate, "hisilicon,clk-gate", hs_clkgate_setup) +CLK_OF_DECLARE(hs_fixed, "hisilicon,clk-fixed-factor", hs_fixed_factor_setup) + +static void __init hs_init_clocks(void) +{ + struct device_node *node = NULL; + + if (!hs_clk.pmctrl) { + /* map pmctrl registers */ + node = of_find_compatible_node(NULL, NULL, "hisilicon,pmctrl"); + hs_clk.pmctrl = of_iomap(node, 0); + WARN_ON(!hs_clk.pmctrl); + } + + if (!hs_clk.sctrl) { + node = of_find_compatible_node(NULL, NULL, "hisilicon,sctrl"); + hs_clk.sctrl = of_iomap(node, 0); + } +}