diff mbox series

[07/14] clock: milbeaut: Add Milbeaut M10V clock control

Message ID 1542589274-13878-8-git-send-email-sugaya.taichi@socionext.com
State New
Headers show
Series None | expand

Commit Message

Sugaya Taichi Nov. 19, 2018, 1:01 a.m. UTC
Add Milbeaut M10V clock ( including PLL ) control.

Signed-off-by: Sugaya Taichi <sugaya.taichi@socionext.com>

---
 drivers/clk/Makefile   |   1 +
 drivers/clk/clk-m10v.c | 671 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 672 insertions(+)
 create mode 100644 drivers/clk/clk-m10v.c

-- 
1.9.1

Comments

Sugaya Taichi Dec. 4, 2018, 8:26 a.m. UTC | #1
Hi,

Thank you for your comments.

On 2018/11/30 17:31, Stephen Boyd wrote:
> Quoting Sugaya Taichi (2018-11-18 17:01:12)

>> Add Milbeaut M10V clock ( including PLL ) control.

> 

> Please give some more details here.


OK, add more description.

> 

>>

>> Signed-off-by: Sugaya Taichi <sugaya.taichi@socionext.com>

>> ---

>>   drivers/clk/Makefile   |   1 +

>>   drivers/clk/clk-m10v.c | 671 +++++++++++++++++++++++++++++++++++++++++++++++++

> 

> And this is different from Uniphier? Maybe we need a socionext

> directory under drivers/clk/.


Yes, M10V is a one of the Milbeaut series ( not Uniphier ).
Anyway, I will talk to Uniphier team about creating a socionext directory.

> 

>>   2 files changed, 672 insertions(+)

>>   create mode 100644 drivers/clk/clk-m10v.c

>>

>> diff --git a/drivers/clk/clk-m10v.c b/drivers/clk/clk-m10v.c

>> new file mode 100644

>> index 0000000..aa92a69

>> --- /dev/null

>> +++ b/drivers/clk/clk-m10v.c

>> @@ -0,0 +1,671 @@

>> +// SPDX-License-Identifier: GPL-2.0

>> +/*

>> + * Copyright (C) 2018 Socionext Inc.

>> + * Copyright (C) 2016 Linaro Ltd.

>> + *

>> + */

>> +

>> +#include <linux/clk-provider.h>

>> +#include <linux/clkdev.h>

> 

> Is this include used?


I will check and drop if they are not used.

> 

>> +#include <linux/delay.h>

>> +#include <linux/err.h>

>> +#include <linux/io.h>

>> +#include <linux/of_address.h>

>> +#include <linux/of.h>

>> +#include <linux/spinlock.h>

>> +

>> +#define CLKSEL1                0x0

>> +#define CLKSEL(n)      (((n) - 1) * 4 + CLKSEL1)

>> +

>> +#define PLLCNT1                0x30

>> +#define PLLCNT(n)      (((n) - 1) * 4 + PLLCNT1)

>> +

>> +#define CLKSTOP1       0x54

>> +#define CLKSTOP(n)     (((n) - 1) * 4 + CLKSTOP1)

>> +

>> +#define CRSWR          0x8c

>> +#define CRRRS          0x90

>> +#define CRRSM          0x94

>> +

>> +#define to_m10v_mux(_hw)       container_of(_hw, struct m10v_mux, hw)

>> +#define to_m10v_gate(_hw)      container_of(_hw, struct m10v_gate, hw)

>> +#define to_m10v_div(_hw)       container_of(_hw, struct m10v_div, hw)

>> +#define to_m10v_pll(_hw)       container_of(_hw, struct m10v_pll, hw)

>> +

>> +static void __iomem *clk_base;

>> +static struct device_node *np_top;

>> +static DEFINE_SPINLOCK(crglock);

> 

> Please make more specific names for these global variables by prefixing

> with m10v_. Also consider getting rid of the iomem and np_top globals

> entirely and associate those with clks differently.


I got it.

> 

>> +

>> +static __init void __iomem *m10v_clk_iomap(void)

>> +{

>> +       if (clk_base)

>> +               return clk_base;

>> +

>> +       np_top = of_find_compatible_node(NULL, NULL,

>> +                       "socionext,milbeaut-m10v-clk-regs");

>> +       if (!np_top) {

>> +               pr_err("%s: CLK iomap failed!\n", __func__);

> 

> We haven't iomapped yet though.


Yes.

> 

>> +               return NULL;

>> +       }

>> +

>> +       clk_base = of_iomap(np_top, 0);

>> +       of_node_put(np_top);

> 

> Would be nicer to use platform_device APIs instead of OF ones.


OK, use platform_device APIs.

> 

>> +

>> +       return clk_base;

>> +}

>> +

>> +struct m10v_mux {

>> +       struct clk_hw hw;

>> +       const char *cname;

>> +       u32 parent;

>> +};

>> +

>> +static u8 m10v_mux_get_parent(struct clk_hw *hw)

