All of lore.kernel.org
 help / color / mirror / Atom feed
From: York Sun <yorksun@freescale.com>
To: <mturquette@linaro.org>
Cc: <linux-kernel@vger.kernel.org>, <linux-i2c@vger.kernel.org>,
	York Sun <yorksun@freescale.com>,
	Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>,
	Guenter Roeck <linux@roeck-us.net>,
	Andrey Filippov <andrey@elphel.com>
Subject: [Patch v2] driver/clk/clk-si5338: Add common clock framework driver for si5338
Date: Mon, 15 Jun 2015 10:07:31 -0700	[thread overview]
Message-ID: <1434388051-23814-1-git-send-email-yorksun@freescale.com> (raw)

SI5338 is a programmable clock generator. It has 4 sets of inputs,
PLL, multisynth and dividers to make 4 outputs. This driver splits
them into multiple clocks to comply with common clock framework.

See Documentation/devicetree/bindings/clock/silabs,si5338.txt for
details.

Signed-off-by: York Sun <yorksun@freescale.com>
CC: Mike Turquette <mturquette@linaro.org>
CC: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
CC: Guenter Roeck <linux@roeck-us.net>
CC: Andrey Filippov <andrey@elphel.com>
---
Change log:
  v2: Fix handling name prefix if the driver is unloaded and loaded again

 .../devicetree/bindings/clock/silabs,si5338.txt    |  173 +
 drivers/clk/Kconfig                                |   14 +-
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5338.c                           | 3656 ++++++++++++++++++++
 drivers/clk/clk-si5338.h                           |  305 ++
 include/dt-bindings/clock/clk-si5338.h             |   69 +
 include/linux/platform_data/si5338.h               |   49 +
 7 files changed, 4266 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5338.txt
 create mode 100644 drivers/clk/clk-si5338.c
 create mode 100644 drivers/clk/clk-si5338.h
 create mode 100644 include/dt-bindings/clock/clk-si5338.h
 create mode 100644 include/linux/platform_data/si5338.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5338.txt b/Documentation/devicetree/bindings/clock/silabs,si5338.txt
