diff mbox

[v2,6/6] clk: mvebu: Add the peripheral clock driver for Armada 3700

Message ID 1467931071-31004-7-git-send-email-gregory.clement@free-electrons.com (mailing list archive)
State Superseded, archived
Delegated to: Stephen Boyd
Headers show

Commit Message

Gregory CLEMENT July 7, 2016, 10:37 p.m. UTC
These clocks are the ones which will be used as source for the
peripherals of the Armada 3700 SoC. On this SoC there is two blocks of
clocks: the North bridge one and the South bridge one.

Most of them are gatable. Most of the time their rate are their parent
rated divided by a ratio depending of two registers. Their parent can be
choose between the TBG clocks for most of them.

However, some of them can't choose their parent or directly depend of the
xtal clocks. Other ones do not use exactly the same pattern to find the
ratio between their parent rate and their rate.

For these reason each clock is a composite clock and the operations they
use are different depending of the clock.

According to the datasheet it would be possible to select the parent
clock and the ratio, however currently the driver does not support it.

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 drivers/clk/mvebu/Makefile             |   1 +
 drivers/clk/mvebu/armada-37xx-periph.c | 457 +++++++++++++++++++++++++++++++++
 2 files changed, 458 insertions(+)
 create mode 100644 drivers/clk/mvebu/armada-37xx-periph.c

Comments

Michael Turquette July 8, 2016, 6:27 p.m. UTC | #1
Quoting Gregory CLEMENT (2016-07-07 15:37:51)
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>

Same question as my previous email. Is clk.h necessary? Is this driver
also a clk consumer?

> +static int armada_3700_add_composite_clk(const struct clk_periph_data *data,
> +                                        const char * const *parent_name,
> +                                        void __iomem *reg, spinlock_t *lock,
> +                                        struct device *dev, struct clk_hw *hw)
> +{
> +       const struct clk_ops *mux_ops = NULL, *gate_ops = NULL,
> +               *div_ops = NULL;
> +       struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *div_hw = NULL;
> +       const char * const *names;
> +       struct clk_mux *mux = NULL;
> +       struct clk_gate *gate = NULL;
> +       struct clk_divider *div = NULL;
> +       struct clk_double_div *double_div = NULL;
> +       int num_parent;
> +       int ret = 0;
> +
> +       if (data->gate_shift != UNUSED) {
> +               gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
> +
> +               if (!gate)
> +                       return -ENOMEM;
> +
> +               gate->reg = reg + CLK_DIS;
> +               gate->bit_idx = data->gate_shift;
> +               gate->lock = lock;
> +               gate_ops = &clk_gate_ops;
> +               gate_hw = &gate->hw;
> +       }
> +
> +       if (data->mux_shift != UNUSED) {
> +               mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> +
> +               if (!mux) {
> +                       ret = -ENOMEM;
> +                       goto free_gate;
> +               }
> +
> +               mux->reg = reg + TBG_SEL;
> +               mux->shift = data->mux_shift;
> +               mux->mask = 0x3;
> +               mux->lock = lock;
> +               mux_ops = &clk_mux_ro_ops;
> +               mux_hw = &mux->hw;
> +       }
> +
> +       if (data->div_reg1 != UNUSED) {
> +               if (data->div_reg2 == UNUSED) {
> +                       const struct clk_div_table *clkt;
> +                       int table_size = 0;
> +
> +                       div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
> +                       if (!div) {
> +                               ret = -ENOMEM;
> +                               goto free_mux;
> +                       }
> +
> +                       div->reg = reg + data->div_reg1;
> +                       div->table = data->table;
> +                       for (clkt = div->table; clkt->div; clkt++)
> +                               table_size++;
> +                       div->width = order_base_2(table_size);
> +                       div->lock = lock;
> +                       div_ops = &clk_divider_ro_ops;
> +                       div_hw = &div->hw;
> +               } else {
> +                       double_div = devm_kzalloc(dev, sizeof(*double_div),
> +                                                 GFP_KERNEL);
> +                       if (!double_div) {
> +                               ret = -ENOMEM;
> +                               goto free_mux;
> +                       }
> +
> +                       double_div->reg1 = reg + data->div_reg1;
> +                       double_div->shift1 = data->div_shift1;
> +                       double_div->reg2 = reg + data->div_reg1;
> +                       double_div->shift2 = data->div_shift2;
> +                       div_ops = &clk_double_div_ops;
> +                       div_hw = &double_div->hw;
> +               }
> +       }
> +
> +       switch (data->flags) {
> +       case XTAL_CHILD:
> +               /* the xtal clock is the 5th clock */
> +               names = &parent_name[4];
> +               num_parent = 1;
> +               break;
> +       case TBGA_S_CHILD:
> +               /* the TBG A S clock is the 3rd clock */
> +               names = &parent_name[2];
> +               num_parent = 1;
> +               break;
> +       case GBE_CORE_CHILD:
> +               names = &gbe_name[1];
> +               num_parent = 1;
> +               break;
> +       case  GBE_50_CHILD:
> +               names = &gbe_name[0];
> +               num_parent = 1;
> +               break;
> +       case  GBE_125_CHILD:
> +               names = &gbe_name[2];
> +               num_parent = 1;
> +               break;
> +       default:
> +               names = parent_name;
> +               num_parent = 4;
> +       }
> +       hw = clk_hw_register_composite(dev, data->name,
> +                                    names, num_parent,
> +                                    mux_hw, mux_ops,
> +                                    div_hw, div_ops,
> +                                    gate_hw, gate_ops,
> +                                    CLK_IGNORE_UNUSED);
> +       if (IS_ERR(hw)) {
> +               ret = PTR_ERR(hw);
> +               goto free_div;
> +       }
> +
> +       return 0;
> +free_div:
> +       devm_kfree(dev, div);
> +       devm_kfree(dev, double_div);
> +free_mux:
> +       devm_kfree(dev, mux);
> +free_gate:
> +       devm_kfree(dev, gate);
> +       return ret;
> +}