>> +{

>> +       struct m10v_mux *mcm = to_m10v_mux(hw);

>> +       struct clk_hw *parent;

>> +       int i;

>> +

>> +       i = clk_hw_get_num_parents(hw);

>> +       while (i--) {

>> +               parent = clk_hw_get_parent_by_index(hw, i);

>> +               if (clk_hw_get_rate(parent))

>> +                       break;

>> +       }

>> +

>> +       if (i < 0) {

>> +               pr_info("%s:%s no parent?!\n",

>> +                       __func__, mcm->cname);

>> +               i = 0;

>> +       }

>> +

>> +       return i;

>> +}

>> +

>> +static int m10v_mux_set_parent(struct clk_hw *hw, u8 index)

>> +{

>> +       struct m10v_mux *mcm = to_m10v_mux(hw);

>> +

>> +       mcm->parent = index;

>> +       return 0;

>> +}

>> +

>> +static const struct clk_ops m10v_mux_ops = {

>> +       .get_parent = m10v_mux_get_parent,

>> +       .set_parent = m10v_mux_set_parent,

>> +       .determine_rate = __clk_mux_determine_rate,

>> +};

>> +

>> +void __init m10v_clk_mux_setup(struct device_node *node)

>> +{

>> +       const char *clk_name = node->name;

>> +       struct clk_init_data init;

>> +       const char **parent_names;

>> +       struct m10v_mux *mcm;

>> +       struct clk *clk;

>> +       int i, parents;

>> +

>> +       if (!m10v_clk_iomap())

>> +               return;

>> +

>> +       of_property_read_string(node, "clock-output-names", &clk_name);

>> +

>> +       parents = of_clk_get_parent_count(node);

>> +       if (parents < 2) {

>> +               pr_err("%s: not a mux\n", clk_name);

> 

> How is this possible?


When the node has more than 1 clks...
Or I am misunderstanding your question?

> 

>> +               return;

>> +       }

>> +

>> +       parent_names = kzalloc((sizeof(char *) * parents), GFP_KERNEL);

>> +       if (!parent_names)

>> +               return;

>> +

>> +       for (i = 0; i < parents; i++)

>> +               parent_names[i] = of_clk_get_parent_name(node, i);

> 

> This is of_clk_parent_fill().


OK, use it instead.

> 

>> +

>> +       mcm = kzalloc(sizeof(*mcm), GFP_KERNEL);

>> +       if (!mcm)

>> +               goto err_mcm;

>> +

>> +       init.name = clk_name;

>> +       init.ops = &m10v_mux_ops;

>> +       init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;

> 

> Please don't use CLK_IS_BASIC unless you need it.


OK, confirm it.

> 

>> +       init.num_parents = parents;

>> +       init.parent_names = parent_names;

>> +

>> +       mcm->cname = clk_name;

>> +       mcm->parent = 0;

>> +       mcm->hw.init = &init;

>> +

>> +       clk = clk_register(NULL, &mcm->hw);

>> +       if (IS_ERR(clk))

>> +               goto err_clk;

>> +

>> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

>> +       return;

>> +

>> +err_clk:

>> +       kfree(mcm);

>> +err_mcm:

>> +       kfree(parent_names);

>> +}

>> +CLK_OF_DECLARE(m10v_clk_mux, "socionext,milbeaut-m10v-clk-mux",

>> +                       m10v_clk_mux_setup);

> 

> Any chance you can use a platform driver?


OK. try to use platform driver.

> 

>> +

>> +struct m10v_pll {

>> +       struct clk_hw hw;

>> +       const char *cname;

>> +       const struct clk_ops ops;

>> +       u32 offset;

>> +       u32 div, mult;

>> +       bool ro;

>> +};

>> +

>> +#define ST     1

>> +#define SEL    2

>> +

>> +static void _mpg_enable(struct clk_hw *hw, unsigned int enable)

>> +{

>> +       struct m10v_pll *mpg = to_m10v_pll(hw);

>> +       unsigned long flags;

>> +       u32 val;

>> +

>> +       if (mpg->ro) {

>> +               pr_debug("%s:%d %s: read-only\n",

>> +                        __func__, __LINE__, mpg->cname);

>> +               return;

>> +       }

>> +

>> +       spin_lock_irqsave(&crglock, flags);

>> +

>> +       val = readl(clk_base + PLLCNT(SEL));

>> +       if (enable)

>> +               val |= BIT(mpg->offset);

>> +       else

>> +               val &= ~BIT(mpg->offset);

>> +       writel(val, clk_base + PLLCNT(SEL));

>> +

>> +       spin_unlock_irqrestore(&crglock, flags);

>> +}

>> +

>> +static int mpg_enable(struct clk_hw *hw)

>> +{

>> +       _mpg_enable(hw, 1);

>> +       return 0;

>> +}

>> +

>> +static void mpg_disable(struct clk_hw *hw)

>> +{

>> +       _mpg_enable(hw, 0);

>> +}

>> +

>> +static int mpg_is_enabled(struct clk_hw *hw)

>> +{

>> +       struct m10v_pll *mpg = to_m10v_pll(hw);

>> +

>> +       return readl(clk_base + PLLCNT(SEL)) & (1 << mpg->offset);

>> +}

>> +

>> +static void _mpg_prepare(struct clk_hw *hw, unsigned int on)

