[v2,3/9] arm: twr-k70f120m: clock driver for Kinetis SoC
diff mbox

Message ID alpine.LNX.2.00.1507031932220.15294@localhost.localdomain
State New
Headers show

Commit Message

Paul Osmialowski July 3, 2015, 5:40 p.m. UTC
Arnd, Thomas,

Thanks for your valuable input and for your patience.

I'm attaching yet another proposal for this clock driver. I've 
flattened the .dts and ensured register access protection. I've also added 
one more clock source (osc0er) and clock gate to it.

Can you comment this one too?

On Fri, 3 Jul 2015, Thomas Gleixner wrote:

> On Thu, 2 Jul 2015, Paul Osmialowski wrote:
>> On Thu, 2 Jul 2015, Arnd Bergmann wrote:
>>
>>> I wonder if you could move out the fixed rate clocks into their own
>>> nodes. Are they actually controlled by the same block? If they are
>>> just fixed, you can use the normal binding for fixed rate clocks
>>> and only describe the clocks that are related to the driver.
>>
>> In my view having these clocks grouped together looks more convincing. After
>> all, they all share the same I/O regs in order to read configuration.
>
> The fact that they share a register is not making them a group. That's
> just a HW design decision and you need to deal with that by protecting
> the register access, but not by trying to group them artificially at
> the functional level.
>
> Thanks,
>
> 	tglx
>

Patch
diff mbox

From e05dcb772d47e4473914ac1e7703b361eb54db14 Mon Sep 17 00:00:00 2001
From: Paul Osmialowski <pawelo@king.net.pl>
Date: Mon, 29 Jun 2015 20:58:55 +0200
Subject: [PATCH 3/9] arm: twr-k70f120m: clock driver for Kinetis SoC

Based on K70P256M150SF3RM.pdf K70 Sub-Family Reference Manual, Rev. 3.

Signed-off-by: Paul Osmialowski <pawelo@king.net.pl>
---
 .../devicetree/bindings/clock/kinetis-clock.txt    |  86 ++++
 arch/arm/boot/dts/kinetis.dtsi                     |  51 +++
 drivers/clk/Makefile                               |   1 +
 drivers/clk/clk-kinetis.c                          | 502 +++++++++++++++++++++
 4 files changed, 640 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/kinetis-clock.txt
 create mode 100644 drivers/clk/clk-kinetis.c

diff --git a/Documentation/devicetree/bindings/clock/kinetis-clock.txt b/Documentation/devicetree/bindings/clock/kinetis-clock.txt
new file mode 100644
index 0000000..507f91b
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/kinetis-clock.txt
@@ -0,0 +1,86 @@ 
+* Clock bindings for Freescale Kinetis SoC
+
+Required properties:
+- compatible: Should be "fsl,kinetis-cmu".
+- reg: For fixed rate clocks, two address ranges:
+	- one for the Multipurpose Clock Genetator (MCG)
+	- one for System Integration Module (SIM) register set.
+	For clock gates, one address range for System Integration Module (SIM)
+	register set.
+- clocks: single element list of parent clocks (only for non-root clocks).
+- #clock-cells: For fixed rate clocks must be <0>,
+	for clock gates must be <2> (see below).
+
+For clock gate addresses see K70 Sub-Family Reference Manual, Rev. 3 pg. 341
+and later. Notice that addresses are zero-based, so SIM_SCGC1 has address 0,
+SIM_SCGC2 has address 1 and so on. The second address component is the bit
+index.
+
+Notice that (non-gate) clock device names must be known to Kinetis CMU drivers.
+
+Currently these are:
+
+o fixed-rate-mcgout (root)
+o fixed-rate-osc0er (root)
+o fixed-rate-cclk (fixed-rate-mcgout child)
+o fixed-rate-pclk (fixed-rate-mcgout child)
+
+Example:
+
+mcg_outclk: fixed-rate-mcgout@40064000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+	#clock-cells = <0>;
+};
+
+mcg_cclk: fixed-rate-cclk@40064000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+	clocks = <&mcg_outclk>;
+	#clock-cells = <0>;
+};
+
+mcg_pclk: fixed-rate-pclk@40064000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+	clocks = <&mcg_outclk>;
+	#clock-cells = <0>;
+};
+
+mcg_cclk_gate: cclk-gate@40047000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40047000 0x1100>;
+	clocks = <&mcg_cclk>;
+	#clock-cells = <2>;
+};
+
+mcg_pclk_gate: pclk-gate@40047000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40047000 0x1100>;
+	clocks = <&mcg_pclk>;
+	#clock-cells = <2>;
+};
+
+osc0_erclk: fixed-rate-osc0er@40064000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+	#clock-cells = <0>;
+};
+
+osc0_erclk_gate: osc0-gate@40047000 {
+	compatible = "fsl,kinetis-cmu";
+	reg = <0x40047000 0x1100>;
+	clocks = <&osc0_erclk>;
+	#clock-cells = <2>;
+};
+
+uart1: serial@4006b000 {
+	compatible = "fsl,kinetis-lpuart";
+	reg = <0x4006b000 0x1000>;
+	interrupts = <47>, <48>;
+	interrupt-names = "uart-stat", "uart-err";
+	clocks = <&mcg_cclk_gate 3 11>;
+	clock-names = "ipg";
+	dmas = <&edma 0 4>;
+	dma-names = "rx";
+};
diff --git a/arch/arm/boot/dts/kinetis.dtsi b/arch/arm/boot/dts/kinetis.dtsi
index 93d2a8a..72eb941 100644
--- a/arch/arm/boot/dts/kinetis.dtsi
+++ b/arch/arm/boot/dts/kinetis.dtsi
@@ -3,3 +3,54 @@ 
  *
  */
 #include "armv7-m.dtsi"