Can this "add" function (aka registration function) be replaced with
static data instead? I think that all of the static data exists already,
this function can be removed and your probe can call clk_hw_register
directly.

It might need a macro though, since composite clock structures are
rather messy. This avoids a lot of unnecessary allocations and time
populating data that we already have access to. In general I am trying
to encourage clk drivers to use only clk_hw_register() in their probe
instead of the helper registration functions.

Similarly I am discouraging drivers from populating hw.init at run-time,
since we already have that data for that at compile-time.

Regards,
Mike
--
To unsubscribe from this list: send the line "unsubscribe linux-clk" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Gregory CLEMENT July 12, 2016, 4:30 p.m. UTC | #2
Hi Michael,
 
 On ven., juil. 08 2016, Michael Turquette <mturquette@baylibre.com> wrote:

> Quoting Gregory CLEMENT (2016-07-07 15:37:51)
>> +#include <linux/clk-provider.h>
>> +#include <linux/clk.h>
>
> Same question as my previous email. Is clk.h necessary? Is this driver
> also a clk consumer?

I think I can remove it indeed.

>
>> +static int armada_3700_add_composite_clk(const struct clk_periph_data *data,
>> +                                        const char * const *parent_name,
>> +                                        void __iomem *reg, spinlock_t *lock,
>> +                                        struct device *dev, struct clk_hw *hw)
>> +{
>> +       const struct clk_ops *mux_ops = NULL, *gate_ops = NULL,
>> +               *div_ops = NULL;
>> +       struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *div_hw = NULL;
>> +       const char * const *names;
>> +       struct clk_mux *mux = NULL;
>> +       struct clk_gate *gate = NULL;
>> +       struct clk_divider *div = NULL;
>> +       struct clk_double_div *double_div = NULL;
>> +       int num_parent;
>> +       int ret = 0;
>> +
>> +       if (data->gate_shift != UNUSED) {
>> +               gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
>> +
>> +               if (!gate)
>> +                       return -ENOMEM;
>> +
>> +               gate->reg = reg + CLK_DIS;
>> +               gate->bit_idx = data->gate_shift;
>> +               gate->lock = lock;
>> +               gate_ops = &clk_gate_ops;
>> +               gate_hw = &gate->hw;
>> +       }
>> +
>> +       if (data->mux_shift != UNUSED) {
>> +               mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
>> +
>> +               if (!mux) {
>> +                       ret = -ENOMEM;
>> +                       goto free_gate;
>> +               }
>> +
>> +               mux->reg = reg + TBG_SEL;
>> +               mux->shift = data->mux_shift;
>> +               mux->mask = 0x3;
>> +               mux->lock = lock;
>> +               mux_ops = &clk_mux_ro_ops;
>> +               mux_hw = &mux->hw;
>> +       }
>> +
>> +       if (data->div_reg1 != UNUSED) {
>> +               if (data->div_reg2 == UNUSED) {
>> +                       const struct clk_div_table *clkt;
>> +                       int table_size = 0;
>> +
>> +                       div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
>> +                       if (!div) {
>> +                               ret = -ENOMEM;
>> +                               goto free_mux;
>> +                       }
>> +
>> +                       div->reg = reg + data->div_reg1;
>> +                       div->table = data->table;
>> +                       for (clkt = div->table; clkt->div; clkt++)
>> +                               table_size++;
>> +                       div->width = order_base_2(table_size);
>> +                       div->lock = lock;
>> +                       div_ops = &clk_divider_ro_ops;
>> +                       div_hw = &div->hw;
>> +               } else {
>> +                       double_div = devm_kzalloc(dev, sizeof(*double_div),
>> +                                                 GFP_KERNEL);
>> +                       if (!double_div) {
>> +                               ret = -ENOMEM;
>> +                               goto free_mux;
>> +                       }
>> +
>> +                       double_div->reg1 = reg + data->div_reg1;
>> +                       double_div->shift1 = data->div_shift1;
>> +                       double_div->reg2 = reg + data->div_reg1;
>> +                       double_div->shift2 = data->div_shift2;
>> +                       div_ops = &clk_double_div_ops;
>> +                       div_hw = &double_div->hw;
>> +               }
>> +       }
>> +
>> +       switch (data->flags) {
>> +       case XTAL_CHILD:
>> +               /* the xtal clock is the 5th clock */
>> +               names = &parent_name[4];
>> +               num_parent = 1;
>> +               break;
>> +       case TBGA_S_CHILD:
>> +               /* the TBG A S clock is the 3rd clock */
>> +               names = &parent_name[2];
>> +               num_parent = 1;
>> +               break;
>> +       case GBE_CORE_CHILD:
>> +               names = &gbe_name[1];
>> +               num_parent = 1;
>> +               break;
>> +       case  GBE_50_CHILD:
>> +               names = &gbe_name[0];
>> +               num_parent = 1;
>> +               break;
>> +       case  GBE_125_CHILD:
>> +               names = &gbe_name[2];
>> +               num_parent = 1;
>> +               break;
>> +       default:
>> +               names = parent_name;
>> +               num_parent = 4;
>> +       }
>> +       hw = clk_hw_register_composite(dev, data->name,
>> +                                    names, num_parent,
>> +                                    mux_hw, mux_ops,
>> +                                    div_hw, div_ops,
>> +                                    gate_hw, gate_ops,
>> +                                    CLK_IGNORE_UNUSED);
>> +       if (IS_ERR(hw)) {
>> +               ret = PTR_ERR(hw);
>> +               goto free_div;
>> +       }
>> +
>> +       return 0;
>> +free_div:
>> +       devm_kfree(dev, div);
>> +       devm_kfree(dev, double_div);
>> +free_mux:
>> +       devm_kfree(dev, mux);
>> +free_gate:
>> +       devm_kfree(dev, gate);
>> +       return ret;
>> +}
>
> Can this "add" function (aka registration function) be replaced with
> static data instead? I think that all of the static data exists already,
> this function can be removed and your probe can call clk_hw_register
> directly.
>