new file mode 100644
index 0000000..aeeb81d
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt
@@ -0,0 +1,173 @@
+Binding for Silicon Labs Si5338 programmable i2c clock generator.
+
+Reference
+[1] Si5338 Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5338.pdf
+
+The Si5338 is a programmable i2c clock generators with up to 4 output
+clocks. It has 4 sets of possible input clocks
+
+IN1/IN2: differential
+IN3: single-ended
+IN4: single-ended
+IN5/IN6: differential
+
+Additionally, IN1/IN2 can be used as XTAL with different setting.
+The clock tree looks like below (without support of zero-delay)
+
+
+      IN1/IN2 IN3         IN4 IN5/IN6
+         |     |           |     |
+   ------|     |           |     |
+   |     |     |           |     |
+   |     \     /           \     /
+   |      \   /             \   /
+   |       \ /               \ /
+ XTAL     REFCLK            FBCLK
+   |       |  \             /   |
+   |       |   \           /    |
+   |       | DIVREFCLK DIVFBCLK |
+   |       |     \       /      |
+   |       |      \     /       |
+   |       |       \   /        |
+   |       |        PLL         |
+   |       |      / | | \       |
+   |       |     /  / \  \      |
+   |       |    /  /   \  \     |
+   |       |   /   |   |   \    |
+   |       |   |   |   |   |    |
+   |       |  MS0 MS1 MS2 MS3   |
+   |       |   |   |   |   |    |
+
+       OUT0  OUT1  OUT2  OUT3
+
+The output clock can choose from any of the above clock as its source, with
+exceptions: MS1 can only be used for OUT1, MS2 can only be used for OUT2, MS3
+can only be used for OUT3.
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be "silabs,si5338".
+- reg: i2c device address, shall be 0x60, 0x61, 0x70, or 0x71
+- #clock-cells: shall be set to 1 for multiple outputs
+- clocks: list of parent clocks in the order of <xtal>, <in1/2>, <in3>, <in4>, <in5/6>
+          Note, xtal and in1/2 are mutually exclusive. Only one can be set.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties if not set by platform driver:
+- silab,ref-source: source of refclk, valid value is defined as
+	#define SI5338_REF_SRC_CLKIN12          0
+	#define SI5338_REF_SRC_CLKIN3           1
+	#define SI5338_REF_SRC_XTAL             4
+- silab,fb-source:  source of fbclk, valid value is defined as
+	#define SI5338_FB_SRC_CLKIN4            2
+	#define SI5338_FB_SRC_CLKIN56           3
+	#define SI5338_FB_SRC_NOCLK             5
+- silabs,pll-source: source of pll, valid value is defined as
+	#define SI5338_PFD_IN_REF_REFCLK           0
+	#define SI5338_PFD_IN_REF_FBCLK            1
+	#define SI5338_PFD_IN_REF_DIVREFCLK        2
+	#define SI5338_PFD_IN_REF_DIVFBCLK         3
+	#define SI5338_PFD_IN_REF_XOCLK            4
+	#define SI5338_PFD_IN_REF_NOCLK            5
+- silabs,pll-master: Pick one MS (0, 1, 2, or 3) to allow chaning PLL rate
+	This is arbitrary since MS0/1/2/3 share one PLL.  PLL can be calculated
+	backward to satisfy MS.
+
+==Child nodes==
+
+Each of the clock outputs can be configured individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, platform driver has to set up.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,drive-config: the configuration of output driver
+  The valid value list is long. Please refer to soruce code.
+- silabs,clock-source: source clock of the output divider
+	#define SI5338_OUT_MUX_FBCLK            0
+	#define SI5338_OUT_MUX_REFCLK           1
+	#deinfe SI5338_OUT_MUX_DIVFBCLK         2
+	#deinfe SI5338_OUT_MUX_DIVREFCLK        3
+	#deinfe SI5338_OUT_MUX_XOCLK            4
+	#deinfe SI5338_OUT_MUX_MS0              5
+	#deinfe SI5338_OUT_MUX_MSN              6 /* MS0/1/2/3 */
+	#deinfe SI5338_OUT_MUX_NOCLK            7
+- silabs,disable-state : clock output disable state, shall be
+	#define SI5338_OUT_DIS_HIZ              0
+	#define SI5338_OUT_DIS_LOW              1
+	#define SI5338_OUT_DIS_HI               2
+	#define SI5338_OUT_DIS_ALWAYS_ON        3
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+clkin56: ref100M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <100000000>;
+};
+i2c-master-node {
+	si5338: clock-generator@70 {
+		compatible = "silabs,si5338";
+		reg = <0x70>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal to 25MHz, in5/in6 to 100MHz */
+		clocks = <&ref25>, <>, <>, <>, <&clkin56>;
+
+		/* connect xtal as source of refclk */
+		silab,ref-source = <SI5338_REF_SRC_XTAL>;
+
+		/* connect in5/in6 as source of fbclk */
+		silab,fb-source = <SI5338_FB_SRC_CLKIN56>;
+
+		/* connect divrefclk as soruce of pll */
+		silab,pll-source = <SI5338_PFD_IN_REF_DIVREFCLK>;
+
+		/* Choose one MS for pll master */
+		silabs,pll-master = <0>;
+
+		/* output */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MS0>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout1 {
+			reg = <1>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout2 {
+			reg = <2>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout3 {
+			reg = <3>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+
+	};
+};
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 0b474a0..07f0b2f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -10,7 +10,7 @@ config HAVE_MACH_CLKDEV
 	bool
 
 config COMMON_CLK
-	bool
+	tristate "Common Clock"
 	select HAVE_CLK_PREPARE
 	select CLKDEV_LOOKUP
 	select SRCU
@@ -78,6 +78,18 @@ config COMMON_CLK_SI570
 	  This driver supports Silicon Labs 570/571/598/599 programmable
 	  clock generators.
 
+config COMMON_CLK_SI5338
+	tristate "Clock driver for SiLabs 5338"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5338 programmable clock generators,
+	  using common clock framework. It needs parent clock as input(s).
+	  Internal clocks are registered with unique names in case multiple
+	  devices exist. See devicetree/bindings/clock/silabs,si5338.txt
+	  under Documentation for details.
+
 config COMMON_CLK_S2MPS11
 	tristate "Clock driver for S2MPS1X/S5M8767 MFD"
 	depends on MFD_SEC_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d478ceb..c6aab0e 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS)		+= clk-palmas.o
 obj-$(CONFIG_CLK_QORIQ)			+= clk-qoriq.o
 obj-$(CONFIG_COMMON_CLK_RK808)		+= clk-rk808.o
 obj-$(CONFIG_COMMON_CLK_S2MPS11)	+= clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SI5338)		+= clk-si5338.o
 obj-$(CONFIG_COMMON_CLK_SI5351)		+= clk-si5351.o
 obj-$(CONFIG_COMMON_CLK_SI570)		+= clk-si570.o
 obj-$(CONFIG_CLK_TWL6040)		+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5338.c b/drivers/clk/clk-si5338.c
new file mode 100644
index 0000000..fa50050
--- /dev/null
+++ b/drivers/clk/clk-si5338.c
@@ -0,0 +1,3656 @@
+/*
+ * clk-si5338.c: Silicon Labs Si5338 I2C Clock Generator
+ *
+ * Copyright 2015 Freescale Semiconductor
+ * York Sun <yorksun@freescale.com>
+ *
+ * Some code is taken from si5338.c by Andrey Filippov  <andrey@elphel.com>
+ * Copyright 2013 Elphel, Inc.
+ *
+ * SI5338 has several blocks, including
+ *   Inputs (IN1/IN2, IN3, IN4, IN5/IN6, XTAL)
+ *   PLL (Synthesis stage 1)
+ *   MultiSynth (Synthesis state 2)
+ *   Outputs (OUT0/1/2/3)
+ * Each block is registered as a clock device to form a tree structure.
+ * See Documentation/devicetree/bindings/clock/silabs,si5338.txt for details.
+ *
+ * This driver uses regmap to cache register values to reduce transactions
+ * on I2C bus. Volatile registers are specified.
+ *
+ * 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.
+ */
+
+#include <dt-bindings/clock/clk-si5338.h>
+#include <linux/bsearch.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/freezer.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5338.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include "clk-si5338.h"
+
+#define MAX_NAME_PREFIX 30 /* max 30 characters for the name_prefix */
+#define MAX_NAME_LENGTH 10 /* max 10 charactors for the internal names */
+
+struct si5338_driver_data;
+
+/* Internal parameters used by PLL and MS
+ * They are used in recalc rate functions before being
+ * written to the device.
+ */
+struct si5338_parameters {
+	u32	p123[3];
+	bool	valid;
+};
+
+/* This structure saves params and num variable for clocks
+ * Internal clocks with parameters of multiple input/output
+ * use this structure.
+ */
+struct si5338_hw_data {
+	struct clk_hw			hw;
+	struct si5338_driver_data	*drvdata;
+	/* params is only used for PLL and multisynth clocks */
+	struct si5338_parameters	params;
+	/*
+	 * For clkin, clkout, multisynth: index of itself
+	 * For refclk, fbclk, pll: index of its source
+	 */
+	u8				num;
+};
+
+struct si5338_driver_data {
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	/* The structure of clocks are
+	 * Input clocks: pclkin12 - IN1/2
+	 *		 pclkin3  - IN3
+	 *		 pclkin4  - IN4
+	 *		 pclkin56 - IN5/6
+	 *		 pxtal    - IN1/2 XTAL
+	 * Internal clocks:
+	 *		 xoclk		- from pxtal
+	 *		 refclk		- from one of IN1/2, IN3, XTAL
+	 *		 divrefclk	- from refclk with divider
+	 *		 fbclk		- from IN4 or IN5/6
+	 *		 divfbclk	- from fbclk
+	 *		 MS0/1/2/3	- from one of xoclk, refclk
+	 *				  diverefclk, fbclk, divfbclk
+	 * Output clocks:
+	 *		 clkout0/1/2/3	- from one of internal clocks
+	 */
+	/* parent clocks */
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk		*pclkin[4];
+	const char		*pclkin_name[4];
+
+	/* internal and output clocks */
+	char name_prefix[MAX_NAME_PREFIX];
+	struct clk_hw		xtal;
+	struct si5338_hw_data	clkin[4];
+	struct si5338_hw_data	refclk;
+	struct clk_hw		divrefclk;
+	struct si5338_hw_data	fbclk;
+	struct clk_hw		divfbclk;
+	struct si5338_hw_data	pll;
+	struct si5338_hw_data	*msynth;
+	struct si5338_hw_data	*clkout;
+};
+
+static const char * const si5338_input_names[] = {
+	"in1/in2", "in3", "in4", "in5/in6", "xtal", "noclk"
+};
+
+static const char * const si5338_pll_src_names[] = {
+	"refclk", "fbclk", "divrefclk", "divfbclk", "xtal", "noclk"
+};
+
+static const char * const si5338_msynth_src_names[] = {
+	"pll"
+};
+
+static const char * const si5338_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3"
+};
+static const char * const si5338_clkout_names[] = {
+	"clkout0", "clkout1", "clkout2", "clkout3"
+};
+static const char * const si5338_clkout_src_names[] = {
+	"fbclk", "refclk", "divfbclk", "divrefclk", "xtal",
+	"ms0",
+	"msn", /* it is actually ms0, ms1, ms2, ms3 dependings on clkout */
+	"noclk",
+};
+
+/* This array is used to determine if a register is writable. It is also
+ * used to create register dump from sysfs. The mask is not used in this
+ * driver. The data is in format of 0xAAAMM where AAA is address, MM is bit
+ * mask. 1 means the corresponding bit is writable.
+ * Created from SiLabs ClockBuilder output.
+ * Note: Register 226, 230, 241, 246, 255 are not included in header file
+ *	 from ClockBuilder v2.7 or later. Manually added here.
+ */
+static const u32 register_masks[] = {
+	0x61d, 0x1b80, 0x1cff, 0x1dff, 0x1eff, 0x1fff, 0x20ff, 0x21ff,
+	0x22ff, 0x23ff, 0x241f, 0x251f, 0x261f, 0x271f, 0x28ff, 0x297f,
+	0x2a3f, 0x2dff, 0x2eff, 0x2f3f, 0x30ff, 0x31ff, 0x32ff, 0x33ff,
+	0x34ff, 0x35ff, 0x36ff, 0x37ff, 0x38ff, 0x39ff, 0x3aff, 0x3bff,
+	0x3cff, 0x3dff, 0x3e3f, 0x3fff, 0x40ff, 0x41ff, 0x42ff, 0x43ff,
+	0x44ff, 0x45ff, 0x46ff, 0x47ff, 0x48ff, 0x493f, 0x4aff, 0x4bff,
+	0x4cff, 0x4dff, 0x4eff, 0x4fff, 0x50ff, 0x51ff, 0x52ff, 0x53ff,
+	0x543f, 0x55ff, 0x56ff, 0x57ff, 0x58ff, 0x59ff, 0x5aff, 0x5bff,
+	0x5cff, 0x5dff, 0x5eff, 0x5f3f, 0x61ff, 0x62ff, 0x63ff, 0x64ff,
+	0x65ff, 0x66ff, 0x67ff, 0x68ff, 0x69ff, 0x6abf, 0x6bff, 0x6cff,
+	0x6dff, 0x6eff, 0x6fff, 0x70ff, 0x71ff, 0x72ff, 0x73ff, 0x74ff,
+	0x75ff, 0x76ff, 0x77ff, 0x78ff, 0x79ff, 0x7aff, 0x7bff, 0x7cff,
+	0x7dff, 0x7eff, 0x7fff, 0x80ff, 0x810f, 0x820f, 0x83ff, 0x84ff,
+	0x85ff, 0x86ff, 0x87ff, 0x88ff, 0x89ff, 0x8aff, 0x8bff, 0x8cff,
+	0x8dff, 0x8eff, 0x8fff, 0x90ff, 0x98ff, 0x99ff, 0x9aff, 0x9bff,
+	0x9cff, 0x9dff, 0x9e0f, 0x9f0f, 0xa0ff, 0xa1ff, 0xa2ff, 0xa3ff,
+	0xa4ff, 0xa5ff, 0xa6ff, 0xa7ff, 0xa8ff, 0xa9ff, 0xaaff, 0xabff,
+	0xacff, 0xadff, 0xaeff, 0xafff, 0xb0ff, 0xb1ff, 0xb2ff, 0xb3ff,
+	0xb4ff, 0xb50f, 0xb6ff, 0xb7ff, 0xb8ff, 0xb9ff, 0xbaff, 0xbbff,
+	0xbcff, 0xbdff, 0xbeff, 0xbfff, 0xc0ff, 0xc1ff, 0xc2ff, 0xc3ff,
+	0xc4ff, 0xc5ff, 0xc6ff, 0xc7ff, 0xc8ff, 0xc9ff, 0xcaff, 0xcb0f,
+	0xccff, 0xcdff, 0xceff, 0xcfff, 0xd0ff, 0xd1ff, 0xd2ff, 0xd3ff,
+	0xd4ff, 0xd5ff, 0xd6ff, 0xd7ff, 0xd8ff, 0xd9ff, 0xe204, 0xe6ff,
+	0xf1ff, 0xf202, 0xf6ff, 0xffff, 0x11fff,
+	0x120ff, 0x121ff, 0x122ff, 0x123ff, 0x124ff, 0x125ff, 0x126ff, 0x127ff,
+	0x128ff, 0x129ff, 0x12aff, 0x12b0f, 0x12fff, 0x130ff, 0x131ff, 0x132ff,
+	0x133ff, 0x134ff, 0x135ff, 0x136ff, 0x137ff, 0x138ff, 0x139ff, 0x13aff,
+	0x13b0f, 0x13fff, 0x140ff, 0x141ff, 0x142ff, 0x143ff, 0x144ff, 0x145ff,
+	0x146ff, 0x147ff, 0x148ff, 0x149ff, 0x14aff, 0x14b0f, 0x14fff, 0x150ff,
+	0x151ff, 0x152ff, 0x153ff, 0x154ff, 0x155ff, 0x156ff, 0x157ff, 0x158ff,
+	0x159ff, 0x15aff, 0x15b0f
+};
+
+/*
+ * Si5338 i2c regmap
+ */
+static inline u8 si5338_reg_read(struct si5338_driver_data *drvdata, u16 reg)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg %02x: return %d\n", reg, ret);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5338_reg_write(struct si5338_driver_data *drvdata,
+				   u16 reg, u8 val, u8 mask)
+{
+	if (mask != 0xff)
+		return regmap_update_bits(drvdata->regmap, reg, mask, val);
+
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static int write_field(struct si5338_driver_data *drvdata, u8 data, u32 awe)
+{
+	int rc, nshift;
+	u8 mask, reg_data;
+	u16 reg;
+
+	if (!drvdata) {
+		pr_err("Invalid drvdata\n");
+		return -EINVAL;
+	}
+	reg = awe >> 8;
+	mask = awe & 0xff;
+	if (mask != 0) {
+		nshift = 0;
+		while (((1 << nshift) & mask) == 0)
+			nshift++;
+		reg_data = (data & 0xff) << nshift;
+		rc = si5338_reg_write(drvdata, reg, reg_data, mask);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int write_multireg64(struct si5338_driver_data *drvdata,
+				u64 data, const u32 *awe)
+{
+	int i, rc, nshift, nbits;
+	u8 mask, reg_data;
+	u16 reg;
+
+	for (i = 0; awe[i] != 0; i++) {
+		reg = awe[i] >> 8;
+		mask = awe[i] & 0xff;
+		if (mask != 0) {
+			nshift = 0;
+			nbits = 1;
+			while (((1 << nshift) & mask) == 0)
+				nshift++;
+			while (((1 << (nshift + nbits)) & mask) != 0)
+				nbits++;
+			/* may have some garbage in high bits,
+			 * will be cut of by mask
+			 */
+			reg_data = (data & 0xff) << nshift;
+			data >>= nbits;
+			rc = si5338_reg_write(drvdata, reg, reg_data, mask);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+static s64 read_multireg64(struct si5338_driver_data *drvdata, const u32 *awe)
+{
+	int i, nshift, nbits, full_shift = 0;
+	u8 mask;
+	u16 reg;
+	s64 data = 0, rc;
+
+	for (i = 0; awe[i] != 0; i++) {
+		reg = awe[i] >> 8;
+		mask = awe[i] & 0xff;
+		if (mask != 0) {
+			nshift = 0;
+			nbits = 1;
+			while (((1 << nshift) & mask) == 0)
+				nshift++;
+			while (((1 << (nshift + nbits)) & mask) != 0)
+				nbits++;
+			rc = si5338_reg_read(drvdata, reg);
+			if (rc < 0)
+				return rc;
+
+			rc &= mask;
+			rc >>= nshift;
+			rc <<= full_shift;
+			data |= rc;
+			full_shift += nbits;
+		}
+	}
+
+	return data;
+}
+
+static int read_field(struct si5338_driver_data *drvdata, u32 awe)
+{
+	int rc, nshift;
+	u8 mask;
+	u16 reg;
+
+	reg = awe >> 8;
+	mask = awe & 0xff;
+
+	if (mask != 0) {
+		nshift = 0;
+		while (((1 << nshift) & mask) == 0)
+			nshift++;
+		rc = si5338_reg_read(drvdata, reg);
+		if (rc < 0)
+			return rc;
+
+		return (rc & mask) >> nshift;
+	}
+
+	return 0;
+}
+
+static int si5338_find_mask(const u32 *reg, const u32 *register_mask)
+{
+	if ((*reg) > ((*register_mask) >> 8))
+		return 1;
+	if ((*reg) < ((*register_mask) >> 8))
+		return -1;
+
+	return 0;
+}
+
+static int find_mask(const void *key, const void *elt)
+{
+	return si5338_find_mask(key, elt);
+}
+
+static bool si5338_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	u32 *mask = NULL;
+
+	mask = bsearch(&reg, register_masks, ARRAY_SIZE(register_masks),
+		       sizeof(u32), find_mask);
+
+	if (mask)
+		return true;
+
+	return false;
+}
+
+static bool si5338_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case (AWE_STATUS >> 8):
+	case (AWE_SOFT_RESET >> 8):
+	case (AWE_FCAL_07_00 >> 8):
+	case (AWE_FCAL_15_08 >> 8):
+	case (AWE_FCAL_17_16 >> 8):
+		return true;
+	}
+	return false;
+}
+static const struct regmap_range_cfg si5338_regmap_range[] = {
+	{
+		.selector_reg = REG5338_PAGE,		/* 255 */
+		.selector_mask  = REG5338_PAGE_MASK,	/* 1 */
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 256,
+		.range_min = 0,
+		.range_max = 347,
+	},
+};
+
+static struct regmap_config si5338_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 347,
+	.ranges = si5338_regmap_range,
+	.num_ranges = ARRAY_SIZE(si5338_regmap_range),
+	.writeable_reg = si5338_regmap_is_writeable,
+	.volatile_reg = si5338_regmap_is_volatile,
+};
+
+/*
+ * SI5338 register access
+ */
+static int _verify_output_channel(int chn)
+{
+	if (chn < 0 || chn > 3) {
+		pr_err("Invalid output channel: %d (only 0..3 are allowed)\n",
+			chn);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int get_fb_mux(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_FB_MUX);
+}
+
+static int get_in_mux(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_IN_MUX);
+}
+
+static int set_in_mux(struct si5338_driver_data *drvdata, int data)
+{
+	int data1, rc;
+
+	switch (data) {
+	case 0:
+		data1 = 0;
+		break;
+	case 1:
+		data1 = 2;
+		break;
+	case 2:
+		data1 = 5;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid value for input multiplexer %d\n", data);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, data, AWE_IN_MUX);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, data1, AWE_IN_MUX1);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int set_fb_mux(struct si5338_driver_data *drvdata, int data)
+{
+	int data1, rc;
+
+	switch (data) {
+	case 0:
+		data1 = 0;
+		break;
+	case 1:
+		data1 = 1;
+		break;
+	case 2:
+		data1 = 0;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid value for feedback multiplexer %d\n", data);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, data, AWE_FB_MUX);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, data1, AWE_FB_MUX1);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * PLL has two inputs, each has multiple sources
+ * 0 - pfd_in_ref
+ * 1 - pfd_in_fb
+ */
+static int get_in_pfd_ref_fb(struct si5338_driver_data *drvdata, int chn)
+{
+	return read_field(drvdata, chn ? AWE_PFD_FB : AWE_PFD_REF);
+}
+
+static int set_in_pfd_ref_fb(struct si5338_driver_data *drvdata,
+				u8 val, int chn)
+{
+	int rc;
+
+	if (val > SI5338_PFD_IN_REF_NOCLK) {
+		dev_err(&drvdata->client->dev,
+			"Invalid value for input pfd selector: %d\n", val);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, val, chn ? AWE_PFD_FB : AWE_PFD_REF);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+
+static const u8 in_div_values[] = { 1, 2, 4, 8, 16, 32 };
+
+/*
+ * Set div for the two dividers
+ * 0 - p1div
+ * 1 - p2div
+ */
+static int set_in_pdiv(struct si5338_driver_data *drvdata, int div, int chn)
+{
+	int rc;
+	u8 val;
+
+	for (val = 0; val < ARRAY_SIZE(in_div_values); val++) {
+		if (in_div_values[val] == div) {
+			rc = write_field(drvdata, val,
+					 chn ? AWE_P2DIV : AWE_P1DIV);
+			if (rc < 0)
+				return rc;
+
+			return 0;
+		}
+	}
+	dev_err(&drvdata->client->dev,
+		"Invalid value for input divider: %d\n", div);
+
+	return -EINVAL;
+}
+
+/*
+ * Si5338 xtal clock input
+ * The clock needs to be within [8MHz .. 30MHz]
+ */
+static int si5338_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, xtal);
+	unsigned long rate = clk_get_rate(hw->clk);
+	int xtal_mode;
+
+	if (rate  < XTAL_FREQMIN) {
+		dev_err(&drvdata->client->dev,
+			"Xtal input frequency too low: %lu < %llu\n",
+			rate, XTAL_FREQMIN);
+		return -EINVAL;
+	}
+	if (rate > XTAL_FREQMAX) {
+		dev_err(&drvdata->client->dev,
+			"Xtal input frequency too high: %lu > %llu\n",
+			rate, XTAL_FREQMAX);
+		return -EINVAL;
+	}
+
+	if (rate > 26000000ll)
+		xtal_mode = 3;
+	else if (rate > 19000000ll)
+		xtal_mode = 2;
+	else if (rate > 11000000ll)
+		xtal_mode = 1;
+	else
+		xtal_mode = 0;
+
+	return write_field(drvdata, xtal_mode, AWE_XTAL_FREQ);
+}
+
+static const struct clk_ops si5338_xtal_ops = {
+	.prepare = si5338_xtal_prepare,
+};
+
+static unsigned long si5338_clkin_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long max_rate;
+
+	max_rate = (hwdata->num == SI5338_INPUT_CLK12 ||
+		    hwdata->num == SI5338_INPUT_CLK56) ?
+			INFREQMAX : INFREQMAX34;
+	if (parent_rate  < INFREQMIN) {
+		dev_err(&drvdata->client->dev,
+			"Input frequency too low: %lu < %llu\n",
+			parent_rate, INFREQMIN);
+		return -EINVAL;
+	}
+	if (parent_rate > max_rate) {
+		dev_err(&drvdata->client->dev,
+			"Input frequency too high: %lu > %lu\n",
+			parent_rate, max_rate);
+		return -EINVAL;
+	}
+
+	return parent_rate;
+}
+
+static const struct clk_ops si5338_clkin_ops = {
+	.recalc_rate = si5338_clkin_recalc_rate,
+};
+
+/*
+ * Si5338 refclk inputs
+ * Input frequency range
+ *	IN1/IN2 differential clock [5MHz..710MHz]
+ *	IN3 single-ended clock [5MHz..200MHz]
+ * Enforced by si5338_clkin_recalc_rate
+ */
+static int si5338_refclk_reparent(struct si5338_driver_data *drvdata, u8 index)
+{
+	int rc = -EINVAL;
+	struct si5338_hw_data *hwdata = &drvdata->refclk;
+
+	hwdata->num = SI5338_FB_SRC_NOCLK;
+	switch (index) {
+	case SI5338_REF_SRC_XTAL:
+		/* in mux to XO */
+		rc = set_in_mux(drvdata, 2);
+		hwdata->num = 2;
+		break;
+	case SI5338_REF_SRC_CLKIN12:
+		/* in mux to IN12 */
+		rc = set_in_mux(drvdata, 0);
+		hwdata->num = 0;
+		break;
+	case SI5338_REF_SRC_CLKIN3:
+		rc = set_in_mux(drvdata, 1);
+		hwdata->num = 1;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent (%d) for refclk\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * refclk's parent
+ * 0 - IN1/IN2
+ * 1 - IN3
+ * 2 - XTAL
+ */
+static int si5338_refclk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc = -EINVAL;
+
+	switch (index) {
+	case 0:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN12);
+		break;
+	case 1:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN3);
+		break;
+	case 2:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_XTAL);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalie parent index for refclk: %d\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+static u8 si5338_refclk_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	/* get_in_mux return value is aligned with the parent index */
+	return (u8)get_in_mux(drvdata);
+}
+
+static const struct clk_ops si5338_refclk_ops = {
+	.set_parent = si5338_refclk_set_parent,
+	.get_parent = si5338_refclk_get_parent,
+};
+
+/*
+ * divrefclk's parent is refclk
+ */
+unsigned long si5338_divrefclk_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, divrefclk);
+	int idiv, rc;
+
+	for (idiv = 0; idiv < 5; idiv++) {
+		if ((parent_rate >> idiv) <= INFREQDIV)
+			break;
+	}
+	rc = set_in_pdiv(drvdata, 1 << idiv, 0);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Error setting p1div\n");
+		return 0;
+	}
+
+	return parent_rate >> idiv;
+}
+
+static const struct clk_ops si5338_divrefclk_ops = {
+	.recalc_rate = si5338_divrefclk_recalc_rate,
+};
+
+/*
+ * Si5338 fbclk inputs
+ * Input frequency range
+ *	IN4 single-ended clock [5MHz..200MHz]
+ *	IN5/IN6 differential clock [5MHz..710MHz]
+ * Enforced by si5338_clkin_recalc_rate
+ */
+static int si5338_fbclk_reparent(struct si5338_driver_data *drvdata, u8 index)
+{
+	struct si5338_hw_data *hwdata = &drvdata->fbclk;
+	int rc = -EINVAL;
+
+	hwdata->num = SI5338_FB_SRC_NOCLK;
+	switch (index) {
+	case SI5338_FB_SRC_CLKIN4:
+		/* in mux to IN4 */
+		rc = set_fb_mux(drvdata, 1);
+		hwdata->num = 0;
+		break;
+	case SI5338_FB_SRC_CLKIN56:
+		/* in mux to IN56 */
+		rc = set_fb_mux(drvdata, 0);
+		hwdata->num = 1;
+		break;
+	case SI5338_FB_SRC_NOCLK:
+		rc = set_fb_mux(drvdata, 2);
+		hwdata->num = 2;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent (%d) for fbclk\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * fbclk's parent can be
+ * 0 - IN4
+ * 1 - IN5/IN6
+ * 2 - NOCLK
+ */
+static int si5338_fbclk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc = -EINVAL;
+
+	switch (index) {
+	case 0:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN4);
+		break;
+	case 1:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN56);
+		break;
+	case 2:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_NOCLK);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent index for fbclk\n");
+	}
+
+	return rc;
+}
+
+static u8 si5338_fbclk_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+
+	/* Return value 0: IN5/IN6
+	 *		1: IN4
+	 *		2: noclk
+	 */
+	rc = get_fb_mux(drvdata);
+	switch (rc) {
+	case 0:
+		return 1;
+	case 1:
+		return 0;
+	case 2:
+		return 2;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+static const struct clk_ops si5338_fbclk_ops = {
+	.set_parent = si5338_fbclk_set_parent,
+	.get_parent = si5338_fbclk_get_parent,
+};
+
+/*
+ * divfbclk's parent is fbclk
+ */
+unsigned long si5338_divfbclk_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, divfbclk);
+	int idiv, rc;
+
+	for (idiv = 0; idiv < 5; idiv++) {
+		if ((parent_rate >> idiv) <= INFREQDIV)
+			break;
+	}
+	rc = set_in_pdiv(drvdata, 1 << idiv, 1);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Error setting p1div\n");
+		return 0;
+	}
+
+	return parent_rate >> idiv;
+}
+
+static const struct clk_ops si5338_divfbclk_ops = {
+	.recalc_rate = si5338_divfbclk_recalc_rate,
+};
+
+/*
+ * PLL and MultiSynth
+ */
+static int remove_common_factor(u64 *num_denom)
+{
+	u64 a, b, r;
+
+	if (num_denom[1] == 0)
+		return -1; /* zero denominator */
+
+	if (num_denom[0] == 0) {
+		num_denom[1] = 1;
+		return 1;
+	}
+
+	a = max(num_denom[0], num_denom[1]);
+	b = min(num_denom[0], num_denom[1]);
+	r = b;
+	while (r > 1) {
+		r = a - b * div64_u64(a, b);
+		if (r == 0) {
+			num_denom[0] = div64_u64(num_denom[0], b);
+			num_denom[1] = div64_u64(num_denom[1], b);
+			return 1;
+		}
+		a = b;
+		b = r;
+	}
+
+	return 0; /* nothing done */
+}
+
+static const u32 awe_msx[5][3][5] = {
+	{
+		{
+			AWE_MS0_P1_07_00,
+			AWE_MS0_P1_15_08,
+			AWE_MS0_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS0_P2_05_00,
+			AWE_MS0_P2_13_06,
+			AWE_MS0_P2_21_14,
+			AWE_MS0_P2_29_22,
+			0
+		},
+		{
+			AWE_MS0_P3_07_00,
+			AWE_MS0_P3_15_08,
+			AWE_MS0_P3_23_16,
+			AWE_MS0_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS1_P1_07_00,
+			AWE_MS1_P1_15_08,
+			AWE_MS1_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS1_P2_05_00,
+			AWE_MS1_P2_13_06,
+			AWE_MS1_P2_21_14,
+			AWE_MS1_P2_29_22,
+			0
+		},
+		{
+			AWE_MS1_P3_07_00,
+			AWE_MS1_P3_15_08,
+			AWE_MS1_P3_23_16,
+			AWE_MS1_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS2_P1_07_00,
+			AWE_MS2_P1_15_08,
+			AWE_MS2_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS2_P2_05_00,
+			AWE_MS2_P2_13_06,
+			AWE_MS2_P2_21_14,
+			AWE_MS2_P2_29_22,
+			0
+		},
+		{
+			AWE_MS2_P3_07_00,
+			AWE_MS2_P3_15_08,
+			AWE_MS2_P3_23_16,
+			AWE_MS2_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS3_P1_07_00,
+			AWE_MS3_P1_15_08,
+			AWE_MS3_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS3_P2_05_00,
+			AWE_MS3_P2_13_06,
+			AWE_MS3_P2_21_14,
+			AWE_MS3_P2_29_22,
+			0
+		},
+		{
+			AWE_MS3_P3_07_00,
+			AWE_MS3_P3_15_08,
+			AWE_MS3_P3_23_16,
+			AWE_MS3_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MSN_P1_07_00,
+			AWE_MSN_P1_15_08,
+			AWE_MSN_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MSN_P2_05_00,
+			AWE_MSN_P2_13_06,
+			AWE_MSN_P2_21_14,
+			AWE_MSN_P2_29_22,
+			0
+		},
+		{
+			AWE_MSN_P3_07_00,
+			AWE_MSN_P3_15_08,
+			AWE_MSN_P3_23_16,
+			AWE_MSN_P3_29_24,
+			0
+		}
+	}
+};
+
+/*
+ * Read parameters of
+ * 0 - MS0
+ * 1 - MS1
+ * 2 - MS2
+ * 3 - MS3
+ * 4 - MSN (PLL)
+ */
+static int get_ms_p123(struct si5338_driver_data *drvdata, u32 *p123, int chn)
+{
+	int i;
+	s64 rc;
+
+	if (chn < 0 || chn > 4) {
+		dev_err(&drvdata->client->dev,
+			"Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",
+			chn);
+		return -EINVAL;
+	}
+	for (i = 0; i < 3; i++) {
+		rc = read_multireg64(drvdata, awe_msx[chn][i]);
+		if (rc < 0)
+			return (int)rc;
+
+		p123[i] = (u32)rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Calculte MS ratio from parameters
+ * ms = a + b / c, where
+ *	a = ms[0], b = ms[1], c = ms[2]
+ * SI5338 RM states the formula of parameters as:
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	p2 = mod((b * 128), c)
+ *	p3 = c
+ * To reverse the formula, we have
+ *	b * 128 = k * c + p2; k < 128, p2 < c
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	   = a * 128 + floor((b * 128) / c) - 512
+ *	   = a * 128 + k - 512
+ *	k = mod(p1, 128) = p1 & 0x7f
+ *	c = p3
+ *	b = (k * c + p2) / 128 = ((p1 & 0x7f) * p3 + p2) >> 7
+ *	a = (p1 + 512) >> 7 = (p1 >> 7) + 4
+ */
+static int p123_to_ms(u64 *ms, u32 *p123)
+{
+	if (p123[0] == 0 && p123[1] == 0 && p123[2] == 0) {
+		/* uninitialized parameters in device */
+		ms[0] = 0;
+		ms[1] = 0;
+		ms[2] = 1;
+	} else {
+		/* c = p3 */
+		ms[2] = p123[2];
+		/* b = (c * (p1 & 0x7f) + p2) >> 7 */
+		ms[1] = (ms[2] * (p123[0] & 0x7f) + p123[1]) >> 7;
+		/* a = (p1 >> 7) + 4 */
+		ms[0] = (p123[0] >> 7) + 4;
+	}
+	pr_debug("ms[]=%llu + %llu/%llu, p123=%u %u %u\n",
+		 ms[0], ms[1], ms[2], p123[0], p123[1], p123[2]);
+
+	return 0;
+}
+
+static const u32 awe_ms_hs[] = {
+	AWE_MS0_HS,
+	AWE_MS1_HS,
+	AWE_MS2_HS,
+	AWE_MS3_HS
+};
+
+/*
+ * Read parameters of
+ * 0 - MS0
+ * 1 - MS1
+ * 2 - MS2
+ * 3 - MS3
+ * 4 - MSN (PLL)
+ */
+static int set_ms_p123(struct si5338_driver_data *drvdata,
+			u32 *p123, int chn)
+{
+	int i, rc, hs = 0;
+
+	if (chn < 0 || chn > 4) {
+		dev_err(&drvdata->client->dev,
+			"Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",
+			chn);
+		return -EINVAL;
+	}
+	/* high speed bit programming */
+	if (p123[0] < 512) { /* div less than 8 */
+		if (p123[0] < 128)
+			p123[0] = 0;
+		else
+			p123[0] = 256;
+		p123[1] = 0;
+		p123[2] = 1;
+		hs = 1;
+		dev_info(&drvdata->client->dev,
+			 "Using high speed divider option on ms%d",
+			 chn);
+	}
+
+	rc = write_field(drvdata, hs, awe_ms_hs[chn]);
+	if (rc < 0)
+		return rc;
+
+	for (i = 0; i < 3; i++) {
+		rc = write_multireg64(drvdata, (u64)p123[i],
+				      awe_msx[chn][i]);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Calculate parameters
+ * ms = a + b / c, where
+ *	a = ms[0], b=ms[1], c = ms[2]\
+ * SI5338 RM stats the fomula of parameters as
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	p2 = mod((b * 128), c)
+ *	p3 = c
+ */
+static int ms_to_p123(u64 *ms, u32 *p123)
+{
+	u64 d;
+	u64 ms_denom = ms[2], ms_num = ms[1], ms_int = ms[0];
+
+	while (ms_denom >= (1 << 30) || ((ms_denom | ms_num) & 1) == 0) {
+		ms_denom >>= 1;
+		ms_num >>= 1;
+	}
+	if (ms_num == 0 || ms_denom == 0) {
+		ms_denom = 1;
+		ms_num = 0;
+	}
+	d = (ms_int * ms_denom + ms_num) << 7;
+	p123[0] = (u32)(div64_u64(d, ms_denom) - 512);
+	d = div64_u64((ms_num << 7), ms_denom);
+	p123[1] = (u32)((ms_num << 7) - d * ms_denom);
+	p123[2] = ms_denom;
+	pr_debug("ms[]=%llu + %llu/%llu Hz, ms_int=%llu, ms_num=%llu, ms_denom=%llu p123=%u %u %u\n",
+		 ms[0], ms[1], ms[2], ms_int, ms_num, ms_denom,
+		 p123[0], p123[1], p123[2]);
+
+	return 0;
+}
+
+/*
+ * Calculate MultiSynth divider (MS0..MS3) for specified output frequency
+ */
+static void cal_ms_p123(unsigned long numerator,
+			unsigned long denominator,
+			u32 *p123)
+{
+	u64 ms[3];
+
+	ms[1] = numerator;
+	ms[2] = denominator;
+	ms[0] = div64_u64(ms[1], ms[2]);
+	ms[1] -= ms[0] * ms[2];
+	while (ms[2] >= (1 << 30)) { /* trim */
+		ms[2] >>= 1;
+		ms[1] >>= 1;
+	}
+	remove_common_factor(&ms[1]);
+
+	if (ms[0] < MSINT_MIN) {
+		pr_err("Calculated MSN ratio is too low: %llu < %u\n",
+			ms[0], MSINT_MIN);
+		ms[0] = MSINT_MIN;
+	} else if (ms[0] == 5 || ms[0] == 7) {
+		pr_err("MSN ratio %llu is invalid\n", ms[0]);
+		ms[0] += 1;
+	} else if (ms[0] > MSINT_MAX) {
+		pr_err("Calculated MSN ratio is too high: %llu > %u\n",
+			ms[0], MSINT_MAX);
+		ms[0] = MSINT_MAX;
+	}
+	pr_debug("MS divider: %llu+%llu/%llu\n", ms[0], ms[1], ms[2]);
+
+	ms_to_p123(ms, p123);
+}
+
+/*
+ * Si5338 pll section
+ */
+static int si5338_pll_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	s64 pll_in_freq;
+	s64 K, Q, kphi_num, kphi_denom, fvco_mhz, fpfd_mhz;
+	int rsel, bwsel, vco_gain, pll_kphi, mscal, ms_pec;
+
+	pll_in_freq = clk_get_rate(clk_get_parent(hw->clk));
+	if (pll_in_freq <= 0) {
+		dev_err(&drvdata->client->dev, "Invalid input clock for pll\n");
+		return -EINVAL;
+	}
+
+	fvco_mhz = div64_u64(clk_get_rate(hw->clk), 1000000ll);
+	fpfd_mhz = div64_u64(pll_in_freq, 1000000ll);
+	if (fpfd_mhz >= 15) {
+		K = 925;
+		rsel = 0;
+		bwsel = 0;
+	} else if (fpfd_mhz >= 8) {
+		K = 325;
+		rsel = 1;
+		bwsel = 1;
+	} else {
+		K = 185;
+		rsel = 3;
+		bwsel = 2;
+	}
+	if (fvco_mhz > 2425) {
+		Q = 3;
+		vco_gain = 0;
+	} else {
+		Q = 4;
+		vco_gain = 1;
+	}
+	kphi_num = K * 2500LL * 2500LL * 2500LL;
+	kphi_denom = 533LL * Q * fpfd_mhz * fvco_mhz * fvco_mhz;
+	pll_kphi = (int)div64_u64(kphi_num + (kphi_denom >> 1), kphi_denom);
+	if (pll_kphi < 1 || pll_kphi > 127) {
+		dev_err(&drvdata->client->dev,
+			"Calculated PLL_KPHI does not fit 1<=%d<=127\n",
+			pll_kphi);
+		if (pll_kphi < 1)
+			pll_kphi = 1;
+		else if (pll_kphi > 127)
+			pll_kphi = 127;
+	}
+	mscal = (int)div64_u64(2067000 - 667 * fvco_mhz + 50000, 100000ll);
+	if (mscal < 0 || mscal > 63) {
+		dev_err(&drvdata->client->dev,
+			"Calculated MSCAL does not fit 0<=%d<=63\n",
+			mscal);
+		if (mscal < 0)
+			mscal = 0;
+		else if (mscal > 63)
+			mscal = 63;
+	}
+	ms_pec = 7;
+	dev_dbg(&drvdata->client->dev,
+		"Calculated values: PLL_KPHI=%d K=%lld RSEL=%d BWSEL=%d VCO_GAIN=%d MSCAL=%d MS_PEC=%d\n",
+		pll_kphi, K, rsel, bwsel, vco_gain, mscal, ms_pec);
+
+	/* setting actual registers */
+	rc = write_field(drvdata, (u8)pll_kphi, AWE_PLL_KPHI);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)(((vco_gain & 7) << 4) |
+				      ((rsel & 3) << 2) | (bwsel & 3)),
+			 AWE_VCO_GAIN_RSEL_BWSEL);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)mscal, AWE_MSCAL);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)ms_pec, AWE_MS_PEC);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 3, AWE_PLL_EN);
+	if (rc < 0)
+		return rc; /* enable PLL */
+
+	return 0;
+}
+
+static int si5338_pll_reparent(struct si5338_driver_data *drvdata,
+				u8 index)
+{
+	struct si5338_hw_data *hwdata = &drvdata->pll;
+	int rc = -EINVAL;
+
+	hwdata->num = SI5338_PFD_IN_REF_NOCLK;
+	switch (index) {
+	case SI5338_PFD_IN_REF_REFCLK:
+	case SI5338_PFD_IN_REF_FBCLK:
+	case SI5338_PFD_IN_REF_DIVREFCLK:
+	case SI5338_PFD_IN_REF_DIVFBCLK:
+	case SI5338_PFD_IN_REF_XOCLK:
+	case SI5338_PFD_IN_REF_NOCLK:
+		/* pfd_in_ref mux */
+		rc = set_in_pfd_ref_fb(drvdata, index, 0);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid pfd_in_ref mux selection %d\n",
+			index);
+		break;
+	}
+
+	if (!rc)
+		hwdata->num = index;	/* record the source of pll */
+
+	return rc;
+}
+
+static unsigned char si5338_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int pfd_in_ref;
+
+	/* Get pfd_in_ref mux value */
+	pfd_in_ref = get_in_pfd_ref_fb(drvdata, 0);
+
+	hwdata->num = pfd_in_ref;
+
+	return pfd_in_ref;
+}
+
+static int si5338_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+
+	if (index > ARRAY_SIZE(si5338_pll_src_names))
+		return -EINVAL;
+
+	return si5338_pll_reparent(hwdata->drvdata, index);
+}
+
+static unsigned long si5338_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	u64 rate[3], ms[3], ms_scaled;
+
+	if (!hwdata->params.valid) {
+		rc = get_ms_p123(drvdata, hwdata->params.p123, 4);
+		if (rc < 0)
+			return 0;
+		hwdata->params.valid = true;
+	}
+
+	p123_to_ms(ms, hwdata->params.p123);
+	if (unlikely(ms[2] == 0)) {
+		/* This should not happen */
+		dev_err(&drvdata->client->dev,
+			"Error %s calculating pll\n", __func__);
+		ms[2] = 1;
+	}
+	ms_scaled = ms[0] * ms[2] + ms[1];
+	if (ms_scaled == 0)	/* uninitialzied */
+		return 0;
+
+	rate[2] = ms[2];
+	rate[1] = parent_rate * ms_scaled;
+	rate[0] = div64_u64(rate[1], rate[2]);
+	rate[1] -= rate[0] * rate[2];
+	remove_common_factor(&rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"PLL output frequency: %llu+%llu/%llu Hz\n",
+		rate[0], rate[1], rate[2]);
+
+	return (unsigned long)rate[0];
+}
+
+static long si5338_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	u64 ms[] = {0, 0, 1};
+	u64 new_rate[3], ms_scaled;
+
+	if (unlikely(rate < FVCOMIN))
+		rate = FVCOMIN;
+	else if (unlikely(rate > FVCOMAX))
+		rate = FVCOMAX;
+
+	cal_ms_p123(rate, *parent_rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	p123_to_ms(ms, hwdata->params.p123);
+	ms_scaled = ms[0] * ms[2] + ms[1];
+
+	new_rate[2] = ms[2];
+	new_rate[1] = *parent_rate * ms_scaled;
+	new_rate[0] = div64_u64(new_rate[1], new_rate[2]);
+	new_rate[1] -= new_rate[0] * new_rate[2];
+	remove_common_factor(&new_rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"PLL output frequency: %llu+%llu/%llu Hz\n",
+		new_rate[0], new_rate[1], new_rate[2]);
+
+	return (unsigned long)new_rate[0];
+}
+
+static int si5338_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	if (unlikely(rate < FVCOMIN))
+		rate = FVCOMIN;
+	else if (unlikely(rate > FVCOMAX))
+		rate = FVCOMAX;
+	cal_ms_p123(rate, parent_rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return set_ms_p123(drvdata, hwdata->params.p123, 4);
+}
+
+static const struct clk_ops si5338_pll_ops = {
+	.prepare = si5338_pll_prepare,
+	.set_parent = si5338_pll_set_parent,
+	.get_parent = si5338_pll_get_parent,
+	.recalc_rate = si5338_pll_recalc_rate,
+	.round_rate = si5338_pll_round_rate,
+	.set_rate = si5338_pll_set_rate,
+};
+
+/*
+ * Si5338 multisynth divider
+ */
+
+static const u32 awe_ms_powerdown[] = {
+	AWE_MS0_PDN,
+	AWE_MS1_PDN,
+	AWE_MS2_PDN,
+	AWE_MS3_PDN
+};
+
+static int set_ms_powerdown(struct si5338_driver_data *drvdata,
+			     int down, int chn)
+{
+	if (chn < 0 || chn > 3)
+		return -EINVAL;
+
+	if (down)
+		down = 1;
+
+	return write_field(drvdata, (u8)down, awe_ms_powerdown[chn]);
+}
+
+static int get_ms_powerdown(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_ms_powerdown[chn]);
+}
+
+static int si5338_msynth_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return set_ms_powerdown(drvdata, 0, hwdata->num);	/* power up */
+}
+
+static void si5338_msynth_unprepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	set_ms_powerdown(drvdata, 1, hwdata->num);	/* power down */
+}
+
+static unsigned long si5338_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	u64 rate[3], ms[3], ms_scaled;
+
+	if (!hwdata->params.valid) {
+		rc = get_ms_p123(drvdata, hwdata->params.p123, hwdata->num);
+		if (rc < 0)
+			return 0;
+		hwdata->params.valid = true;
+	}
+
+	p123_to_ms(ms, hwdata->params.p123);
+	if (unlikely(ms[2] == 0)) {
+		/* This should not happen */
+		dev_err(&drvdata->client->dev,
+			"Error %s calculating MS%d\n", __func__, hwdata->num);
+		ms[2] = 1;
+	}
+	/* trim MS divider fraction */
+	while (ms[2] >= 0x1000) {
+		ms[1] >>= 1;
+		ms[2] >>= 1;
+	}
+	ms_scaled = ms[0] * ms[2] + ms[1];
+	if (ms_scaled == 0)	/* uninitialized */
+		return 0;
+
+	rate[2] = ms_scaled;
+	rate[1] = parent_rate * ms[2];
+	rate[0] = div64_u64(rate[1], rate[2]);
+	rate[1] -= rate[0] * rate[2];
+	remove_common_factor(&rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"MS%d output frequency: %llu+%llu/%llu Hz\n",
+		hwdata->num, rate[0], rate[1], rate[2]);
+
+	return (unsigned long)rate[0];
+}
+
+/*
+ * Based on PLL input clock, estimate best ratio for desired output
+ * if pll vco is not specified.
+ */
+static long si5338_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	s64 rate_scaled, pll_in_freq;
+	s64 center, center_diff, best_center_diff = 0;
+	s64 out_div, best_out_div = 1;
+	s64 d, in_div, best_in_div;
+	s64 err, best_err = 0;
+	s64 synth_out;
+	u64 ms[3];
+
+	if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) {
+		/* Get rate of the parent of PLL
+		 *(could be refclk, fbclk, etc.)
+		 */
+		pll_in_freq =
+			clk_get_rate(clk_get_parent(clk_get_parent(hw->clk)));
+
+		center = (FVCOMAX + FVCOMIN) >> 1;
+
+		best_in_div = 0;
+		for (out_div = 4; out_div <= MSINT_MAX; out_div++) {
+			if (out_div == 5 || out_div == 7)
+				continue;
+
+			/* here scaled by denominator */
+			rate_scaled = rate * out_div;
+			if (rate_scaled < FVCOMIN || rate_scaled > FVCOMAX)
+				continue;
+
+			in_div = div64_u64(rate_scaled +
+					   (pll_in_freq >> 1),
+					   pll_in_freq); /* round */
+
+			/* actual pll frequency scaled by out_denom */
+			d = pll_in_freq * in_div;
+			synth_out = div64_u64(d + (out_div >> 1), out_div);
+			center_diff = d - center;
+			if (center_diff < 0)
+				center_diff = -center_diff;
+			err = synth_out - rate;
+			if (err < 0)
+				err = -err;
+			if (best_in_div == 0 ||
+			    err < best_err ||
+			    (err == best_err &&
+			     center_diff < best_center_diff)) {
+				dev_dbg(&drvdata->client->dev,
+					"synth_out: %lld center: %lld rate:%lu err: %lld (%lld) center_diff:%lld(%lld)\n",
+					synth_out, center, rate, err, best_err,
+					center_diff, best_center_diff);
+				best_err = err;
+				best_in_div = in_div;
+				best_out_div = out_div;
+				best_center_diff = center_diff;
+			}
+		}
+		if (best_in_div == 0) {
+			dev_err(&drvdata->client->dev,
+				"Failed to find suitable integer coefficients for pll input %lld Hz\n",
+				pll_in_freq);
+		}
+		*parent_rate = pll_in_freq * best_in_div;
+		rate = *parent_rate / (unsigned long)best_out_div;
+		dev_dbg(&drvdata->client->dev,
+			"Best MS output frequency: %lu Hz, MS input divider: %lld, MS output divider: %lld\n",
+			rate, best_in_div, best_out_div);
+	} else {
+		ms[1] = *parent_rate;
+		ms[2] = rate;
+		ms[0] = div64_u64(ms[1], ms[2]);
+		ms[1] -= ms[0] * ms[2];
+		remove_common_factor(&ms[1]);
+		if (ms[0]  == 5 || ms[0] == 7)
+			out_div++;
+		rate = div64_u64(*parent_rate * ms[2], ms[1] + ms[0] * ms[2]);
+		dev_dbg(&drvdata->client->dev,
+			"Cloest MS output frequency: %lu Hz, output divider %llu+%llu/%llu\n",
+			rate, ms[0], ms[1], ms[2]);
+	}
+
+	cal_ms_p123(*parent_rate, rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return rate;
+}
+
+/*
+ * multisynth's parent is PLL
+ */
+static int si5338_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	cal_ms_p123(parent_rate, rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return set_ms_p123(drvdata, hwdata->params.p123, hwdata->num);
+}
+
+static const struct clk_ops si5338_msynth_ops = {
+	.prepare = si5338_msynth_prepare,
+	.unprepare = si5338_msynth_unprepare,
+	.recalc_rate = si5338_msynth_recalc_rate,
+	.round_rate = si5338_msynth_round_rate,
+	.set_rate = si5338_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout
+ */
+
+static const u32 awe_out_disable[] = {
+	AWE_OUT0_DIS,
+	AWE_OUT1_DIS,
+	AWE_OUT2_DIS,
+	AWE_OUT3_DIS,
+	AWE_OUT_ALL_DIS
+};
+
+static int set_out_disable(struct si5338_driver_data *drvdata,
+			   int dis, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (dis)
+		dis = 1;
+
+	return write_field(drvdata, (u8)dis, awe_out_disable[chn]);
+}
+
+static int get_out_disable(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (chn != 4 && rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_out_disable[chn]);
+}
+
+static const u32 awe_drv_dis_state[] = {
+	AWE_OUT0_DIS_STATE,
+	AWE_OUT1_DIS_STATE,
+	AWE_OUT2_DIS_STATE,
+	AWE_OUT3_DIS_STATE
+};
+
+static int si5338_clkout_set_disable_state(struct si5338_driver_data *drvdata,
+					   int chn, int typ)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 3) {
+		dev_err(&drvdata->client->dev,
+			"Invalid disabled state %d. Only 0..3 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_dis_state[chn]);
+}
+
+static int get_drv_disabled_state(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_dis_state[chn]);
+}
+
+static const u32 awe_rdiv_in[] = {
+	AWE_R0DIV_IN,
+	AWE_R1DIV_IN,
+	AWE_R2DIV_IN,
+	AWE_R3DIV_IN
+};
+
+/*
+ * src	0: fbclk
+ *	1: refclk
+ *	2: divfbclk
+ *	3: divrefclk
+ *	4: xoclk
+ *	5: MS0
+ *	6: MS1/2/3 respetivelly
+ */
+static int set_out_mux(struct si5338_driver_data *drvdata, int chn, int src)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (src < 0 || src > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid source %d. Only 0...7 are supported\n",
+			src);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)src, awe_rdiv_in[chn]);
+}
+
+static int get_out_mux(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_rdiv_in[chn]);
+}
+
+static const u32 awe_drv_fmt[] = {
+	AWE_DRV0_FMT,
+	AWE_DRV1_FMT,
+	AWE_DRV2_FMT,
+	AWE_DRV3_FMT
+};
+
+static int set_drv_type(struct si5338_driver_data *drvdata, int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..7 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_fmt[chn]);
+}
+
+static int get_drv_type(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_fmt[chn]);
+}
+
+static const u32 awe_drv_vddo[] = {
+	AWE_DRV0_VDDO,
+	AWE_DRV1_VDDO,
+	AWE_DRV2_VDDO,
+	AWE_DRV3_VDDO
+};
+
+static int set_drv_vdd(struct si5338_driver_data *drvdata, int vdd, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (vdd < 0 || vdd > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..3 are supported\n",
+			vdd);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)vdd, awe_drv_vddo[chn]);
+}
+
+static int get_drv_vdd(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_vddo[chn]);
+}
+
+static const u32 awe_drv_trim[][3] = {
+	{ AWE_DRV0_TRIM, 0, 0 },
+	{ AWE_DRV1_TRIM_A, AWE_DRV1_TRIM_B, 0},
+	{ AWE_DRV2_TRIM, 0, 0},
+	{ AWE_DRV3_TRIM, 0, 0}
+};
+
+static int set_drv_trim_any(struct si5338_driver_data *drvdata,
+			    int trim, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (trim < 0 || trim > 31) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..31 are supported\n",
+			trim);
+		return -EINVAL;
+	}
+
+	return write_multireg64(drvdata, trim, awe_drv_trim[chn]);
+}
+
+static int get_drv_trim(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return (int)read_multireg64(drvdata, awe_drv_trim[chn]);
+}
+
+static const u32 awe_drv_invert[] = {
+	AWE_DRV0_INV,
+	AWE_DRV1_INV,
+	AWE_DRV2_INV,
+	AWE_DRV3_INV
+};
+
+static int set_drv_invert(struct si5338_driver_data *drvdata, int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 3) {
+		dev_err(&drvdata->client->dev,
+			"Invalid invert drivers %d. Only 0..3 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_invert[chn]);
+}
+
+static int get_drv_invert(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_invert[chn]);
+}
+
+static const u32 awe_drv_powerdown[] = {
+	AWE_DRV0_PDN,
+	AWE_DRV1_PDN,
+	AWE_DRV2_PDN,
+	AWE_DRV3_PDN
+};
+
+static int get_drv_powerdown(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_powerdown[chn]);
+}
+
+static int set_drv_powerdown(struct si5338_driver_data *drvdata,
+			     int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ)
+		typ = 1;
+
+	return write_field(drvdata, (u8)typ, awe_drv_powerdown[chn]);
+}
+
+static const struct si5338_drv_t const drv_configs[] = {
+	{"3V3_CMOS_A+",	0x1, 0x0, 0x17, 0x8}, /* bX0 */
+	{"3V3_CMOS_A-",	0x1, 0x0, 0x17, 0x9}, /* bX1 */
+	{"3V3_CMOS_B+",	0x2, 0x0, 0x17, 0x4}, /* b0X */
+	{"3V3_CMOS_B-",	0x2, 0x0, 0x17, 0x6}, /* b1X */
+	{"3V3_CMOS_A+B+", 0x3, 0x0, 0x17, 0x8},
+	{"3V3_CMOS_A-B+", 0x3, 0x0, 0x17, 0x9},
+	{"3V3_CMOS_A+B-", 0x3, 0x0, 0x17, 0x4},
+	{"3V3_CMOS_A-B-", 0x3, 0x0, 0x17, 0x6},
+
+	{"2V5_CMOS_A+",	0x1, 0x1, 0x13, 0x8},
+	{"2V5_CMOS_A-",	0x1, 0x1, 0x13, 0x9},
+	{"2V5_CMOS_B+",	0x2, 0x1, 0x13, 0x4},
+	{"2V5_CMOS_B-",	0x2, 0x1, 0x13, 0x6},
+	{"2V5_CMOS_A+B+", 0x3, 0x1, 0x13, 0x8},
+	{"2V5_CMOS_A-B+", 0x3, 0x1, 0x13, 0x9},
+	{"2V5_CMOS_A+B-", 0x3, 0x1, 0x13, 0x4},
+	{"2V5_CMOS_A-B-", 0x3, 0x1, 0x13, 0x6},
+
+	{"1V8_CMOS_A+",	0x1, 0x2, 0x15, 0x8},
+	{"1V8_CMOS_A-",	0x1, 0x2, 0x15, 0x9},
+	{"1V8_CMOS_B+",	0x2, 0x2, 0x15, 0x4},
+	{"1V8_CMOS_B-",	0x2, 0x2, 0x15, 0x6},
+	{"1V8_CMOS_A+B+", 0x3, 0x2, 0x15, 0x8},
+	{"1V8_CMOS_A-B+", 0x3, 0x2, 0x15, 0x9},
+	{"1V8_CMOS_A+B-", 0x3, 0x2, 0x15, 0x4},
+	{"1V8_CMOS_A-B-", 0x3, 0x2, 0x15, 0x6},
+
+	{"1V5_HSTL_A+",	0x1, 0x3, 0x1f, 0x8},
+	{"1V5_HSTL_A-",	0x1, 0x3, 0x1f, 0x9},
+	{"1V5_HSTL_B+",	0x2, 0x3, 0x1f, 0x4},
+	{"1V5_HSTL_B-",	0x2, 0x3, 0x1f, 0x6},
+	{"1V5_HSTL_A+B+", 0x3, 0x3, 0x1f, 0x8},
+	{"1V5_HSTL_A-B+", 0x3, 0x3, 0x1f, 0x9},
+	{"1V5_HSTL_A+B-", 0x3, 0x3, 0x1f, 0x4},
+	{"1V5_HSTL_A-B-", 0x3, 0x3, 0x1f, 0x6},
+
+	{"3V3_SSTL_A+",	0x1, 0x0, 0x04, 0x8},
+	{"3V3_SSTL_A-",	0x1, 0x0, 0x04, 0x9},
+	{"3V3_SSTL_B+",	0x2, 0x0, 0x04, 0x4},
+	{"3V3_SSTL_B-",	0x2, 0x0, 0x04, 0x6},
+	{"3V3_SSTL_A+B+", 0x3, 0x0, 0x04, 0x8},
+	{"3V3_SSTL_A-B+", 0x3, 0x0, 0x04, 0x9},
+	{"3V3_SSTL_A+B-", 0x3, 0x0, 0x04, 0x5},
+	{"3V3_SSTL_A-B-", 0x3, 0x0, 0x04, 0x6},
+
+	{"2V5_SSTL_A+",	0x1, 0x1, 0x0d, 0x8},
+	{"2V5_SSTL_A-",	0x1, 0x1, 0x0d, 0x9},
+	{"2V5_SSTL_B+",	0x2, 0x1, 0x0d, 0x4},
+	{"2V5_SSTL_B-",	0x2, 0x1, 0x0d, 0x6},
+	{"2V5_SSTL_A+B+", 0x3, 0x1, 0x0d, 0x8},
+	{"2V5_SSTL_A-B+", 0x3, 0x1, 0x0d, 0x9},
+	{"2V5_SSTL_A+B-", 0x3, 0x1, 0x0d, 0x5},
+	{"2V5_SSTL_A-B-", 0x3, 0x1, 0x0d, 0x6},
+
+	{"1V8_SSTL_A+",	0x1, 0x2, 0x17, 0x8},
+	{"1V8_SSTL_A-",	0x1, 0x2, 0x17, 0x9},
+	{"1V8_SSTL_B+",	0x2, 0x2, 0x17, 0x4},
+	{"1V8_SSTL_B-",	0x2, 0x2, 0x17, 0x6},
+	{"1V8_SSTL_A+B+", 0x3, 0x2, 0x17, 0x8},
+	{"1V8_SSTL_A-B+", 0x3, 0x2, 0x17, 0x9},
+	{"1V8_SSTL_A+B-", 0x3, 0x2, 0x17, 0x4},
+	{"1V8_SSTL_A-B-", 0x3, 0x2, 0x17, 0x6},
+
+	{"3V3_LVPECL",	0x4, 0x0, 0x0f, 0xc},
+	{"2V5_LVPECL",	0x4, 0x1, 0x10, 0xc},
+	{"3V3_LVDS",	0x6, 0x0, 0x03, 0xc},
+	{"2V5_LVDS",	0x6, 0x1, 0x04, 0xc},
+	{"1V8_LVDS",	0x6, 0x2, 0x04, 0xc},
+
+	{NULL,		0x0, 0x0, 0x0, 0x0},
+};
+
+static int find_drive_config(const char *name)
+{
+	int i;
+
+	for (i = 0; drv_configs[i].description; i++) {
+		if (strcmp(name, drv_configs[i].description) == 0)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int si5338_clkout_set_drive_config(struct si5338_driver_data *drvdata,
+				   int chn, const char *name)
+{
+	int i, rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	i = find_drive_config(name);
+	if (i < 0) {
+		dev_err(&drvdata->client->dev,
+			"Invalid driver configuration\n");
+		return -EINVAL;
+	}
+
+	rc = set_drv_type(drvdata, drv_configs[i].fmt, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_vdd(drvdata, drv_configs[i].vdd, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_trim_any(drvdata, drv_configs[i].trim, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_invert(drvdata,
+			    drv_configs[i].invert & 3, chn);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static const u32 awe_rdiv_k[] = {
+	AWE_R0DIV,
+	AWE_R1DIV,
+	AWE_R2DIV,
+	AWE_R3DIV
+};
+
+static const u8 out_div_values[] = {
+	1, 2, 4, 8, 16, 32
+};
+
+static int get_out_div(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	rc = read_field(drvdata, awe_rdiv_k[chn]);
+	if (rc < 0)
+		return rc;
+
+	if (rc >= ARRAY_SIZE(out_div_values)) {
+		dev_err(&drvdata->client->dev,
+			"Invalid value for output divider: %d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	return out_div_values[rc];
+}
+
+static int set_out_div(struct si5338_driver_data *drvdata, int div, int chn)
+{
+	int rc;
+	u8 val;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	for (val = 0; val < ARRAY_SIZE(out_div_values); val++) {
+		if (out_div_values[val] == div) {
+			rc = write_field(drvdata, val, awe_rdiv_k[chn]);
+			if (rc < 0)
+				return rc;
+
+			return 0;
+		}
+	}
+	dev_err(&drvdata->client->dev,
+		"Invalid value for output divider: %d\n",
+		div);
+
+	return -EINVAL;
+}
+
+static int get_status(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_STATUS);
+}
+
+static int power_up_down_needed_ms(struct si5338_driver_data *drvdata)
+{
+	int rc, chn, out_src;
+	int ms_used = 0;
+
+	for (chn = 0; chn < 4; chn++) {
+		out_src = get_out_mux(drvdata, chn);
+		if (out_src < 0)
+			return out_src;
+
+		switch (out_src) {
+		case 5:
+			ms_used |= 1;
+			break;
+		case 6:
+			ms_used |= (1 << chn);
+			break;
+		}
+	}
+	for (chn = 0; chn < 4; chn++) {
+		rc = set_ms_powerdown(drvdata,
+				      (ms_used & (1 << chn)) ? 0 : 1, chn);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static u32 awe_fcal[] = {
+	AWE_FCAL_07_00,
+	AWE_FCAL_15_08,
+	AWE_FCAL_17_16,
+	0
+};
+
+static u32 awe_fcal_ovrd[] = {
+	AWE_FCAL_OVRD_07_00,
+	AWE_FCAL_OVRD_15_08,
+	AWE_FCAL_OVRD_17_15,
+	0
+};
+
+static int reset_ms(struct si5338_driver_data *drvdata)
+{
+	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wait_q);
+	int rc;
+
+	dev_dbg(&drvdata->client->dev, "Resetting MS dividers");
+	/* SET MS RESET = 1 */
+	rc = write_field(drvdata, 1, AWE_MS_RESET);
+	if (rc < 0)
+		return rc;
+
+	/* Wait for 10ms */
+	wait_event_freezable_timeout(wait_q, false, msecs_to_jiffies(10));
+
+	/* SET MS RESET = 0 */
+	rc = write_field(drvdata, 0, AWE_MS_RESET);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int set_misc_registers(struct si5338_driver_data *drvdata)
+{
+	/* ST52238 Reference Manual R1.2 p.28 */
+	int rc;
+
+	rc = write_field(drvdata, 0x5, AWE_MISC_47);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_106);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_116);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_42);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_06A);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_06B);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_28);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* Disable interrupt, all outputs */
+static int pre_init(struct si5338_driver_data *drvdata)
+{
+	int rc, chn;
+
+	/* Disable interrupts */
+	rc = write_field(drvdata, 0x1d, AWE_INT_MASK);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Failed to disable interrupt\n");
+		return rc;
+	}
+
+	/* setup miscelalneous registers */
+	rc = set_misc_registers(drvdata);
+	if (rc < 0)
+		return rc;
+
+	/* disable all outputs */
+	rc = write_field(drvdata, 1, AWE_OUT_ALL_DIS);
+	if (rc < 0)
+		return rc;
+
+	/* pause LOL */
+	rc = write_field(drvdata, 1, AWE_DIS_LOS);
+	if (rc < 0)
+		return rc;
+
+	/* clears outputs pll input/fb muxes to be set later */
+	for (chn = 0; chn < 4; chn++) {
+		rc = set_ms_powerdown(drvdata, 1, chn);
+		if (rc < 0)
+			return rc;
+		rc = set_out_disable(drvdata, 1, chn);
+		if (rc < 0)
+			return rc;
+	}
+	/* to be explicitly enabled if needed */
+	rc = set_in_pfd_ref_fb(drvdata, 5, 0);	/* noclk */
+	if (rc < 0)
+		return rc;
+
+	rc = set_in_pfd_ref_fb(drvdata, 5, 1);	/* noclk */
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* See SI5338 RM for programming procedure */
+static int post_init(struct si5338_driver_data *drvdata)
+{
+	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wait_q);
+	int rc = 0, i, in_src, fb_src, ext_fb, check_los = 0;
+	int timeout = INIT_TIMEOUT;
+	s64 fcal;
+
+	/* validate input clock status */
+	in_src = get_in_pfd_ref_fb(drvdata, 0);
+	if (in_src < 0)
+		return in_src;
+
+	switch (in_src) {
+	case SI5338_PFD_IN_REF_REFCLK:
+	case SI5338_PFD_IN_REF_DIVREFCLK:
+	case SI5338_PFD_IN_REF_XOCLK:
+		check_los |= AWE_STATUS_PLL_LOS_CLKIN;
+		break;
+	case SI5338_PFD_IN_REF_FBCLK:
+	case SI5338_PFD_IN_REF_DIVFBCLK:
+		check_los |= AWE_STATUS_PLL_LOS_FDBK;
+		break;
+	}
+	ext_fb = read_field(drvdata, AWE_PFD_EXTFB);
+	if (ext_fb < 0)
+		return ext_fb;
+
+	if (ext_fb) {
+		fb_src = get_in_pfd_ref_fb(drvdata, 1);
+		if (fb_src < 0)
+			return fb_src;
+
+		switch (in_src) {
+		case SI5338_PFD_IN_FB_REFCLK:
+		case SI5338_PFD_IN_FB_DIVREFCLK:
+			check_los |= AWE_STATUS_PLL_LOS_CLKIN;
+			break;
+		case SI5338_PFD_IN_FB_FBCLK:
+		case SI5338_PFD_IN_FB_DIVFBCLK:
+			check_los |= AWE_STATUS_PLL_LOS_FDBK;
+			break;
+		}
+	}
+	check_los &= 0xf;
+	for (i = 0; i < timeout; i++) {
+		rc = get_status(drvdata);
+		if (rc < 0)
+			return rc;
+
+		if ((rc & check_los) == 0)
+			break; /* inputs OK */
+	}
+	if (i >= timeout) {
+		dev_err(&drvdata->client->dev,
+			"Timeout waiting for input clocks, status=0x%x, mask=0x%x\n",
+			rc, check_los);
+		return -EPIPE;
+	}
+	dev_dbg(&drvdata->client->dev,
+		"Validated input clocks, t=%d cycles (timeout= %d cycles), status =0x%x, mask=0x%x\n",
+		i, timeout, rc, check_los);
+
+	/* Configure PLL for locking, set FCAL_OVRD_EN = 0 */
+	rc = write_field(drvdata, 0, AWE_FCAL_OVRD_EN);
+	if (rc < 0)
+		return rc;
+
+	/* Configure PLL for locking, set SOFT_RESET = 1 (ignore i2c error) */
+	write_field(drvdata, 1, AWE_SOFT_RESET);
+	wait_event_freezable_timeout(wait_q, false, msecs_to_jiffies(25));
+
+	/* re-enable LOL, set reg 241 = 0x65 */
+	rc = write_field(drvdata, 0x65, AWE_REG241);
+	if (rc < 0)
+		return rc;
+
+	check_los |= AWE_STATUS_PLL_LOL | AWE_STATUS_PLL_SYS_CAL;
+	check_los &= 0xf;
+	for (i = 0; i < timeout; i++) {
+		rc = get_status(drvdata);
+		if (rc < 0)
+			return rc;
+
+		if ((rc & check_los) == 0)
+			break; /* alarms not set OK */
+	}
+	if (i >= timeout) {
+		dev_err(&drvdata->client->dev,
+			"Timeout (%d) waiting for PLL lock, status=0x%x, mask=0x%x\n",
+			i, rc, check_los);
+		return -EPIPE;
+	}
+	dev_dbg(&drvdata->client->dev,
+		"Validated PLL locked, t=%d cycles (timeout= %d cycles), status =0x%x, mask=0x%x\n",
+		i, timeout, rc, check_los);
+
+	/* copy FCAL values to active registers */
+	fcal = read_multireg64(drvdata, awe_fcal);
+	if (fcal < 0)
+		return (int)fcal;
+
+	rc = write_multireg64(drvdata, fcal, awe_fcal_ovrd);
+	if (rc < 0)
+		return rc;
+
+	dev_dbg(&drvdata->client->dev, "Copied FCAL data 0x%llx\n", fcal);
+	/* Set 47[7:2] to 000101b */
+	rc = write_field(drvdata, 5, AWE_REG47_72);
+	if (rc < 0)
+		return rc;
+
+	/* SET PLL to use FCAL values, set FCAL_OVRD_EN=1 */
+	rc = write_field(drvdata, 1, AWE_FCAL_OVRD_EN);
+	if (rc < 0)
+		return rc;
+
+	/* only needed if using down-spread. Won't hurt to do anyway */
+	rc = reset_ms(drvdata);
+	if (rc < 0)
+		return rc;
+
+	/* Enable all (enabled individually) outputs */
+	rc = write_field(drvdata, 0, AWE_OUT_ALL_DIS);
+	if (rc < 0)
+		return rc;
+
+	/* Clearing */
+	write_field(drvdata, 0, AWE_SOFT_RESET);
+
+	/* fixme: This is not needed */
+	rc = power_up_down_needed_ms(drvdata);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int si5338_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+
+	rc = set_drv_powerdown(drvdata, 0, hwdata->num);
+	if (rc) {
+		dev_err(&drvdata->client->dev,
+			"Error power up clkout%d\n", hwdata->num);
+		return rc;
+	}
+	rc = set_out_disable(drvdata, 0, hwdata->num);	/* enable */
+	if (rc) {
+		dev_err(&drvdata->client->dev,
+			"Error enabling clkout%d\n", hwdata->num);
+	}
+	dev_dbg(&drvdata->client->dev, "Clkout%d prepared\n", hwdata->num);
+
+	return rc;
+}
+
+static void si5338_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	set_out_disable(drvdata, 1, hwdata->num);	/* disable */
+}
+
+static int si5338_clkout_reparent(struct si5338_driver_data *drvdata,
+				   int num, u8 parent)
+{
+	return set_out_mux(drvdata, num, parent);
+}
+
+static u8 si5338_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return (u8)get_out_mux(drvdata, hwdata->num);
+}
+
+static int si5338_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return si5338_clkout_reparent(drvdata, hwdata->num, index);
+}
+
+static unsigned long si5338_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long rate = parent_rate;
+	int rc;
+
+	rc = get_out_div(drvdata, hwdata->num);
+	if (rc < 0) {
+		rate = 0;
+		dev_err(&drvdata->client->dev,
+			"Error recalculating rate for clk%d\n", hwdata->num);
+	} else {
+		rate /= rc;
+	}
+	dev_dbg(&drvdata->client->dev, "Recalculted clkout%d rate %lu\n",
+		hwdata->num, rate);
+
+	return rate;
+}
+
+static long si5338_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	u64 out_freq_scaled, scaled_max;
+	unsigned long err, new_rate, new_err;
+	u8 r_div = 1;
+
+	out_freq_scaled = rate;
+	/* Request frequency if multisynth master */
+	if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) {
+		scaled_max = div64_u64(FVCOMAX,  MSINT_MAX);
+		while (r_div < 32 && out_freq_scaled < scaled_max) {
+			out_freq_scaled <<= 1;
+			r_div <<= 1;
+		}
+		if (out_freq_scaled < scaled_max) {
+			dev_err(&drvdata->client->dev,
+				"Specified output frequency is too low: %lu < %lld\n",
+				rate, scaled_max >> 5);
+			r_div = 32;
+			*parent_rate = scaled_max;
+		} else {
+			*parent_rate = out_freq_scaled;
+		}
+	} else {
+		/* round to closest r_div */
+		new_rate = *parent_rate;
+		new_err = abs(new_rate - rate);
+		do {
+			err = new_err;
+			new_rate >>= 1;
+			r_div <<= 1;
+			new_err = abs(new_rate - rate);
+		} while (new_err < err && r_div < 32);
+		r_div >>= 1;
+	}
+	rate = *parent_rate / r_div;
+
+	dev_dbg(&drvdata->client->dev,
+		"%s - %s: r_div = %u, rate = %lu, requesting parent_rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), r_div,
+		rate, *parent_rate);
+
+	return rate;
+}
+
+static int si5338_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long err, new_rate, new_err;
+	int r_div = 1;
+
+	/* round to closest r_div */
+	new_rate = parent_rate;
+	new_err = abs(new_rate - rate);
+	do {
+		err = new_err;
+		new_rate >>= 1;
+		r_div <<= 1;
+		new_err = abs(new_rate - rate);
+	} while (new_err < err && r_div < 32);
+	r_div >>= 1;
+
+	dev_dbg(&drvdata->client->dev,
+		"%s - %s: r_div = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), r_div,
+		parent_rate, rate);
+
+	return set_out_div(drvdata, r_div, hwdata->num);
+}
+
+static const struct clk_ops si5338_clkout_ops = {
+	.prepare = si5338_clkout_prepare,
+	.unprepare = si5338_clkout_unprepare,
+	.set_parent = si5338_clkout_set_parent,
+	.get_parent = si5338_clkout_get_parent,
+	.recalc_rate = si5338_clkout_recalc_rate,
+	.round_rate = si5338_clkout_round_rate,
+	.set_rate = si5338_clkout_set_rate,
+};
+
+/*
+ * Create sysfs files for status and dumping register for each si5338
+ * device. Current common clock framework doesn't have API to create
+ * individual sub-folder for each device in debugfs.
+ */
+static const char * const out_status[] = {
+	"output0_status",
+	"output1_status",
+	"output2_status",
+	"output3_status",
+	"outputs_status",
+	NULL
+};
+
+static ssize_t output_status_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+	int i, i1, j, rc, len = 0, show_number, match = 0;
+	int drv_type, drv_vdd, drv_trim, drv_invert;
+	int out_src, src_group = 0, src = 0;
+	const int in_numbers[] = {
+		12, 3, 4, 56
+	};
+
+	for (i = 0; out_status[i]; i++) {
+		if (strcmp(attr->attr.name, out_status[i]) == 0)
+			break;
+	}
+	if (!out_status[i])
+		return -EINVAL;
+
+	if (i == 4) { /* all outputs */
+		i = 0;
+		i1 = 4;
+		show_number = 1;
+	} else {
+		i1 = i+1;
+		show_number = 0;
+	}
+
+	for ( ; i < i1; i++) {
+		if (show_number) {
+			rc = sprintf(buf, "%d: ", i);
+			buf += rc;
+			len += rc;
+		}
+		if (get_out_disable(drvdata, i)) {
+			rc = sprintf(buf, "disabled");
+			buf += rc;
+			len += rc;
+			rc = 0;
+			switch (get_drv_disabled_state(drvdata, i)) {
+			case SI5338_OUT_DIS_HIZ:
+				rc = sprintf(buf, " (high-Z)\n");
+				break;
+			case SI5338_OUT_DIS_LOW:
+				rc = sprintf(buf, " (low)\n");
+				break;
+			case SI5338_OUT_DIS_HI:
+				rc = sprintf(buf, " (high)\n");
+				break;
+			case SI5338_OUT_DIS_ALWAYS_ON:
+				rc = sprintf(buf, " (always on)\n");
+				break;
+			}
+			buf += rc;
+			len += rc;
+			continue;
+		} else {
+			rc = sprintf(buf, "enabled ");
+			buf += rc;
+			len += rc;
+		}
+		drv_type = get_drv_type(drvdata, i);
+		if (drv_type < 0)
+			return drv_type;
+
+		drv_vdd =  get_drv_vdd(drvdata, i);
+		if (drv_vdd < 0)
+			return drv_vdd;
+
+		drv_trim = get_drv_trim(drvdata, i);
+		if (drv_trim < 0)
+			return drv_trim;
+
+		drv_invert = get_drv_invert(drvdata, i);
+		if (drv_invert < 0)
+			return drv_invert;
+
+		for (j = 0; drv_configs[j].description; j++) {
+			if (drv_configs[j].fmt == drv_type &&
+			    drv_configs[j].vdd == drv_vdd &&
+			    drv_configs[j].trim == drv_trim &&
+			    (drv_invert | (drv_configs[j].invert >> 2)) ==
+				((drv_configs[j].invert & 3) |
+				 (drv_configs[j].invert>>2)))
+				rc = sprintf(buf, drv_configs[j].description);
+				buf += rc;
+				len += rc;
+				match = 1;
+		}
+
+		if (match == 0) {
+			rc = sprintf(buf,
+				     "Invalid output configuration: type = %d, vdd=%d, trim=%d, invert=%d\n",
+				     drv_type, drv_vdd, drv_trim, drv_invert);
+			buf += rc;
+			len += rc;
+		}
+
+		rc = sprintf(buf, ", R%d and out %d power %s",
+			     i, i,
+			     get_drv_powerdown(drvdata, i) ? "down" : "up");
+		buf += rc;
+		len += rc;
+
+		rc = sprintf(buf, ", Output route ");
+		buf += rc;
+		len += rc;
+
+		out_src = get_out_mux(drvdata, i);
+		if (out_src < 0)
+			return out_src;
+
+		switch (out_src) {
+		case 0: /* p2div in */
+		case 2: /* p2div out */
+			src = get_fb_mux(drvdata);
+			if (src < 0)
+				return src;
+
+			src_group = 0;
+			src = src ? 2 : 3; /* mod src: 0 - IN56, 1 - IN4 */
+			break;
+		case 1: /* p1div in */
+		case 3: /* p1div out */
+			src = get_in_mux(drvdata);
+			if (src < 0)
+				return src;
+
+			if (src == 2) {
+				src_group = 1;
+				src = 0;
+			} else {
+				src_group = 0; /* keep src: 0 - IN12, 1 - IN3 */
+			}
+			break;
+		case 4:
+			src_group = 1;
+			break;
+		case 5:
+			src_group = 2;
+			src = 0;
+			break;
+		case 6:
+			src_group = 2;
+			src = i;
+			 break;
+		case 7:
+			src_group = 3;
+			break;
+		}
+		rc = 0;
+		switch (src_group) {
+		case 0:
+			rc = sprintf(buf, "IN%d", in_numbers[src]);
+			buf += rc;
+			len += rc;
+			break;
+		case 1:
+			rc = sprintf(buf, "XO");
+			buf += rc;
+			len += rc;
+			break;
+		case 2:
+			rc = sprintf(buf, "MS%d", src);
+			buf += rc;
+			len += rc;
+			break;
+		case 3:
+			rc = sprintf(buf, "No clock");
+			buf += rc;
+			len += rc;
+			break;
+		}
+
+		if (out_src == 5 || out_src == 6) {
+			rc = sprintf(buf, " power %s",
+				     get_ms_powerdown(drvdata, i) ?
+					"down" : "up");
+			buf += rc;
+			len += rc;
+		}
+
+		rc = sprintf(buf, "\n");
+		buf += rc;
+		len += rc;
+	}
+
+	return len;
+}
+
+static ssize_t register_dump_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	int i, rc, len = 0;
+	u8 val;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+
+	for (i = 0; i < ARRAY_SIZE(register_masks); i++) {
+		val = si5338_reg_read(drvdata, register_masks[i] >> 8);
+		rc = sprintf(buf, " 0x%x",
+				((register_masks[i] & 0x1ff00) << 8) |
+				(register_masks[i] & 0xff) |
+				((val & 0xff) << 8));
+		buf += rc;
+		len += rc;
+		if (((i + 1) & 0x7) == 0) {
+			rc = sprintf(buf, "\n");
+			buf += rc;
+			len += rc;
+		}
+	}
+	rc = sprintf(buf, "\n");
+	buf += rc;
+	len += rc;
+
+	return len;
+}
+
+static DEVICE_ATTR(output0_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output1_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output2_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output3_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(outputs_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(register_dump, 0444, register_dump_show, NULL);
+
+static int si5338_sysfs_register(struct device *dev)
+{
+	int rc;
+
+	rc = device_create_file(dev, &dev_attr_output0_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output1_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output2_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output3_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_outputs_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_register_dump);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static void si5338_sysfs_unregister(struct device *dev)
+{
+	device_remove_file(dev, &dev_attr_output0_status);
+	device_remove_file(dev, &dev_attr_output1_status);
+	device_remove_file(dev, &dev_attr_output2_status);
+	device_remove_file(dev, &dev_attr_output3_status);
+	device_remove_file(dev, &dev_attr_outputs_status);
+	device_remove_file(dev, &dev_attr_register_dump);
+}
+
+/*
+ * Si5351 i2c probe and device tree parsing
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5338_dt_ids[] = {
+	{ .compatible = "silabs,si5338" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5338_dt_ids);
+
+static int si5338_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5338_platform_data *pdata;
+	u32 val, num;
+	int i;
+
+	if (np == NULL)
+		return 0;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->clk_xtal = of_clk_get(np, 0);
+
+	for (i = 0; i < 4; i++)
+		pdata->clkin[i] = of_clk_get(np, i + 1);
+
+	if (!IS_ERR(pdata->clk_xtal) && !IS_ERR(pdata->clkin[0])) {
+		dev_err(&client->dev,
+			"Error in device tree: IN1/IN2 and XTAL are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	/* property silab,name-prefix */
+	of_property_read_string(np, "silab,name-prefix", &pdata->name_prefix);
+
+	/* property silab,ref-source */
+	if (!of_property_read_u32(np, "silab,ref-source", &val)) {
+		switch (val) {
+		case SI5338_REF_SRC_CLKIN12:
+		case SI5338_REF_SRC_CLKIN3:
+		case SI5338_REF_SRC_XTAL:
+			pdata->ref_src = val;
+			dev_dbg(&client->dev, "ref-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for refclk %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,fb-source */
+	if (!of_property_read_u32(np, "silab,fb-source", &val)) {
+		switch (val) {
+		case SI5338_FB_SRC_CLKIN4:
+		case SI5338_FB_SRC_CLKIN56:
+		case SI5338_FB_SRC_NOCLK:
+			pdata->fb_src = val;
+			dev_dbg(&client->dev, "fb-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for fbclk %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,pll-source */
+	if (!of_property_read_u32(np, "silab,pll-source", &val)) {
+		switch (val) {
+		case SI5338_PFD_IN_REF_REFCLK:
+		case SI5338_PFD_IN_REF_FBCLK:
+		case SI5338_PFD_IN_REF_DIVREFCLK:
+		case SI5338_PFD_IN_REF_DIVFBCLK:
+		case SI5338_PFD_IN_REF_XOCLK:
+		case SI5338_PFD_IN_REF_NOCLK:
+			pdata->pll_src = val;
+			dev_dbg(&client->dev, "pll-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for pll %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,pll-vco */
+	if (!of_property_read_u32(np, "silab,pll-vco", &val)) {
+		if (val < FVCOMIN || val > FVCOMAX) {
+			dev_err(&client->dev,
+				"pll-vco out of range [%lldu..%lldu]\n",
+				FVCOMIN, FVCOMAX);
+		} else {
+			pdata->pll_vco = val;
+		}
+	}
+
+	if (!of_property_read_u32(np, "silab,pll-master", &val)) {
+		if (val > 3) {
+			dev_err(&client->dev,
+				"Invalid pll-master %u\n", val);
+			return -EINVAL;
+		}
+		pdata->pll_master = val;
+		dev_dbg(&client->dev, "pll-master = %d\n", val);
+	}
+
+	/* per clock out */
+	for_each_child_of_node(np, child) {
+		if (of_property_read_u32(child, "reg", &num)) {
+			dev_err(&client->dev, "Missing reg property of %s\n",
+				child->name);
+			return -EINVAL;
+		}
+		if (num > 4) {
+			dev_err(&client->dev, "Invalid clkout %u\n", num);
+			return -EINVAL;
+		}
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case SI5338_OUT_MUX_FBCLK:
+			case SI5338_OUT_MUX_REFCLK:
+			case SI5338_OUT_MUX_DIVFBCLK:
+			case SI5338_OUT_MUX_DIVREFCLK:
+			case SI5338_OUT_MUX_XOCLK:
+			case SI5338_OUT_MUX_MS0:
+			case SI5338_OUT_MUX_MSN:
+			case SI5338_OUT_MUX_NOCLK:
+				pdata->clkout[num].clkout_src = val;
+				dev_dbg(&client->dev, "clkout_src = %d\n", val);
+				break;
+			default:
+				dev_err(&client->dev,
+					"Invalid source for output %u\n", num);
+				return -EINVAL;
+			}
+		}
+		if (!of_property_read_string(child, "silabs,drive-config",
+					     &pdata->clkout[num].drive)) {
+			if (find_drive_config(pdata->clkout[num].drive) < 0) {
+				dev_err(&client->dev,
+					"Invalid drive config for output %u\n",
+					num);
+				return -EINVAL;
+			}
+			dev_dbg(&client->dev, "drive-config = %s\n",
+				pdata->clkout[num].drive);
+		}
+		if (!of_property_read_u32(child,
+					  "silabs,disable-state",
+					  &val)) {
+			switch (val) {
+			case SI5338_OUT_DIS_HIZ:
+			case SI5338_OUT_DIS_LOW:
+			case SI5338_OUT_DIS_HI:
+			case SI5338_OUT_DIS_ALWAYS_ON:
+				pdata->clkout[num].disable_state = val;
+				dev_dbg(&client->dev,
+					"disable-state = %d\n", val);
+				break;
+			default:
+				dev_err(&client->dev,
+					"Invalid disable state for output %u\n",
+					num);
+				return -EINVAL;
+			}
+		}
+		if (!of_property_read_u32(child, "clock-frequency", &val)) {
+			pdata->clkout[num].rate = val;
+			dev_dbg(&client->dev, "clock-frequency = %d\n", val);
+		}
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5338_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+/*
+ * Returns the clk registered, or an error code. If successful, the clk pointer
+ * is also save in hw->clk.
+ */
+static struct clk *si5338_register_clock(struct device *dev,
+			      struct clk_hw *hw,
+			      const char *name,
+			      const char **parent_names,
+			      u8 num_parents,
+			      const struct clk_ops *ops,
+			      unsigned long flags)
+{
+	struct clk *clk;
+	struct clk_init_data init;
+
+	memset(&init, 0, sizeof(init));
+	init.name = name;
+	init.ops = ops;
+	init.flags = flags;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	hw->init = &init;
+	dev_dbg(dev, "Registering %s\n", name);
+	clk = devm_clk_register(dev, hw);
+
+	if (IS_ERR(clk))
+		dev_err(dev, "unable to register %s\n", name);
+
+	return clk;
+}
+
+static int si5338_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5338_platform_data *pdata;
+	struct si5338_driver_data *drvdata;
+	struct clk *clk = NULL;
+	char name_buf[8][MAX_NAME_PREFIX + MAX_NAME_LENGTH];
+	char register_name[MAX_NAME_PREFIX + MAX_NAME_LENGTH];
+	const char *parent_names[8] = {
+		name_buf[0], name_buf[1], name_buf[2], name_buf[3],
+		name_buf[4], name_buf[5], name_buf[6], name_buf[7]
+	};
+	int ret, n;
+	bool require_xtal = false;
+	bool require_ref = false;
+	bool require_fb = false;
+	bool require_pll = false;
+	unsigned long flags;
+
+	ret = si5338_dt_parse(client);
+	if (ret)
+		return ret;
+
+	pdata = client->dev.platform_data;
+	if (!pdata)
+		return -EINVAL;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->pxtal = pdata->clk_xtal;
+	for (n = 0; n < 4; n++)
+		drvdata->pclkin[n] = pdata->clkin[n];
+	if (!pdata->name_prefix) {
+		strlcpy(drvdata->name_prefix,
+			dev_name(&client->dev), MAX_NAME_PREFIX);
+		strncat(drvdata->name_prefix, "-", MAX_NAME_PREFIX);
+	} else {
+		strlcpy(drvdata->name_prefix,
+			pdata->name_prefix, MAX_NAME_PREFIX);
+	}
+
+	if (!IS_ERR(drvdata->pxtal) && drvdata->pxtal &&
+	    !IS_ERR(drvdata->pclkin[0]) && drvdata->pclkin[0]) {
+		dev_err(&client->dev,
+			"Error in device tree: IN1/IN2 and XTAL are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	/* Check if clkout config is valid */
+	for (n = 0; n < 4; n++) {
+		/* check clkout source config */
+		switch (pdata->clkout[n].clkout_src) {
+		case SI5338_OUT_MUX_NOCLK:
+			if (pdata->clkout[n].rate != 0)
+				pdata->clkout[n].rate = 0;
+			break;
+		case SI5338_OUT_MUX_REFCLK:
+		case SI5338_OUT_MUX_DIVREFCLK:
+			require_ref = true;
+			break;
+		case SI5338_OUT_MUX_FBCLK:
+		case SI5338_OUT_MUX_DIVFBCLK:
+			require_fb = true;
+			break;
+		case SI5338_OUT_MUX_XOCLK:
+			require_xtal = true;
+			break;
+		case SI5338_OUT_MUX_MS0:
+		case SI5338_OUT_MUX_MSN:
+			require_pll = true;
+			break;
+		default:
+			dev_err(&client->dev, "Invalid clkout source\n");
+			return -EINVAL;
+		}
+
+		/* check clkout drive config */
+		if (find_drive_config(pdata->clkout[n].drive) < 0) {
+			dev_err(&client->dev,
+				"Invalid drive config for output %u\n", n);
+			return -EINVAL;
+		}
+
+		/* check clkout disable state config */
+		switch (pdata->clkout[n].disable_state) {
+		case SI5338_OUT_DIS_HIZ:
+		case SI5338_OUT_DIS_LOW:
+		case SI5338_OUT_DIS_HI:
+		case SI5338_OUT_DIS_ALWAYS_ON:
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid disable state for output %u\n", n);
+			return -EINVAL;
+		}
+
+	}
+	/* check pll source */
+	if (require_pll) {
+		switch (pdata->pll_src) {
+		case SI5338_PFD_IN_REF_XOCLK:
+			require_xtal = true;
+			break;
+		case SI5338_PFD_IN_REF_REFCLK:
+		case SI5338_PFD_IN_REF_DIVREFCLK:
+			require_ref = true;
+			break;
+		case SI5338_PFD_IN_REF_FBCLK:
+		case SI5338_PFD_IN_REF_DIVFBCLK:
+			require_fb = true;
+			break;
+		case SI5338_PFD_IN_REF_NOCLK:
+		default:
+			dev_err(&client->dev, "Invalid pll source\n");
+			return -EINVAL;
+		}
+	}
+	/* check refclk source */
+	if (require_ref) {
+		switch (pdata->ref_src) {
+		case SI5338_REF_SRC_CLKIN12:
+			if (IS_ERR(drvdata->pclkin[0]) || !drvdata->pclkin[0]) {
+				dev_err(&client->dev,
+					"IN1/IN2 doesn't a have source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_REF_SRC_CLKIN3:
+			if (IS_ERR(drvdata->pclkin[1]) || !drvdata->pclkin[1]) {
+				dev_err(&client->dev,
+					"IN3 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for refclk\n");
+			return -EINVAL;
+		}
+	}
+	/* check fbclk source */
+	if (require_fb) {
+		switch (pdata->fb_src) {
+		case SI5338_FB_SRC_CLKIN4:
+			if (IS_ERR(drvdata->pclkin[2]) || !drvdata->pclkin[2]) {
+				dev_err(&client->dev,
+					"IN4 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_FB_SRC_CLKIN56:
+			if (IS_ERR(drvdata->pclkin[3]) || drvdata->pclkin[3]) {
+				dev_err(&client->dev,
+					"IN5/IN6 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_FB_SRC_NOCLK:
+		default:
+			dev_err(&client->dev,
+				"Invalid source for fbclk\n");
+			return -EINVAL;
+		}
+	}
+	/* check xtal */
+	if (require_xtal) {
+		if (IS_ERR(drvdata->pxtal) || !drvdata->pxtal) {
+			dev_err(&client->dev,
+				"XTAL doesn't have a source\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Register regmap */
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5338_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	ret = regmap_read(drvdata->regmap, REG5338_DEV_CONFIG2, &n);
+	if (ret) {
+		dev_err(&client->dev, "Failed to access regmap\n");
+		return ret;
+	}
+
+	/* Check if si5338 exists */
+	if ((n & REG5338_DEV_CONFIG2_MASK) != REG5338_DEV_CONFIG2_VAL) {
+		dev_err(&client->dev,
+			"Chip returned unexpected value from reg %d: %d, expected %d. It is not %s\n",
+			REG5338_DEV_CONFIG2, n, REG5338_DEV_CONFIG2_VAL,
+			id->name);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "Chip %s is found\n", id->name);
+
+	ret = pre_init(drvdata);		/* Disable all */
+	if (ret)
+		return ret;
+
+	/*
+	 * Set up clock structure
+	 * These clocks have fixed parent
+	 *	xtal => xoclk
+	 *	refclk => divrefclk
+	 *	fbclk => divfbclk
+	 *	pll => multisynth
+	 */
+
+	/* setup refclk parent */
+	ret = si5338_refclk_reparent(drvdata, pdata->ref_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent refclk to %d\n", pdata->ref_src);
+		return ret;
+	}
+
+	/* setup fbclk parent */
+	ret = si5338_fbclk_reparent(drvdata, pdata->fb_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent fbclk to %d\n", pdata->fb_src);
+		return ret;
+	}
+
+	/* setup pll parent */
+	ret = si5338_pll_reparent(drvdata, pdata->pll_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent pll %d to %d\n",
+			n, pdata->pll_src);
+		return ret;
+	}
+
+	for (n = 0; n < 4; n++) {
+		ret = si5338_clkout_reparent(drvdata, n,
+					      pdata->clkout[n].clkout_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent clkout %d to %d\n",
+				n, pdata->clkout[n].clkout_src);
+			return ret;
+		}
+
+		ret = si5338_clkout_set_drive_config(drvdata, n,
+					      pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive config of clkout%d to %s\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+
+		ret = si5338_clkout_set_disable_state(drvdata, n,
+						pdata->clkout[n].disable_state);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set disable state of clkout%d to %d\n",
+				n, pdata->clkout[n].disable_state);
+			return ret;
+		}
+	}
+
+	/* Register xtal input clock */
+	if (!IS_ERR(drvdata->pxtal) && drvdata->pxtal) {
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_input_names[4], MAX_NAME_LENGTH);
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		clk = si5338_register_clock(&client->dev, &drvdata->xtal,
+					 register_name, &drvdata->pxtal_name, 1,
+					 &si5338_xtal_ops, 0);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+	}
+
+	/* Register clkin input clock */
+	for (n = 0; n < 4; n++) {
+		if (IS_ERR(drvdata->pclkin[n]) || !drvdata->pclkin[n])
+			continue;
+
+		drvdata->clkin[n].drvdata = drvdata;
+		drvdata->clkin[n].num = n;
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_input_names[n], MAX_NAME_LENGTH);
+		drvdata->pclkin_name[n] = __clk_get_name(drvdata->pclkin[n]);
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->clkin[n].hw,
+					    register_name,
+					    &drvdata->pclkin_name[n],
+					    1,
+					    &si5338_clkin_ops,
+					    0);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+	}
+
+	/*
+	 * Create unique internal names in case multiple devices exist
+	 *
+	 * Register refclk, parents can be in1/in2, in3, xtal, noclk
+	 */
+	drvdata->refclk.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[1], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[2], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[3], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_input_names[0], MAX_NAME_LENGTH);
+	strncat(name_buf[1], si5338_input_names[1], MAX_NAME_LENGTH);
+	strncat(name_buf[2], si5338_input_names[4], MAX_NAME_LENGTH);
+	strncat(name_buf[3], si5338_input_names[5], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[0], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->refclk.hw,
+				 register_name, parent_names, 4,
+				 &si5338_refclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register divrefclk, parent is refclk */
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_pll_src_names[0], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[2], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->divrefclk,
+				 register_name, parent_names, 1,
+				 &si5338_divrefclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register fbclk, parents can be in4, in5/in6, noclk */
+	drvdata->fbclk.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[1], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[2], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_input_names[2], MAX_NAME_LENGTH);
+	strncat(name_buf[1], si5338_input_names[3], MAX_NAME_LENGTH);
+	strncat(name_buf[2], si5338_input_names[5], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[1], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->fbclk.hw,
+				 register_name, parent_names, 3,
+				 &si5338_fbclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register divfbclk, parent is fbclk */
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_pll_src_names[1], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[3], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->divfbclk,
+				 register_name, parent_names, 1,
+				 &si5338_divfbclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* register PLL */
+	drvdata->pll.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	for (n = 0; n < ARRAY_SIZE(si5338_pll_src_names); n++) {
+		strlcpy(name_buf[n], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[n], si5338_pll_src_names[n], MAX_NAME_LENGTH);
+	}
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_msynth_src_names[0], MAX_NAME_LENGTH);
+	clk = si5338_register_clock(&client->dev, &drvdata->pll.hw,
+				 register_name, parent_names, 5,
+				 &si5338_pll_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* If pll_vco is specified, always use it to set pll clock */
+	if (require_pll && pdata->pll_vco) {
+		if (pdata->pll_vco > FVCOMIN && pdata->pll_vco < FVCOMAX) {
+			dev_dbg(&client->dev, "Setting pll vco rate to %u\n",
+				pdata->pll_vco);
+			ret = clk_set_rate(clk, pdata->pll_vco);
+			if (ret != 0) {
+				dev_err(&client->dev, "Cannot set pll vco rate : %d\n",
+					ret);
+				ret = -EIO;
+				return ret;
+			}
+		} else {
+			pdata->pll_vco = 0;
+		}
+	}
+
+	/* register clk multisync and clk out divider */
+	drvdata->msynth = devm_kzalloc(&client->dev, 4 *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, 4 *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = 4;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		4 * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks)) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	for (n = 0; n < 4; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		memset(name_buf, 0, sizeof(name_buf));
+		strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[0], si5338_msynth_src_names[0],
+			MAX_NAME_LENGTH);
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_msynth_names[n], MAX_NAME_LENGTH);
+		flags = (!pdata->pll_vco && n == pdata->pll_master) ?
+			CLK_SET_RATE_PARENT : 0;
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->msynth[n].hw,
+					    register_name,
+					    parent_names,
+					    1,
+					    &si5338_msynth_ops,
+					    flags);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
+	}
+
+	/*
+	 * ms0 is available for all clkout
+	 * ms0/ms1/ms2/ms3 is available for each clkout respectivelly
+	 */
+	memset(name_buf, 0, sizeof(name_buf));
+	for (n = 0; n < 8; n++) {
+		strlcpy(name_buf[n], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[n], si5338_clkout_src_names[n],
+			MAX_NAME_LENGTH);
+	}
+
+	for (n = 0; n < 4; n++) {
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		/*
+		 * Update source
+		 * ms0 for clkout0
+		 * ms1 for clkout1
+		 * ms2 for clkout2
+		 * ms3 for clkout3
+		 */
+		strlcpy(name_buf[6], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[6], si5338_msynth_names[n], MAX_NAME_LENGTH);
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_clkout_names[n], MAX_NAME_LENGTH);
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->clkout[n].hw,
+					    register_name,
+					    parent_names,
+					    8,
+					    &si5338_clkout_ops,
+					    CLK_SET_RATE_PARENT);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
+		drvdata->onecell.clks[n] = clk;
+
+		/* set initial clkout rate */
+		if (pdata->clkout[n].rate != 0) {
+			dev_dbg(&client->dev, "Setting clkout%d rate to %lu\n",
+				n, pdata->clkout[n].rate);
+			ret = clk_set_rate(clk, pdata->clkout[n].rate);
+			if (ret != 0) {
+				dev_err(&client->dev,
+					"Cannot set rate for clkout%d: %d\n",
+					n, ret);
+			}
+			/* "prepare" clkout
+			 * This transverse up to all parent clocks
+			 */
+			ret = clk_prepare(clk);
+			if (ret != 0) {
+				dev_err(&client->dev,
+					"Cannot prepare clk%d\n", n);
+			}
+		}	/* else it should be left disabled out of reset */
+	}
+
+	/*
+	 * Important: Go through the procedure to check PLL locking
+	 * and other steps required by si5338 reference manual.
+	 */
+	ret = post_init(drvdata);
+	if (ret)
+		return ret;
+
+	dev_info(&client->dev, "%s clocks are registered\n", id->name);
+	si5338_sysfs_register(&client->dev);	/* ignore return value */
+
+#ifdef CONFIG_OF
+	ret = of_clk_add_provider(client->dev.of_node,
+				  of_clk_src_onecell_get,
+				  &drvdata->onecell);
+	if (ret) {
+		dev_err(&client->dev, "unable to add clk provider\n");
+		return ret;
+	}
+#endif
+
+
+	return 0;
+}
+
+static int si5338_i2c_remove(struct i2c_client *client)
+{
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+	int i;
+
+	si5338_sysfs_unregister(&client->dev);
+
+	if (!IS_ERR(drvdata->pxtal) && !drvdata->pxtal)
+		clk_put(drvdata->pxtal);
+
+	for (i = 0; i < 4; i++) {
+		if (!IS_ERR(drvdata->pclkin[i]) && !drvdata->pclkin[i])
+			clk_put(drvdata->pclkin[i]);
+	}
+
+	return 0;
+}
+
+
+static const struct i2c_device_id si5338_i2c_ids[] = {
+	{ "si5338", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5338_i2c_ids);
+
+static struct i2c_driver si5338_driver = {
+	.driver = {
+		.name = "si5338",
+		.of_match_table = of_match_ptr(si5338_dt_ids),
+	},
+	.probe = si5338_i2c_probe,
+	.remove = si5338_i2c_remove,
+	.id_table = si5338_i2c_ids,
+};
+module_i2c_driver(si5338_driver);
+
+MODULE_AUTHOR("York Sun <yorksun@freescale.com");
+MODULE_DESCRIPTION("Silicon Labs Si5338 clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5338.h b/drivers/clk/clk-si5338.h
new file mode 100644
index 0000000..3d2532d
--- /dev/null
+++ b/drivers/clk/clk-si5338.h
@@ -0,0 +1,305 @@
+/*
+ * clk-si5338.h: Silicon Labs Si5338 I2C Clock Generator
+ *
+ * Copyright 2015 Freescale Semiconductor
+ * York Sun <yorksun@freescale.com>
+ *
+ * Partially taken from si5338.c by Andrey Filippov  <andrey@elphel.com>
+ * Copyright (C) 2013 Elphel, Inc.
+ *
+ * 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.
+ */
+
+#ifndef _CLK_SI5338_H_
+#define _CLK_SI5338_H_
+
+#define REG5338_PAGE			255
+#define REG5338_PAGE_MASK		1
+#define REG5338_DEV_CONFIG2		2
+#define REG5338_DEV_CONFIG2_MASK	0x3f
+#define REG5338_DEV_CONFIG2_VAL		38 /* last 2 digits of part number */
+#define LAST_REG			347
+
+#define FVCOMIN			2200000000LL
+#define FVCOMAX			2840000000LL
+#define XTAL_FREQMIN		8000000LL
+#define XTAL_FREQMAX		30000000LL
+#define INFREQMIN		5000000LL
+#define INFREQMAX		710000000LL
+#define INFREQMAX34		200000000LL
+#define INFREQDIV		40000000LL /* divide input frequency if above */
+
+#define SI5338_SPREAD_SPECTRUM
+#define SPREAD_RATE_MIN		31500	/* 31.5 KHz */
+#define SPREAD_RATE_MAX		63000	/* 63 KHz */
+#define SPREAD_AMP_MIN		10	/* 0.1% */
+#define SPREAD_AMP_MAX		500	/* 5.0% */
+#define SPREAD_AMP_DENOM	10000	/* 0.01% amplitude step */
+
+#define SPREAD_RATE_DFLT	31500	/* 31.5 KHz */
+#define SPREAD_AMP_DFLT		50	/* 0.5% */
+
+
+#define MSINT_MIN		4 /* need to exclude 5, 7 in the code */
+#define MSINT_MAX		567
+
+/* reads of the I2C status register (1 cycle ~ 0.1 ms) */
+#define INIT_TIMEOUT		1000	/* About 1s on 100KHz I2C clock */
+
+#define AWE_INT_MASK		0x061d
+
+#define AWE_IN_MUX		0x1d18
+#define AWE_IN_MUX1		0x1c1c
+#define AWE_FB_MUX		0x1e18
+#define AWE_FB_MUX1		0x1c20
+
+#define AWE_XTAL_FREQ		0x1c03
+#define AWE_PFD_REF		0x1de0
+#define AWE_PFD_FB		0x1ee0
+#define AWE_P1DIV		0x1d07
+#define AWE_P2DIV		0x1e07
+#define AWE_DRV0_PDN		0x1f01
+#define AWE_MS0_PDN		0x1f02
+#define AWE_R0DIV		0x1f1c
+#define AWE_R0DIV_IN		0x1fe0
+#define AWE_DRV1_PDN		0x2001
+#define AWE_MS1_PDN		0x2002
+#define AWE_R1DIV		0x201c
+#define AWE_R1DIV_IN		0x20e0
+#define AWE_DRV2_PDN		0x2101
+#define AWE_MS2_PDN		0x2102
+#define AWE_R2DIV		0x211c
+#define AWE_R2DIV_IN		0x21e0
+#define AWE_DRV3_PDN		0x2201
+#define AWE_MS3_PDN		0x2202
+#define AWE_R3DIV		0x221c
+#define AWE_R3DIV_IN		0x22e0
+
+#define AWE_DRV0_VDDO		0x2303
+#define AWE_DRV1_VDDO		0x230c
+#define AWE_DRV2_VDDO		0x2330
+#define AWE_DRV3_VDDO		0x23c0
+#define AWE_DRV0_FMT		0x2407
+#define AWE_DRV0_INV		0x2418
+#define AWE_DRV1_FMT		0x2507
+#define AWE_DRV1_INV		0x2518
+#define AWE_DRV2_FMT		0x2607
+#define AWE_DRV2_INV		0x2618
+#define AWE_DRV3_FMT		0x2707
+#define AWE_DRV3_INV		0x2718
+
+#define AWE_DRV0_TRIM		0x281f
+#define AWE_DRV1_TRIM_A		0x28e0
+#define AWE_DRV1_TRIM_B		0x2903
+#define AWE_DRV2_TRIM		0x297c
+#define AWE_DRV3_TRIM		0x2a1f
+
+#define AWE_FCAL_OVRD_07_00	0x2dff
+#define AWE_FCAL_OVRD_15_08	0x2eff
+#define AWE_FCAL_OVRD_17_15	0x2f03
+#define AWE_REG47_72		0x2ffc
+#define AWE_PFD_EXTFB		0x3080
+#define AWE_PLL_KPHI		0x307f
+#define AWE_FCAL_OVRD_EN	0x3180
+#define AWE_VCO_GAIN		0x3170
+#define AWE_RSEL		0x310c
+#define AWE_BWSEL		0x3103
+#define AWE_VCO_GAIN_RSEL_BWSEL	0x317f
+
+#define AWE_PLL_EN		0x32c0
+#define AWE_MSCAL		0x323f
+#define AWE_MS3_HS		0x3380
+#define AWE_MS2_HS		0x3340
+#define AWE_MS1_HS		0x3320
+#define AWE_MS0_HS		0x3310
+#define AWE_MS_PEC		0x3307
+
+#define AWE_MS0_FIDCT		0x3460
+#define AWE_MS0_FIDDIS		0x3410
+#define AWE_MS0_SSMODE		0x340C
+#define AWE_MS0_PHIDCT		0x3403
+#define AWE_MS0_P1_07_00	0x35ff
+#define AWE_MS0_P1_15_08	0x36ff
+#define AWE_MS0_P1_17_16	0x3703
+#define AWE_MS0_P2_05_00	0x37fc
+#define AWE_MS0_P2_13_06	0x38ff
+#define AWE_MS0_P2_21_14	0x39ff
+#define AWE_MS0_P2_29_22	0x3aff
+#define AWE_MS0_P3_07_00	0x3bff
+#define AWE_MS0_P3_15_08	0x3cff
+#define AWE_MS0_P3_23_16	0x3dff
+#define AWE_MS0_P3_29_24	0x3e3f
+
+
+#define AWE_MS1_FIDCT		0x3f60
+#define AWE_MS1_FIDDIS		0x3f10
+#define AWE_MS1_SSMODE		0x3f0C
+#define AWE_MS1_PHIDCT		0x3f03
+#define AWE_MS1_P1_07_00	0x40ff
+#define AWE_MS1_P1_15_08	0x41ff
+#define AWE_MS1_P1_17_16	0x4203
+#define AWE_MS1_P2_05_00	0x42fc
+#define AWE_MS1_P2_13_06	0x43ff
+#define AWE_MS1_P2_21_14	0x44ff
+#define AWE_MS1_P2_29_22	0x45ff
+#define AWE_MS1_P3_07_00	0x46ff
+#define AWE_MS1_P3_15_08	0x47ff
+#define AWE_MS1_P3_23_16	0x48ff
+#define AWE_MS1_P3_29_24	0x493f
+
+#define AWE_MS2_FRCTL		0x4a60 /* different name? */
+#define AWE_MS2_FIDDIS		0x4a10
+#define AWE_MS2_SSMODE		0x4a0C
+#define AWE_MS2_PHIDCT		0x4a03
+#define AWE_MS2_P1_07_00	0x4bff
+#define AWE_MS2_P1_15_08	0x4cff
+#define AWE_MS2_P1_17_16	0x4d03
+#define AWE_MS2_P2_05_00	0x4dfc
+#define AWE_MS2_P2_13_06	0x4eff
+#define AWE_MS2_P2_21_14	0x4fff
+#define AWE_MS2_P2_29_22	0x50ff
+#define AWE_MS2_P3_07_00	0x51ff
+#define AWE_MS2_P3_15_08	0x52ff
+#define AWE_MS2_P3_23_16	0x53ff
+#define AWE_MS2_P3_29_24	0x543f
+
+#define AWE_MS3_FIDCT		0x5560
+#define AWE_MS3_FIDDIS		0x5510
+#define AWE_MS3_SSMODE		0x550C
+#define AWE_MS3_PHIDCT		0x5503
+#define AWE_MS3_P1_07_00	0x56ff
+#define AWE_MS3_P1_15_08	0x57ff
+#define AWE_MS3_P1_17_16	0x5803
+#define AWE_MS3_P2_05_00	0x58fc
+#define AWE_MS3_P2_13_06	0x59ff
+#define AWE_MS3_P2_21_14	0x5aff
+#define AWE_MS3_P2_29_22	0x5bff
+#define AWE_MS3_P3_07_00	0x5cff
+#define AWE_MS3_P3_15_08	0x5dff
+#define AWE_MS3_P3_23_16	0x5eff
+#define AWE_MS3_P3_29_24	0x5f3f
+
+#define AWE_MSN_P1_07_00	0x61ff
+#define AWE_MSN_P1_15_08	0x62ff
+#define AWE_MSN_P1_17_16	0x6303
+#define AWE_MSN_P2_05_00	0x63fc
+#define AWE_MSN_P2_13_06	0x64ff
+#define AWE_MSN_P2_21_14	0x65ff
+#define AWE_MSN_P2_29_22	0x66ff
+#define AWE_MSN_P3_07_00	0x67ff
+#define AWE_MSN_P3_15_08	0x68ff
+#define AWE_MSN_P3_23_16	0x69ff
+#define AWE_MSN_P3_29_24	0x6a3f
+
+#define AWE_OUT0_DIS_STATE	0x6ec0
+#define AWE_OUT1_DIS_STATE	0x72c0
+#define AWE_OUT2_DIS_STATE	0x76c0
+#define AWE_OUT3_DIS_STATE	0x7ac0
+
+#define AWE_STATUS			0xdaff
+#define AWE_STATUS_PLL_LOL		0xda10
+#define AWE_STATUS_PLL_LOS_FDBK		0xda08
+#define AWE_STATUS_PLL_LOS_CLKIN	0xda04
+#define AWE_STATUS_PLL_SYS_CAL		0xda01
+
+#define AWE_MS_RESET		0xe204
+
+#define AWE_OUT0_DIS		0xe601
+#define AWE_OUT1_DIS		0xe602
+#define AWE_OUT2_DIS		0xe604
+#define AWE_OUT3_DIS		0xe608
+#define AWE_OUT_ALL_DIS		0xe610
+
+#define AWE_FCAL_07_00		0xebff
+#define AWE_FCAL_15_08		0xecff
+#define AWE_FCAL_17_16		0xed03
+
+
+#define AWE_DIS_LOS		0xf180
+#define AWE_REG241		0xf1ff
+
+#define AWE_SOFT_RESET		0xf602
+
+#define AWE_MS0_SSUPP2_07_00	0x11fff
+#define AWE_MS0_SSUPP2_14_08	0x1207f
+#define AWE_MS0_SSUPP3_07_00	0x121ff /* set them to 0 - default==1 */
+#define AWE_MS0_SSUPP3_14_08	0x1227f
+#define AWE_MS0_SSUPP1_07_00	0x123ff
+#define AWE_MS0_SSUPP1_11_08	0x1240f
+#define AWE_MS0_SSUDP1_03_00	0x124f0
+#define AWE_MS0_SSUDP1_11_04	0x125ff
+#define AWE_MS0_SSDNP2_07_00	0x126ff
+#define AWE_MS0_SSDNP2_14_08	0x1277f
+#define AWE_MS0_SSDNP3_07_00	0x128ff
+#define AWE_MS0_SSDNP3_14_08	0x1297f
+#define AWE_MS0_SSDNP1_07_00	0x12aff
+#define AWE_MS0_SSDNP1_11_08	0x12b0f
+
+#define AWE_MS1_SSUPP2_07_00	0x12fff
+#define AWE_MS1_SSUPP2_14_08	0x1307f
+#define AWE_MS1_SSUPP3_07_00	0x131ff
+#define AWE_MS1_SSUPP3_14_08	0x1327f
+#define AWE_MS1_SSUPP1_07_00	0x133ff
+#define AWE_MS1_SSUPP1_11_08	0x1340f
+#define AWE_MS1_SSUDP1_03_00	0x134f0
+#define AWE_MS1_SSUDP1_11_04	0x135ff
+#define AWE_MS1_SSDNP2_07_00	0x136ff
+#define AWE_MS1_SSDNP2_14_08	0x1377f
+#define AWE_MS1_SSDNP3_07_00	0x138ff
+#define AWE_MS1_SSDNP3_14_08	0x1397f
+#define AWE_MS1_SSDNP1_07_00	0x13aff
+#define AWE_MS1_SSDNP1_11_08	0x13b0f
+
+#define AWE_MS2_SSUPP2_07_00	0x13fff
+#define AWE_MS2_SSUPP2_14_08	0x1407f
+#define AWE_MS2_SSUPP3_07_00	0x141ff
+#define AWE_MS2_SSUPP3_14_08	0x1427f
+#define AWE_MS2_SSUPP1_07_00	0x143ff
+#define AWE_MS2_SSUPP1_11_08	0x1440f
+#define AWE_MS2_SSUDP1_03_00	0x144f0
+#define AWE_MS2_SSUDP1_11_04	0x145ff
+#define AWE_MS2_SSDNP2_07_00	0x146ff
+#define AWE_MS2_SSDNP2_14_08	0x1477f
+#define AWE_MS2_SSDNP3_07_00	0x148ff
+#define AWE_MS2_SSDNP3_14_08	0x1497f
+#define AWE_MS2_SSDNP1_07_00	0x14aff
+#define AWE_MS2_SSDNP1_11_08	0x14b0f
+
+#define AWE_MS3_SSUPP2_07_00	0x14fff
+#define AWE_MS3_SSUPP2_14_08	0x1507f
+#define AWE_MS3_SSUPP3_07_00	0x151ff
+#define AWE_MS3_SSUPP3_14_08	0x1527f
+#define AWE_MS3_SSUPP1_07_00	0x153ff
+#define AWE_MS3_SSUPP1_11_08	0x1540f
+#define AWE_MS3_SSUDP1_03_00	0x154f0
+#define AWE_MS3_SSUDP1_11_04	0x155ff
+#define AWE_MS3_SSDNP2_07_00	0x156ff
+#define AWE_MS3_SSDNP2_14_08	0x1577f
+#define AWE_MS3_SSDNP3_07_00	0x158ff
+#define AWE_MS3_SSDNP3_14_08	0x1597f
+#define AWE_MS3_SSDNP1_07_00	0x15aff
+#define AWE_MS3_SSDNP1_11_08	0x15b0f
+
+#define AWE_MISC_47		0x2ffc /* write 0x5 */
+#define AWE_MISC_106		0x6a80 /* write 0x1 */
+#define AWE_MISC_116		0x7480 /* write 0x1 */
+#define AWE_MISC_42		0x2a20 /* write 0x1 */
+#define AWE_MISC_06A		0x06e0 /* write 0x0 */
+#define AWE_MISC_06B		0x0602 /* write 0x0 */
+#define AWE_MISC_28		0x1cc0 /* write 0x0 */
+
+struct si5338_drv_t {
+	const char *description;
+	u8 fmt;
+	u8 vdd;
+	u8 trim;
+	/* bits [1:0} data,
+	 * [3:2] - don't care ([3]==1 - [1] - any, [2]==1 - [0] - any
+	 */
+	u8 invert;
+};
+
+#endif
diff --git a/include/dt-bindings/clock/clk-si5338.h b/include/dt-bindings/clock/clk-si5338.h
new file mode 100644
index 0000000..ebce4ac
--- /dev/null
+++ b/include/dt-bindings/clock/clk-si5338.h
@@ -0,0 +1,69 @@
+/*
+ * This header provides constants for SI5338 I2C clock generator
+ *
+ * The constants defined in this header are used in dts files
+ *
+ * Copyright 2015 Freescale Semiconductor
+ *
+ * York Sun <yorksun@freescale.com>
+ *
+ * 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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DT_BINDINGS_CLK_DSI5338_H
+#define _DT_BINDINGS_CLK_DSI5338_H
+
+/* Used to identify input clock */
+#define SI5338_INPUT_CLK12		0
+#define SI5338_INPUT_CLK3		1
+#define SI5338_INPUT_CLK4		2
+#define SI5338_INPUT_CLK56		3
+
+/* Used to identify the mux source */
+#define SI5338_REF_SRC_CLKIN12		0
+#define SI5338_REF_SRC_CLKIN3		1
+#define SI5338_FB_SRC_CLKIN4		2
+#define SI5338_FB_SRC_CLKIN56		3
+#define SI5338_REF_SRC_XTAL		4
+#define SI5338_FB_SRC_NOCLK		5
+
+/* Used to identify the pfd_in_ref mux source */
+#define SI5338_PFD_IN_REF_REFCLK	0
+#define SI5338_PFD_IN_REF_FBCLK		1
+#define SI5338_PFD_IN_REF_DIVREFCLK	2
+#define SI5338_PFD_IN_REF_DIVFBCLK	3
+#define SI5338_PFD_IN_REF_XOCLK		4
+#define SI5338_PFD_IN_REF_NOCLK		5
+
+/* Used to identify the pfd_in_fb mux source */
+#define SI5338_PFD_IN_FB_FBCLK		0
+#define SI5338_PFD_IN_FB_REFCLK		1
+#define SI5338_PFD_IN_FB_DIVFBCLK	2
+#define SI5338_PFD_IN_FB_DIVREFCLK	3
+#define SI5338_PFD_IN_FB_RESERVED	4
+#define SI5338_PFD_IN_FB_NOCLK		5
+
+/* Used to identify the mux source */
+#define SI5338_OUT_MUX_FBCLK		0
+#define SI5338_OUT_MUX_REFCLK		1
+#define SI5338_OUT_MUX_DIVFBCLK		2
+#define SI5338_OUT_MUX_DIVREFCLK	3
+#define SI5338_OUT_MUX_XOCLK		4
+#define SI5338_OUT_MUX_MS0		5
+#define SI5338_OUT_MUX_MSN		6	/* MS0/1/2/3 respectivelly */
+#define SI5338_OUT_MUX_NOCLK		7
+
+#define SI5338_OUT_DIS_HIZ		0
+#define SI5338_OUT_DIS_LOW		1
+#define SI5338_OUT_DIS_HI		2
+#define SI5338_OUT_DIS_ALWAYS_ON	3
+
+#endif
+
diff --git a/include/linux/platform_data/si5338.h b/include/linux/platform_data/si5338.h
new file mode 100644
index 0000000..86fcc84
--- /dev/null
+++ b/include/linux/platform_data/si5338.h
@@ -0,0 +1,49 @@
+/*
+ * Si5338A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5338_H__
+#define __LINUX_PLATFORM_DATA_SI5338_H__
+
+struct clk;
+
+/**
+ * struct si5338_clkout_config - Si5338 clock output configuration
+ * @clkout: clkout number
+ * @clkout_src: clkout source clock
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5338_clkout_config {
+	u8 clkout_src;
+	const char *drive;
+	u8 disable_state;
+	unsigned long rate;
+};
+
+/**
+ * struct si5338_platform_data - Platform data for the Si5338 clock driver
+ * @name_prefix: prefix to clock names
+ *		 In case multiple clock chips exist, each can have unique names
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @ref_src: reference clock source
+ * @fb_src: feedback clock source
+ * @pll_src: array of pll source clock setting
+ * @pll_master: index of MS (1 of 4) which can change pll clock
+ * @pll_vco: set pll vco clock. If this is set, pll_master is ignored
+ * @clkout: array of clkout configuration
+ */
+struct si5338_platform_data {
+	const char *name_prefix;
+	struct clk *clk_xtal;
+	struct clk *clkin[4];
+	u8 ref_src;
+	u8 fb_src;
+	u8 pll_src;
+	u8 pll_master;
+	u32 pll_vco;
+	struct si5338_clkout_config clkout[4];
+};
+
+#endif
-- 
1.7.9.5


WARNING: multiple messages have this Message-ID (diff)
From: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
To: mturquette-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>,
	Sebastian Hesselbarth
	<sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>,
	Andrey Filippov <andrey-MoRZu3FOBbXQT0dZR+AlfA@public.gmane.org>
Subject: [Patch v2] driver/clk/clk-si5338: Add common clock framework driver for si5338
Date: Mon, 15 Jun 2015 10:07:31 -0700	[thread overview]
Message-ID: <1434388051-23814-1-git-send-email-yorksun@freescale.com> (raw)

SI5338 is a programmable clock generator. It has 4 sets of inputs,
PLL, multisynth and dividers to make 4 outputs. This driver splits
them into multiple clocks to comply with common clock framework.

See Documentation/devicetree/bindings/clock/silabs,si5338.txt for
details.

Signed-off-by: York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
CC: Mike Turquette <mturquette-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
CC: Sebastian Hesselbarth <sebastian.hesselbarth-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
CC: Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
CC: Andrey Filippov <andrey-MoRZu3FOBbXQT0dZR+AlfA@public.gmane.org>
---
Change log:
  v2: Fix handling name prefix if the driver is unloaded and loaded again

 .../devicetree/bindings/clock/silabs,si5338.txt    |  173 +
 drivers/clk/Kconfig                                |   14 +-
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5338.c                           | 3656 ++++++++++++++++++++
 drivers/clk/clk-si5338.h                           |  305 ++
 include/dt-bindings/clock/clk-si5338.h             |   69 +
 include/linux/platform_data/si5338.h               |   49 +
 7 files changed, 4266 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5338.txt
 create mode 100644 drivers/clk/clk-si5338.c
 create mode 100644 drivers/clk/clk-si5338.h
 create mode 100644 include/dt-bindings/clock/clk-si5338.h
 create mode 100644 include/linux/platform_data/si5338.h

diff --git a/Documentation/devicetree/bindings/clock/silabs,si5338.txt b/Documentation/devicetree/bindings/clock/silabs,si5338.txt
new file mode 100644
index 0000000..aeeb81d
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt
@@ -0,0 +1,173 @@
+Binding for Silicon Labs Si5338 programmable i2c clock generator.
+
+Reference
+[1] Si5338 Data Sheet
+    http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5338.pdf
+
+The Si5338 is a programmable i2c clock generators with up to 4 output
+clocks. It has 4 sets of possible input clocks
+
+IN1/IN2: differential
+IN3: single-ended
+IN4: single-ended
+IN5/IN6: differential
+
+Additionally, IN1/IN2 can be used as XTAL with different setting.
+The clock tree looks like below (without support of zero-delay)
+
+
+      IN1/IN2 IN3         IN4 IN5/IN6
+         |     |           |     |
+   ------|     |           |     |
+   |     |     |           |     |
+   |     \     /           \     /
+   |      \   /             \   /
+   |       \ /               \ /
+ XTAL     REFCLK            FBCLK
+   |       |  \             /   |
+   |       |   \           /    |
+   |       | DIVREFCLK DIVFBCLK |
+   |       |     \       /      |
+   |       |      \     /       |
+   |       |       \   /        |
+   |       |        PLL         |
+   |       |      / | | \       |
+   |       |     /  / \  \      |
+   |       |    /  /   \  \     |
+   |       |   /   |   |   \    |
+   |       |   |   |   |   |    |
+   |       |  MS0 MS1 MS2 MS3   |
+   |       |   |   |   |   |    |
+
+       OUT0  OUT1  OUT2  OUT3
+
+The output clock can choose from any of the above clock as its source, with
+exceptions: MS1 can only be used for OUT1, MS2 can only be used for OUT2, MS3
+can only be used for OUT3.
+
+==I2C device node==
+
+Required properties:
+- compatible: shall be "silabs,si5338".
+- reg: i2c device address, shall be 0x60, 0x61, 0x70, or 0x71
+- #clock-cells: shall be set to 1 for multiple outputs
+- clocks: list of parent clocks in the order of <xtal>, <in1/2>, <in3>, <in4>, <in5/6>
+          Note, xtal and in1/2 are mutually exclusive. Only one can be set.
+- #address-cells: shall be set to 1.
+- #size-cells: shall be set to 0.
+
+Optional properties if not set by platform driver:
+- silab,ref-source: source of refclk, valid value is defined as
+	#define SI5338_REF_SRC_CLKIN12          0
+	#define SI5338_REF_SRC_CLKIN3           1
+	#define SI5338_REF_SRC_XTAL             4
+- silab,fb-source:  source of fbclk, valid value is defined as
+	#define SI5338_FB_SRC_CLKIN4            2
+	#define SI5338_FB_SRC_CLKIN56           3
+	#define SI5338_FB_SRC_NOCLK             5
+- silabs,pll-source: source of pll, valid value is defined as
+	#define SI5338_PFD_IN_REF_REFCLK           0
+	#define SI5338_PFD_IN_REF_FBCLK            1
+	#define SI5338_PFD_IN_REF_DIVREFCLK        2
+	#define SI5338_PFD_IN_REF_DIVFBCLK         3
+	#define SI5338_PFD_IN_REF_XOCLK            4
+	#define SI5338_PFD_IN_REF_NOCLK            5
+- silabs,pll-master: Pick one MS (0, 1, 2, or 3) to allow chaning PLL rate
+	This is arbitrary since MS0/1/2/3 share one PLL.  PLL can be calculated
+	backward to satisfy MS.
+
+==Child nodes==
+
+Each of the clock outputs can be configured individually by
+using a child node to the I2C device node. If a child node for a clock
+output is not set, platform driver has to set up.
+
+Required child node properties:
+- reg: number of clock output.
+
+Optional child node properties:
+- silabs,drive-config: the configuration of output driver
+  The valid value list is long. Please refer to soruce code.
+- silabs,clock-source: source clock of the output divider
+	#define SI5338_OUT_MUX_FBCLK            0
+	#define SI5338_OUT_MUX_REFCLK           1
+	#deinfe SI5338_OUT_MUX_DIVFBCLK         2
+	#deinfe SI5338_OUT_MUX_DIVREFCLK        3
+	#deinfe SI5338_OUT_MUX_XOCLK            4
+	#deinfe SI5338_OUT_MUX_MS0              5
+	#deinfe SI5338_OUT_MUX_MSN              6 /* MS0/1/2/3 */
+	#deinfe SI5338_OUT_MUX_NOCLK            7
+- silabs,disable-state : clock output disable state, shall be
+	#define SI5338_OUT_DIS_HIZ              0
+	#define SI5338_OUT_DIS_LOW              1
+	#define SI5338_OUT_DIS_HI               2
+	#define SI5338_OUT_DIS_ALWAYS_ON        3
+
+==Example==
+
+/* 25MHz reference crystal */
+ref25: ref25M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <25000000>;
+};
+clkin56: ref100M {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <100000000>;
+};
+i2c-master-node {
+	si5338: clock-generator@70 {
+		compatible = "silabs,si5338";
+		reg = <0x70>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#clock-cells = <1>;
+
+		/* connect xtal to 25MHz, in5/in6 to 100MHz */
+		clocks = <&ref25>, <>, <>, <>, <&clkin56>;
+
+		/* connect xtal as source of refclk */
+		silab,ref-source = <SI5338_REF_SRC_XTAL>;
+
+		/* connect in5/in6 as source of fbclk */
+		silab,fb-source = <SI5338_FB_SRC_CLKIN56>;
+
+		/* connect divrefclk as soruce of pll */
+		silab,pll-source = <SI5338_PFD_IN_REF_DIVREFCLK>;
+
+		/* Choose one MS for pll master */
+		silabs,pll-master = <0>;
+
+		/* output */
+		clkout0 {
+			reg = <0>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MS0>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout1 {
+			reg = <1>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout2 {
+			reg = <2>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+		clkout3 {
+			reg = <3>;
+			silabs,drive-config = "1V8_LVDS";
+			silabs,clock-source = <SI5338_OUT_MUX_MSN>;
+			silabs,disable-state = <SI5338_OUT_DIS_HIZ>;
+			clock-frequency = <125000000>;
+		};
+
+	};
+};
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 0b474a0..07f0b2f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -10,7 +10,7 @@ config HAVE_MACH_CLKDEV
 	bool
 
 config COMMON_CLK
-	bool
+	tristate "Common Clock"
 	select HAVE_CLK_PREPARE
 	select CLKDEV_LOOKUP
 	select SRCU
@@ -78,6 +78,18 @@ config COMMON_CLK_SI570
 	  This driver supports Silicon Labs 570/571/598/599 programmable
 	  clock generators.
 
+config COMMON_CLK_SI5338
+	tristate "Clock driver for SiLabs 5338"
+	depends on I2C
+	select REGMAP_I2C
+	select RATIONAL
+	---help---
+	  This driver supports Silicon Labs 5338 programmable clock generators,
+	  using common clock framework. It needs parent clock as input(s).
+	  Internal clocks are registered with unique names in case multiple
+	  devices exist. See devicetree/bindings/clock/silabs,si5338.txt
+	  under Documentation for details.
+
 config COMMON_CLK_S2MPS11
 	tristate "Clock driver for S2MPS1X/S5M8767 MFD"
 	depends on MFD_SEC_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d478ceb..c6aab0e 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS)		+= clk-palmas.o
 obj-$(CONFIG_CLK_QORIQ)			+= clk-qoriq.o
 obj-$(CONFIG_COMMON_CLK_RK808)		+= clk-rk808.o
 obj-$(CONFIG_COMMON_CLK_S2MPS11)	+= clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SI5338)		+= clk-si5338.o
 obj-$(CONFIG_COMMON_CLK_SI5351)		+= clk-si5351.o
 obj-$(CONFIG_COMMON_CLK_SI570)		+= clk-si570.o
 obj-$(CONFIG_CLK_TWL6040)		+= clk-twl6040.o
diff --git a/drivers/clk/clk-si5338.c b/drivers/clk/clk-si5338.c
new file mode 100644
index 0000000..fa50050
--- /dev/null
+++ b/drivers/clk/clk-si5338.c
@@ -0,0 +1,3656 @@
+/*
+ * clk-si5338.c: Silicon Labs Si5338 I2C Clock Generator
+ *
+ * Copyright 2015 Freescale Semiconductor
+ * York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
+ *
+ * Some code is taken from si5338.c by Andrey Filippov  <andrey-MoRZu3FOBbXQT0dZR+AlfA@public.gmane.org>
+ * Copyright 2013 Elphel, Inc.
+ *
+ * SI5338 has several blocks, including
+ *   Inputs (IN1/IN2, IN3, IN4, IN5/IN6, XTAL)
+ *   PLL (Synthesis stage 1)
+ *   MultiSynth (Synthesis state 2)
+ *   Outputs (OUT0/1/2/3)
+ * Each block is registered as a clock device to form a tree structure.
+ * See Documentation/devicetree/bindings/clock/silabs,si5338.txt for details.
+ *
+ * This driver uses regmap to cache register values to reduce transactions
+ * on I2C bus. Volatile registers are specified.
+ *
+ * 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.
+ */
+
+#include <dt-bindings/clock/clk-si5338.h>
+#include <linux/bsearch.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/freezer.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/si5338.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include "clk-si5338.h"
+
+#define MAX_NAME_PREFIX 30 /* max 30 characters for the name_prefix */
+#define MAX_NAME_LENGTH 10 /* max 10 charactors for the internal names */
+
+struct si5338_driver_data;
+
+/* Internal parameters used by PLL and MS
+ * They are used in recalc rate functions before being
+ * written to the device.
+ */
+struct si5338_parameters {
+	u32	p123[3];
+	bool	valid;
+};
+
+/* This structure saves params and num variable for clocks
+ * Internal clocks with parameters of multiple input/output
+ * use this structure.
+ */
+struct si5338_hw_data {
+	struct clk_hw			hw;
+	struct si5338_driver_data	*drvdata;
+	/* params is only used for PLL and multisynth clocks */
+	struct si5338_parameters	params;
+	/*
+	 * For clkin, clkout, multisynth: index of itself
+	 * For refclk, fbclk, pll: index of its source
+	 */
+	u8				num;
+};
+
+struct si5338_driver_data {
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk_onecell_data onecell;
+
+	/* The structure of clocks are
+	 * Input clocks: pclkin12 - IN1/2
+	 *		 pclkin3  - IN3
+	 *		 pclkin4  - IN4
+	 *		 pclkin56 - IN5/6
+	 *		 pxtal    - IN1/2 XTAL
+	 * Internal clocks:
+	 *		 xoclk		- from pxtal
+	 *		 refclk		- from one of IN1/2, IN3, XTAL
+	 *		 divrefclk	- from refclk with divider
+	 *		 fbclk		- from IN4 or IN5/6
+	 *		 divfbclk	- from fbclk
+	 *		 MS0/1/2/3	- from one of xoclk, refclk
+	 *				  diverefclk, fbclk, divfbclk
+	 * Output clocks:
+	 *		 clkout0/1/2/3	- from one of internal clocks
+	 */
+	/* parent clocks */
+	struct clk		*pxtal;
+	const char		*pxtal_name;
+	struct clk		*pclkin[4];
+	const char		*pclkin_name[4];
+
+	/* internal and output clocks */
+	char name_prefix[MAX_NAME_PREFIX];
+	struct clk_hw		xtal;
+	struct si5338_hw_data	clkin[4];
+	struct si5338_hw_data	refclk;
+	struct clk_hw		divrefclk;
+	struct si5338_hw_data	fbclk;
+	struct clk_hw		divfbclk;
+	struct si5338_hw_data	pll;
+	struct si5338_hw_data	*msynth;
+	struct si5338_hw_data	*clkout;
+};
+
+static const char * const si5338_input_names[] = {
+	"in1/in2", "in3", "in4", "in5/in6", "xtal", "noclk"
+};
+
+static const char * const si5338_pll_src_names[] = {
+	"refclk", "fbclk", "divrefclk", "divfbclk", "xtal", "noclk"
+};
+
+static const char * const si5338_msynth_src_names[] = {
+	"pll"
+};
+
+static const char * const si5338_msynth_names[] = {
+	"ms0", "ms1", "ms2", "ms3"
+};
+static const char * const si5338_clkout_names[] = {
+	"clkout0", "clkout1", "clkout2", "clkout3"
+};
+static const char * const si5338_clkout_src_names[] = {
+	"fbclk", "refclk", "divfbclk", "divrefclk", "xtal",
+	"ms0",
+	"msn", /* it is actually ms0, ms1, ms2, ms3 dependings on clkout */
+	"noclk",
+};
+
+/* This array is used to determine if a register is writable. It is also
+ * used to create register dump from sysfs. The mask is not used in this
+ * driver. The data is in format of 0xAAAMM where AAA is address, MM is bit
+ * mask. 1 means the corresponding bit is writable.
+ * Created from SiLabs ClockBuilder output.
+ * Note: Register 226, 230, 241, 246, 255 are not included in header file
+ *	 from ClockBuilder v2.7 or later. Manually added here.
+ */
+static const u32 register_masks[] = {
+	0x61d, 0x1b80, 0x1cff, 0x1dff, 0x1eff, 0x1fff, 0x20ff, 0x21ff,
+	0x22ff, 0x23ff, 0x241f, 0x251f, 0x261f, 0x271f, 0x28ff, 0x297f,
+	0x2a3f, 0x2dff, 0x2eff, 0x2f3f, 0x30ff, 0x31ff, 0x32ff, 0x33ff,
+	0x34ff, 0x35ff, 0x36ff, 0x37ff, 0x38ff, 0x39ff, 0x3aff, 0x3bff,
+	0x3cff, 0x3dff, 0x3e3f, 0x3fff, 0x40ff, 0x41ff, 0x42ff, 0x43ff,
+	0x44ff, 0x45ff, 0x46ff, 0x47ff, 0x48ff, 0x493f, 0x4aff, 0x4bff,
+	0x4cff, 0x4dff, 0x4eff, 0x4fff, 0x50ff, 0x51ff, 0x52ff, 0x53ff,
+	0x543f, 0x55ff, 0x56ff, 0x57ff, 0x58ff, 0x59ff, 0x5aff, 0x5bff,
+	0x5cff, 0x5dff, 0x5eff, 0x5f3f, 0x61ff, 0x62ff, 0x63ff, 0x64ff,
+	0x65ff, 0x66ff, 0x67ff, 0x68ff, 0x69ff, 0x6abf, 0x6bff, 0x6cff,
+	0x6dff, 0x6eff, 0x6fff, 0x70ff, 0x71ff, 0x72ff, 0x73ff, 0x74ff,
+	0x75ff, 0x76ff, 0x77ff, 0x78ff, 0x79ff, 0x7aff, 0x7bff, 0x7cff,
+	0x7dff, 0x7eff, 0x7fff, 0x80ff, 0x810f, 0x820f, 0x83ff, 0x84ff,
+	0x85ff, 0x86ff, 0x87ff, 0x88ff, 0x89ff, 0x8aff, 0x8bff, 0x8cff,
+	0x8dff, 0x8eff, 0x8fff, 0x90ff, 0x98ff, 0x99ff, 0x9aff, 0x9bff,
+	0x9cff, 0x9dff, 0x9e0f, 0x9f0f, 0xa0ff, 0xa1ff, 0xa2ff, 0xa3ff,
+	0xa4ff, 0xa5ff, 0xa6ff, 0xa7ff, 0xa8ff, 0xa9ff, 0xaaff, 0xabff,
+	0xacff, 0xadff, 0xaeff, 0xafff, 0xb0ff, 0xb1ff, 0xb2ff, 0xb3ff,
+	0xb4ff, 0xb50f, 0xb6ff, 0xb7ff, 0xb8ff, 0xb9ff, 0xbaff, 0xbbff,
+	0xbcff, 0xbdff, 0xbeff, 0xbfff, 0xc0ff, 0xc1ff, 0xc2ff, 0xc3ff,
+	0xc4ff, 0xc5ff, 0xc6ff, 0xc7ff, 0xc8ff, 0xc9ff, 0xcaff, 0xcb0f,
+	0xccff, 0xcdff, 0xceff, 0xcfff, 0xd0ff, 0xd1ff, 0xd2ff, 0xd3ff,
+	0xd4ff, 0xd5ff, 0xd6ff, 0xd7ff, 0xd8ff, 0xd9ff, 0xe204, 0xe6ff,
+	0xf1ff, 0xf202, 0xf6ff, 0xffff, 0x11fff,
+	0x120ff, 0x121ff, 0x122ff, 0x123ff, 0x124ff, 0x125ff, 0x126ff, 0x127ff,
+	0x128ff, 0x129ff, 0x12aff, 0x12b0f, 0x12fff, 0x130ff, 0x131ff, 0x132ff,
+	0x133ff, 0x134ff, 0x135ff, 0x136ff, 0x137ff, 0x138ff, 0x139ff, 0x13aff,
+	0x13b0f, 0x13fff, 0x140ff, 0x141ff, 0x142ff, 0x143ff, 0x144ff, 0x145ff,
+	0x146ff, 0x147ff, 0x148ff, 0x149ff, 0x14aff, 0x14b0f, 0x14fff, 0x150ff,
+	0x151ff, 0x152ff, 0x153ff, 0x154ff, 0x155ff, 0x156ff, 0x157ff, 0x158ff,
+	0x159ff, 0x15aff, 0x15b0f
+};
+
+/*
+ * Si5338 i2c regmap
+ */
+static inline u8 si5338_reg_read(struct si5338_driver_data *drvdata, u16 reg)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read(drvdata->regmap, reg, &val);
+	if (ret) {
+		dev_err(&drvdata->client->dev,
+			"unable to read from reg %02x: return %d\n", reg, ret);
+		return 0;
+	}
+
+	return (u8)val;
+}
+
+static inline int si5338_reg_write(struct si5338_driver_data *drvdata,
+				   u16 reg, u8 val, u8 mask)
+{
+	if (mask != 0xff)
+		return regmap_update_bits(drvdata->regmap, reg, mask, val);
+
+	return regmap_write(drvdata->regmap, reg, val);
+}
+
+static int write_field(struct si5338_driver_data *drvdata, u8 data, u32 awe)
+{
+	int rc, nshift;
+	u8 mask, reg_data;
+	u16 reg;
+
+	if (!drvdata) {
+		pr_err("Invalid drvdata\n");
+		return -EINVAL;
+	}
+	reg = awe >> 8;
+	mask = awe & 0xff;
+	if (mask != 0) {
+		nshift = 0;
+		while (((1 << nshift) & mask) == 0)
+			nshift++;
+		reg_data = (data & 0xff) << nshift;
+		rc = si5338_reg_write(drvdata, reg, reg_data, mask);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int write_multireg64(struct si5338_driver_data *drvdata,
+				u64 data, const u32 *awe)
+{
+	int i, rc, nshift, nbits;
+	u8 mask, reg_data;
+	u16 reg;
+
+	for (i = 0; awe[i] != 0; i++) {
+		reg = awe[i] >> 8;
+		mask = awe[i] & 0xff;
+		if (mask != 0) {
+			nshift = 0;
+			nbits = 1;
+			while (((1 << nshift) & mask) == 0)
+				nshift++;
+			while (((1 << (nshift + nbits)) & mask) != 0)
+				nbits++;
+			/* may have some garbage in high bits,
+			 * will be cut of by mask
+			 */
+			reg_data = (data & 0xff) << nshift;
+			data >>= nbits;
+			rc = si5338_reg_write(drvdata, reg, reg_data, mask);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+static s64 read_multireg64(struct si5338_driver_data *drvdata, const u32 *awe)
+{
+	int i, nshift, nbits, full_shift = 0;
+	u8 mask;
+	u16 reg;
+	s64 data = 0, rc;
+
+	for (i = 0; awe[i] != 0; i++) {
+		reg = awe[i] >> 8;
+		mask = awe[i] & 0xff;
+		if (mask != 0) {
+			nshift = 0;
+			nbits = 1;
+			while (((1 << nshift) & mask) == 0)
+				nshift++;
+			while (((1 << (nshift + nbits)) & mask) != 0)
+				nbits++;
+			rc = si5338_reg_read(drvdata, reg);
+			if (rc < 0)
+				return rc;
+
+			rc &= mask;
+			rc >>= nshift;
+			rc <<= full_shift;
+			data |= rc;
+			full_shift += nbits;
+		}
+	}
+
+	return data;
+}
+
+static int read_field(struct si5338_driver_data *drvdata, u32 awe)
+{
+	int rc, nshift;
+	u8 mask;
+	u16 reg;
+
+	reg = awe >> 8;
+	mask = awe & 0xff;
+
+	if (mask != 0) {
+		nshift = 0;
+		while (((1 << nshift) & mask) == 0)
+			nshift++;
+		rc = si5338_reg_read(drvdata, reg);
+		if (rc < 0)
+			return rc;
+
+		return (rc & mask) >> nshift;
+	}
+
+	return 0;
+}
+
+static int si5338_find_mask(const u32 *reg, const u32 *register_mask)
+{
+	if ((*reg) > ((*register_mask) >> 8))
+		return 1;
+	if ((*reg) < ((*register_mask) >> 8))
+		return -1;
+
+	return 0;
+}
+
+static int find_mask(const void *key, const void *elt)
+{
+	return si5338_find_mask(key, elt);
+}
+
+static bool si5338_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+	u32 *mask = NULL;
+
+	mask = bsearch(&reg, register_masks, ARRAY_SIZE(register_masks),
+		       sizeof(u32), find_mask);
+
+	if (mask)
+		return true;
+
+	return false;
+}
+
+static bool si5338_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case (AWE_STATUS >> 8):
+	case (AWE_SOFT_RESET >> 8):
+	case (AWE_FCAL_07_00 >> 8):
+	case (AWE_FCAL_15_08 >> 8):
+	case (AWE_FCAL_17_16 >> 8):
+		return true;
+	}
+	return false;
+}
+static const struct regmap_range_cfg si5338_regmap_range[] = {
+	{
+		.selector_reg = REG5338_PAGE,		/* 255 */
+		.selector_mask  = REG5338_PAGE_MASK,	/* 1 */
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 256,
+		.range_min = 0,
+		.range_max = 347,
+	},
+};
+
+static struct regmap_config si5338_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = 347,
+	.ranges = si5338_regmap_range,
+	.num_ranges = ARRAY_SIZE(si5338_regmap_range),
+	.writeable_reg = si5338_regmap_is_writeable,
+	.volatile_reg = si5338_regmap_is_volatile,
+};
+
+/*
+ * SI5338 register access
+ */
+static int _verify_output_channel(int chn)
+{
+	if (chn < 0 || chn > 3) {
+		pr_err("Invalid output channel: %d (only 0..3 are allowed)\n",
+			chn);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int get_fb_mux(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_FB_MUX);
+}
+
+static int get_in_mux(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_IN_MUX);
+}
+
+static int set_in_mux(struct si5338_driver_data *drvdata, int data)
+{
+	int data1, rc;
+
+	switch (data) {
+	case 0:
+		data1 = 0;
+		break;
+	case 1:
+		data1 = 2;
+		break;
+	case 2:
+		data1 = 5;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid value for input multiplexer %d\n", data);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, data, AWE_IN_MUX);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, data1, AWE_IN_MUX1);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int set_fb_mux(struct si5338_driver_data *drvdata, int data)
+{
+	int data1, rc;
+
+	switch (data) {
+	case 0:
+		data1 = 0;
+		break;
+	case 1:
+		data1 = 1;
+		break;
+	case 2:
+		data1 = 0;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid value for feedback multiplexer %d\n", data);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, data, AWE_FB_MUX);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, data1, AWE_FB_MUX1);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * PLL has two inputs, each has multiple sources
+ * 0 - pfd_in_ref
+ * 1 - pfd_in_fb
+ */
+static int get_in_pfd_ref_fb(struct si5338_driver_data *drvdata, int chn)
+{
+	return read_field(drvdata, chn ? AWE_PFD_FB : AWE_PFD_REF);
+}
+
+static int set_in_pfd_ref_fb(struct si5338_driver_data *drvdata,
+				u8 val, int chn)
+{
+	int rc;
+
+	if (val > SI5338_PFD_IN_REF_NOCLK) {
+		dev_err(&drvdata->client->dev,
+			"Invalid value for input pfd selector: %d\n", val);
+		return -EINVAL;
+	}
+	rc = write_field(drvdata, val, chn ? AWE_PFD_FB : AWE_PFD_REF);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+
+static const u8 in_div_values[] = { 1, 2, 4, 8, 16, 32 };
+
+/*
+ * Set div for the two dividers
+ * 0 - p1div
+ * 1 - p2div
+ */
+static int set_in_pdiv(struct si5338_driver_data *drvdata, int div, int chn)
+{
+	int rc;
+	u8 val;
+
+	for (val = 0; val < ARRAY_SIZE(in_div_values); val++) {
+		if (in_div_values[val] == div) {
+			rc = write_field(drvdata, val,
+					 chn ? AWE_P2DIV : AWE_P1DIV);
+			if (rc < 0)
+				return rc;
+
+			return 0;
+		}
+	}
+	dev_err(&drvdata->client->dev,
+		"Invalid value for input divider: %d\n", div);
+
+	return -EINVAL;
+}
+
+/*
+ * Si5338 xtal clock input
+ * The clock needs to be within [8MHz .. 30MHz]
+ */
+static int si5338_xtal_prepare(struct clk_hw *hw)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, xtal);
+	unsigned long rate = clk_get_rate(hw->clk);
+	int xtal_mode;
+
+	if (rate  < XTAL_FREQMIN) {
+		dev_err(&drvdata->client->dev,
+			"Xtal input frequency too low: %lu < %llu\n",
+			rate, XTAL_FREQMIN);
+		return -EINVAL;
+	}
+	if (rate > XTAL_FREQMAX) {
+		dev_err(&drvdata->client->dev,
+			"Xtal input frequency too high: %lu > %llu\n",
+			rate, XTAL_FREQMAX);
+		return -EINVAL;
+	}
+
+	if (rate > 26000000ll)
+		xtal_mode = 3;
+	else if (rate > 19000000ll)
+		xtal_mode = 2;
+	else if (rate > 11000000ll)
+		xtal_mode = 1;
+	else
+		xtal_mode = 0;
+
+	return write_field(drvdata, xtal_mode, AWE_XTAL_FREQ);
+}
+
+static const struct clk_ops si5338_xtal_ops = {
+	.prepare = si5338_xtal_prepare,
+};
+
+static unsigned long si5338_clkin_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long max_rate;
+
+	max_rate = (hwdata->num == SI5338_INPUT_CLK12 ||
+		    hwdata->num == SI5338_INPUT_CLK56) ?
+			INFREQMAX : INFREQMAX34;
+	if (parent_rate  < INFREQMIN) {
+		dev_err(&drvdata->client->dev,
+			"Input frequency too low: %lu < %llu\n",
+			parent_rate, INFREQMIN);
+		return -EINVAL;
+	}
+	if (parent_rate > max_rate) {
+		dev_err(&drvdata->client->dev,
+			"Input frequency too high: %lu > %lu\n",
+			parent_rate, max_rate);
+		return -EINVAL;
+	}
+
+	return parent_rate;
+}
+
+static const struct clk_ops si5338_clkin_ops = {
+	.recalc_rate = si5338_clkin_recalc_rate,
+};
+
+/*
+ * Si5338 refclk inputs
+ * Input frequency range
+ *	IN1/IN2 differential clock [5MHz..710MHz]
+ *	IN3 single-ended clock [5MHz..200MHz]
+ * Enforced by si5338_clkin_recalc_rate
+ */
+static int si5338_refclk_reparent(struct si5338_driver_data *drvdata, u8 index)
+{
+	int rc = -EINVAL;
+	struct si5338_hw_data *hwdata = &drvdata->refclk;
+
+	hwdata->num = SI5338_FB_SRC_NOCLK;
+	switch (index) {
+	case SI5338_REF_SRC_XTAL:
+		/* in mux to XO */
+		rc = set_in_mux(drvdata, 2);
+		hwdata->num = 2;
+		break;
+	case SI5338_REF_SRC_CLKIN12:
+		/* in mux to IN12 */
+		rc = set_in_mux(drvdata, 0);
+		hwdata->num = 0;
+		break;
+	case SI5338_REF_SRC_CLKIN3:
+		rc = set_in_mux(drvdata, 1);
+		hwdata->num = 1;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent (%d) for refclk\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * refclk's parent
+ * 0 - IN1/IN2
+ * 1 - IN3
+ * 2 - XTAL
+ */
+static int si5338_refclk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc = -EINVAL;
+
+	switch (index) {
+	case 0:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN12);
+		break;
+	case 1:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_CLKIN3);
+		break;
+	case 2:
+		rc = si5338_refclk_reparent(drvdata, SI5338_REF_SRC_XTAL);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalie parent index for refclk: %d\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+static u8 si5338_refclk_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	/* get_in_mux return value is aligned with the parent index */
+	return (u8)get_in_mux(drvdata);
+}
+
+static const struct clk_ops si5338_refclk_ops = {
+	.set_parent = si5338_refclk_set_parent,
+	.get_parent = si5338_refclk_get_parent,
+};
+
+/*
+ * divrefclk's parent is refclk
+ */
+unsigned long si5338_divrefclk_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, divrefclk);
+	int idiv, rc;
+
+	for (idiv = 0; idiv < 5; idiv++) {
+		if ((parent_rate >> idiv) <= INFREQDIV)
+			break;
+	}
+	rc = set_in_pdiv(drvdata, 1 << idiv, 0);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Error setting p1div\n");
+		return 0;
+	}
+
+	return parent_rate >> idiv;
+}
+
+static const struct clk_ops si5338_divrefclk_ops = {
+	.recalc_rate = si5338_divrefclk_recalc_rate,
+};
+
+/*
+ * Si5338 fbclk inputs
+ * Input frequency range
+ *	IN4 single-ended clock [5MHz..200MHz]
+ *	IN5/IN6 differential clock [5MHz..710MHz]
+ * Enforced by si5338_clkin_recalc_rate
+ */
+static int si5338_fbclk_reparent(struct si5338_driver_data *drvdata, u8 index)
+{
+	struct si5338_hw_data *hwdata = &drvdata->fbclk;
+	int rc = -EINVAL;
+
+	hwdata->num = SI5338_FB_SRC_NOCLK;
+	switch (index) {
+	case SI5338_FB_SRC_CLKIN4:
+		/* in mux to IN4 */
+		rc = set_fb_mux(drvdata, 1);
+		hwdata->num = 0;
+		break;
+	case SI5338_FB_SRC_CLKIN56:
+		/* in mux to IN56 */
+		rc = set_fb_mux(drvdata, 0);
+		hwdata->num = 1;
+		break;
+	case SI5338_FB_SRC_NOCLK:
+		rc = set_fb_mux(drvdata, 2);
+		hwdata->num = 2;
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent (%d) for fbclk\n", index);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * fbclk's parent can be
+ * 0 - IN4
+ * 1 - IN5/IN6
+ * 2 - NOCLK
+ */
+static int si5338_fbclk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc = -EINVAL;
+
+	switch (index) {
+	case 0:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN4);
+		break;
+	case 1:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_CLKIN56);
+		break;
+	case 2:
+		rc = si5338_fbclk_reparent(drvdata, SI5338_FB_SRC_NOCLK);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid parent index for fbclk\n");
+	}
+
+	return rc;
+}
+
+static u8 si5338_fbclk_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+
+	/* Return value 0: IN5/IN6
+	 *		1: IN4
+	 *		2: noclk
+	 */
+	rc = get_fb_mux(drvdata);
+	switch (rc) {
+	case 0:
+		return 1;
+	case 1:
+		return 0;
+	case 2:
+		return 2;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+static const struct clk_ops si5338_fbclk_ops = {
+	.set_parent = si5338_fbclk_set_parent,
+	.get_parent = si5338_fbclk_get_parent,
+};
+
+/*
+ * divfbclk's parent is fbclk
+ */
+unsigned long si5338_divfbclk_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct si5338_driver_data *drvdata =
+		container_of(hw, struct si5338_driver_data, divfbclk);
+	int idiv, rc;
+
+	for (idiv = 0; idiv < 5; idiv++) {
+		if ((parent_rate >> idiv) <= INFREQDIV)
+			break;
+	}
+	rc = set_in_pdiv(drvdata, 1 << idiv, 1);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Error setting p1div\n");
+		return 0;
+	}
+
+	return parent_rate >> idiv;
+}
+
+static const struct clk_ops si5338_divfbclk_ops = {
+	.recalc_rate = si5338_divfbclk_recalc_rate,
+};
+
+/*
+ * PLL and MultiSynth
+ */
+static int remove_common_factor(u64 *num_denom)
+{
+	u64 a, b, r;
+
+	if (num_denom[1] == 0)
+		return -1; /* zero denominator */
+
+	if (num_denom[0] == 0) {
+		num_denom[1] = 1;
+		return 1;
+	}
+
+	a = max(num_denom[0], num_denom[1]);
+	b = min(num_denom[0], num_denom[1]);
+	r = b;
+	while (r > 1) {
+		r = a - b * div64_u64(a, b);
+		if (r == 0) {
+			num_denom[0] = div64_u64(num_denom[0], b);
+			num_denom[1] = div64_u64(num_denom[1], b);
+			return 1;
+		}
+		a = b;
+		b = r;
+	}
+
+	return 0; /* nothing done */
+}
+
+static const u32 awe_msx[5][3][5] = {
+	{
+		{
+			AWE_MS0_P1_07_00,
+			AWE_MS0_P1_15_08,
+			AWE_MS0_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS0_P2_05_00,
+			AWE_MS0_P2_13_06,
+			AWE_MS0_P2_21_14,
+			AWE_MS0_P2_29_22,
+			0
+		},
+		{
+			AWE_MS0_P3_07_00,
+			AWE_MS0_P3_15_08,
+			AWE_MS0_P3_23_16,
+			AWE_MS0_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS1_P1_07_00,
+			AWE_MS1_P1_15_08,
+			AWE_MS1_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS1_P2_05_00,
+			AWE_MS1_P2_13_06,
+			AWE_MS1_P2_21_14,
+			AWE_MS1_P2_29_22,
+			0
+		},
+		{
+			AWE_MS1_P3_07_00,
+			AWE_MS1_P3_15_08,
+			AWE_MS1_P3_23_16,
+			AWE_MS1_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS2_P1_07_00,
+			AWE_MS2_P1_15_08,
+			AWE_MS2_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS2_P2_05_00,
+			AWE_MS2_P2_13_06,
+			AWE_MS2_P2_21_14,
+			AWE_MS2_P2_29_22,
+			0
+		},
+		{
+			AWE_MS2_P3_07_00,
+			AWE_MS2_P3_15_08,
+			AWE_MS2_P3_23_16,
+			AWE_MS2_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MS3_P1_07_00,
+			AWE_MS3_P1_15_08,
+			AWE_MS3_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MS3_P2_05_00,
+			AWE_MS3_P2_13_06,
+			AWE_MS3_P2_21_14,
+			AWE_MS3_P2_29_22,
+			0
+		},
+		{
+			AWE_MS3_P3_07_00,
+			AWE_MS3_P3_15_08,
+			AWE_MS3_P3_23_16,
+			AWE_MS3_P3_29_24,
+			0
+		}
+	},
+	{
+		{
+			AWE_MSN_P1_07_00,
+			AWE_MSN_P1_15_08,
+			AWE_MSN_P1_17_16,
+			0,
+			0
+		},
+		{
+			AWE_MSN_P2_05_00,
+			AWE_MSN_P2_13_06,
+			AWE_MSN_P2_21_14,
+			AWE_MSN_P2_29_22,
+			0
+		},
+		{
+			AWE_MSN_P3_07_00,
+			AWE_MSN_P3_15_08,
+			AWE_MSN_P3_23_16,
+			AWE_MSN_P3_29_24,
+			0
+		}
+	}
+};
+
+/*
+ * Read parameters of
+ * 0 - MS0
+ * 1 - MS1
+ * 2 - MS2
+ * 3 - MS3
+ * 4 - MSN (PLL)
+ */
+static int get_ms_p123(struct si5338_driver_data *drvdata, u32 *p123, int chn)
+{
+	int i;
+	s64 rc;
+
+	if (chn < 0 || chn > 4) {
+		dev_err(&drvdata->client->dev,
+			"Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",
+			chn);
+		return -EINVAL;
+	}
+	for (i = 0; i < 3; i++) {
+		rc = read_multireg64(drvdata, awe_msx[chn][i]);
+		if (rc < 0)
+			return (int)rc;
+
+		p123[i] = (u32)rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Calculte MS ratio from parameters
+ * ms = a + b / c, where
+ *	a = ms[0], b = ms[1], c = ms[2]
+ * SI5338 RM states the formula of parameters as:
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	p2 = mod((b * 128), c)
+ *	p3 = c
+ * To reverse the formula, we have
+ *	b * 128 = k * c + p2; k < 128, p2 < c
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	   = a * 128 + floor((b * 128) / c) - 512
+ *	   = a * 128 + k - 512
+ *	k = mod(p1, 128) = p1 & 0x7f
+ *	c = p3
+ *	b = (k * c + p2) / 128 = ((p1 & 0x7f) * p3 + p2) >> 7
+ *	a = (p1 + 512) >> 7 = (p1 >> 7) + 4
+ */
+static int p123_to_ms(u64 *ms, u32 *p123)
+{
+	if (p123[0] == 0 && p123[1] == 0 && p123[2] == 0) {
+		/* uninitialized parameters in device */
+		ms[0] = 0;
+		ms[1] = 0;
+		ms[2] = 1;
+	} else {
+		/* c = p3 */
+		ms[2] = p123[2];
+		/* b = (c * (p1 & 0x7f) + p2) >> 7 */
+		ms[1] = (ms[2] * (p123[0] & 0x7f) + p123[1]) >> 7;
+		/* a = (p1 >> 7) + 4 */
+		ms[0] = (p123[0] >> 7) + 4;
+	}
+	pr_debug("ms[]=%llu + %llu/%llu, p123=%u %u %u\n",
+		 ms[0], ms[1], ms[2], p123[0], p123[1], p123[2]);
+
+	return 0;
+}
+
+static const u32 awe_ms_hs[] = {
+	AWE_MS0_HS,
+	AWE_MS1_HS,
+	AWE_MS2_HS,
+	AWE_MS3_HS
+};
+
+/*
+ * Read parameters of
+ * 0 - MS0
+ * 1 - MS1
+ * 2 - MS2
+ * 3 - MS3
+ * 4 - MSN (PLL)
+ */
+static int set_ms_p123(struct si5338_driver_data *drvdata,
+			u32 *p123, int chn)
+{
+	int i, rc, hs = 0;
+
+	if (chn < 0 || chn > 4) {
+		dev_err(&drvdata->client->dev,
+			"Invalid channel %d. Only 0,1,2,3 and 4 (for MSN) are supported\n",
+			chn);
+		return -EINVAL;
+	}
+	/* high speed bit programming */
+	if (p123[0] < 512) { /* div less than 8 */
+		if (p123[0] < 128)
+			p123[0] = 0;
+		else
+			p123[0] = 256;
+		p123[1] = 0;
+		p123[2] = 1;
+		hs = 1;
+		dev_info(&drvdata->client->dev,
+			 "Using high speed divider option on ms%d",
+			 chn);
+	}
+
+	rc = write_field(drvdata, hs, awe_ms_hs[chn]);
+	if (rc < 0)
+		return rc;
+
+	for (i = 0; i < 3; i++) {
+		rc = write_multireg64(drvdata, (u64)p123[i],
+				      awe_msx[chn][i]);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Calculate parameters
+ * ms = a + b / c, where
+ *	a = ms[0], b=ms[1], c = ms[2]\
+ * SI5338 RM stats the fomula of parameters as
+ *	p1 = floor(((a * c + b) * 128) / c - 512)
+ *	p2 = mod((b * 128), c)
+ *	p3 = c
+ */
+static int ms_to_p123(u64 *ms, u32 *p123)
+{
+	u64 d;
+	u64 ms_denom = ms[2], ms_num = ms[1], ms_int = ms[0];
+
+	while (ms_denom >= (1 << 30) || ((ms_denom | ms_num) & 1) == 0) {
+		ms_denom >>= 1;
+		ms_num >>= 1;
+	}
+	if (ms_num == 0 || ms_denom == 0) {
+		ms_denom = 1;
+		ms_num = 0;
+	}
+	d = (ms_int * ms_denom + ms_num) << 7;
+	p123[0] = (u32)(div64_u64(d, ms_denom) - 512);
+	d = div64_u64((ms_num << 7), ms_denom);
+	p123[1] = (u32)((ms_num << 7) - d * ms_denom);
+	p123[2] = ms_denom;
+	pr_debug("ms[]=%llu + %llu/%llu Hz, ms_int=%llu, ms_num=%llu, ms_denom=%llu p123=%u %u %u\n",
+		 ms[0], ms[1], ms[2], ms_int, ms_num, ms_denom,
+		 p123[0], p123[1], p123[2]);
+
+	return 0;
+}
+
+/*
+ * Calculate MultiSynth divider (MS0..MS3) for specified output frequency
+ */
+static void cal_ms_p123(unsigned long numerator,
+			unsigned long denominator,
+			u32 *p123)
+{
+	u64 ms[3];
+
+	ms[1] = numerator;
+	ms[2] = denominator;
+	ms[0] = div64_u64(ms[1], ms[2]);
+	ms[1] -= ms[0] * ms[2];
+	while (ms[2] >= (1 << 30)) { /* trim */
+		ms[2] >>= 1;
+		ms[1] >>= 1;
+	}
+	remove_common_factor(&ms[1]);
+
+	if (ms[0] < MSINT_MIN) {
+		pr_err("Calculated MSN ratio is too low: %llu < %u\n",
+			ms[0], MSINT_MIN);
+		ms[0] = MSINT_MIN;
+	} else if (ms[0] == 5 || ms[0] == 7) {
+		pr_err("MSN ratio %llu is invalid\n", ms[0]);
+		ms[0] += 1;
+	} else if (ms[0] > MSINT_MAX) {
+		pr_err("Calculated MSN ratio is too high: %llu > %u\n",
+			ms[0], MSINT_MAX);
+		ms[0] = MSINT_MAX;
+	}
+	pr_debug("MS divider: %llu+%llu/%llu\n", ms[0], ms[1], ms[2]);
+
+	ms_to_p123(ms, p123);
+}
+
+/*
+ * Si5338 pll section
+ */
+static int si5338_pll_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	s64 pll_in_freq;
+	s64 K, Q, kphi_num, kphi_denom, fvco_mhz, fpfd_mhz;
+	int rsel, bwsel, vco_gain, pll_kphi, mscal, ms_pec;
+
+	pll_in_freq = clk_get_rate(clk_get_parent(hw->clk));
+	if (pll_in_freq <= 0) {
+		dev_err(&drvdata->client->dev, "Invalid input clock for pll\n");
+		return -EINVAL;
+	}
+
+	fvco_mhz = div64_u64(clk_get_rate(hw->clk), 1000000ll);
+	fpfd_mhz = div64_u64(pll_in_freq, 1000000ll);
+	if (fpfd_mhz >= 15) {
+		K = 925;
+		rsel = 0;
+		bwsel = 0;
+	} else if (fpfd_mhz >= 8) {
+		K = 325;
+		rsel = 1;
+		bwsel = 1;
+	} else {
+		K = 185;
+		rsel = 3;
+		bwsel = 2;
+	}
+	if (fvco_mhz > 2425) {
+		Q = 3;
+		vco_gain = 0;
+	} else {
+		Q = 4;
+		vco_gain = 1;
+	}
+	kphi_num = K * 2500LL * 2500LL * 2500LL;
+	kphi_denom = 533LL * Q * fpfd_mhz * fvco_mhz * fvco_mhz;
+	pll_kphi = (int)div64_u64(kphi_num + (kphi_denom >> 1), kphi_denom);
+	if (pll_kphi < 1 || pll_kphi > 127) {
+		dev_err(&drvdata->client->dev,
+			"Calculated PLL_KPHI does not fit 1<=%d<=127\n",
+			pll_kphi);
+		if (pll_kphi < 1)
+			pll_kphi = 1;
+		else if (pll_kphi > 127)
+			pll_kphi = 127;
+	}
+	mscal = (int)div64_u64(2067000 - 667 * fvco_mhz + 50000, 100000ll);
+	if (mscal < 0 || mscal > 63) {
+		dev_err(&drvdata->client->dev,
+			"Calculated MSCAL does not fit 0<=%d<=63\n",
+			mscal);
+		if (mscal < 0)
+			mscal = 0;
+		else if (mscal > 63)
+			mscal = 63;
+	}
+	ms_pec = 7;
+	dev_dbg(&drvdata->client->dev,
+		"Calculated values: PLL_KPHI=%d K=%lld RSEL=%d BWSEL=%d VCO_GAIN=%d MSCAL=%d MS_PEC=%d\n",
+		pll_kphi, K, rsel, bwsel, vco_gain, mscal, ms_pec);
+
+	/* setting actual registers */
+	rc = write_field(drvdata, (u8)pll_kphi, AWE_PLL_KPHI);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)(((vco_gain & 7) << 4) |
+				      ((rsel & 3) << 2) | (bwsel & 3)),
+			 AWE_VCO_GAIN_RSEL_BWSEL);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)mscal, AWE_MSCAL);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, (u8)ms_pec, AWE_MS_PEC);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 3, AWE_PLL_EN);
+	if (rc < 0)
+		return rc; /* enable PLL */
+
+	return 0;
+}
+
+static int si5338_pll_reparent(struct si5338_driver_data *drvdata,
+				u8 index)
+{
+	struct si5338_hw_data *hwdata = &drvdata->pll;
+	int rc = -EINVAL;
+
+	hwdata->num = SI5338_PFD_IN_REF_NOCLK;
+	switch (index) {
+	case SI5338_PFD_IN_REF_REFCLK:
+	case SI5338_PFD_IN_REF_FBCLK:
+	case SI5338_PFD_IN_REF_DIVREFCLK:
+	case SI5338_PFD_IN_REF_DIVFBCLK:
+	case SI5338_PFD_IN_REF_XOCLK:
+	case SI5338_PFD_IN_REF_NOCLK:
+		/* pfd_in_ref mux */
+		rc = set_in_pfd_ref_fb(drvdata, index, 0);
+		break;
+	default:
+		dev_err(&drvdata->client->dev,
+			"Invalid pfd_in_ref mux selection %d\n",
+			index);
+		break;
+	}
+
+	if (!rc)
+		hwdata->num = index;	/* record the source of pll */
+
+	return rc;
+}
+
+static unsigned char si5338_pll_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int pfd_in_ref;
+
+	/* Get pfd_in_ref mux value */
+	pfd_in_ref = get_in_pfd_ref_fb(drvdata, 0);
+
+	hwdata->num = pfd_in_ref;
+
+	return pfd_in_ref;
+}
+
+static int si5338_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+
+	if (index > ARRAY_SIZE(si5338_pll_src_names))
+		return -EINVAL;
+
+	return si5338_pll_reparent(hwdata->drvdata, index);
+}
+
+static unsigned long si5338_pll_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	u64 rate[3], ms[3], ms_scaled;
+
+	if (!hwdata->params.valid) {
+		rc = get_ms_p123(drvdata, hwdata->params.p123, 4);
+		if (rc < 0)
+			return 0;
+		hwdata->params.valid = true;
+	}
+
+	p123_to_ms(ms, hwdata->params.p123);
+	if (unlikely(ms[2] == 0)) {
+		/* This should not happen */
+		dev_err(&drvdata->client->dev,
+			"Error %s calculating pll\n", __func__);
+		ms[2] = 1;
+	}
+	ms_scaled = ms[0] * ms[2] + ms[1];
+	if (ms_scaled == 0)	/* uninitialzied */
+		return 0;
+
+	rate[2] = ms[2];
+	rate[1] = parent_rate * ms_scaled;
+	rate[0] = div64_u64(rate[1], rate[2]);
+	rate[1] -= rate[0] * rate[2];
+	remove_common_factor(&rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"PLL output frequency: %llu+%llu/%llu Hz\n",
+		rate[0], rate[1], rate[2]);
+
+	return (unsigned long)rate[0];
+}
+
+static long si5338_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	u64 ms[] = {0, 0, 1};
+	u64 new_rate[3], ms_scaled;
+
+	if (unlikely(rate < FVCOMIN))
+		rate = FVCOMIN;
+	else if (unlikely(rate > FVCOMAX))
+		rate = FVCOMAX;
+
+	cal_ms_p123(rate, *parent_rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	p123_to_ms(ms, hwdata->params.p123);
+	ms_scaled = ms[0] * ms[2] + ms[1];
+
+	new_rate[2] = ms[2];
+	new_rate[1] = *parent_rate * ms_scaled;
+	new_rate[0] = div64_u64(new_rate[1], new_rate[2]);
+	new_rate[1] -= new_rate[0] * new_rate[2];
+	remove_common_factor(&new_rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"PLL output frequency: %llu+%llu/%llu Hz\n",
+		new_rate[0], new_rate[1], new_rate[2]);
+
+	return (unsigned long)new_rate[0];
+}
+
+static int si5338_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	if (unlikely(rate < FVCOMIN))
+		rate = FVCOMIN;
+	else if (unlikely(rate > FVCOMAX))
+		rate = FVCOMAX;
+	cal_ms_p123(rate, parent_rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return set_ms_p123(drvdata, hwdata->params.p123, 4);
+}
+
+static const struct clk_ops si5338_pll_ops = {
+	.prepare = si5338_pll_prepare,
+	.set_parent = si5338_pll_set_parent,
+	.get_parent = si5338_pll_get_parent,
+	.recalc_rate = si5338_pll_recalc_rate,
+	.round_rate = si5338_pll_round_rate,
+	.set_rate = si5338_pll_set_rate,
+};
+
+/*
+ * Si5338 multisynth divider
+ */
+
+static const u32 awe_ms_powerdown[] = {
+	AWE_MS0_PDN,
+	AWE_MS1_PDN,
+	AWE_MS2_PDN,
+	AWE_MS3_PDN
+};
+
+static int set_ms_powerdown(struct si5338_driver_data *drvdata,
+			     int down, int chn)
+{
+	if (chn < 0 || chn > 3)
+		return -EINVAL;
+
+	if (down)
+		down = 1;
+
+	return write_field(drvdata, (u8)down, awe_ms_powerdown[chn]);
+}
+
+static int get_ms_powerdown(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_ms_powerdown[chn]);
+}
+
+static int si5338_msynth_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return set_ms_powerdown(drvdata, 0, hwdata->num);	/* power up */
+}
+
+static void si5338_msynth_unprepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	set_ms_powerdown(drvdata, 1, hwdata->num);	/* power down */
+}
+
+static unsigned long si5338_msynth_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+	u64 rate[3], ms[3], ms_scaled;
+
+	if (!hwdata->params.valid) {
+		rc = get_ms_p123(drvdata, hwdata->params.p123, hwdata->num);
+		if (rc < 0)
+			return 0;
+		hwdata->params.valid = true;
+	}
+
+	p123_to_ms(ms, hwdata->params.p123);
+	if (unlikely(ms[2] == 0)) {
+		/* This should not happen */
+		dev_err(&drvdata->client->dev,
+			"Error %s calculating MS%d\n", __func__, hwdata->num);
+		ms[2] = 1;
+	}
+	/* trim MS divider fraction */
+	while (ms[2] >= 0x1000) {
+		ms[1] >>= 1;
+		ms[2] >>= 1;
+	}
+	ms_scaled = ms[0] * ms[2] + ms[1];
+	if (ms_scaled == 0)	/* uninitialized */
+		return 0;
+
+	rate[2] = ms_scaled;
+	rate[1] = parent_rate * ms[2];
+	rate[0] = div64_u64(rate[1], rate[2]);
+	rate[1] -= rate[0] * rate[2];
+	remove_common_factor(&rate[1]);
+	dev_dbg(&drvdata->client->dev,
+		"MS%d output frequency: %llu+%llu/%llu Hz\n",
+		hwdata->num, rate[0], rate[1], rate[2]);
+
+	return (unsigned long)rate[0];
+}
+
+/*
+ * Based on PLL input clock, estimate best ratio for desired output
+ * if pll vco is not specified.
+ */
+static long si5338_msynth_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	s64 rate_scaled, pll_in_freq;
+	s64 center, center_diff, best_center_diff = 0;
+	s64 out_div, best_out_div = 1;
+	s64 d, in_div, best_in_div;
+	s64 err, best_err = 0;
+	s64 synth_out;
+	u64 ms[3];
+
+	if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) {
+		/* Get rate of the parent of PLL
+		 *(could be refclk, fbclk, etc.)
+		 */
+		pll_in_freq =
+			clk_get_rate(clk_get_parent(clk_get_parent(hw->clk)));
+
+		center = (FVCOMAX + FVCOMIN) >> 1;
+
+		best_in_div = 0;
+		for (out_div = 4; out_div <= MSINT_MAX; out_div++) {
+			if (out_div == 5 || out_div == 7)
+				continue;
+
+			/* here scaled by denominator */
+			rate_scaled = rate * out_div;
+			if (rate_scaled < FVCOMIN || rate_scaled > FVCOMAX)
+				continue;
+
+			in_div = div64_u64(rate_scaled +
+					   (pll_in_freq >> 1),
+					   pll_in_freq); /* round */
+
+			/* actual pll frequency scaled by out_denom */
+			d = pll_in_freq * in_div;
+			synth_out = div64_u64(d + (out_div >> 1), out_div);
+			center_diff = d - center;
+			if (center_diff < 0)
+				center_diff = -center_diff;
+			err = synth_out - rate;
+			if (err < 0)
+				err = -err;
+			if (best_in_div == 0 ||
+			    err < best_err ||
+			    (err == best_err &&
+			     center_diff < best_center_diff)) {
+				dev_dbg(&drvdata->client->dev,
+					"synth_out: %lld center: %lld rate:%lu err: %lld (%lld) center_diff:%lld(%lld)\n",
+					synth_out, center, rate, err, best_err,
+					center_diff, best_center_diff);
+				best_err = err;
+				best_in_div = in_div;
+				best_out_div = out_div;
+				best_center_diff = center_diff;
+			}
+		}
+		if (best_in_div == 0) {
+			dev_err(&drvdata->client->dev,
+				"Failed to find suitable integer coefficients for pll input %lld Hz\n",
+				pll_in_freq);
+		}
+		*parent_rate = pll_in_freq * best_in_div;
+		rate = *parent_rate / (unsigned long)best_out_div;
+		dev_dbg(&drvdata->client->dev,
+			"Best MS output frequency: %lu Hz, MS input divider: %lld, MS output divider: %lld\n",
+			rate, best_in_div, best_out_div);
+	} else {
+		ms[1] = *parent_rate;
+		ms[2] = rate;
+		ms[0] = div64_u64(ms[1], ms[2]);
+		ms[1] -= ms[0] * ms[2];
+		remove_common_factor(&ms[1]);
+		if (ms[0]  == 5 || ms[0] == 7)
+			out_div++;
+		rate = div64_u64(*parent_rate * ms[2], ms[1] + ms[0] * ms[2]);
+		dev_dbg(&drvdata->client->dev,
+			"Cloest MS output frequency: %lu Hz, output divider %llu+%llu/%llu\n",
+			rate, ms[0], ms[1], ms[2]);
+	}
+
+	cal_ms_p123(*parent_rate, rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return rate;
+}
+
+/*
+ * multisynth's parent is PLL
+ */
+static int si5338_msynth_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	cal_ms_p123(parent_rate, rate, hwdata->params.p123);
+	hwdata->params.valid = true;
+
+	return set_ms_p123(drvdata, hwdata->params.p123, hwdata->num);
+}
+
+static const struct clk_ops si5338_msynth_ops = {
+	.prepare = si5338_msynth_prepare,
+	.unprepare = si5338_msynth_unprepare,
+	.recalc_rate = si5338_msynth_recalc_rate,
+	.round_rate = si5338_msynth_round_rate,
+	.set_rate = si5338_msynth_set_rate,
+};
+
+/*
+ * Si5351 clkout
+ */
+
+static const u32 awe_out_disable[] = {
+	AWE_OUT0_DIS,
+	AWE_OUT1_DIS,
+	AWE_OUT2_DIS,
+	AWE_OUT3_DIS,
+	AWE_OUT_ALL_DIS
+};
+
+static int set_out_disable(struct si5338_driver_data *drvdata,
+			   int dis, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (dis)
+		dis = 1;
+
+	return write_field(drvdata, (u8)dis, awe_out_disable[chn]);
+}
+
+static int get_out_disable(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (chn != 4 && rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_out_disable[chn]);
+}
+
+static const u32 awe_drv_dis_state[] = {
+	AWE_OUT0_DIS_STATE,
+	AWE_OUT1_DIS_STATE,
+	AWE_OUT2_DIS_STATE,
+	AWE_OUT3_DIS_STATE
+};
+
+static int si5338_clkout_set_disable_state(struct si5338_driver_data *drvdata,
+					   int chn, int typ)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 3) {
+		dev_err(&drvdata->client->dev,
+			"Invalid disabled state %d. Only 0..3 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_dis_state[chn]);
+}
+
+static int get_drv_disabled_state(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_dis_state[chn]);
+}
+
+static const u32 awe_rdiv_in[] = {
+	AWE_R0DIV_IN,
+	AWE_R1DIV_IN,
+	AWE_R2DIV_IN,
+	AWE_R3DIV_IN
+};
+
+/*
+ * src	0: fbclk
+ *	1: refclk
+ *	2: divfbclk
+ *	3: divrefclk
+ *	4: xoclk
+ *	5: MS0
+ *	6: MS1/2/3 respetivelly
+ */
+static int set_out_mux(struct si5338_driver_data *drvdata, int chn, int src)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (src < 0 || src > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid source %d. Only 0...7 are supported\n",
+			src);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)src, awe_rdiv_in[chn]);
+}
+
+static int get_out_mux(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_rdiv_in[chn]);
+}
+
+static const u32 awe_drv_fmt[] = {
+	AWE_DRV0_FMT,
+	AWE_DRV1_FMT,
+	AWE_DRV2_FMT,
+	AWE_DRV3_FMT
+};
+
+static int set_drv_type(struct si5338_driver_data *drvdata, int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..7 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_fmt[chn]);
+}
+
+static int get_drv_type(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_fmt[chn]);
+}
+
+static const u32 awe_drv_vddo[] = {
+	AWE_DRV0_VDDO,
+	AWE_DRV1_VDDO,
+	AWE_DRV2_VDDO,
+	AWE_DRV3_VDDO
+};
+
+static int set_drv_vdd(struct si5338_driver_data *drvdata, int vdd, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (vdd < 0 || vdd > 7) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..3 are supported\n",
+			vdd);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)vdd, awe_drv_vddo[chn]);
+}
+
+static int get_drv_vdd(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_vddo[chn]);
+}
+
+static const u32 awe_drv_trim[][3] = {
+	{ AWE_DRV0_TRIM, 0, 0 },
+	{ AWE_DRV1_TRIM_A, AWE_DRV1_TRIM_B, 0},
+	{ AWE_DRV2_TRIM, 0, 0},
+	{ AWE_DRV3_TRIM, 0, 0}
+};
+
+static int set_drv_trim_any(struct si5338_driver_data *drvdata,
+			    int trim, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (trim < 0 || trim > 31) {
+		dev_err(&drvdata->client->dev,
+			"Invalid output type %d. Only 0..31 are supported\n",
+			trim);
+		return -EINVAL;
+	}
+
+	return write_multireg64(drvdata, trim, awe_drv_trim[chn]);
+}
+
+static int get_drv_trim(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return (int)read_multireg64(drvdata, awe_drv_trim[chn]);
+}
+
+static const u32 awe_drv_invert[] = {
+	AWE_DRV0_INV,
+	AWE_DRV1_INV,
+	AWE_DRV2_INV,
+	AWE_DRV3_INV
+};
+
+static int set_drv_invert(struct si5338_driver_data *drvdata, int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ < 0 || typ > 3) {
+		dev_err(&drvdata->client->dev,
+			"Invalid invert drivers %d. Only 0..3 are supported\n",
+			typ);
+		return -EINVAL;
+	}
+
+	return write_field(drvdata, (u8)typ, awe_drv_invert[chn]);
+}
+
+static int get_drv_invert(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_invert[chn]);
+}
+
+static const u32 awe_drv_powerdown[] = {
+	AWE_DRV0_PDN,
+	AWE_DRV1_PDN,
+	AWE_DRV2_PDN,
+	AWE_DRV3_PDN
+};
+
+static int get_drv_powerdown(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	return read_field(drvdata, awe_drv_powerdown[chn]);
+}
+
+static int set_drv_powerdown(struct si5338_driver_data *drvdata,
+			     int typ, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	if (typ)
+		typ = 1;
+
+	return write_field(drvdata, (u8)typ, awe_drv_powerdown[chn]);
+}
+
+static const struct si5338_drv_t const drv_configs[] = {
+	{"3V3_CMOS_A+",	0x1, 0x0, 0x17, 0x8}, /* bX0 */
+	{"3V3_CMOS_A-",	0x1, 0x0, 0x17, 0x9}, /* bX1 */
+	{"3V3_CMOS_B+",	0x2, 0x0, 0x17, 0x4}, /* b0X */
+	{"3V3_CMOS_B-",	0x2, 0x0, 0x17, 0x6}, /* b1X */
+	{"3V3_CMOS_A+B+", 0x3, 0x0, 0x17, 0x8},
+	{"3V3_CMOS_A-B+", 0x3, 0x0, 0x17, 0x9},
+	{"3V3_CMOS_A+B-", 0x3, 0x0, 0x17, 0x4},
+	{"3V3_CMOS_A-B-", 0x3, 0x0, 0x17, 0x6},
+
+	{"2V5_CMOS_A+",	0x1, 0x1, 0x13, 0x8},
+	{"2V5_CMOS_A-",	0x1, 0x1, 0x13, 0x9},
+	{"2V5_CMOS_B+",	0x2, 0x1, 0x13, 0x4},
+	{"2V5_CMOS_B-",	0x2, 0x1, 0x13, 0x6},
+	{"2V5_CMOS_A+B+", 0x3, 0x1, 0x13, 0x8},
+	{"2V5_CMOS_A-B+", 0x3, 0x1, 0x13, 0x9},
+	{"2V5_CMOS_A+B-", 0x3, 0x1, 0x13, 0x4},
+	{"2V5_CMOS_A-B-", 0x3, 0x1, 0x13, 0x6},
+
+	{"1V8_CMOS_A+",	0x1, 0x2, 0x15, 0x8},
+	{"1V8_CMOS_A-",	0x1, 0x2, 0x15, 0x9},
+	{"1V8_CMOS_B+",	0x2, 0x2, 0x15, 0x4},
+	{"1V8_CMOS_B-",	0x2, 0x2, 0x15, 0x6},
+	{"1V8_CMOS_A+B+", 0x3, 0x2, 0x15, 0x8},
+	{"1V8_CMOS_A-B+", 0x3, 0x2, 0x15, 0x9},
+	{"1V8_CMOS_A+B-", 0x3, 0x2, 0x15, 0x4},
+	{"1V8_CMOS_A-B-", 0x3, 0x2, 0x15, 0x6},
+
+	{"1V5_HSTL_A+",	0x1, 0x3, 0x1f, 0x8},
+	{"1V5_HSTL_A-",	0x1, 0x3, 0x1f, 0x9},
+	{"1V5_HSTL_B+",	0x2, 0x3, 0x1f, 0x4},
+	{"1V5_HSTL_B-",	0x2, 0x3, 0x1f, 0x6},
+	{"1V5_HSTL_A+B+", 0x3, 0x3, 0x1f, 0x8},
+	{"1V5_HSTL_A-B+", 0x3, 0x3, 0x1f, 0x9},
+	{"1V5_HSTL_A+B-", 0x3, 0x3, 0x1f, 0x4},
+	{"1V5_HSTL_A-B-", 0x3, 0x3, 0x1f, 0x6},
+
+	{"3V3_SSTL_A+",	0x1, 0x0, 0x04, 0x8},
+	{"3V3_SSTL_A-",	0x1, 0x0, 0x04, 0x9},
+	{"3V3_SSTL_B+",	0x2, 0x0, 0x04, 0x4},
+	{"3V3_SSTL_B-",	0x2, 0x0, 0x04, 0x6},
+	{"3V3_SSTL_A+B+", 0x3, 0x0, 0x04, 0x8},
+	{"3V3_SSTL_A-B+", 0x3, 0x0, 0x04, 0x9},
+	{"3V3_SSTL_A+B-", 0x3, 0x0, 0x04, 0x5},
+	{"3V3_SSTL_A-B-", 0x3, 0x0, 0x04, 0x6},
+
+	{"2V5_SSTL_A+",	0x1, 0x1, 0x0d, 0x8},
+	{"2V5_SSTL_A-",	0x1, 0x1, 0x0d, 0x9},
+	{"2V5_SSTL_B+",	0x2, 0x1, 0x0d, 0x4},
+	{"2V5_SSTL_B-",	0x2, 0x1, 0x0d, 0x6},
+	{"2V5_SSTL_A+B+", 0x3, 0x1, 0x0d, 0x8},
+	{"2V5_SSTL_A-B+", 0x3, 0x1, 0x0d, 0x9},
+	{"2V5_SSTL_A+B-", 0x3, 0x1, 0x0d, 0x5},
+	{"2V5_SSTL_A-B-", 0x3, 0x1, 0x0d, 0x6},
+
+	{"1V8_SSTL_A+",	0x1, 0x2, 0x17, 0x8},
+	{"1V8_SSTL_A-",	0x1, 0x2, 0x17, 0x9},
+	{"1V8_SSTL_B+",	0x2, 0x2, 0x17, 0x4},
+	{"1V8_SSTL_B-",	0x2, 0x2, 0x17, 0x6},
+	{"1V8_SSTL_A+B+", 0x3, 0x2, 0x17, 0x8},
+	{"1V8_SSTL_A-B+", 0x3, 0x2, 0x17, 0x9},
+	{"1V8_SSTL_A+B-", 0x3, 0x2, 0x17, 0x4},
+	{"1V8_SSTL_A-B-", 0x3, 0x2, 0x17, 0x6},
+
+	{"3V3_LVPECL",	0x4, 0x0, 0x0f, 0xc},
+	{"2V5_LVPECL",	0x4, 0x1, 0x10, 0xc},
+	{"3V3_LVDS",	0x6, 0x0, 0x03, 0xc},
+	{"2V5_LVDS",	0x6, 0x1, 0x04, 0xc},
+	{"1V8_LVDS",	0x6, 0x2, 0x04, 0xc},
+
+	{NULL,		0x0, 0x0, 0x0, 0x0},
+};
+
+static int find_drive_config(const char *name)
+{
+	int i;
+
+	for (i = 0; drv_configs[i].description; i++) {
+		if (strcmp(name, drv_configs[i].description) == 0)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int si5338_clkout_set_drive_config(struct si5338_driver_data *drvdata,
+				   int chn, const char *name)
+{
+	int i, rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	i = find_drive_config(name);
+	if (i < 0) {
+		dev_err(&drvdata->client->dev,
+			"Invalid driver configuration\n");
+		return -EINVAL;
+	}
+
+	rc = set_drv_type(drvdata, drv_configs[i].fmt, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_vdd(drvdata, drv_configs[i].vdd, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_trim_any(drvdata, drv_configs[i].trim, chn);
+	if (rc < 0)
+		return rc;
+
+	rc = set_drv_invert(drvdata,
+			    drv_configs[i].invert & 3, chn);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static const u32 awe_rdiv_k[] = {
+	AWE_R0DIV,
+	AWE_R1DIV,
+	AWE_R2DIV,
+	AWE_R3DIV
+};
+
+static const u8 out_div_values[] = {
+	1, 2, 4, 8, 16, 32
+};
+
+static int get_out_div(struct si5338_driver_data *drvdata, int chn)
+{
+	int rc;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	rc = read_field(drvdata, awe_rdiv_k[chn]);
+	if (rc < 0)
+		return rc;
+
+	if (rc >= ARRAY_SIZE(out_div_values)) {
+		dev_err(&drvdata->client->dev,
+			"Invalid value for output divider: %d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	return out_div_values[rc];
+}
+
+static int set_out_div(struct si5338_driver_data *drvdata, int div, int chn)
+{
+	int rc;
+	u8 val;
+
+	rc = _verify_output_channel(chn);
+	if (rc < 0)
+		return rc;
+
+	for (val = 0; val < ARRAY_SIZE(out_div_values); val++) {
+		if (out_div_values[val] == div) {
+			rc = write_field(drvdata, val, awe_rdiv_k[chn]);
+			if (rc < 0)
+				return rc;
+
+			return 0;
+		}
+	}
+	dev_err(&drvdata->client->dev,
+		"Invalid value for output divider: %d\n",
+		div);
+
+	return -EINVAL;
+}
+
+static int get_status(struct si5338_driver_data *drvdata)
+{
+	return read_field(drvdata, AWE_STATUS);
+}
+
+static int power_up_down_needed_ms(struct si5338_driver_data *drvdata)
+{
+	int rc, chn, out_src;
+	int ms_used = 0;
+
+	for (chn = 0; chn < 4; chn++) {
+		out_src = get_out_mux(drvdata, chn);
+		if (out_src < 0)
+			return out_src;
+
+		switch (out_src) {
+		case 5:
+			ms_used |= 1;
+			break;
+		case 6:
+			ms_used |= (1 << chn);
+			break;
+		}
+	}
+	for (chn = 0; chn < 4; chn++) {
+		rc = set_ms_powerdown(drvdata,
+				      (ms_used & (1 << chn)) ? 0 : 1, chn);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static u32 awe_fcal[] = {
+	AWE_FCAL_07_00,
+	AWE_FCAL_15_08,
+	AWE_FCAL_17_16,
+	0
+};
+
+static u32 awe_fcal_ovrd[] = {
+	AWE_FCAL_OVRD_07_00,
+	AWE_FCAL_OVRD_15_08,
+	AWE_FCAL_OVRD_17_15,
+	0
+};
+
+static int reset_ms(struct si5338_driver_data *drvdata)
+{
+	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wait_q);
+	int rc;
+
+	dev_dbg(&drvdata->client->dev, "Resetting MS dividers");
+	/* SET MS RESET = 1 */
+	rc = write_field(drvdata, 1, AWE_MS_RESET);
+	if (rc < 0)
+		return rc;
+
+	/* Wait for 10ms */
+	wait_event_freezable_timeout(wait_q, false, msecs_to_jiffies(10));
+
+	/* SET MS RESET = 0 */
+	rc = write_field(drvdata, 0, AWE_MS_RESET);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int set_misc_registers(struct si5338_driver_data *drvdata)
+{
+	/* ST52238 Reference Manual R1.2 p.28 */
+	int rc;
+
+	rc = write_field(drvdata, 0x5, AWE_MISC_47);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_106);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_116);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x1, AWE_MISC_42);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_06A);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_06B);
+	if (rc < 0)
+		return rc;
+
+	rc = write_field(drvdata, 0x0, AWE_MISC_28);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* Disable interrupt, all outputs */
+static int pre_init(struct si5338_driver_data *drvdata)
+{
+	int rc, chn;
+
+	/* Disable interrupts */
+	rc = write_field(drvdata, 0x1d, AWE_INT_MASK);
+	if (rc) {
+		dev_err(&drvdata->client->dev, "Failed to disable interrupt\n");
+		return rc;
+	}
+
+	/* setup miscelalneous registers */
+	rc = set_misc_registers(drvdata);
+	if (rc < 0)
+		return rc;
+
+	/* disable all outputs */
+	rc = write_field(drvdata, 1, AWE_OUT_ALL_DIS);
+	if (rc < 0)
+		return rc;
+
+	/* pause LOL */
+	rc = write_field(drvdata, 1, AWE_DIS_LOS);
+	if (rc < 0)
+		return rc;
+
+	/* clears outputs pll input/fb muxes to be set later */
+	for (chn = 0; chn < 4; chn++) {
+		rc = set_ms_powerdown(drvdata, 1, chn);
+		if (rc < 0)
+			return rc;
+		rc = set_out_disable(drvdata, 1, chn);
+		if (rc < 0)
+			return rc;
+	}
+	/* to be explicitly enabled if needed */
+	rc = set_in_pfd_ref_fb(drvdata, 5, 0);	/* noclk */
+	if (rc < 0)
+		return rc;
+
+	rc = set_in_pfd_ref_fb(drvdata, 5, 1);	/* noclk */
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* See SI5338 RM for programming procedure */
+static int post_init(struct si5338_driver_data *drvdata)
+{
+	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wait_q);
+	int rc = 0, i, in_src, fb_src, ext_fb, check_los = 0;
+	int timeout = INIT_TIMEOUT;
+	s64 fcal;
+
+	/* validate input clock status */
+	in_src = get_in_pfd_ref_fb(drvdata, 0);
+	if (in_src < 0)
+		return in_src;
+
+	switch (in_src) {
+	case SI5338_PFD_IN_REF_REFCLK:
+	case SI5338_PFD_IN_REF_DIVREFCLK:
+	case SI5338_PFD_IN_REF_XOCLK:
+		check_los |= AWE_STATUS_PLL_LOS_CLKIN;
+		break;
+	case SI5338_PFD_IN_REF_FBCLK:
+	case SI5338_PFD_IN_REF_DIVFBCLK:
+		check_los |= AWE_STATUS_PLL_LOS_FDBK;
+		break;
+	}
+	ext_fb = read_field(drvdata, AWE_PFD_EXTFB);
+	if (ext_fb < 0)
+		return ext_fb;
+
+	if (ext_fb) {
+		fb_src = get_in_pfd_ref_fb(drvdata, 1);
+		if (fb_src < 0)
+			return fb_src;
+
+		switch (in_src) {
+		case SI5338_PFD_IN_FB_REFCLK:
+		case SI5338_PFD_IN_FB_DIVREFCLK:
+			check_los |= AWE_STATUS_PLL_LOS_CLKIN;
+			break;
+		case SI5338_PFD_IN_FB_FBCLK:
+		case SI5338_PFD_IN_FB_DIVFBCLK:
+			check_los |= AWE_STATUS_PLL_LOS_FDBK;
+			break;
+		}
+	}
+	check_los &= 0xf;
+	for (i = 0; i < timeout; i++) {
+		rc = get_status(drvdata);
+		if (rc < 0)
+			return rc;
+
+		if ((rc & check_los) == 0)
+			break; /* inputs OK */
+	}
+	if (i >= timeout) {
+		dev_err(&drvdata->client->dev,
+			"Timeout waiting for input clocks, status=0x%x, mask=0x%x\n",
+			rc, check_los);
+		return -EPIPE;
+	}
+	dev_dbg(&drvdata->client->dev,
+		"Validated input clocks, t=%d cycles (timeout= %d cycles), status =0x%x, mask=0x%x\n",
+		i, timeout, rc, check_los);
+
+	/* Configure PLL for locking, set FCAL_OVRD_EN = 0 */
+	rc = write_field(drvdata, 0, AWE_FCAL_OVRD_EN);
+	if (rc < 0)
+		return rc;
+
+	/* Configure PLL for locking, set SOFT_RESET = 1 (ignore i2c error) */
+	write_field(drvdata, 1, AWE_SOFT_RESET);
+	wait_event_freezable_timeout(wait_q, false, msecs_to_jiffies(25));
+
+	/* re-enable LOL, set reg 241 = 0x65 */
+	rc = write_field(drvdata, 0x65, AWE_REG241);
+	if (rc < 0)
+		return rc;
+
+	check_los |= AWE_STATUS_PLL_LOL | AWE_STATUS_PLL_SYS_CAL;
+	check_los &= 0xf;
+	for (i = 0; i < timeout; i++) {
+		rc = get_status(drvdata);
+		if (rc < 0)
+			return rc;
+
+		if ((rc & check_los) == 0)
+			break; /* alarms not set OK */
+	}
+	if (i >= timeout) {
+		dev_err(&drvdata->client->dev,
+			"Timeout (%d) waiting for PLL lock, status=0x%x, mask=0x%x\n",
+			i, rc, check_los);
+		return -EPIPE;
+	}
+	dev_dbg(&drvdata->client->dev,
+		"Validated PLL locked, t=%d cycles (timeout= %d cycles), status =0x%x, mask=0x%x\n",
+		i, timeout, rc, check_los);
+
+	/* copy FCAL values to active registers */
+	fcal = read_multireg64(drvdata, awe_fcal);
+	if (fcal < 0)
+		return (int)fcal;
+
+	rc = write_multireg64(drvdata, fcal, awe_fcal_ovrd);
+	if (rc < 0)
+		return rc;
+
+	dev_dbg(&drvdata->client->dev, "Copied FCAL data 0x%llx\n", fcal);
+	/* Set 47[7:2] to 000101b */
+	rc = write_field(drvdata, 5, AWE_REG47_72);
+	if (rc < 0)
+		return rc;
+
+	/* SET PLL to use FCAL values, set FCAL_OVRD_EN=1 */
+	rc = write_field(drvdata, 1, AWE_FCAL_OVRD_EN);
+	if (rc < 0)
+		return rc;
+
+	/* only needed if using down-spread. Won't hurt to do anyway */
+	rc = reset_ms(drvdata);
+	if (rc < 0)
+		return rc;
+
+	/* Enable all (enabled individually) outputs */
+	rc = write_field(drvdata, 0, AWE_OUT_ALL_DIS);
+	if (rc < 0)
+		return rc;
+
+	/* Clearing */
+	write_field(drvdata, 0, AWE_SOFT_RESET);
+
+	/* fixme: This is not needed */
+	rc = power_up_down_needed_ms(drvdata);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int si5338_clkout_prepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	int rc;
+
+	rc = set_drv_powerdown(drvdata, 0, hwdata->num);
+	if (rc) {
+		dev_err(&drvdata->client->dev,
+			"Error power up clkout%d\n", hwdata->num);
+		return rc;
+	}
+	rc = set_out_disable(drvdata, 0, hwdata->num);	/* enable */
+	if (rc) {
+		dev_err(&drvdata->client->dev,
+			"Error enabling clkout%d\n", hwdata->num);
+	}
+	dev_dbg(&drvdata->client->dev, "Clkout%d prepared\n", hwdata->num);
+
+	return rc;
+}
+
+static void si5338_clkout_unprepare(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	set_out_disable(drvdata, 1, hwdata->num);	/* disable */
+}
+
+static int si5338_clkout_reparent(struct si5338_driver_data *drvdata,
+				   int num, u8 parent)
+{
+	return set_out_mux(drvdata, num, parent);
+}
+
+static u8 si5338_clkout_get_parent(struct clk_hw *hw)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return (u8)get_out_mux(drvdata, hwdata->num);
+}
+
+static int si5338_clkout_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+
+	return si5338_clkout_reparent(drvdata, hwdata->num, index);
+}
+
+static unsigned long si5338_clkout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long rate = parent_rate;
+	int rc;
+
+	rc = get_out_div(drvdata, hwdata->num);
+	if (rc < 0) {
+		rate = 0;
+		dev_err(&drvdata->client->dev,
+			"Error recalculating rate for clk%d\n", hwdata->num);
+	} else {
+		rate /= rc;
+	}
+	dev_dbg(&drvdata->client->dev, "Recalculted clkout%d rate %lu\n",
+		hwdata->num, rate);
+
+	return rate;
+}
+
+static long si5338_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	u64 out_freq_scaled, scaled_max;
+	unsigned long err, new_rate, new_err;
+	u8 r_div = 1;
+
+	out_freq_scaled = rate;
+	/* Request frequency if multisynth master */
+	if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) {
+		scaled_max = div64_u64(FVCOMAX,  MSINT_MAX);
+		while (r_div < 32 && out_freq_scaled < scaled_max) {
+			out_freq_scaled <<= 1;
+			r_div <<= 1;
+		}
+		if (out_freq_scaled < scaled_max) {
+			dev_err(&drvdata->client->dev,
+				"Specified output frequency is too low: %lu < %lld\n",
+				rate, scaled_max >> 5);
+			r_div = 32;
+			*parent_rate = scaled_max;
+		} else {
+			*parent_rate = out_freq_scaled;
+		}
+	} else {
+		/* round to closest r_div */
+		new_rate = *parent_rate;
+		new_err = abs(new_rate - rate);
+		do {
+			err = new_err;
+			new_rate >>= 1;
+			r_div <<= 1;
+			new_err = abs(new_rate - rate);
+		} while (new_err < err && r_div < 32);
+		r_div >>= 1;
+	}
+	rate = *parent_rate / r_div;
+
+	dev_dbg(&drvdata->client->dev,
+		"%s - %s: r_div = %u, rate = %lu, requesting parent_rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), r_div,
+		rate, *parent_rate);
+
+	return rate;
+}
+
+static int si5338_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct si5338_hw_data *hwdata =
+		container_of(hw, struct si5338_hw_data, hw);
+	struct si5338_driver_data *drvdata = hwdata->drvdata;
+	unsigned long err, new_rate, new_err;
+	int r_div = 1;
+
+	/* round to closest r_div */
+	new_rate = parent_rate;
+	new_err = abs(new_rate - rate);
+	do {
+		err = new_err;
+		new_rate >>= 1;
+		r_div <<= 1;
+		new_err = abs(new_rate - rate);
+	} while (new_err < err && r_div < 32);
+	r_div >>= 1;
+
+	dev_dbg(&drvdata->client->dev,
+		"%s - %s: r_div = %u, parent_rate = %lu, rate = %lu\n",
+		__func__, __clk_get_name(hwdata->hw.clk), r_div,
+		parent_rate, rate);
+
+	return set_out_div(drvdata, r_div, hwdata->num);
+}
+
+static const struct clk_ops si5338_clkout_ops = {
+	.prepare = si5338_clkout_prepare,
+	.unprepare = si5338_clkout_unprepare,
+	.set_parent = si5338_clkout_set_parent,
+	.get_parent = si5338_clkout_get_parent,
+	.recalc_rate = si5338_clkout_recalc_rate,
+	.round_rate = si5338_clkout_round_rate,
+	.set_rate = si5338_clkout_set_rate,
+};
+
+/*
+ * Create sysfs files for status and dumping register for each si5338
+ * device. Current common clock framework doesn't have API to create
+ * individual sub-folder for each device in debugfs.
+ */
+static const char * const out_status[] = {
+	"output0_status",
+	"output1_status",
+	"output2_status",
+	"output3_status",
+	"outputs_status",
+	NULL
+};
+
+static ssize_t output_status_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+	int i, i1, j, rc, len = 0, show_number, match = 0;
+	int drv_type, drv_vdd, drv_trim, drv_invert;
+	int out_src, src_group = 0, src = 0;
+	const int in_numbers[] = {
+		12, 3, 4, 56
+	};
+
+	for (i = 0; out_status[i]; i++) {
+		if (strcmp(attr->attr.name, out_status[i]) == 0)
+			break;
+	}
+	if (!out_status[i])
+		return -EINVAL;
+
+	if (i == 4) { /* all outputs */
+		i = 0;
+		i1 = 4;
+		show_number = 1;
+	} else {
+		i1 = i+1;
+		show_number = 0;
+	}
+
+	for ( ; i < i1; i++) {
+		if (show_number) {
+			rc = sprintf(buf, "%d: ", i);
+			buf += rc;
+			len += rc;
+		}
+		if (get_out_disable(drvdata, i)) {
+			rc = sprintf(buf, "disabled");
+			buf += rc;
+			len += rc;
+			rc = 0;
+			switch (get_drv_disabled_state(drvdata, i)) {
+			case SI5338_OUT_DIS_HIZ:
+				rc = sprintf(buf, " (high-Z)\n");
+				break;
+			case SI5338_OUT_DIS_LOW:
+				rc = sprintf(buf, " (low)\n");
+				break;
+			case SI5338_OUT_DIS_HI:
+				rc = sprintf(buf, " (high)\n");
+				break;
+			case SI5338_OUT_DIS_ALWAYS_ON:
+				rc = sprintf(buf, " (always on)\n");
+				break;
+			}
+			buf += rc;
+			len += rc;
+			continue;
+		} else {
+			rc = sprintf(buf, "enabled ");
+			buf += rc;
+			len += rc;
+		}
+		drv_type = get_drv_type(drvdata, i);
+		if (drv_type < 0)
+			return drv_type;
+
+		drv_vdd =  get_drv_vdd(drvdata, i);
+		if (drv_vdd < 0)
+			return drv_vdd;
+
+		drv_trim = get_drv_trim(drvdata, i);
+		if (drv_trim < 0)
+			return drv_trim;
+
+		drv_invert = get_drv_invert(drvdata, i);
+		if (drv_invert < 0)
+			return drv_invert;
+
+		for (j = 0; drv_configs[j].description; j++) {
+			if (drv_configs[j].fmt == drv_type &&
+			    drv_configs[j].vdd == drv_vdd &&
+			    drv_configs[j].trim == drv_trim &&
+			    (drv_invert | (drv_configs[j].invert >> 2)) ==
+				((drv_configs[j].invert & 3) |
+				 (drv_configs[j].invert>>2)))
+				rc = sprintf(buf, drv_configs[j].description);
+				buf += rc;
+				len += rc;
+				match = 1;
+		}
+
+		if (match == 0) {
+			rc = sprintf(buf,
+				     "Invalid output configuration: type = %d, vdd=%d, trim=%d, invert=%d\n",
+				     drv_type, drv_vdd, drv_trim, drv_invert);
+			buf += rc;
+			len += rc;
+		}
+
+		rc = sprintf(buf, ", R%d and out %d power %s",
+			     i, i,
+			     get_drv_powerdown(drvdata, i) ? "down" : "up");
+		buf += rc;
+		len += rc;
+
+		rc = sprintf(buf, ", Output route ");
+		buf += rc;
+		len += rc;
+
+		out_src = get_out_mux(drvdata, i);
+		if (out_src < 0)
+			return out_src;
+
+		switch (out_src) {
+		case 0: /* p2div in */
+		case 2: /* p2div out */
+			src = get_fb_mux(drvdata);
+			if (src < 0)
+				return src;
+
+			src_group = 0;
+			src = src ? 2 : 3; /* mod src: 0 - IN56, 1 - IN4 */
+			break;
+		case 1: /* p1div in */
+		case 3: /* p1div out */
+			src = get_in_mux(drvdata);
+			if (src < 0)
+				return src;
+
+			if (src == 2) {
+				src_group = 1;
+				src = 0;
+			} else {
+				src_group = 0; /* keep src: 0 - IN12, 1 - IN3 */
+			}
+			break;
+		case 4:
+			src_group = 1;
+			break;
+		case 5:
+			src_group = 2;
+			src = 0;
+			break;
+		case 6:
+			src_group = 2;
+			src = i;
+			 break;
+		case 7:
+			src_group = 3;
+			break;
+		}
+		rc = 0;
+		switch (src_group) {
+		case 0:
+			rc = sprintf(buf, "IN%d", in_numbers[src]);
+			buf += rc;
+			len += rc;
+			break;
+		case 1:
+			rc = sprintf(buf, "XO");
+			buf += rc;
+			len += rc;
+			break;
+		case 2:
+			rc = sprintf(buf, "MS%d", src);
+			buf += rc;
+			len += rc;
+			break;
+		case 3:
+			rc = sprintf(buf, "No clock");
+			buf += rc;
+			len += rc;
+			break;
+		}
+
+		if (out_src == 5 || out_src == 6) {
+			rc = sprintf(buf, " power %s",
+				     get_ms_powerdown(drvdata, i) ?
+					"down" : "up");
+			buf += rc;
+			len += rc;
+		}
+
+		rc = sprintf(buf, "\n");
+		buf += rc;
+		len += rc;
+	}
+
+	return len;
+}
+
+static ssize_t register_dump_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	int i, rc, len = 0;
+	u8 val;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+
+	for (i = 0; i < ARRAY_SIZE(register_masks); i++) {
+		val = si5338_reg_read(drvdata, register_masks[i] >> 8);
+		rc = sprintf(buf, " 0x%x",
+				((register_masks[i] & 0x1ff00) << 8) |
+				(register_masks[i] & 0xff) |
+				((val & 0xff) << 8));
+		buf += rc;
+		len += rc;
+		if (((i + 1) & 0x7) == 0) {
+			rc = sprintf(buf, "\n");
+			buf += rc;
+			len += rc;
+		}
+	}
+	rc = sprintf(buf, "\n");
+	buf += rc;
+	len += rc;
+
+	return len;
+}
+
+static DEVICE_ATTR(output0_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output1_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output2_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(output3_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(outputs_status, 0444, output_status_show, NULL);
+static DEVICE_ATTR(register_dump, 0444, register_dump_show, NULL);
+
+static int si5338_sysfs_register(struct device *dev)
+{
+	int rc;
+
+	rc = device_create_file(dev, &dev_attr_output0_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output1_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output2_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_output3_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_outputs_status);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	rc = device_create_file(dev, &dev_attr_register_dump);
+	if (rc) {
+		dev_err(dev, "Failed to add to sysfs\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static void si5338_sysfs_unregister(struct device *dev)
+{
+	device_remove_file(dev, &dev_attr_output0_status);
+	device_remove_file(dev, &dev_attr_output1_status);
+	device_remove_file(dev, &dev_attr_output2_status);
+	device_remove_file(dev, &dev_attr_output3_status);
+	device_remove_file(dev, &dev_attr_outputs_status);
+	device_remove_file(dev, &dev_attr_register_dump);
+}
+
+/*
+ * Si5351 i2c probe and device tree parsing
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id si5338_dt_ids[] = {
+	{ .compatible = "silabs,si5338" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, si5338_dt_ids);
+
+static int si5338_dt_parse(struct i2c_client *client)
+{
+	struct device_node *child, *np = client->dev.of_node;
+	struct si5338_platform_data *pdata;
+	u32 val, num;
+	int i;
+
+	if (np == NULL)
+		return 0;
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->clk_xtal = of_clk_get(np, 0);
+
+	for (i = 0; i < 4; i++)
+		pdata->clkin[i] = of_clk_get(np, i + 1);
+
+	if (!IS_ERR(pdata->clk_xtal) && !IS_ERR(pdata->clkin[0])) {
+		dev_err(&client->dev,
+			"Error in device tree: IN1/IN2 and XTAL are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	/* property silab,name-prefix */
+	of_property_read_string(np, "silab,name-prefix", &pdata->name_prefix);
+
+	/* property silab,ref-source */
+	if (!of_property_read_u32(np, "silab,ref-source", &val)) {
+		switch (val) {
+		case SI5338_REF_SRC_CLKIN12:
+		case SI5338_REF_SRC_CLKIN3:
+		case SI5338_REF_SRC_XTAL:
+			pdata->ref_src = val;
+			dev_dbg(&client->dev, "ref-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for refclk %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,fb-source */
+	if (!of_property_read_u32(np, "silab,fb-source", &val)) {
+		switch (val) {
+		case SI5338_FB_SRC_CLKIN4:
+		case SI5338_FB_SRC_CLKIN56:
+		case SI5338_FB_SRC_NOCLK:
+			pdata->fb_src = val;
+			dev_dbg(&client->dev, "fb-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for fbclk %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,pll-source */
+	if (!of_property_read_u32(np, "silab,pll-source", &val)) {
+		switch (val) {
+		case SI5338_PFD_IN_REF_REFCLK:
+		case SI5338_PFD_IN_REF_FBCLK:
+		case SI5338_PFD_IN_REF_DIVREFCLK:
+		case SI5338_PFD_IN_REF_DIVFBCLK:
+		case SI5338_PFD_IN_REF_XOCLK:
+		case SI5338_PFD_IN_REF_NOCLK:
+			pdata->pll_src = val;
+			dev_dbg(&client->dev, "pll-source = %d\n", val);
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for pll %u\n", val);
+			break;
+		}
+	}
+
+	/* property silab,pll-vco */
+	if (!of_property_read_u32(np, "silab,pll-vco", &val)) {
+		if (val < FVCOMIN || val > FVCOMAX) {
+			dev_err(&client->dev,
+				"pll-vco out of range [%lldu..%lldu]\n",
+				FVCOMIN, FVCOMAX);
+		} else {
+			pdata->pll_vco = val;
+		}
+	}
+
+	if (!of_property_read_u32(np, "silab,pll-master", &val)) {
+		if (val > 3) {
+			dev_err(&client->dev,
+				"Invalid pll-master %u\n", val);
+			return -EINVAL;
+		}
+		pdata->pll_master = val;
+		dev_dbg(&client->dev, "pll-master = %d\n", val);
+	}
+
+	/* per clock out */
+	for_each_child_of_node(np, child) {
+		if (of_property_read_u32(child, "reg", &num)) {
+			dev_err(&client->dev, "Missing reg property of %s\n",
+				child->name);
+			return -EINVAL;
+		}
+		if (num > 4) {
+			dev_err(&client->dev, "Invalid clkout %u\n", num);
+			return -EINVAL;
+		}
+		if (!of_property_read_u32(child, "silabs,clock-source", &val)) {
+			switch (val) {
+			case SI5338_OUT_MUX_FBCLK:
+			case SI5338_OUT_MUX_REFCLK:
+			case SI5338_OUT_MUX_DIVFBCLK:
+			case SI5338_OUT_MUX_DIVREFCLK:
+			case SI5338_OUT_MUX_XOCLK:
+			case SI5338_OUT_MUX_MS0:
+			case SI5338_OUT_MUX_MSN:
+			case SI5338_OUT_MUX_NOCLK:
+				pdata->clkout[num].clkout_src = val;
+				dev_dbg(&client->dev, "clkout_src = %d\n", val);
+				break;
+			default:
+				dev_err(&client->dev,
+					"Invalid source for output %u\n", num);
+				return -EINVAL;
+			}
+		}
+		if (!of_property_read_string(child, "silabs,drive-config",
+					     &pdata->clkout[num].drive)) {
+			if (find_drive_config(pdata->clkout[num].drive) < 0) {
+				dev_err(&client->dev,
+					"Invalid drive config for output %u\n",
+					num);
+				return -EINVAL;
+			}
+			dev_dbg(&client->dev, "drive-config = %s\n",
+				pdata->clkout[num].drive);
+		}
+		if (!of_property_read_u32(child,
+					  "silabs,disable-state",
+					  &val)) {
+			switch (val) {
+			case SI5338_OUT_DIS_HIZ:
+			case SI5338_OUT_DIS_LOW:
+			case SI5338_OUT_DIS_HI:
+			case SI5338_OUT_DIS_ALWAYS_ON:
+				pdata->clkout[num].disable_state = val;
+				dev_dbg(&client->dev,
+					"disable-state = %d\n", val);
+				break;
+			default:
+				dev_err(&client->dev,
+					"Invalid disable state for output %u\n",
+					num);
+				return -EINVAL;
+			}
+		}
+		if (!of_property_read_u32(child, "clock-frequency", &val)) {
+			pdata->clkout[num].rate = val;
+			dev_dbg(&client->dev, "clock-frequency = %d\n", val);
+		}
+	}
+	client->dev.platform_data = pdata;
+
+	return 0;
+}
+#else
+static int si5338_dt_parse(struct i2c_client *client)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+/*
+ * Returns the clk registered, or an error code. If successful, the clk pointer
+ * is also save in hw->clk.
+ */
+static struct clk *si5338_register_clock(struct device *dev,
+			      struct clk_hw *hw,
+			      const char *name,
+			      const char **parent_names,
+			      u8 num_parents,
+			      const struct clk_ops *ops,
+			      unsigned long flags)
+{
+	struct clk *clk;
+	struct clk_init_data init;
+
+	memset(&init, 0, sizeof(init));
+	init.name = name;
+	init.ops = ops;
+	init.flags = flags;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	hw->init = &init;
+	dev_dbg(dev, "Registering %s\n", name);
+	clk = devm_clk_register(dev, hw);
+
+	if (IS_ERR(clk))
+		dev_err(dev, "unable to register %s\n", name);
+
+	return clk;
+}
+
+static int si5338_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct si5338_platform_data *pdata;
+	struct si5338_driver_data *drvdata;
+	struct clk *clk = NULL;
+	char name_buf[8][MAX_NAME_PREFIX + MAX_NAME_LENGTH];
+	char register_name[MAX_NAME_PREFIX + MAX_NAME_LENGTH];
+	const char *parent_names[8] = {
+		name_buf[0], name_buf[1], name_buf[2], name_buf[3],
+		name_buf[4], name_buf[5], name_buf[6], name_buf[7]
+	};
+	int ret, n;
+	bool require_xtal = false;
+	bool require_ref = false;
+	bool require_fb = false;
+	bool require_pll = false;
+	unsigned long flags;
+
+	ret = si5338_dt_parse(client);
+	if (ret)
+		return ret;
+
+	pdata = client->dev.platform_data;
+	if (!pdata)
+		return -EINVAL;
+
+	drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, drvdata);
+	drvdata->client = client;
+	drvdata->pxtal = pdata->clk_xtal;
+	for (n = 0; n < 4; n++)
+		drvdata->pclkin[n] = pdata->clkin[n];
+	if (!pdata->name_prefix) {
+		strlcpy(drvdata->name_prefix,
+			dev_name(&client->dev), MAX_NAME_PREFIX);
+		strncat(drvdata->name_prefix, "-", MAX_NAME_PREFIX);
+	} else {
+		strlcpy(drvdata->name_prefix,
+			pdata->name_prefix, MAX_NAME_PREFIX);
+	}
+
+	if (!IS_ERR(drvdata->pxtal) && drvdata->pxtal &&
+	    !IS_ERR(drvdata->pclkin[0]) && drvdata->pclkin[0]) {
+		dev_err(&client->dev,
+			"Error in device tree: IN1/IN2 and XTAL are mutually exclusive\n");
+		return -EINVAL;
+	}
+
+	/* Check if clkout config is valid */
+	for (n = 0; n < 4; n++) {
+		/* check clkout source config */
+		switch (pdata->clkout[n].clkout_src) {
+		case SI5338_OUT_MUX_NOCLK:
+			if (pdata->clkout[n].rate != 0)
+				pdata->clkout[n].rate = 0;
+			break;
+		case SI5338_OUT_MUX_REFCLK:
+		case SI5338_OUT_MUX_DIVREFCLK:
+			require_ref = true;
+			break;
+		case SI5338_OUT_MUX_FBCLK:
+		case SI5338_OUT_MUX_DIVFBCLK:
+			require_fb = true;
+			break;
+		case SI5338_OUT_MUX_XOCLK:
+			require_xtal = true;
+			break;
+		case SI5338_OUT_MUX_MS0:
+		case SI5338_OUT_MUX_MSN:
+			require_pll = true;
+			break;
+		default:
+			dev_err(&client->dev, "Invalid clkout source\n");
+			return -EINVAL;
+		}
+
+		/* check clkout drive config */
+		if (find_drive_config(pdata->clkout[n].drive) < 0) {
+			dev_err(&client->dev,
+				"Invalid drive config for output %u\n", n);
+			return -EINVAL;
+		}
+
+		/* check clkout disable state config */
+		switch (pdata->clkout[n].disable_state) {
+		case SI5338_OUT_DIS_HIZ:
+		case SI5338_OUT_DIS_LOW:
+		case SI5338_OUT_DIS_HI:
+		case SI5338_OUT_DIS_ALWAYS_ON:
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid disable state for output %u\n", n);
+			return -EINVAL;
+		}
+
+	}
+	/* check pll source */
+	if (require_pll) {
+		switch (pdata->pll_src) {
+		case SI5338_PFD_IN_REF_XOCLK:
+			require_xtal = true;
+			break;
+		case SI5338_PFD_IN_REF_REFCLK:
+		case SI5338_PFD_IN_REF_DIVREFCLK:
+			require_ref = true;
+			break;
+		case SI5338_PFD_IN_REF_FBCLK:
+		case SI5338_PFD_IN_REF_DIVFBCLK:
+			require_fb = true;
+			break;
+		case SI5338_PFD_IN_REF_NOCLK:
+		default:
+			dev_err(&client->dev, "Invalid pll source\n");
+			return -EINVAL;
+		}
+	}
+	/* check refclk source */
+	if (require_ref) {
+		switch (pdata->ref_src) {
+		case SI5338_REF_SRC_CLKIN12:
+			if (IS_ERR(drvdata->pclkin[0]) || !drvdata->pclkin[0]) {
+				dev_err(&client->dev,
+					"IN1/IN2 doesn't a have source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_REF_SRC_CLKIN3:
+			if (IS_ERR(drvdata->pclkin[1]) || !drvdata->pclkin[1]) {
+				dev_err(&client->dev,
+					"IN3 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		default:
+			dev_err(&client->dev,
+				"Invalid source for refclk\n");
+			return -EINVAL;
+		}
+	}
+	/* check fbclk source */
+	if (require_fb) {
+		switch (pdata->fb_src) {
+		case SI5338_FB_SRC_CLKIN4:
+			if (IS_ERR(drvdata->pclkin[2]) || !drvdata->pclkin[2]) {
+				dev_err(&client->dev,
+					"IN4 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_FB_SRC_CLKIN56:
+			if (IS_ERR(drvdata->pclkin[3]) || drvdata->pclkin[3]) {
+				dev_err(&client->dev,
+					"IN5/IN6 doesn't have a source\n");
+				return -EINVAL;
+			}
+			break;
+		case SI5338_FB_SRC_NOCLK:
+		default:
+			dev_err(&client->dev,
+				"Invalid source for fbclk\n");
+			return -EINVAL;
+		}
+	}
+	/* check xtal */
+	if (require_xtal) {
+		if (IS_ERR(drvdata->pxtal) || !drvdata->pxtal) {
+			dev_err(&client->dev,
+				"XTAL doesn't have a source\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Register regmap */
+	drvdata->regmap = devm_regmap_init_i2c(client, &si5338_regmap_config);
+	if (IS_ERR(drvdata->regmap)) {
+		dev_err(&client->dev, "failed to allocate register map\n");
+		return PTR_ERR(drvdata->regmap);
+	}
+
+	ret = regmap_read(drvdata->regmap, REG5338_DEV_CONFIG2, &n);
+	if (ret) {
+		dev_err(&client->dev, "Failed to access regmap\n");
+		return ret;
+	}
+
+	/* Check if si5338 exists */
+	if ((n & REG5338_DEV_CONFIG2_MASK) != REG5338_DEV_CONFIG2_VAL) {
+		dev_err(&client->dev,
+			"Chip returned unexpected value from reg %d: %d, expected %d. It is not %s\n",
+			REG5338_DEV_CONFIG2, n, REG5338_DEV_CONFIG2_VAL,
+			id->name);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "Chip %s is found\n", id->name);
+
+	ret = pre_init(drvdata);		/* Disable all */
+	if (ret)
+		return ret;
+
+	/*
+	 * Set up clock structure
+	 * These clocks have fixed parent
+	 *	xtal => xoclk
+	 *	refclk => divrefclk
+	 *	fbclk => divfbclk
+	 *	pll => multisynth
+	 */
+
+	/* setup refclk parent */
+	ret = si5338_refclk_reparent(drvdata, pdata->ref_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent refclk to %d\n", pdata->ref_src);
+		return ret;
+	}
+
+	/* setup fbclk parent */
+	ret = si5338_fbclk_reparent(drvdata, pdata->fb_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent fbclk to %d\n", pdata->fb_src);
+		return ret;
+	}
+
+	/* setup pll parent */
+	ret = si5338_pll_reparent(drvdata, pdata->pll_src);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to reparent pll %d to %d\n",
+			n, pdata->pll_src);
+		return ret;
+	}
+
+	for (n = 0; n < 4; n++) {
+		ret = si5338_clkout_reparent(drvdata, n,
+					      pdata->clkout[n].clkout_src);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed to reparent clkout %d to %d\n",
+				n, pdata->clkout[n].clkout_src);
+			return ret;
+		}
+
+		ret = si5338_clkout_set_drive_config(drvdata, n,
+					      pdata->clkout[n].drive);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set drive config of clkout%d to %s\n",
+				n, pdata->clkout[n].drive);
+			return ret;
+		}
+
+		ret = si5338_clkout_set_disable_state(drvdata, n,
+						pdata->clkout[n].disable_state);
+		if (ret) {
+			dev_err(&client->dev,
+				"failed set disable state of clkout%d to %d\n",
+				n, pdata->clkout[n].disable_state);
+			return ret;
+		}
+	}
+
+	/* Register xtal input clock */
+	if (!IS_ERR(drvdata->pxtal) && drvdata->pxtal) {
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_input_names[4], MAX_NAME_LENGTH);
+		drvdata->pxtal_name = __clk_get_name(drvdata->pxtal);
+		clk = si5338_register_clock(&client->dev, &drvdata->xtal,
+					 register_name, &drvdata->pxtal_name, 1,
+					 &si5338_xtal_ops, 0);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+	}
+
+	/* Register clkin input clock */
+	for (n = 0; n < 4; n++) {
+		if (IS_ERR(drvdata->pclkin[n]) || !drvdata->pclkin[n])
+			continue;
+
+		drvdata->clkin[n].drvdata = drvdata;
+		drvdata->clkin[n].num = n;
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_input_names[n], MAX_NAME_LENGTH);
+		drvdata->pclkin_name[n] = __clk_get_name(drvdata->pclkin[n]);
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->clkin[n].hw,
+					    register_name,
+					    &drvdata->pclkin_name[n],
+					    1,
+					    &si5338_clkin_ops,
+					    0);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+	}
+
+	/*
+	 * Create unique internal names in case multiple devices exist
+	 *
+	 * Register refclk, parents can be in1/in2, in3, xtal, noclk
+	 */
+	drvdata->refclk.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[1], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[2], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[3], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_input_names[0], MAX_NAME_LENGTH);
+	strncat(name_buf[1], si5338_input_names[1], MAX_NAME_LENGTH);
+	strncat(name_buf[2], si5338_input_names[4], MAX_NAME_LENGTH);
+	strncat(name_buf[3], si5338_input_names[5], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[0], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->refclk.hw,
+				 register_name, parent_names, 4,
+				 &si5338_refclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register divrefclk, parent is refclk */
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_pll_src_names[0], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[2], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->divrefclk,
+				 register_name, parent_names, 1,
+				 &si5338_divrefclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register fbclk, parents can be in4, in5/in6, noclk */
+	drvdata->fbclk.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[1], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strlcpy(name_buf[2], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_input_names[2], MAX_NAME_LENGTH);
+	strncat(name_buf[1], si5338_input_names[3], MAX_NAME_LENGTH);
+	strncat(name_buf[2], si5338_input_names[5], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[1], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->fbclk.hw,
+				 register_name, parent_names, 3,
+				 &si5338_fbclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Register divfbclk, parent is fbclk */
+	memset(name_buf, 0, sizeof(name_buf));
+	strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(name_buf[0], si5338_pll_src_names[1], MAX_NAME_LENGTH);
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_pll_src_names[3], MAX_NAME_LENGTH);
+
+	clk = si5338_register_clock(&client->dev, &drvdata->divfbclk,
+				 register_name, parent_names, 1,
+				 &si5338_divfbclk_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* register PLL */
+	drvdata->pll.drvdata = drvdata;
+	memset(name_buf, 0, sizeof(name_buf));
+	for (n = 0; n < ARRAY_SIZE(si5338_pll_src_names); n++) {
+		strlcpy(name_buf[n], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[n], si5338_pll_src_names[n], MAX_NAME_LENGTH);
+	}
+	memset(register_name, 0, sizeof(register_name));
+	strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+	strncat(register_name, si5338_msynth_src_names[0], MAX_NAME_LENGTH);
+	clk = si5338_register_clock(&client->dev, &drvdata->pll.hw,
+				 register_name, parent_names, 5,
+				 &si5338_pll_ops, 0);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* If pll_vco is specified, always use it to set pll clock */
+	if (require_pll && pdata->pll_vco) {
+		if (pdata->pll_vco > FVCOMIN && pdata->pll_vco < FVCOMAX) {
+			dev_dbg(&client->dev, "Setting pll vco rate to %u\n",
+				pdata->pll_vco);
+			ret = clk_set_rate(clk, pdata->pll_vco);
+			if (ret != 0) {
+				dev_err(&client->dev, "Cannot set pll vco rate : %d\n",
+					ret);
+				ret = -EIO;
+				return ret;
+			}
+		} else {
+			pdata->pll_vco = 0;
+		}
+	}
+
+	/* register clk multisync and clk out divider */
+	drvdata->msynth = devm_kzalloc(&client->dev, 4 *
+				       sizeof(*drvdata->msynth), GFP_KERNEL);
+	drvdata->clkout = devm_kzalloc(&client->dev, 4 *
+				       sizeof(*drvdata->clkout), GFP_KERNEL);
+
+	drvdata->onecell.clk_num = 4;
+	drvdata->onecell.clks = devm_kzalloc(&client->dev,
+		4 * sizeof(*drvdata->onecell.clks), GFP_KERNEL);
+
+	if (WARN_ON(!drvdata->msynth || !drvdata->clkout ||
+		    !drvdata->onecell.clks)) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	for (n = 0; n < 4; n++) {
+		drvdata->msynth[n].num = n;
+		drvdata->msynth[n].drvdata = drvdata;
+		memset(name_buf, 0, sizeof(name_buf));
+		strlcpy(name_buf[0], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[0], si5338_msynth_src_names[0],
+			MAX_NAME_LENGTH);
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_msynth_names[n], MAX_NAME_LENGTH);
+		flags = (!pdata->pll_vco && n == pdata->pll_master) ?
+			CLK_SET_RATE_PARENT : 0;
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->msynth[n].hw,
+					    register_name,
+					    parent_names,
+					    1,
+					    &si5338_msynth_ops,
+					    flags);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
+	}
+
+	/*
+	 * ms0 is available for all clkout
+	 * ms0/ms1/ms2/ms3 is available for each clkout respectivelly
+	 */
+	memset(name_buf, 0, sizeof(name_buf));
+	for (n = 0; n < 8; n++) {
+		strlcpy(name_buf[n], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[n], si5338_clkout_src_names[n],
+			MAX_NAME_LENGTH);
+	}
+
+	for (n = 0; n < 4; n++) {
+		drvdata->clkout[n].num = n;
+		drvdata->clkout[n].drvdata = drvdata;
+		/*
+		 * Update source
+		 * ms0 for clkout0
+		 * ms1 for clkout1
+		 * ms2 for clkout2
+		 * ms3 for clkout3
+		 */
+		strlcpy(name_buf[6], drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(name_buf[6], si5338_msynth_names[n], MAX_NAME_LENGTH);
+		memset(register_name, 0, sizeof(register_name));
+		strlcpy(register_name, drvdata->name_prefix, MAX_NAME_PREFIX);
+		strncat(register_name, si5338_clkout_names[n], MAX_NAME_LENGTH);
+
+		clk = si5338_register_clock(&client->dev,
+					    &drvdata->clkout[n].hw,
+					    register_name,
+					    parent_names,
+					    8,
+					    &si5338_clkout_ops,
+					    CLK_SET_RATE_PARENT);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
+		drvdata->onecell.clks[n] = clk;
+
+		/* set initial clkout rate */
+		if (pdata->clkout[n].rate != 0) {
+			dev_dbg(&client->dev, "Setting clkout%d rate to %lu\n",
+				n, pdata->clkout[n].rate);
+			ret = clk_set_rate(clk, pdata->clkout[n].rate);
+			if (ret != 0) {
+				dev_err(&client->dev,
+					"Cannot set rate for clkout%d: %d\n",
+					n, ret);
+			}
+			/* "prepare" clkout
+			 * This transverse up to all parent clocks
+			 */
+			ret = clk_prepare(clk);
+			if (ret != 0) {
+				dev_err(&client->dev,
+					"Cannot prepare clk%d\n", n);
+			}
+		}	/* else it should be left disabled out of reset */
+	}
+
+	/*
+	 * Important: Go through the procedure to check PLL locking
+	 * and other steps required by si5338 reference manual.
+	 */
+	ret = post_init(drvdata);
+	if (ret)
+		return ret;
+
+	dev_info(&client->dev, "%s clocks are registered\n", id->name);
+	si5338_sysfs_register(&client->dev);	/* ignore return value */
+
+#ifdef CONFIG_OF
+	ret = of_clk_add_provider(client->dev.of_node,
+				  of_clk_src_onecell_get,
+				  &drvdata->onecell);
+	if (ret) {
+		dev_err(&client->dev, "unable to add clk provider\n");
+		return ret;
+	}
+#endif
+
+
+	return 0;
+}
+
+static int si5338_i2c_remove(struct i2c_client *client)
+{
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+	int i;
+
+	si5338_sysfs_unregister(&client->dev);
+
+	if (!IS_ERR(drvdata->pxtal) && !drvdata->pxtal)
+		clk_put(drvdata->pxtal);
+
+	for (i = 0; i < 4; i++) {
+		if (!IS_ERR(drvdata->pclkin[i]) && !drvdata->pclkin[i])
+			clk_put(drvdata->pclkin[i]);
+	}
+
+	return 0;
+}
+
+
+static const struct i2c_device_id si5338_i2c_ids[] = {
+	{ "si5338", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si5338_i2c_ids);
+
+static struct i2c_driver si5338_driver = {
+	.driver = {
+		.name = "si5338",
+		.of_match_table = of_match_ptr(si5338_dt_ids),
+	},
+	.probe = si5338_i2c_probe,
+	.remove = si5338_i2c_remove,
+	.id_table = si5338_i2c_ids,
+};
+module_i2c_driver(si5338_driver);
+
+MODULE_AUTHOR("York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org");
+MODULE_DESCRIPTION("Silicon Labs Si5338 clock generator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-si5338.h b/drivers/clk/clk-si5338.h
new file mode 100644
index 0000000..3d2532d
--- /dev/null
+++ b/drivers/clk/clk-si5338.h
@@ -0,0 +1,305 @@
+/*
+ * clk-si5338.h: Silicon Labs Si5338 I2C Clock Generator
+ *
+ * Copyright 2015 Freescale Semiconductor
+ * York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
+ *
+ * Partially taken from si5338.c by Andrey Filippov  <andrey-MoRZu3FOBbXQT0dZR+AlfA@public.gmane.org>
+ * Copyright (C) 2013 Elphel, Inc.
+ *
+ * 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.
+ */
+
+#ifndef _CLK_SI5338_H_
+#define _CLK_SI5338_H_
+
+#define REG5338_PAGE			255
+#define REG5338_PAGE_MASK		1
+#define REG5338_DEV_CONFIG2		2
+#define REG5338_DEV_CONFIG2_MASK	0x3f
+#define REG5338_DEV_CONFIG2_VAL		38 /* last 2 digits of part number */
+#define LAST_REG			347
+
+#define FVCOMIN			2200000000LL
+#define FVCOMAX			2840000000LL
+#define XTAL_FREQMIN		8000000LL
+#define XTAL_FREQMAX		30000000LL
+#define INFREQMIN		5000000LL
+#define INFREQMAX		710000000LL
+#define INFREQMAX34		200000000LL
+#define INFREQDIV		40000000LL /* divide input frequency if above */
+
+#define SI5338_SPREAD_SPECTRUM
+#define SPREAD_RATE_MIN		31500	/* 31.5 KHz */
+#define SPREAD_RATE_MAX		63000	/* 63 KHz */
+#define SPREAD_AMP_MIN		10	/* 0.1% */
+#define SPREAD_AMP_MAX		500	/* 5.0% */
+#define SPREAD_AMP_DENOM	10000	/* 0.01% amplitude step */
+
+#define SPREAD_RATE_DFLT	31500	/* 31.5 KHz */
+#define SPREAD_AMP_DFLT		50	/* 0.5% */
+
+
+#define MSINT_MIN		4 /* need to exclude 5, 7 in the code */
+#define MSINT_MAX		567
+
+/* reads of the I2C status register (1 cycle ~ 0.1 ms) */
+#define INIT_TIMEOUT		1000	/* About 1s on 100KHz I2C clock */
+
+#define AWE_INT_MASK		0x061d
+
+#define AWE_IN_MUX		0x1d18
+#define AWE_IN_MUX1		0x1c1c
+#define AWE_FB_MUX		0x1e18
+#define AWE_FB_MUX1		0x1c20
+
+#define AWE_XTAL_FREQ		0x1c03
+#define AWE_PFD_REF		0x1de0
+#define AWE_PFD_FB		0x1ee0
+#define AWE_P1DIV		0x1d07
+#define AWE_P2DIV		0x1e07
+#define AWE_DRV0_PDN		0x1f01
+#define AWE_MS0_PDN		0x1f02
+#define AWE_R0DIV		0x1f1c
+#define AWE_R0DIV_IN		0x1fe0
+#define AWE_DRV1_PDN		0x2001
+#define AWE_MS1_PDN		0x2002
+#define AWE_R1DIV		0x201c
+#define AWE_R1DIV_IN		0x20e0
+#define AWE_DRV2_PDN		0x2101
+#define AWE_MS2_PDN		0x2102
+#define AWE_R2DIV		0x211c
+#define AWE_R2DIV_IN		0x21e0
+#define AWE_DRV3_PDN		0x2201
+#define AWE_MS3_PDN		0x2202
+#define AWE_R3DIV		0x221c
+#define AWE_R3DIV_IN		0x22e0
+
+#define AWE_DRV0_VDDO		0x2303
+#define AWE_DRV1_VDDO		0x230c
+#define AWE_DRV2_VDDO		0x2330
+#define AWE_DRV3_VDDO		0x23c0
+#define AWE_DRV0_FMT		0x2407
+#define AWE_DRV0_INV		0x2418
+#define AWE_DRV1_FMT		0x2507
+#define AWE_DRV1_INV		0x2518
+#define AWE_DRV2_FMT		0x2607
+#define AWE_DRV2_INV		0x2618
+#define AWE_DRV3_FMT		0x2707
+#define AWE_DRV3_INV		0x2718
+
+#define AWE_DRV0_TRIM		0x281f
+#define AWE_DRV1_TRIM_A		0x28e0
+#define AWE_DRV1_TRIM_B		0x2903
+#define AWE_DRV2_TRIM		0x297c
+#define AWE_DRV3_TRIM		0x2a1f
+
+#define AWE_FCAL_OVRD_07_00	0x2dff
+#define AWE_FCAL_OVRD_15_08	0x2eff
+#define AWE_FCAL_OVRD_17_15	0x2f03
+#define AWE_REG47_72		0x2ffc
+#define AWE_PFD_EXTFB		0x3080
+#define AWE_PLL_KPHI		0x307f
+#define AWE_FCAL_OVRD_EN	0x3180
+#define AWE_VCO_GAIN		0x3170
+#define AWE_RSEL		0x310c
+#define AWE_BWSEL		0x3103
+#define AWE_VCO_GAIN_RSEL_BWSEL	0x317f
+
+#define AWE_PLL_EN		0x32c0
+#define AWE_MSCAL		0x323f
+#define AWE_MS3_HS		0x3380
+#define AWE_MS2_HS		0x3340
+#define AWE_MS1_HS		0x3320
+#define AWE_MS0_HS		0x3310
+#define AWE_MS_PEC		0x3307
+
+#define AWE_MS0_FIDCT		0x3460
+#define AWE_MS0_FIDDIS		0x3410
+#define AWE_MS0_SSMODE		0x340C
+#define AWE_MS0_PHIDCT		0x3403
+#define AWE_MS0_P1_07_00	0x35ff
+#define AWE_MS0_P1_15_08	0x36ff
+#define AWE_MS0_P1_17_16	0x3703
+#define AWE_MS0_P2_05_00	0x37fc
+#define AWE_MS0_P2_13_06	0x38ff
+#define AWE_MS0_P2_21_14	0x39ff
+#define AWE_MS0_P2_29_22	0x3aff
+#define AWE_MS0_P3_07_00	0x3bff
+#define AWE_MS0_P3_15_08	0x3cff
+#define AWE_MS0_P3_23_16	0x3dff
+#define AWE_MS0_P3_29_24	0x3e3f
+
+
+#define AWE_MS1_FIDCT		0x3f60
+#define AWE_MS1_FIDDIS		0x3f10
+#define AWE_MS1_SSMODE		0x3f0C
+#define AWE_MS1_PHIDCT		0x3f03
+#define AWE_MS1_P1_07_00	0x40ff
+#define AWE_MS1_P1_15_08	0x41ff
+#define AWE_MS1_P1_17_16	0x4203
+#define AWE_MS1_P2_05_00	0x42fc
+#define AWE_MS1_P2_13_06	0x43ff
+#define AWE_MS1_P2_21_14	0x44ff
+#define AWE_MS1_P2_29_22	0x45ff
+#define AWE_MS1_P3_07_00	0x46ff
+#define AWE_MS1_P3_15_08	0x47ff
+#define AWE_MS1_P3_23_16	0x48ff
+#define AWE_MS1_P3_29_24	0x493f
+
+#define AWE_MS2_FRCTL		0x4a60 /* different name? */
+#define AWE_MS2_FIDDIS		0x4a10
+#define AWE_MS2_SSMODE		0x4a0C
+#define AWE_MS2_PHIDCT		0x4a03
+#define AWE_MS2_P1_07_00	0x4bff
+#define AWE_MS2_P1_15_08	0x4cff
+#define AWE_MS2_P1_17_16	0x4d03
+#define AWE_MS2_P2_05_00	0x4dfc
+#define AWE_MS2_P2_13_06	0x4eff
+#define AWE_MS2_P2_21_14	0x4fff
+#define AWE_MS2_P2_29_22	0x50ff
+#define AWE_MS2_P3_07_00	0x51ff
+#define AWE_MS2_P3_15_08	0x52ff
+#define AWE_MS2_P3_23_16	0x53ff
+#define AWE_MS2_P3_29_24	0x543f
+
+#define AWE_MS3_FIDCT		0x5560
+#define AWE_MS3_FIDDIS		0x5510
+#define AWE_MS3_SSMODE		0x550C
+#define AWE_MS3_PHIDCT		0x5503
+#define AWE_MS3_P1_07_00	0x56ff
+#define AWE_MS3_P1_15_08	0x57ff
+#define AWE_MS3_P1_17_16	0x5803
+#define AWE_MS3_P2_05_00	0x58fc
+#define AWE_MS3_P2_13_06	0x59ff
+#define AWE_MS3_P2_21_14	0x5aff
+#define AWE_MS3_P2_29_22	0x5bff
+#define AWE_MS3_P3_07_00	0x5cff
+#define AWE_MS3_P3_15_08	0x5dff
+#define AWE_MS3_P3_23_16	0x5eff
+#define AWE_MS3_P3_29_24	0x5f3f
+
+#define AWE_MSN_P1_07_00	0x61ff
+#define AWE_MSN_P1_15_08	0x62ff
+#define AWE_MSN_P1_17_16	0x6303
+#define AWE_MSN_P2_05_00	0x63fc
+#define AWE_MSN_P2_13_06	0x64ff
+#define AWE_MSN_P2_21_14	0x65ff
+#define AWE_MSN_P2_29_22	0x66ff
+#define AWE_MSN_P3_07_00	0x67ff
+#define AWE_MSN_P3_15_08	0x68ff
+#define AWE_MSN_P3_23_16	0x69ff
+#define AWE_MSN_P3_29_24	0x6a3f
+
+#define AWE_OUT0_DIS_STATE	0x6ec0
+#define AWE_OUT1_DIS_STATE	0x72c0
+#define AWE_OUT2_DIS_STATE	0x76c0
+#define AWE_OUT3_DIS_STATE	0x7ac0
+
+#define AWE_STATUS			0xdaff
+#define AWE_STATUS_PLL_LOL		0xda10
+#define AWE_STATUS_PLL_LOS_FDBK		0xda08
+#define AWE_STATUS_PLL_LOS_CLKIN	0xda04
+#define AWE_STATUS_PLL_SYS_CAL		0xda01
+
+#define AWE_MS_RESET		0xe204
+
+#define AWE_OUT0_DIS		0xe601
+#define AWE_OUT1_DIS		0xe602
+#define AWE_OUT2_DIS		0xe604
+#define AWE_OUT3_DIS		0xe608
+#define AWE_OUT_ALL_DIS		0xe610
+
+#define AWE_FCAL_07_00		0xebff
+#define AWE_FCAL_15_08		0xecff
+#define AWE_FCAL_17_16		0xed03
+
+
+#define AWE_DIS_LOS		0xf180
+#define AWE_REG241		0xf1ff
+
+#define AWE_SOFT_RESET		0xf602
+
+#define AWE_MS0_SSUPP2_07_00	0x11fff
+#define AWE_MS0_SSUPP2_14_08	0x1207f
+#define AWE_MS0_SSUPP3_07_00	0x121ff /* set them to 0 - default==1 */
+#define AWE_MS0_SSUPP3_14_08	0x1227f
+#define AWE_MS0_SSUPP1_07_00	0x123ff
+#define AWE_MS0_SSUPP1_11_08	0x1240f
+#define AWE_MS0_SSUDP1_03_00	0x124f0
+#define AWE_MS0_SSUDP1_11_04	0x125ff
+#define AWE_MS0_SSDNP2_07_00	0x126ff
+#define AWE_MS0_SSDNP2_14_08	0x1277f
+#define AWE_MS0_SSDNP3_07_00	0x128ff
+#define AWE_MS0_SSDNP3_14_08	0x1297f
+#define AWE_MS0_SSDNP1_07_00	0x12aff
+#define AWE_MS0_SSDNP1_11_08	0x12b0f
+
+#define AWE_MS1_SSUPP2_07_00	0x12fff
+#define AWE_MS1_SSUPP2_14_08	0x1307f
+#define AWE_MS1_SSUPP3_07_00	0x131ff
+#define AWE_MS1_SSUPP3_14_08	0x1327f
+#define AWE_MS1_SSUPP1_07_00	0x133ff
+#define AWE_MS1_SSUPP1_11_08	0x1340f
+#define AWE_MS1_SSUDP1_03_00	0x134f0
+#define AWE_MS1_SSUDP1_11_04	0x135ff
+#define AWE_MS1_SSDNP2_07_00	0x136ff
+#define AWE_MS1_SSDNP2_14_08	0x1377f
+#define AWE_MS1_SSDNP3_07_00	0x138ff
+#define AWE_MS1_SSDNP3_14_08	0x1397f
+#define AWE_MS1_SSDNP1_07_00	0x13aff
+#define AWE_MS1_SSDNP1_11_08	0x13b0f
+
+#define AWE_MS2_SSUPP2_07_00	0x13fff
+#define AWE_MS2_SSUPP2_14_08	0x1407f
+#define AWE_MS2_SSUPP3_07_00	0x141ff
+#define AWE_MS2_SSUPP3_14_08	0x1427f
+#define AWE_MS2_SSUPP1_07_00	0x143ff
+#define AWE_MS2_SSUPP1_11_08	0x1440f
+#define AWE_MS2_SSUDP1_03_00	0x144f0
+#define AWE_MS2_SSUDP1_11_04	0x145ff
+#define AWE_MS2_SSDNP2_07_00	0x146ff
+#define AWE_MS2_SSDNP2_14_08	0x1477f
+#define AWE_MS2_SSDNP3_07_00	0x148ff
+#define AWE_MS2_SSDNP3_14_08	0x1497f
+#define AWE_MS2_SSDNP1_07_00	0x14aff
+#define AWE_MS2_SSDNP1_11_08	0x14b0f
+
+#define AWE_MS3_SSUPP2_07_00	0x14fff
+#define AWE_MS3_SSUPP2_14_08	0x1507f
+#define AWE_MS3_SSUPP3_07_00	0x151ff
+#define AWE_MS3_SSUPP3_14_08	0x1527f
+#define AWE_MS3_SSUPP1_07_00	0x153ff
+#define AWE_MS3_SSUPP1_11_08	0x1540f
+#define AWE_MS3_SSUDP1_03_00	0x154f0
+#define AWE_MS3_SSUDP1_11_04	0x155ff
+#define AWE_MS3_SSDNP2_07_00	0x156ff
+#define AWE_MS3_SSDNP2_14_08	0x1577f
+#define AWE_MS3_SSDNP3_07_00	0x158ff
+#define AWE_MS3_SSDNP3_14_08	0x1597f
+#define AWE_MS3_SSDNP1_07_00	0x15aff
+#define AWE_MS3_SSDNP1_11_08	0x15b0f
+
+#define AWE_MISC_47		0x2ffc /* write 0x5 */
+#define AWE_MISC_106		0x6a80 /* write 0x1 */
+#define AWE_MISC_116		0x7480 /* write 0x1 */
+#define AWE_MISC_42		0x2a20 /* write 0x1 */
+#define AWE_MISC_06A		0x06e0 /* write 0x0 */
+#define AWE_MISC_06B		0x0602 /* write 0x0 */
+#define AWE_MISC_28		0x1cc0 /* write 0x0 */
+
+struct si5338_drv_t {
+	const char *description;
+	u8 fmt;
+	u8 vdd;
+	u8 trim;
+	/* bits [1:0} data,
+	 * [3:2] - don't care ([3]==1 - [1] - any, [2]==1 - [0] - any
+	 */
+	u8 invert;
+};
+
+#endif
diff --git a/include/dt-bindings/clock/clk-si5338.h b/include/dt-bindings/clock/clk-si5338.h
new file mode 100644
index 0000000..ebce4ac
--- /dev/null
+++ b/include/dt-bindings/clock/clk-si5338.h
@@ -0,0 +1,69 @@
+/*
+ * This header provides constants for SI5338 I2C clock generator
+ *
+ * The constants defined in this header are used in dts files
+ *
+ * Copyright 2015 Freescale Semiconductor
+ *
+ * York Sun <yorksun-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
+ *
+ * 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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DT_BINDINGS_CLK_DSI5338_H
+#define _DT_BINDINGS_CLK_DSI5338_H
+
+/* Used to identify input clock */
+#define SI5338_INPUT_CLK12		0
+#define SI5338_INPUT_CLK3		1
+#define SI5338_INPUT_CLK4		2
+#define SI5338_INPUT_CLK56		3
+
+/* Used to identify the mux source */
+#define SI5338_REF_SRC_CLKIN12		0
+#define SI5338_REF_SRC_CLKIN3		1
+#define SI5338_FB_SRC_CLKIN4		2
+#define SI5338_FB_SRC_CLKIN56		3
+#define SI5338_REF_SRC_XTAL		4
+#define SI5338_FB_SRC_NOCLK		5
+
+/* Used to identify the pfd_in_ref mux source */
+#define SI5338_PFD_IN_REF_REFCLK	0
+#define SI5338_PFD_IN_REF_FBCLK		1
+#define SI5338_PFD_IN_REF_DIVREFCLK	2
+#define SI5338_PFD_IN_REF_DIVFBCLK	3
+#define SI5338_PFD_IN_REF_XOCLK		4
+#define SI5338_PFD_IN_REF_NOCLK		5
+
+/* Used to identify the pfd_in_fb mux source */
+#define SI5338_PFD_IN_FB_FBCLK		0
+#define SI5338_PFD_IN_FB_REFCLK		1
+#define SI5338_PFD_IN_FB_DIVFBCLK	2
+#define SI5338_PFD_IN_FB_DIVREFCLK	3
+#define SI5338_PFD_IN_FB_RESERVED	4
+#define SI5338_PFD_IN_FB_NOCLK		5
+
+/* Used to identify the mux source */
+#define SI5338_OUT_MUX_FBCLK		0
+#define SI5338_OUT_MUX_REFCLK		1
+#define SI5338_OUT_MUX_DIVFBCLK		2
+#define SI5338_OUT_MUX_DIVREFCLK	3
+#define SI5338_OUT_MUX_XOCLK		4
+#define SI5338_OUT_MUX_MS0		5
+#define SI5338_OUT_MUX_MSN		6	/* MS0/1/2/3 respectivelly */
+#define SI5338_OUT_MUX_NOCLK		7
+
+#define SI5338_OUT_DIS_HIZ		0
+#define SI5338_OUT_DIS_LOW		1
+#define SI5338_OUT_DIS_HI		2
+#define SI5338_OUT_DIS_ALWAYS_ON	3
+
+#endif
+
diff --git a/include/linux/platform_data/si5338.h b/include/linux/platform_data/si5338.h
new file mode 100644
index 0000000..86fcc84
--- /dev/null
+++ b/include/linux/platform_data/si5338.h
@@ -0,0 +1,49 @@
+/*
+ * Si5338A/B/C programmable clock generator platform_data.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SI5338_H__
+#define __LINUX_PLATFORM_DATA_SI5338_H__
+
+struct clk;
+
+/**
+ * struct si5338_clkout_config - Si5338 clock output configuration
+ * @clkout: clkout number
+ * @clkout_src: clkout source clock
+ * @drive: output drive strength
+ * @rate: initial clkout rate, or default if 0
+ */
+struct si5338_clkout_config {
+	u8 clkout_src;
+	const char *drive;
+	u8 disable_state;
+	unsigned long rate;
+};
+
+/**
+ * struct si5338_platform_data - Platform data for the Si5338 clock driver
+ * @name_prefix: prefix to clock names
+ *		 In case multiple clock chips exist, each can have unique names
+ * @clk_xtal: xtal input clock
+ * @clk_clkin: clkin input clock
+ * @ref_src: reference clock source
+ * @fb_src: feedback clock source
+ * @pll_src: array of pll source clock setting
+ * @pll_master: index of MS (1 of 4) which can change pll clock
+ * @pll_vco: set pll vco clock. If this is set, pll_master is ignored
+ * @clkout: array of clkout configuration
+ */
+struct si5338_platform_data {
+	const char *name_prefix;
+	struct clk *clk_xtal;
+	struct clk *clkin[4];
+	u8 ref_src;
+	u8 fb_src;
+	u8 pll_src;
+	u8 pll_master;
+	u32 pll_vco;
+	struct si5338_clkout_config clkout[4];
+};
+
+#endif
-- 
1.7.9.5

             reply	other threads:[~2015-06-15 17:23 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-15 17:07 York Sun [this message]
2015-06-15 17:07 ` [Patch v2] driver/clk/clk-si5338: Add common clock framework driver for si5338 York Sun
2015-06-16  8:21 ` Paul Bolle
2015-06-16 15:18   ` York Sun
2015-06-16 15:18     ` York Sun
2015-06-16 15:38     ` York Sun
2015-06-16 15:38       ` York Sun

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1434388051-23814-1-git-send-email-yorksun@freescale.com \
    --to=yorksun@freescale.com \
    --cc=andrey@elphel.com \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=mturquette@linaro.org \
    --cc=sebastian.hesselbarth@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.