+
+/ {
+	soc {
+		mcg_outclk: fixed-rate-mcgout@40064000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+			#clock-cells = <0>;
+		};
+
+		mcg_cclk: fixed-rate-cclk@40064000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+			clocks = <&mcg_outclk>;
+			#clock-cells = <0>;
+		};
+
+		mcg_pclk: fixed-rate-pclk@40064000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+			clocks = <&mcg_outclk>;
+			#clock-cells = <0>;
+		};
+
+		mcg_cclk_gate: cclk-gate@40047000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40047000 0x1100>;
+			clocks = <&mcg_cclk>;
+			#clock-cells = <2>;
+		};
+
+		mcg_pclk_gate: pclk-gate@40047000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40047000 0x1100>;
+			clocks = <&mcg_pclk>;
+			#clock-cells = <2>;
+		};
+
+		osc0_erclk: fixed-rate-osc0er@40064000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40064000 0x14>, <0x40047000 0x1100>;
+			#clock-cells = <0>;
+		};
+
+		osc0_erclk_gate: osc0-gate@40047000 {
+			compatible = "fsl,kinetis-cmu";
+			reg = <0x40047000 0x1100>;
+			clocks = <&osc0_erclk>;
+			#clock-cells = <2>;
+		};
+	};
+};
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 63418cf..412d76b 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -24,6 +24,7 @@  obj-$(CONFIG_COMMON_CLK_CDCE706)	+= clk-cdce706.o
 obj-$(CONFIG_ARCH_CLPS711X)		+= clk-clps711x.o
 obj-$(CONFIG_ARCH_EFM32)		+= clk-efm32gg.o
 obj-$(CONFIG_ARCH_HIGHBANK)		+= clk-highbank.o
+obj-$(CONFIG_ARCH_KINETIS)		+= clk-kinetis.o
 obj-$(CONFIG_MACH_LOONGSON32)		+= clk-ls1x.o
 obj-$(CONFIG_COMMON_CLK_MAX_GEN)	+= clk-max-gen.o
 obj-$(CONFIG_COMMON_CLK_MAX77686)	+= clk-max77686.o