>> +{

>> +       struct m10v_pll *mpg = to_m10v_pll(hw);

>> +       unsigned long flags;

>> +       u32 val;

>> +

>> +       if (mpg->ro) {

> 

> Should have different RO ops for read-only clks.


I got it.

> 

>> +               pr_debug("%s:%d %s: read-only\n",

>> +                        __func__, __LINE__, mpg->cname);

>> +               return;

>> +       }

>> +

>> +       val = readl(clk_base + PLLCNT(ST));

>> +       if (!on == !(val & BIT(mpg->offset)))

>> +               return;

>> +

>> +       /* disable */

> 

> Please remove obvious comments.


Oops, OK remove.

> 

>> +       mpg_disable(hw);

>> +

>> +       spin_lock_irqsave(&crglock, flags);

>> +

>> +       val = readl(clk_base + PLLCNT(ST));

>> +       if (on)

>> +               val |= BIT(mpg->offset);

>> +       else

>> +               val &= ~BIT(mpg->offset);

>> +       writel(val, clk_base + PLLCNT(ST));

>> +

>> +       spin_unlock_irqrestore(&crglock, flags);

>> +

>> +       udelay(on ? 200 : 10);

>> +}

>> +

>> +static int mpg_prepare(struct clk_hw *hw)

>> +{

>> +       _mpg_prepare(hw, 1);

>> +       return 0;

>> +}

>> +

>> +static void mpg_unprepare(struct clk_hw *hw)

>> +{

>> +       _mpg_prepare(hw, 0);

>> +}

>> +

>> +static int mpg_set_rate(struct clk_hw *hw, unsigned long rate,

>> +                               unsigned long prate)

>> +{

> 

> Why is this implemented then?


This is not necessary maybe. so consider whether getting rid of it.

> 

>> +       return 0;

>> +}

>> +

>> +static unsigned long mpg_recalc_rate(struct clk_hw *hw,

>> +               unsigned long prate)

>> +{

>> +       struct m10v_pll *mpg = to_m10v_pll(hw);

>> +       unsigned long long rate = prate;

>> +

>> +       if (mpg_is_enabled(hw)) {

>> +               rate = (unsigned long long)prate * mpg->mult;

>> +               do_div(rate, mpg->div);

>> +       }

>> +

>> +       return (unsigned long)rate;

>> +}

>> +

>> +static long mpg_round_rate(struct clk_hw *hw, unsigned long rate,

>> +                               unsigned long *prate)

>> +{

>> +       struct m10v_pll *mpg = to_m10v_pll(hw);

>> +       unsigned long long temp_rate = (unsigned long long)*prate * mpg->mult;

>> +

>> +       if (mpg->ro)

>> +               return mpg_recalc_rate(hw, *prate);

>> +

>> +       return do_div(temp_rate, mpg->div);

> 

> There shouldn't be round_rate implemented at all if the device is

> 'read-only' or can't change frequency because set_rate op is empty.


I understand. I will describe correctly.

> 

>> +}

>> +

> [..]

>> +

>> +static void mdc_set_div(struct m10v_div *mdc, u32 div)

>> +{

>> +       u32 off, shift, val;

>> +

>> +       off = mdc->offset / 32 * 4;

>> +       shift = mdc->offset % 32;

>> +

>> +       val = readl(clk_base + CLKSEL1 + off);

>> +       val &= ~(mdc->mask << shift);

>> +       val |= (div << shift);

>> +       writel(val, clk_base + CLKSEL1 + off);

>> +

>> +       if (mdc->waitdchreq) {

>> +               unsigned int count = 250;

>> +

>> +               writel(1, clk_base + CLKSEL(11));

>> +

>> +               do {

>> +                       udelay(1);

>> +               } while (--count && readl(clk_base + CLKSEL(11)) & 1);

> 

> Use readl_poll_timeout()?


OK. use it instead.

> 

>> +

>> +               if (!count)

>> +                       pr_err("%s:%s CLK(%d) couldn't stabilize\n",

>> +                               __func__, mdc->cname, mdc->offset);

>> +       }

>> +}

>> +

> [...]

>> +

>> +void __init m10v_clk_gate_setup(struct device_node *node)

>> +{

>> +       const char *clk_name = node->name;

>> +       struct clk_init_data init;

>> +       const char *parent_name;

>> +       struct m10v_gate *mgc;

>> +       struct clk *clk;

>> +       u32 offset;

>> +       int ret;

>> +

>> +       if (!m10v_clk_iomap())

>> +               return;

>> +

>> +       of_property_read_string(node, "clock-output-names", &clk_name);

>> +

>> +       ret = of_property_read_u32(node, "offset", &offset);

>> +       if (ret) {

>> +               pr_err("%s: missing 'offset' property\n", clk_name);

>> +               return;

>> +       }

>> +

>> +       parent_name = of_clk_get_parent_name(node, 0);

>> +

>> +       mgc = kzalloc(sizeof(*mgc), GFP_KERNEL);

>> +       if (!mgc)

>> +               return;

>> +

>> +       init.name = clk_name;

>> +       init.ops = &m10v_gate_ops;

>> +       init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;

>> +       init.parent_names = &parent_name;

>> +       init.num_parents = 1;

>> +

>> +       mgc->cname = clk_name;

>> +       mgc->offset = offset;

>> +       mgc->hw.init = &init;

>> +       if (of_get_property(node, "read-only", NULL))

>> +               mgc->ro = true;

>> +

>> +       clk = clk_register(NULL, &mgc->hw);

> 

> Please use clk_hw based registration and provider APIs.


I got it. I try to describe with referring other drivers.

> 

>> +       if (IS_ERR(clk))

>> +               kfree(mgc);

>> +       else

>> +               of_clk_add_provider(node, of_clk_src_simple_get, clk);

>> +}