I see your point and indeed we can remove some allocation. However using
clk_hw_register with composite clock is not straight forward. Indeed the
clk_ops is filled in the clk_hw_register_composite function and none of
these operations are exported outside the clk-composite.c file.

We can't directly point a clk_gate_ops structure or a clk_divider_ops as
you did in the drivers/clk/meson/gxbb.c file for example. For clk
composite it would need modify the framework to either export all the
operation or to create set of operations directly usable and I would
like to avoid doing it when we are close to the merge window.

However I can use static data for the clk_mux, clk_gate and clk_divider
struct.

Is it OK for you?

Thanks,

Gregory

> It might need a macro though, since composite clock structures are
> rather messy. This avoids a lot of unnecessary allocations and time
> populating data that we already have access to. In general I am trying
> to encourage clk drivers to use only clk_hw_register() in their probe
> instead of the helper registration functions.
>
> Similarly I am discouraging drivers from populating hw.init at run-time,
> since we already have that data for that at compile-time.
>
> Regards,
> Mike
diff mbox

Patch

diff --git a/drivers/clk/mvebu/Makefile b/drivers/clk/mvebu/Makefile
index 72e3512a9d4a..d9ae97fb43c4 100644
--- a/drivers/clk/mvebu/Makefile
+++ b/drivers/clk/mvebu/Makefile
@@ -8,6 +8,7 @@  obj-$(CONFIG_ARMADA_38X_CLK)	+= armada-38x.o
 obj-$(CONFIG_ARMADA_39X_CLK)	+= armada-39x.o
 obj-$(CONFIG_ARMADA_37XX_CLK)	+= armada-37xx-xtal.o
 obj-$(CONFIG_ARMADA_37XX_CLK)	+= armada-37xx-tbg.o