diff --git a/drivers/clk/clk-kinetis.c b/drivers/clk/clk-kinetis.c
new file mode 100644
index 0000000..8e0a0d7
--- /dev/null
+++ b/drivers/clk/clk-kinetis.c
@@ -0,0 +1,502 @@ 
+/*
+ * clk-kinetis.c - Clock driver for Kinetis K70 MCG
+ *
+ * Based on legacy pre-OF code by Alexander Potashev <aspotashev@emcraft.com>
+ *
+ * Copyright (C) 2015 Paul Osmialowski <pawelo@king.net.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+
+enum kinetis_clk_ids {
+	KINETIS_CLK_MCGOUT = 0,
+	KINETIS_CLK_OSC0ER,
+	KINETIS_CLK_CCLK,
+	KINETIS_CLK_PCLK,
+	KINETIS_CLK_NUM
+};
+
+static const struct {
+	enum kinetis_clk_ids	id;
+	const char		*name;
+} kinetis_clks[KINETIS_CLK_NUM] = {
+	{ .id = KINETIS_CLK_MCGOUT,	.name = "fixed-rate-mcgout",	},
+	{ .id = KINETIS_CLK_OSC0ER,	.name = "fixed-rate-osc0er",	},
+	{ .id = KINETIS_CLK_CCLK,	.name = "fixed-rate-cclk",	},
+	{ .id = KINETIS_CLK_PCLK,	.name = "fixed-rate-pclk",	},
+};
+
+/*
+ * Frequencies on OSC0 (EXTAL0/XTAL0) and OSC1 (EXTAL1/XTAL1)
+ *
+ * These frequencies should be set to the same values as in U-Boot.
+ */
+#define KINETIS_OSC0_RATE	50000000	/* 50 MHz */
+#define KINETIS_OSC1_RATE	12000000	/* 12 MHz */
+
+#define KINETIS_SIM_CG_NUMREGS	7
+
+/*
+ * System Integration Module (SIM) register map
+ *
+ * This map actually covers two hardware modules:
+ *     1. SIM low-power logic, at 0x40047000
+ *     2. System integration module (SIM), at 0x40048000
+ */
+struct kinetis_sim_regs {
+	u32 sopt1;	/* System Options Register 1 */
+	u32 rsv0[1024];
+	u32 sopt2;	/* System Options Register 2 */
+	u32 rsv1;
+	u32 sopt4;	/* System Options Register 4 */
+	u32 sopt5;	/* System Options Register 5 */
+	u32 sopt6;	/* System Options Register 6 */
+	u32 sopt7;	/* System Options Register 7 */
+	u32 rsv2[2];
+	u32 sdid;	/* System Device Identification Register */
+	u32 scgc[KINETIS_SIM_CG_NUMREGS];	/* Clock Gating Regs 1...7 */
+	u32 clkdiv1;	/* System Clock Divider Register 1 */
+	u32 clkdiv2;	/* System Clock Divider Register 2 */
+	u32 fcfg1;	/* Flash Configuration Register 1 */
+	u32 fcfg2;	/* Flash Configuration Register 2 */
+	u32 uidh;	/* Unique Identification Register High */
+	u32 uidmh;	/* Unique Identification Register Mid-High */
+	u32 uidml;	/* Unique Identification Register Mid Low */
+	u32 uidl;	/* Unique Identification Register Low */
+	u32 clkdiv3;	/* System Clock Divider Register 3 */
+	u32 clkdiv4;	/* System Clock Divider Register 4 */
+	u32 mcr;	/* Misc Control Register */
+};
+#define KINETIS_SIM_PTR(base, reg) \
+	(&(((struct kinetis_sim_regs *)(base))->reg))
+
+/*
+ * System Clock Divider Register 1
+ */
+/* Clock 1 output divider value (for the core/system clock) */
+#define KINETIS_SIM_CLKDIV1_OUTDIV1_BITS	28
+#define KINETIS_SIM_CLKDIV1_OUTDIV1_MSK \
+	(((1 << 4) - 1) << KINETIS_SIM_CLKDIV1_OUTDIV1_BITS)
+/* Clock 2 output divider value (for the peripheral clock) */
+#define KINETIS_SIM_CLKDIV1_OUTDIV2_BITS	24
+#define KINETIS_SIM_CLKDIV1_OUTDIV2_MSK \
+	(((1 << 4) - 1) << KINETIS_SIM_CLKDIV1_OUTDIV2_BITS)
+
+/*
+ * System Clock Divider Register 2
+ */
+/* USB HS clock divider fraction */
+#define KINETIS_SIM_CLKDIV2_USBHSFRAC_BIT	8
+#define KINETIS_SIM_CLKDIV2_USBHSFRAC_MSK \
+	(1 << KINETIS_SIM_CLKDIV2_USBHSFRAC_BIT)
+/* USB HS clock divider divisor */
+#define KINETIS_SIM_CLKDIV2_USBHSDIV_BIT	9
+#define KINETIS_SIM_CLKDIV2_USBHSDIV_MSK \
+	(7 << KINETIS_SIM_CLKDIV2_USBHSDIV_BIT)
+
+/*
+ * MCG Control 5 Register
+ */
+/* PLL External Reference Divider */
+#define KINETIS_MCG_C5_PRDIV_BITS	0
+#define KINETIS_MCG_C5_PRDIV_MSK \
+	(((1 << 3) - 1) << KINETIS_MCG_C5_PRDIV_BITS)
+/* PLL Stop Enable */
+#define KINETIS_MCG_C5_PLLSTEN_MSK	(1 << 5)
+/* PLL Clock Enable */
+#define KINETIS_MCG_C5_PLLCLKEN_MSK	(1 << 6)
+/* PLL External Reference Select (for K70@120MHz) */
+#define KINETIS_MCG_C5_PLLREFSEL_BIT	7
+#define KINETIS_MCG_C5_PLLREFSEL_MSK	(1 << KINETIS_MCG_C5_PLLREFSEL_BIT)
+/*
+ * MCG Control 6 Register
+ */
+/* VCO Divider */
+#define KINETIS_MCG_C6_VDIV_BITS	0
+#define KINETIS_MCG_C6_VDIV_MSK \
+	(((1 << 5) - 1) << KINETIS_MCG_C6_VDIV_BITS)
+/* PLL Select */
+#define KINETIS_MCG_C6_PLLS_MSK		(1 << 6)
+/*
+ * MCG Control 11 Register
+ */
+/* PLL1 External Reference Divider */
+#define KINETIS_MCG_C11_PRDIV_BITS	0
+#define KINETIS_MCG_C11_PRDIV_MSK \
+	(((1 << 3) - 1) << KINETIS_MCG_C11_PRDIV_BITS)
+/* PLL Clock Select: PLL0 or PLL1 */
+#define KINETIS_MCG_C11_PLLCS_MSK	(1 << 4)
+/* PLL1 Stop Enable */
+#define KINETIS_MCG_C11_PLLSTEN1_MSK	(1 << 5)
+/* PLL1 Clock Enable */
+#define KINETIS_MCG_C11_PLLCLKEN1_MSK	(1 << 6)
+/* PLL1 External Reference Select (for K70@120MHz) */
+#define KINETIS_MCG_C11_PLLREFSEL1_BIT	7
+#define KINETIS_MCG_C11_PLLREFSEL1_MSK	(1 << KINETIS_MCG_C11_PLLREFSEL1_BIT)
+/*
+ * MCG Control 12 Register
+ */
+/* VCO1 Divider */
+#define KINETIS_MCG_C12_VDIV1_BITS	0
+#define KINETIS_MCG_C12_VDIV1_MSK \
+	(((1 << 5) - 1) << KINETIS_MCG_C12_VDIV1_BITS)
+
+/*
+ * Multipurpose Clock Generator (MCG) register map
+ *
+ * See Chapter 25 of the K70 Reference Manual
+ */
+struct kinetis_mcg_regs {
+	u8 c1;		/* MCG Control 1 Register */
+	u8 c2;		/* MCG Control 2 Register */
+	u8 c3;		/* MCG Control 3 Register */
+	u8 c4;		/* MCG Control 4 Register */
+	u8 c5;		/* MCG Control 5 Register */
+	u8 c6;		/* MCG Control 6 Register */
+	u8 status;	/* MCG Status Register */
+	u8 rsv0;
+	u8 atc;		/* MCG Auto Trim Control Register */
+	u8 rsv1;
+	u8 atcvh;	/* MCG Auto Trim Compare Value High Register */
+	u8 atcvl;	/* MCG Auto Trim Compare Value Low Register */
+	u8 c7;		/* MCG Control 7 Register */
+	u8 c8;		/* MCG Control 8 Register */
+	u8 rsv2;
+	u8 c10;		/* MCG Control 10 Register */
+	u8 c11;		/* MCG Control 11 Register */
+	u8 c12;		/* MCG Control 12 Register */
+	u8 status2;	/* MCG Status 2 Register */
+	u8 rsv3;
+};
+#define KINETIS_MCG_PTR(base, reg) \
+	(&(((struct kinetis_mcg_regs *)(base))->reg))
+
+struct kinetis_clk_gate {
+	const char *clk_gate_name;
+	struct clk *clk;
+	u32 reg;
+	u32 idx;
+	struct list_head clk_list;
+};
+
+struct kinetis_clk_gate_data {
+	void __iomem *sim;
+	struct list_head clk_gate_list;
+};
+
+struct kinetis_scgc {
+	unsigned long paddr;
+	spinlock_t lock;
+	struct list_head scgc_list;
+};
+
+static struct list_head kinetis_scgc_list = LIST_HEAD_INIT(kinetis_scgc_list);
+
+static const char *kinetis_of_clk_get_name(struct device_node *np)
+{
+	struct of_phandle_args clkspec;
+	int ret;
+	const char *retval;
+
+	ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells", 0,
+					&clkspec);
+	if (ret)
+		return NULL;
+
+	retval = clkspec.np->full_name;
+
+	of_node_put(clkspec.np);
+
+	return retval;
+}
+
+static struct clk *kinetis_find_clk_gate(
+		struct kinetis_clk_gate_data *clk_gate_data, u32 reg, u32 idx)
+{
+	struct kinetis_clk_gate *gate;
+
+	list_for_each_entry(gate, &clk_gate_data->clk_gate_list, clk_list)
+		if ((gate->reg == reg) && (gate->idx == idx))
+			return gate->clk;
+
+	return NULL;
+}
+
+static struct kinetis_scgc *kinetis_find_scgc(unsigned long paddr)
+{
+	struct kinetis_scgc *scgc;
+
+	list_for_each_entry(scgc, &kinetis_scgc_list, scgc_list)
+		if (scgc->paddr == paddr)
+			return scgc;
+
+	return NULL;
+}
+
+static struct clk *kinetis_clk_gate_get(struct of_phandle_args *clkspec,
+					void *data)
+{
+	const char *clk_name;
+	struct kinetis_clk_gate_data *clk_gate_data = data;
+	struct kinetis_clk_gate *gate;
+	u32 reg = clkspec->args[0];
+	u32 idx = clkspec->args[1];
+	struct clk *clk;
+	struct kinetis_scgc *scgc;
+	void __iomem *addr = KINETIS_SIM_PTR(clk_gate_data->sim, scgc[reg]);
+	unsigned long paddr = virt_to_phys(addr);
+
+	clk = kinetis_find_clk_gate(clk_gate_data, reg, idx);
+	if (clk)
+		return clk;
+
+	clk_name = kinetis_of_clk_get_name(clkspec->np);
+	BUG_ON(!clk_name);
+
+	scgc = kinetis_find_scgc(paddr);
+	if (!scgc) {
+		scgc = kzalloc(sizeof(struct kinetis_scgc), GFP_KERNEL);
+		if (!scgc)
+			return ERR_PTR(-ENOMEM);
+		scgc->paddr = paddr;
+		spin_lock_init(&scgc->lock);
+
+		list_add(&scgc->scgc_list, &kinetis_scgc_list);
+	}
+
+	gate = kzalloc(sizeof(struct kinetis_clk_gate), GFP_KERNEL);
+	if (!gate)
+		return ERR_PTR(-ENOMEM);
+
+	gate->clk_gate_name = kasprintf(GFP_KERNEL, "%s:%u:%u",
+				clkspec->np->full_name, reg, idx);
+	if (!gate->clk_gate_name) {
+		kfree(gate);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	clk = clk_register_gate(NULL, gate->clk_gate_name, clk_name, 0, addr,
+							idx, 0, &scgc->lock);
+	if (IS_ERR(clk)) {
+		pr_err("Cannot register gate to clock %s\n", clk_name);
+		kfree_const(gate->clk_gate_name);
+		kfree(gate);
+		return clk;
+	}
+
+	gate->clk = clk;
+	gate->reg = reg;
+	gate->idx = idx;
+
+	list_add(&gate->clk_list, &clk_gate_data->clk_gate_list);
+
+	return clk;
+}
+
+static void kinetis_of_register_clk_gate(struct device_node *np)
+{
+	const char *clk_parent_name = kinetis_of_clk_get_name(np);
+	struct kinetis_clk_gate_data *clk_gate;
+	void __iomem *sim;
+	int ret;
+
+	if (!clk_parent_name) {
+		pr_err("no clock specified for gate %s\n", np->full_name);
+		return;
+	}
+
+	sim = of_iomap(np, 0);
+	if (!sim) {
+		pr_err("failed to map SIM address range for %s\n",
+							np->full_name);
+		return;
+	}
+
+	clk_gate = kzalloc(sizeof(struct kinetis_clk_gate_data), GFP_KERNEL);
+	if (!clk_gate) {
+		iounmap(sim);
+		return;
+	}
+
+	clk_gate->sim = sim;
+	INIT_LIST_HEAD(&clk_gate->clk_gate_list);
+
+	ret = of_clk_add_provider(np, kinetis_clk_gate_get, clk_gate);
+	if (ret < 0) {
+		pr_err("Could not add clock provider %s\n", np->full_name);
+		kfree(clk_gate);
+		iounmap(sim);
+		return;
+	}
+}
+
+static void kinetis_of_register_fixed_rate(struct device_node *np, u32 clk_val)
+{
+	const char *clk_parent_name = kinetis_of_clk_get_name(np);
+	struct clk *clk;
+	int ret;
+
+	clk = clk_register_fixed_rate(NULL, np->full_name,
+					clk_parent_name,
+					clk_parent_name ? 0 : CLK_IS_ROOT,
+					clk_val);
+	if (IS_ERR(clk)) {
+		pr_err("Could not register clock %s\n", np->full_name);
+		return;
+	}
+
+	ret = of_clk_add_provider(np, of_clk_src_simple_get, clk);
+	if (ret < 0) {
+		pr_err("Could not add clock provider %s\n", np->full_name);
+		clk_unregister(clk);
+		return;
+	}
+}
+
+static void __init kinetis_mcg_init(struct device_node *np)
+{
+	const int vco_div = 2;
+	const int vdiv_min = 16;
+	u32 clock_val[] = { [0 ... KINETIS_CLK_NUM] = 0 };
+	void __iomem *base;
+	void __iomem *sim;
+	int pll_sel;
+	int osc_sel;
+	unsigned long mcgout;
+	enum kinetis_clk_ids clk_id;
+	u32 clock_cells;
+
+	if (of_property_read_u32_index(np, "#clock-cells", 0, &clock_cells)) {
+		pr_err("no #clock-cells set for %s\n", np->full_name);
+		return;
+	}
+
+	/*
+	 * Handle clock gates (recognized by two clock cells)
+	 */
+	if (2 == clock_cells) {
+		kinetis_of_register_clk_gate(np);
+		return;
+	}
+
+	base = of_iomap(np, 0);
+	if (!base) {
+		pr_err("failed to map MCG address range for %s\n",
+								np->full_name);
+		return;
+	}
+
+	sim = of_iomap(np, 1);
+	if (!sim) {
+		pr_err("failed to map SIM address range for %s\n",
+								np->full_name);
+		iounmap(base);
+		return;
+	}
+
+	/*
+	 * Check whether PLL0 or PLL1 is used for MCGOUTCLK
+	 */
+	pll_sel = !!(ioread8(KINETIS_MCG_PTR(base, c11)) &
+				KINETIS_MCG_C11_PLLCS_MSK);
+
+	/*
+	 * Check whether OSC0 or OSC1 is used to source the main PLL
+	 */
+	if (pll_sel)
+		osc_sel = !!(ioread8(KINETIS_MCG_PTR(base, c11)) &
+				KINETIS_MCG_C11_PLLREFSEL1_MSK);
+	else
+		osc_sel = !!(ioread8(KINETIS_MCG_PTR(base, c5)) &
+				KINETIS_MCG_C5_PLLREFSEL_MSK);
+
+	/*
+	 * Start with the MCG input clock
+	 */
+	mcgout = osc_sel ? KINETIS_OSC1_RATE : KINETIS_OSC0_RATE;
+
+	/*
+	 * Apply dividers and multipliers of the selected PLL
+	 */
+	if (pll_sel) {
+		/*
+		 * PLL1 internal divider (PRDIV)
+		 */
+		mcgout /= ((ioread8(KINETIS_MCG_PTR(base, c11)) &
+		  KINETIS_MCG_C11_PRDIV_MSK) >> KINETIS_MCG_C11_PRDIV_BITS) + 1;
+
+		/*
+		 * PLL1 multiplication factor (VDIV)
+		 */
+		mcgout *= ((ioread8(KINETIS_MCG_PTR(base, c12)) &
+		  KINETIS_MCG_C12_VDIV1_MSK) >> KINETIS_MCG_C12_VDIV1_BITS) +
+								    vdiv_min;
+	} else {
+		/*
+		 * PLL0 internal divider (PRDIV)
+		 */
+		mcgout /= ((ioread8(KINETIS_MCG_PTR(base, c5)) &
+			KINETIS_MCG_C5_PRDIV_MSK) >>
+			KINETIS_MCG_C5_PRDIV_BITS) + 1;
+
+		/*
+		 * PLL0 multiplication factor (VDIV)
+		 */
+		mcgout *= ((ioread8(KINETIS_MCG_PTR(base, c6)) &
+			KINETIS_MCG_C6_VDIV_MSK) >>
+			KINETIS_MCG_C6_VDIV_BITS) + vdiv_min;
+	}
+
+	/*
+	 * Apply the PLL output divider
+	 */
+	mcgout /= vco_div;
+
+	clock_val[KINETIS_CLK_MCGOUT] = mcgout;
+	clock_val[KINETIS_CLK_OSC0ER] = KINETIS_OSC0_RATE;
+
+	clock_val[KINETIS_CLK_CCLK] = mcgout /
+		(((ioread32(KINETIS_SIM_PTR(sim, clkdiv1)) &
+			KINETIS_SIM_CLKDIV1_OUTDIV1_MSK) >>
+				KINETIS_SIM_CLKDIV1_OUTDIV1_BITS) + 1);
+
+	/*
+	 * Peripheral (bus) clock
+	 */
+	clock_val[KINETIS_CLK_PCLK] = mcgout /
+		(((ioread32(KINETIS_SIM_PTR(sim, clkdiv1)) &
+			KINETIS_SIM_CLKDIV1_OUTDIV2_MSK) >>
+				KINETIS_SIM_CLKDIV1_OUTDIV2_BITS) + 1);
+
+	iounmap(sim);
+	iounmap(base);
+
+	for (clk_id = 0; clk_id < KINETIS_CLK_NUM; clk_id++)
+		if (!of_node_cmp(np->name, kinetis_clks[clk_id].name))
+			break;
+
+	if (KINETIS_CLK_NUM == clk_id) {
+		pr_err("unknown clock %s specified\n", np->name);
+		return;
+	}
+
+	if (!clock_val[clk_id]) {
+		pr_err("no clock rate for %s\n", np->name);
+		return;
+	}
+
+	kinetis_of_register_fixed_rate(np, clock_val[clk_id]);
+}
+
+CLK_OF_DECLARE(kinetis_mcg, "fsl,kinetis-cmu", kinetis_mcg_init);
-- 
2.3.6