>> +CLK_OF_DECLARE(m10v_clk_gate, "socionext,milbeaut-m10v-clk-gate",

>> +               m10v_clk_gate_setup);

>> -- 

> 

> I suspect this driver will significantly change so I'm not reviewing

> any further until it's sent again.


I understand. I study and try to renew the driver.


>
Stephen Boyd Dec. 5, 2018, 6:57 a.m. UTC | #2
Quoting Masahiro Yamada (2018-12-04 20:26:06)
> On Wed, Dec 5, 2018 at 3:14 AM Stephen Boyd <sboyd@kernel.org> wrote:

> >

> > Quoting Masahiro Yamada (2018-12-04 03:03:53)

> > > Hi Stephen,

> > >

> > >

> > > On Fri, Nov 30, 2018 at 5:31 PM Stephen Boyd <sboyd@kernel.org> wrote:

> > > >

> > > > Quoting Sugaya Taichi (2018-11-18 17:01:12)

> > > > > Add Milbeaut M10V clock ( including PLL ) control.

> > > >

> > > > Please give some more details here.

> > > >

> > > > >

> > > > > Signed-off-by: Sugaya Taichi <sugaya.taichi@socionext.com>

> > > > > ---

> > > > >  drivers/clk/Makefile   |   1 +

> > > > >  drivers/clk/clk-m10v.c | 671 +++++++++++++++++++++++++++++++++++++++++++++++++

> > > >

> > > > And this is different from Uniphier? Maybe we need a socionext

> > > > directory under drivers/clk/.

> > >

> > >

> > >

> > > This is always a difficult question,

> > > and I do not have a strong opinion.

> > >

> > >

> > > I am fine with moving the files to drivers/clk/socionext

> > > although no file would be shared.

> > >

> > >

> > > FYI

> > >

> > > UniPhier and Milbeaut are completely different platforms

> > > developed/maintained by different teams.

> > >

> > > They happen to live in the same company now

> > > just because Socionext merged the LSI business from Panasonic and Fujitsu.

> > >

> > > UniPhier originates in Panasonic, while Milbeaut in Fujitsu.

> > >

> >

> > Thanks for the background info. I'd prefer to defer to however the dts

> > files are getting split up into directories. If they're all put under

> > arch/arm64/boot/dts/socionext/ then I would say combine the two clk

> > drivers into a socionext directory. Otherwise, keep them split out.

> 

> 

> If you want to align with the DT directory structure,

> the answer is clear.

> 

> 

> Milbeaut DT files will be put together with UniPhier ones

> into socionext directory.

> 

> 

> For arm64, DT directories are already sorted out by vendors.

> 

> Even 32-bit ARM is going to that way.

> 

> Rob Herring just posted a python script

> to move all DT files in arch/arm/boot/dts/

> into vendor subdirectories.

> 

> 

> Please let me know if you want me to