+obj-$(CONFIG_ARMADA_37XX_CLK)	+= armada-37xx-periph.o
 obj-$(CONFIG_ARMADA_XP_CLK)	+= armada-xp.o
 obj-$(CONFIG_ARMADA_AP806_SYSCON) += ap806-system-controller.o
 obj-$(CONFIG_ARMADA_CP110_SYSCON) += cp110-system-controller.o
diff --git a/drivers/clk/mvebu/armada-37xx-periph.c b/drivers/clk/mvebu/armada-37xx-periph.c
new file mode 100644
index 000000000000..d8c2330fd685
--- /dev/null
+++ b/drivers/clk/mvebu/armada-37xx-periph.c
@@ -0,0 +1,457 @@ 
+/*
+ * Marvell Armada 37xx SoC Peripheral clocks
+ *
+ * Copyright (C) 2016 Marvell
+ *
+ * Gregory CLEMENT <gregory.clement@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ *
+ * Most of the peripheral clocks can be modelled like this:
+ *             _____    _______    _______
+ * TBG-A-P  --|     |  |       |  |       |   ______
+ * TBG-B-P  --| Mux |--| /div1 |--| /div2 |--| Gate |--> perip_clk
+ * TBG-A-S  --|     |  |       |  |       |  |______|
+ * TBG-B-S  --|_____|  |_______|  |_______|
+ *
+ * However some clocks may use only one or two block or and use the
+ * xtal clock as parent.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define PARENT_NUM	5
+
+#define TBG_SEL		0x0
+#define DIV_SEL0	0x4
+#define DIV_SEL1	0x8
+#define DIV_SEL2	0xC
+#define CLK_SEL		0x10
+#define CLK_DIS		0x14
+
+
+#define UNUSED	0xFFFF
+#define XTAL_CHILD	0x1 /* Xtal is the only parent of the clock */
+#define TBGA_S_CHILD	0x2 /* TBG A S is the only parent of the clock */
+#define GBE_CORE_CHILD	0x3 /* GBE core is the only parent of the clock */
+#define GBE_50_CHILD	0x4 /* GBE 50 is the only parent of the clock */
+#define GBE_125_CHILD	0x5 /* GBE 125 is the only parent of the clock */
+
+struct clk_double_div {
+	struct clk_hw hw;
+	void __iomem *reg1;
+	int shift1;
+	void __iomem *reg2;
+	int shift2;
+};
+
+#define to_clk_double_div(_hw) container_of(_hw, struct clk_double_div, hw)
+
+struct clk_periph_data {
+	char *name;
+	int gate_shift;
+	int mux_shift;
+	u32 div_reg1;
+	int div_shift1;
+	u32 div_reg2;
+	int div_shift2;
+	const struct clk_div_table *table;
+	int flags;
+};
+
+static const struct clk_div_table clk_table6[] = {
+	{ .val = 1, .div = 1, },
+	{ .val = 2, .div = 2, },
+	{ .val = 3, .div = 3, },
+	{ .val = 4, .div = 4, },
+	{ .val = 5, .div = 5, },
+	{ .val = 6, .div = 6, },
+	{ .val = 0, .div = 0, }, /* last entry */
+};
+
+static const struct clk_div_table clk_table1[] = {
+	{ .val = 0, .div = 1, },
+	{ .val = 1, .div = 2, },
+	{ .val = 0, .div = 0, }, /* last entry */
+};
+
+static const struct clk_div_table clk_table2[] = {
+	{ .val = 0, .div = 2, },
+	{ .val = 1, .div = 4, },
+	{ .val = 0, .div = 0, }, /* last entry */
+};
+
+static const struct clk_periph_data data_nb[] = {
+	{ .name = "mmc",	.gate_shift =  2, .mux_shift = 0,
+	  .div_reg1 = DIV_SEL2, .div_shift1 = 16, .div_reg2 = DIV_SEL2,
+	  .div_shift2 = 13, .table = NULL, .flags = 0 },
+	{ .name = "sata_host",	.gate_shift =  3, .mux_shift = 2,
+	  .div_reg1 = DIV_SEL2, .div_shift1 = 10, .div_reg2 = DIV_SEL2,
+	  .div_shift2 = 7, .table = NULL, .flags = 0 },
+	{ .name = "sec at",	.gate_shift =  6, .mux_shift = 4,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 3, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 0, .table = NULL, .flags = 0 },
+	{ .name = "sec_dap",	.gate_shift =  7, .mux_shift = 6,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 9, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 6, .table = NULL, .flags = 0 },
+	{ .name = "tsecm",	.gate_shift =  8, .mux_shift = 8,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 15, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 12, .table = NULL, .flags = 0 },
+	{ .name = "setm_tmx",	.gate_shift =  10, .mux_shift = 10,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 18, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table6, .flags = 0 },
+	{ .name = "avs",	.gate_shift =  11, .mux_shift = UNUSED,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = XTAL_CHILD},
+	{ .name = "sqf",	.gate_shift =  12, .mux_shift = 12,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 27, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 24, .table = NULL, .flags = 0 },
+	{ .name = "pwm",	.gate_shift =  13, .mux_shift = 14,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 3, .div_reg2 = DIV_SEL0,
+	  .div_shift2 = 0, .table = NULL, .flags = 0 },
+	{ .name = "i2c_2",	.gate_shift =  16, .mux_shift = UNUSED,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = XTAL_CHILD },
+	{ .name = "i2c_1",	.gate_shift =  17, .mux_shift = UNUSED,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = XTAL_CHILD },
+	{ .name = "ddr_phy",	.gate_shift =  19, .mux_shift = UNUSED,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 18, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table2, .flags = TBGA_S_CHILD },
+	{ .name = "ddr_fclk",	.gate_shift =  21, .mux_shift = 16,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 15, .div_reg2 = DIV_SEL0,
+	  .div_shift2 = 12, .table = NULL, .flags = 0 },
+	{ .name = "trace",	.gate_shift =  22, .mux_shift = 18,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 20, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table6, .flags = 0 },
+	{ .name = "counter",	.gate_shift =  23, .mux_shift = 20,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 23, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table6, .flags = 0 },
+	{ .name = "eip97",	.gate_shift =  24, .mux_shift = 24,
+	  .div_reg1 = DIV_SEL2, .div_shift1 = 22, .div_reg2 = DIV_SEL2,
+	  .div_shift2 = 19, .table = NULL, .flags = 0 },
+	{ .name = "cpu",	.gate_shift =  UNUSED, .mux_shift = 22,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 28, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table6, .flags = 0 },
+	{ },
+};
+
+static const struct clk_periph_data data_sb[] = {
+	{ .name = "gbe-50",	.gate_shift =  UNUSED, .mux_shift = 6,
+	  .div_reg1 = DIV_SEL2, .div_shift1 = 6, .div_reg2 = DIV_SEL2,
+	  .div_shift2 = 9, .table = NULL, .flags = 0 },
+	{ .name = "gbe-core",	.gate_shift =  UNUSED, .mux_shift = 8,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 18, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 21, .table = NULL, .flags = 0 },
+	{ .name = "gbe-125",	.gate_shift =  UNUSED, .mux_shift = 10,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 6, .div_reg2 = DIV_SEL1,
+	  .div_shift2 = 9, .table = NULL, .flags = 0 },
+	{ .name = "gbe1-50",	.gate_shift =  0, .mux_shift = 0,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = GBE_50_CHILD },
+	{ .name = "gbe0-50",	.gate_shift =  1, .mux_shift = 2,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = GBE_50_CHILD },
+	{ .name = "gbe1-125",	.gate_shift =  2, .mux_shift = 4,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = GBE_125_CHILD },
+	{ .name = "gbe0-125",	.gate_shift =  3, .mux_shift = 6,
+	  .div_reg1 = UNUSED, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = NULL, .flags = GBE_125_CHILD },
+	{ .name = "gbe1-core",	.gate_shift =  4, .mux_shift = 8,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 13, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table1, .flags = GBE_CORE_CHILD },
+	{ .name = "gbe0-core",	.gate_shift =  5, .mux_shift = 10,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 14, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table1, .flags = GBE_CORE_CHILD },
+	{ .name = "gbe-bm",	.gate_shift =  12, .mux_shift = UNUSED,
+	  .div_reg1 = DIV_SEL1, .div_shift1 = 0, .div_reg2 = UNUSED,
+	  .div_shift2 = 0, .table = clk_table1, .flags = GBE_CORE_CHILD },
+	{ .name = "sdio",	.gate_shift =  11, .mux_shift = 14,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 3, .div_reg2 = DIV_SEL0,
+	  .div_shift2 = 6, .table = NULL, .flags = 0 },
+	{ .name = "usb32-usb2-sys",	.gate_shift =  16, .mux_shift = 16,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 9, .div_reg2 = DIV_SEL0,
+	  .div_shift2 = 12, .table = NULL, .flags = 0 },
+	{ .name = "usb32-ss-sys",	.gate_shift =  17, .mux_shift = 18,
+	  .div_reg1 = DIV_SEL0, .div_shift1 = 15, .div_reg2 = DIV_SEL0,
+	  .div_shift2 = 18, .table = NULL, .flags = 0 },
+	{  },
+};
+
+static const char * const gbe_name[] = {
+	"gbe-50", "gbe-core", "gbe-125",
+};
+
+struct clk_periph_driver_data {
+	struct clk_hw_onecell_data *hw_data;
+	spinlock_t lock;
+};
+
+static unsigned int get_div(void __iomem *reg, int shift)
+{
+	u32 val;
+
+	val = (readl(reg) >> shift) & 0x7;
+	if (val > 6)
+		return 0;
+	return val;
+}
+
+static unsigned long clk_double_div_recalc_rate(struct clk_hw *hw,
+		unsigned long parent_rate)
+{
+	struct clk_double_div *double_div = to_clk_double_div(hw);
+	unsigned int div;
+
+	div = get_div(double_div->reg1, double_div->shift1);
+	div *= get_div(double_div->reg2, double_div->shift2);
+
+	return DIV_ROUND_UP_ULL((u64)parent_rate, div);
+}
+
+static const struct clk_ops clk_double_div_ops = {
+	.recalc_rate = clk_double_div_recalc_rate,
+};
+
+static int armada_3700_add_composite_clk(const struct clk_periph_data *data,
+					 const char * const *parent_name,
+					 void __iomem *reg, spinlock_t *lock,
+					 struct device *dev, struct clk_hw *hw)
+{
+	const struct clk_ops *mux_ops = NULL, *gate_ops = NULL,
+		*div_ops = NULL;
+	struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *div_hw = NULL;
+	const char * const *names;
+	struct clk_mux *mux = NULL;
+	struct clk_gate *gate = NULL;
+	struct clk_divider *div = NULL;
+	struct clk_double_div *double_div = NULL;
+	int num_parent;
+	int ret = 0;
+
+	if (data->gate_shift != UNUSED) {
+		gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
+
+		if (!gate)
+			return -ENOMEM;
+
+		gate->reg = reg + CLK_DIS;
+		gate->bit_idx = data->gate_shift;
+		gate->lock = lock;
+		gate_ops = &clk_gate_ops;
+		gate_hw = &gate->hw;
+	}
+
+	if (data->mux_shift != UNUSED) {
+		mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+
+		if (!mux) {
+			ret = -ENOMEM;
+			goto free_gate;
+		}
+
+		mux->reg = reg + TBG_SEL;
+		mux->shift = data->mux_shift;
+		mux->mask = 0x3;
+		mux->lock = lock;
+		mux_ops = &clk_mux_ro_ops;
+		mux_hw = &mux->hw;
+	}
+
+	if (data->div_reg1 != UNUSED) {
+		if (data->div_reg2 == UNUSED) {
+			const struct clk_div_table *clkt;
+			int table_size = 0;
+
+			div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+			if (!div) {
+				ret = -ENOMEM;
+				goto free_mux;
+			}
+
+			div->reg = reg + data->div_reg1;
+			div->table = data->table;
+			for (clkt = div->table; clkt->div; clkt++)
+				table_size++;
+			div->width = order_base_2(table_size);
+			div->lock = lock;
+			div_ops = &clk_divider_ro_ops;
+			div_hw = &div->hw;
+		} else {
+			double_div = devm_kzalloc(dev, sizeof(*double_div),
+						  GFP_KERNEL);
+			if (!double_div) {
+				ret = -ENOMEM;
+				goto free_mux;
+			}
+
+			double_div->reg1 = reg + data->div_reg1;
+			double_div->shift1 = data->div_shift1;
+			double_div->reg2 = reg + data->div_reg1;
+			double_div->shift2 = data->div_shift2;
+			div_ops = &clk_double_div_ops;
+			div_hw = &double_div->hw;
+		}
+	}
+
+	switch (data->flags) {
+	case XTAL_CHILD:
+		/* the xtal clock is the 5th clock */
+		names = &parent_name[4];
+		num_parent = 1;
+		break;
+	case TBGA_S_CHILD:
+		/* the TBG A S clock is the 3rd clock */
+		names = &parent_name[2];
+		num_parent = 1;
+		break;
+	case GBE_CORE_CHILD:
+		names = &gbe_name[1];
+		num_parent = 1;
+		break;
+	case  GBE_50_CHILD:
+		names = &gbe_name[0];
+		num_parent = 1;
+		break;
+	case  GBE_125_CHILD:
+		names = &gbe_name[2];
+		num_parent = 1;
+		break;
+	default:
+		names = parent_name;
+		num_parent = 4;
+	}
+	hw = clk_hw_register_composite(dev, data->name,
+				     names, num_parent,
+				     mux_hw, mux_ops,
+				     div_hw, div_ops,
+				     gate_hw, gate_ops,
+				     CLK_IGNORE_UNUSED);
+	if (IS_ERR(hw)) {
+		ret = PTR_ERR(hw);
+		goto free_div;
+	}
+
+	return 0;
+free_div:
+	devm_kfree(dev, div);
+	devm_kfree(dev, double_div);
+free_mux:
+	devm_kfree(dev, mux);
+free_gate:
+	devm_kfree(dev, gate);
+	return ret;
+}
+
+static const struct of_device_id armada_3700_periph_clock_of_match[] = {
+	{ .compatible = "marvell,armada-3700-periph-clock-nb",
+	  .data = data_nb, },
+	{ .compatible = "marvell,armada-3700-periph-clock-sb",
+	  .data = data_sb, },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, armada_3700_periph_clock_of_match);
+
+static int armada_3700_periph_clock_probe(struct platform_device *pdev)
+{
+	struct clk_periph_driver_data *driver_data;
+	struct device_node *np = pdev->dev.of_node;
+	const char *parent_name[PARENT_NUM];
+	const struct clk_periph_data *data;
+	struct device *dev = &pdev->dev;
+	int num_periph = 0, i, ret;
+	struct resource *res;
+	void __iomem *reg;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -ENODEV;
+
+	while (data[num_periph].name)
+		num_periph++;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	reg = devm_ioremap_resource(dev, res);
+	if (IS_ERR(reg)) {
+		dev_err(dev, "Could not map the periph clock registers\n");
+		return PTR_ERR(reg);
+	}
+
+	ret = of_clk_parent_fill(np, parent_name, PARENT_NUM);
+	if (ret != PARENT_NUM) {
+		dev_err(dev, "Could not retrieve the parents\n");
+		return -EINVAL;
+	}
+
+	driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL);
+	if (!driver_data)
+		return -ENOMEM;
+
+	driver_data->hw_data = devm_kzalloc(dev, sizeof(*driver_data->hw_data) +
+			    sizeof(*driver_data->hw_data->hws) * num_periph,
+			    GFP_KERNEL);
+	if (!driver_data->hw_data)
+		return -ENOMEM;
+	driver_data->hw_data->num = num_periph;
+
+	spin_lock_init(&driver_data->lock);
+
+	for (i = 0; i < num_periph; i++) {
+		struct clk_hw *hw = driver_data->hw_data->hws[i];
+
+		if (armada_3700_add_composite_clk(&data[i], parent_name, reg,
+						  &driver_data->lock, dev, hw))
+			dev_err(dev, "Can't register periph clock %s\n",
+			       data[i].name);
+	}
+
+	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get,
+				  driver_data->hw_data);
+	if (ret) {
+		for (i = 0; i < num_periph; i++)
+			clk_hw_unregister(driver_data->hw_data->hws[i]);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, driver_data);
+	return 0;
+}
+
+static int armada_3700_periph_clock_remove(struct platform_device *pdev)
+{
+	struct clk_periph_driver_data *data = platform_get_drvdata(pdev);
+	struct clk_hw_onecell_data *hw_data = data->hw_data;
+	int i;
+
+	of_clk_del_provider(pdev->dev.of_node);
+
+	for (i = 0; i < hw_data->num; i++)
+		clk_hw_unregister(hw_data->hws[i]);
+
+	return 0;
+}
+
+static struct platform_driver armada_3700_periph_clock_driver = {
+	.probe = armada_3700_periph_clock_probe,
+	.remove = armada_3700_periph_clock_remove,
+	.driver		= {
+		.name	= "marvell-armada-3700-periph-clock",
+		.of_match_table = armada_3700_periph_clock_of_match,
+	},
+};
+
+module_platform_driver(armada_3700_periph_clock_driver);
+
+MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>");
+MODULE_DESCRIPTION("Marvell Armada 37xx SoC Peripheral clocks driver");
+MODULE_LICENSE("GPL v2");