> move drivers/clk/uniphier/* to drivers/clk/socionext/*.

> 


Maybe the dts needs to be split up instead? Looks like the gpio drivers
are in a uniphier directory and there is some precedence to keep the
"taken over" company name when vendors are merged into other vendors.
Maybe that's how things have happened here? It would be nice to be
consistent, but I leave the decision up to you to figure out if that
really matters to you. I'll be fine either way.
Sugaya Taichi Dec. 28, 2018, 6:38 a.m. UTC | #3
Hi

On 2018/12/28 9:39, Stephen Boyd wrote:
> Quoting Sugaya, Taichi (2018-12-25 17:35:27)

>> Hi

>>

>> On 2018/11/30 17:31, Stephen Boyd wrote:

>>>> +       init.num_parents = parents;

>>>> +       init.parent_names = parent_names;

>>>> +

>>>> +       mcm->cname = clk_name;

>>>> +       mcm->parent = 0;

>>>> +       mcm->hw.init = &init;

>>>> +

>>>> +       clk = clk_register(NULL, &mcm->hw);

>>>> +       if (IS_ERR(clk))

>>>> +               goto err_clk;

>>>> +

>>>> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

>>>> +       return;

>>>> +

>>>> +err_clk:

>>>> +       kfree(mcm);

>>>> +err_mcm:

>>>> +       kfree(parent_names);

>>>> +}

>>>> +CLK_OF_DECLARE(m10v_clk_mux, "socionext,milbeaut-m10v-clk-mux",

>>>> +                       m10v_clk_mux_setup);

>>>

>>> Any chance you can use a platform driver?

>>>

>>

>> Excuse me to re-ask you.

>> Why do you recommend to use a platform driver? Is that current fad?

> 

> Not exactly a fad. We've been doing it for some time now. From an older

> email on the list:

> 

> Reasons (in no particular order):

> 

>    1. We get a dev pointer to use with clk_hw_register()

> 

>    2. We can handle probe defer if some resource is not available

> 

>    3. Using device model gets us a hook into power management frameworks

>       like runtime PM and system PM for things like suspend and hibernate

> 

>    4. It encourages a single DT node clk controller style binding

>       instead of a single node per clk style binding

> 

>    5. We can use non-DT specific functions like devm_ioremap_resource() to map

>       registers and acquire other resources, leading to more portable and

>       generic code

> 

>    6. We may be able to make the device driver a module, which will

>       make distros happy if we don't have to compile in all

>       these clk drivers to the resulting vmlinux

> 


Great thanks for answering. I strongly understand.
#It takes a bit of time to send v2.

Best Regards,
Sugaya Taichi
diff mbox series

Patch

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 72be7a3..da5b282 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -30,6 +30,7 @@  obj-$(CONFIG_COMMON_CLK_GEMINI)		+= clk-gemini.o
 obj-$(CONFIG_COMMON_CLK_ASPEED)		+= clk-aspeed.o
 obj-$(CONFIG_ARCH_HIGHBANK)		+= clk-highbank.o
 obj-$(CONFIG_CLK_HSDK)			+= clk-hsdk-pll.o
+obj-$(CONFIG_ARCH_MILBEAUT_M10V)	+= clk-m10v.o
 obj-$(CONFIG_COMMON_CLK_MAX77686)	+= clk-max77686.o
 obj-$(CONFIG_COMMON_CLK_MAX9485)	+= clk-max9485.o
 obj-$(CONFIG_ARCH_MOXART)		+= clk-moxart.o
diff --git a/drivers/clk/clk-m10v.c b/drivers/clk/clk-m10v.c
new file mode 100644
index 0000000..aa92a69
--- /dev/null
+++ b/drivers/clk/clk-m10v.c
@@ -0,0 +1,671 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Socionext Inc.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+
+#define CLKSEL1		0x0
+#define CLKSEL(n)	(((n) - 1) * 4 + CLKSEL1)
+
+#define PLLCNT1		0x30
+#define PLLCNT(n)	(((n) - 1) * 4 + PLLCNT1)
+
+#define CLKSTOP1	0x54
+#define CLKSTOP(n)	(((n) - 1) * 4 + CLKSTOP1)
+
+#define CRSWR		0x8c
+#define CRRRS		0x90
+#define CRRSM		0x94
+
+#define to_m10v_mux(_hw)	container_of(_hw, struct m10v_mux, hw)
+#define to_m10v_gate(_hw)	container_of(_hw, struct m10v_gate, hw)
+#define to_m10v_div(_hw)	container_of(_hw, struct m10v_div, hw)
+#define to_m10v_pll(_hw)	container_of(_hw, struct m10v_pll, hw)
+
+static void __iomem *clk_base;
+static struct device_node *np_top;
+static DEFINE_SPINLOCK(crglock);
+
+static __init void __iomem *m10v_clk_iomap(void)
+{
+	if (clk_base)
+		return clk_base;
+
+	np_top = of_find_compatible_node(NULL, NULL,
+			"socionext,milbeaut-m10v-clk-regs");
+	if (!np_top) {
+		pr_err("%s: CLK iomap failed!\n", __func__);
+		return NULL;
+	}
+
+	clk_base = of_iomap(np_top, 0);
+	of_node_put(np_top);
+
+	return clk_base;
+}
+
+struct m10v_mux {
+	struct clk_hw hw;
+	const char *cname;
+	u32 parent;
+};
+
+static u8 m10v_mux_get_parent(struct clk_hw *hw)
+{
+	struct m10v_mux *mcm = to_m10v_mux(hw);
+	struct clk_hw *parent;
+	int i;
+
+	i = clk_hw_get_num_parents(hw);
+	while (i--) {
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (clk_hw_get_rate(parent))
+			break;
+	}
+
+	if (i < 0) {
+		pr_info("%s:%s no parent?!\n",
+			__func__, mcm->cname);
+		i = 0;
+	}
+
+	return i;
+}
+
+static int m10v_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct m10v_mux *mcm = to_m10v_mux(hw);
+
+	mcm->parent = index;
+	return 0;
+}
+
+static const struct clk_ops m10v_mux_ops = {
+	.get_parent = m10v_mux_get_parent,
+	.set_parent = m10v_mux_set_parent,
+	.determine_rate = __clk_mux_determine_rate,
+};
+
+void __init m10v_clk_mux_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	struct clk_init_data init;
+	const char **parent_names;
+	struct m10v_mux *mcm;
+	struct clk *clk;
+	int i, parents;
+
+	if (!m10v_clk_iomap())
+		return;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	parents = of_clk_get_parent_count(node);
+	if (parents < 2) {
+		pr_err("%s: not a mux\n", clk_name);
+		return;
+	}
+
+	parent_names = kzalloc((sizeof(char *) * parents), GFP_KERNEL);
+	if (!parent_names)
+		return;
+
+	for (i = 0; i < parents; i++)
+		parent_names[i] = of_clk_get_parent_name(node, i);
+
+	mcm = kzalloc(sizeof(*mcm), GFP_KERNEL);
+	if (!mcm)
+		goto err_mcm;
+
+	init.name = clk_name;
+	init.ops = &m10v_mux_ops;
+	init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;
+	init.num_parents = parents;
+	init.parent_names = parent_names;
+
+	mcm->cname = clk_name;
+	mcm->parent = 0;
+	mcm->hw.init = &init;
+
+	clk = clk_register(NULL, &mcm->hw);
+	if (IS_ERR(clk))
+		goto err_clk;
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+	return;
+
+err_clk:
+	kfree(mcm);
+err_mcm:
+	kfree(parent_names);
+}
+CLK_OF_DECLARE(m10v_clk_mux, "socionext,milbeaut-m10v-clk-mux",
+			m10v_clk_mux_setup);
+
+struct m10v_pll {
+	struct clk_hw hw;
+	const char *cname;
+	const struct clk_ops ops;
+	u32 offset;
+	u32 div, mult;
+	bool ro;
+};
+
+#define ST	1
+#define SEL	2
+
+static void _mpg_enable(struct clk_hw *hw, unsigned int enable)
+{
+	struct m10v_pll *mpg = to_m10v_pll(hw);
+	unsigned long flags;
+	u32 val;
+
+	if (mpg->ro) {
+		pr_debug("%s:%d %s: read-only\n",
+			 __func__, __LINE__, mpg->cname);
+		return;
+	}
+
+	spin_lock_irqsave(&crglock, flags);
+
+	val = readl(clk_base + PLLCNT(SEL));
+	if (enable)
+		val |= BIT(mpg->offset);
+	else
+		val &= ~BIT(mpg->offset);
+	writel(val, clk_base + PLLCNT(SEL));
+
+	spin_unlock_irqrestore(&crglock, flags);
+}
+
+static int mpg_enable(struct clk_hw *hw)
+{
+	_mpg_enable(hw, 1);
+	return 0;
+}
+
+static void mpg_disable(struct clk_hw *hw)
+{
+	_mpg_enable(hw, 0);
+}
+
+static int mpg_is_enabled(struct clk_hw *hw)
+{
+	struct m10v_pll *mpg = to_m10v_pll(hw);
+
+	return readl(clk_base + PLLCNT(SEL)) & (1 << mpg->offset);
+}
+
+static void _mpg_prepare(struct clk_hw *hw, unsigned int on)
+{
+	struct m10v_pll *mpg = to_m10v_pll(hw);
+	unsigned long flags;
+	u32 val;
+
+	if (mpg->ro) {
+		pr_debug("%s:%d %s: read-only\n",
+			 __func__, __LINE__, mpg->cname);
+		return;
+	}
+
+	val = readl(clk_base + PLLCNT(ST));
+	if (!on == !(val & BIT(mpg->offset)))
+		return;
+
+	/* disable */
+	mpg_disable(hw);
+
+	spin_lock_irqsave(&crglock, flags);
+
+	val = readl(clk_base + PLLCNT(ST));
+	if (on)
+		val |= BIT(mpg->offset);
+	else
+		val &= ~BIT(mpg->offset);
+	writel(val, clk_base + PLLCNT(ST));
+
+	spin_unlock_irqrestore(&crglock, flags);
+
+	udelay(on ? 200 : 10);
+}
+
+static int mpg_prepare(struct clk_hw *hw)
+{
+	_mpg_prepare(hw, 1);
+	return 0;
+}
+
+static void mpg_unprepare(struct clk_hw *hw)
+{
+	_mpg_prepare(hw, 0);
+}
+
+static int mpg_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long prate)
+{
+	return 0;
+}
+
+static unsigned long mpg_recalc_rate(struct clk_hw *hw,
+		unsigned long prate)
+{
+	struct m10v_pll *mpg = to_m10v_pll(hw);
+	unsigned long long rate = prate;
+
+	if (mpg_is_enabled(hw)) {
+		rate = (unsigned long long)prate * mpg->mult;
+		do_div(rate, mpg->div);
+	}
+
+	return (unsigned long)rate;
+}
+
+static long mpg_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *prate)
+{
+	struct m10v_pll *mpg = to_m10v_pll(hw);
+	unsigned long long temp_rate = (unsigned long long)*prate * mpg->mult;
+
+	if (mpg->ro)
+		return mpg_recalc_rate(hw, *prate);
+
+	return do_div(temp_rate, mpg->div);
+}
+
+static const struct clk_ops m10v_pll_ops = {
+	.prepare = mpg_prepare,
+	.enable = mpg_enable,
+	.is_enabled = mpg_is_enabled,
+	.disable = mpg_disable,
+	.unprepare = mpg_unprepare,
+	.round_rate = mpg_round_rate,
+	.set_rate = mpg_set_rate,
+	.recalc_rate = mpg_recalc_rate,
+};
+
+void __init m10v_pll_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	struct clk_init_data init;
+	const char *parent_name;
+	u32 offset, div, mult;
+	struct m10v_pll *mpg;
+	struct clk *clk;
+	int ret;
+
+	if (!m10v_clk_iomap())
+		return;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	ret = of_property_read_u32(node, "offset", &offset);
+	if (ret) {
+		pr_err("%s: missing 'offset' property\n", clk_name);
+		return;
+	}
+
+	div = mult = 1;
+	of_property_read_u32(node, "clock-div", &div);
+	of_property_read_u32(node, "clock-mult", &mult);
+
+	parent_name = of_clk_get_parent_name(node, 0);
+
+	mpg = kzalloc(sizeof(*mpg), GFP_KERNEL);
+	if (!mpg)
+		return;
+
+	init.name = clk_name;
+	init.ops = &m10v_pll_ops;
+	init.flags = CLK_IS_BASIC | CLK_SET_RATE_GATE;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	mpg->cname = clk_name;
+	mpg->offset = offset;
+	mpg->div = div;
+	mpg->mult = mult;
+	mpg->hw.init = &init;
+	if (of_get_property(node, "read-only", NULL))
+		mpg->ro = true;
+
+	clk = clk_register(NULL, &mpg->hw);
+	if (IS_ERR(clk))
+		kfree(mpg);
+	else
+		of_clk_add_provider(node, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(m10v_clk_pll_gate, "socionext,milbeaut-m10v-pll-fixed-factor",
+		m10v_pll_setup);
+
+struct m10v_div {
+	struct clk_hw hw;
+	const char *cname;
+	bool waitdchreq;
+	u32 offset;
+	u32 mask;
+	u32 *table;
+	u32 tlen;
+	bool ro;
+};
+
+static void mdc_set_div(struct m10v_div *mdc, u32 div)
+{
+	u32 off, shift, val;
+
+	off = mdc->offset / 32 * 4;
+	shift = mdc->offset % 32;
+
+	val = readl(clk_base + CLKSEL1 + off);
+	val &= ~(mdc->mask << shift);
+	val |= (div << shift);
+	writel(val, clk_base + CLKSEL1 + off);
+
+	if (mdc->waitdchreq) {
+		unsigned int count = 250;
+
+		writel(1, clk_base + CLKSEL(11));
+
+		do {
+			udelay(1);
+		} while (--count && readl(clk_base + CLKSEL(11)) & 1);
+
+		if (!count)
+			pr_err("%s:%s CLK(%d) couldn't stabilize\n",
+				__func__, mdc->cname, mdc->offset);
+	}
+}
+
+static u32 mdc_get_div(struct m10v_div *mdc)
+{
+	u32 off, shift, div;
+
+	off = mdc->offset / 32 * 4;
+	shift = mdc->offset % 32;
+
+	div = readl(clk_base + CLKSEL1 + off);
+	div >>= shift;
+	div &= mdc->mask;
+
+	return div;
+}
+
+static int mdc_div_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long prate)
+{
+	struct m10v_div *mdc = to_m10v_div(hw);
+	u64 pr;
+	int i;
+
+	if (mdc->ro) {
+		pr_debug("%s:%d %s: read-only\n",
+			 __func__, __LINE__, mdc->cname);
+		return 0;
+	}
+
+	/* divisors are already in descending order in DT */
+	for (i = mdc->tlen - 2; i >= 0; i -= 2) {
+		pr = prate;
+		do_div(pr, mdc->table[i]);
+
+		if (rate >= pr)
+			break;
+	}
+	if (i < 0)
+		i = 0;
+
+	mdc_set_div(mdc, mdc->table[i + 1]);
+
+	return 0;
+}
+
+static unsigned long mdc_div_recalc_rate(struct clk_hw *hw,
+		unsigned long prate)
+{
+	struct m10v_div *mdc = to_m10v_div(hw);
+	u64 prate64 = prate;
+	u32 div;
+	int i;
+
+	div = mdc_get_div(mdc);
+
+	for (i = 1; i < mdc->tlen && div != mdc->table[i]; i += 2)
+		if (div == (mdc->table[i] & mdc->mask))
+			break; /* the MSB is already read back as 0 */
+
+	if (i > mdc->tlen) /* some other is enabled in the mux */
+		prate64 = 0;
+	else
+		do_div(prate64, mdc->table[i - 1]);
+
+	return (unsigned long)prate64;
+}
+
+static long mdc_div_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *parent)
+{
+	struct m10v_div *mdc = to_m10v_div(hw);
+	u64 prate;
+	int i;
+
+	if (mdc->ro)
+		return mdc_div_recalc_rate(hw, *parent);
+
+	/* divisors are already in descending order in DT */
+	for (i = mdc->tlen - 2; i >= 0; i -= 2) {
+		prate = *parent;
+		do_div(prate, mdc->table[i]);
+
+		if (rate >= prate)
+			break;
+	}
+
+	return (unsigned long)prate;
+}
+
+static const struct clk_ops m10v_div_ops = {
+	.round_rate = mdc_div_round_rate,
+	.set_rate = mdc_div_set_rate,
+	.recalc_rate = mdc_div_recalc_rate,
+};
+
+void __init m10v_clk_div_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	struct clk_init_data init;
+	const char *parent_name;
+	struct m10v_div *mdc;
+	struct clk *clk;
+	u32 *table, mask;
+	u32 offset;
+	int count, ret;
+
+	if (!m10v_clk_iomap())
+		return;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	parent_name = of_clk_get_parent_name(node, 0);
+	if (!parent_name) {
+		pr_err("%s: no parent specified\n", clk_name);
+		return;
+	}
+
+	ret = of_property_read_u32(node, "offset", &offset);
+	if (ret) {
+		pr_err("%s: missing 'offset' property\n", clk_name);
+		return;
+	}
+
+	ret = of_property_read_u32(node, "mask", &mask);
+	if (ret) {
+		pr_err("%s: missing 'mask' property\n", clk_name);
+		return;
+	}
+
+	count = of_property_count_u32_elems(node, "ratios");
+	if (count < 2 || count%2) {
+		pr_err("%s: invalid 'ratios' property\n", clk_name);
+		return;
+	}
+
+	table = kzalloc(sizeof(*table) * count, GFP_KERNEL);
+	if (!table)
+		return;
+
+	/*
+	 * The 'ratios' must be in descending order, we park at
+	 * first ratio (biggest divider) when disabled.
+	 */
+	ret = of_property_read_u32_array(node, "ratios", table, count);
+	if (ret) {
+		pr_err("%s: 'ratios' property read fail\n", clk_name);
+		goto err_mdc;
+	}
+
+	mdc = kzalloc(sizeof(*mdc), GFP_KERNEL);
+	if (!mdc)
+		goto err_mdc;
+
+	if (of_get_property(node, "wait-on-dchreq", NULL))
+		mdc->waitdchreq = true;
+
+	init.name = clk_name;
+	init.ops = &m10v_div_ops;
+	init.flags = CLK_IS_BASIC;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	mdc->cname = clk_name;
+	mdc->offset = offset;
+	mdc->mask = mask;
+	mdc->table = table;
+	mdc->tlen = count;
+	mdc->hw.init = &init;
+	if (of_get_property(node, "read-only", NULL))
+		mdc->ro = true;
+
+	clk = clk_register(NULL, &mdc->hw);
+	if (IS_ERR(clk))
+		goto err_clk;
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+	return;
+
+err_clk:
+	kfree(mdc);
+err_mdc:
+	kfree(table);
+}
+CLK_OF_DECLARE(m10v_clk_div, "socionext,milbeaut-m10v-clk-div",
+			m10v_clk_div_setup);
+
+struct m10v_gate {
+	struct clk_hw hw;
+	const char *cname;
+	u32 offset;
+	bool ro;
+};
+
+static void _mgc_enable(struct clk_hw *hw, bool en)
+{
+	struct m10v_gate *mgc = to_m10v_gate(hw);
+	u32 off, mask;
+
+	if (mgc->ro) {
+		pr_debug("%s:%d %s: read-only\n",
+			 __func__, __LINE__, mgc->cname);
+		return;
+	}
+
+	off = CLKSTOP1 + (mgc->offset / 32) * 4;
+
+	mask = (en ? 2 : 3) << (mgc->offset % 32);
+
+	writel(mask, clk_base + off);
+}
+
+static int mgc_enable(struct clk_hw *hw)
+{
+	_mgc_enable(hw, true);
+	return 0;
+}
+
+static void mgc_disable(struct clk_hw *hw)
+{
+	_mgc_enable(hw, false);
+}
+
+static int mgc_is_enabled(struct clk_hw *hw)
+{
+	struct m10v_gate *mgc = to_m10v_gate(hw);
+	u32 off, val, mask = 1 << (mgc->offset % 32);
+
+	off = CLKSTOP1 + (mgc->offset / 32) * 4;
+	val = readl(clk_base + off);
+
+	return !(val & mask);
+}
+
+static const struct clk_ops m10v_gate_ops = {
+	.enable = mgc_enable,
+	.disable = mgc_disable,
+	.is_enabled = mgc_is_enabled,
+};
+
+void __init m10v_clk_gate_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	struct clk_init_data init;
+	const char *parent_name;
+	struct m10v_gate *mgc;
+	struct clk *clk;
+	u32 offset;
+	int ret;
+
+	if (!m10v_clk_iomap())
+		return;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	ret = of_property_read_u32(node, "offset", &offset);
+	if (ret) {
+		pr_err("%s: missing 'offset' property\n", clk_name);
+		return;
+	}
+
+	parent_name = of_clk_get_parent_name(node, 0);
+
+	mgc = kzalloc(sizeof(*mgc), GFP_KERNEL);
+	if (!mgc)
+		return;
+
+	init.name = clk_name;
+	init.ops = &m10v_gate_ops;
+	init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	mgc->cname = clk_name;
+	mgc->offset = offset;
+	mgc->hw.init = &init;
+	if (of_get_property(node, "read-only", NULL))
+		mgc->ro = true;
+
+	clk = clk_register(NULL, &mgc->hw);
+	if (IS_ERR(clk))
+		kfree(mgc);
+	else
+		of_clk_add_provider(node, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(m10v_clk_gate, "socionext,milbeaut-m10v-clk-gate",
+		m10v_clk_gate_setup);