lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <1434566944-8748-1-git-send-email-yorksun@freescale.com>
Date:	Wed, 17 Jun 2015 11:49:04 -0700
From:	York Sun <yorksun@...escale.com>
To:	<mturquette@...libre.com>
CC:	<linux-kernel@...r.kernel.org>, <linux-i2c@...r.kernel.org>,
	York Sun <yorksun@...escale.com>,
	Sebastian Hesselbarth <sebastian.hesselbarth@...il.com>,
	Guenter Roeck <linux@...ck-us.net>,
	Andrey Filippov <andrey@...hel.com>,
	Paul Bolle <pebolle@...cali.nl>
Subject: [Patch v4] driver/clk/clk-si5338: Add common clock framework driver for si5338

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.

Export __clk_is_prepared from clk.c so drivers can check and unprepare
clocks upon removal.

Signed-off-by: York Sun <yorksun@...escale.com>
CC: Mike Turquette <mturquette@...libre.com>
CC: Sebastian Hesselbarth <sebastian.hesselbarth@...il.com>
CC: Guenter Roeck <linux@...ck-us.net>
CC: Andrey Filippov <andrey@...hel.com>
CC: Paul Bolle <pebolle@...cali.nl>

---
Change log:
  v4: Add binding silabs,pll-vco
      Set pll rate initial value
      Separate COMMON_CLK change from this patch

  v3: Add calling unprepare upon removal
      Add registering to clkdev so the clk can be acquired when device
        tree is not in use
      Add a dev_info message when driver is removed
      Add missing "static" to two functions in clk-si5338.c
      Cosmatic fix in dt-bindings.clock/clk-si5338.h

  v2: Fix handling name prefix if the driver is unloaded and loaded again

 .../devicetree/bindings/clock/silabs,si5338.txt    |  178 +
 drivers/clk/Kconfig                                |   12 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/clk-si5338.c                           | 3682 ++++++++++++++++++++
 drivers/clk/clk-si5338.h                           |  305 ++
 drivers/clk/clk.c                                  |    1 +
 include/dt-bindings/clock/clk-si5338.h             |   68 +
 include/linux/platform_data/si5338.h               |   49 +
 8 files changed, 4296 insertions(+)
 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..807d5f6
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si5338.txt
@@ -0,0 +1,178 @@
+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.
+- silabs,pll-vco: Specify VCO frequency for optimal ratios for all outputs.
+	If specified, silabs,pll-master is ignored.
+
+==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>;
+
+		/* Specify pll-vco frequency. pll-master is ignored. */
+		silabs,pll-vco = <2450000000>;
+
+		/* 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 2754b0c..07f0b2f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -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..d75bd06
--- /dev/null
+++ b/drivers/clk/clk-si5338.c
@@ -0,0 +1,3682 @@
+/*
+ * clk-si5338.c: Silicon Labs Si5338 I2C Clock Generator
+ *
+ * Copyright 2015 Freescale Semiconductor
+ * York Sun <yorksun@...escale.com>
+ *
+ * Some code is taken from si5338.c by Andrey Filippov  <andrey@...hel.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;
+	struct clk_lookup	*lookup[4];
+};
+
+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
+ */
+static 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
+ */
+static 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;
+
+	if (rate == 0)
+		rate = div64_u64(parent_rate + MSINT_MAX - 1, MSINT_MAX);
+
+	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
+	for (n = 0; n < 4; n++) {
+		clk = drvdata->clkout[n].hw.clk;
+		drvdata->lookup[n] = clkdev_alloc(clk,
+						  __clk_get_name(clk),
+						  NULL);
+		if (!drvdata->lookup[n]) {
+			dev_err(&client->dev,
+				"Unable to add clkout%d to clkdev\n", n);
+			continue;
+		}
+		clkdev_add(drvdata->lookup[n]);
+	}
+
+	return 0;
+}
+
+static int si5338_i2c_remove(struct i2c_client *client)
+{
+	struct si5338_driver_data *drvdata = i2c_get_clientdata(client);
+	int n;
+
+	of_clk_del_provider(client->dev.of_node);
+
+	for (n = 0; n < 4; n++) {
+		if (__clk_is_prepared(drvdata->clkout[n].hw.clk))
+			clk_unprepare(drvdata->clkout[n].hw.clk);
+		if (drvdata->lookup[n])
+			clkdev_drop(drvdata->lookup[n]);
+	}
+
+	si5338_sysfs_unregister(&client->dev);
+
+	if (!IS_ERR(drvdata->pxtal) && !drvdata->pxtal)
+		clk_put(drvdata->pxtal);
+
+	for (n = 0; n < 4; n++) {
+		if (!IS_ERR(drvdata->pclkin[n]) && !drvdata->pclkin[n])
+			clk_put(drvdata->pclkin[n]);
+	}
+
+	dev_info(&client->dev, "Removed\n");
+
+	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@...escale.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@...escale.com>
+ *
+ * Partially taken from si5338.c by Andrey Filippov  <andrey@...hel.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/drivers/clk/clk.c b/drivers/clk/clk.c
index 237f23f..e22fea1c 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -701,6 +701,7 @@ bool __clk_is_prepared(struct clk *clk)
 
 	return clk_core_is_prepared(clk->core);
 }
+EXPORT_SYMBOL_GPL(__clk_is_prepared);
 
 static bool clk_core_is_enabled(struct clk_core *clk)
 {
diff --git a/include/dt-bindings/clock/clk-si5338.h b/include/dt-bindings/clock/clk-si5338.h
new file mode 100644
index 0000000..545c80d
--- /dev/null
+++ b/include/dt-bindings/clock/clk-si5338.h
@@ -0,0 +1,68 @@
+/*
+ * 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@...escale.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_SI5338_H
+#define _DT_BINDINGS_CLK_SI5338_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 /* _DT_BINDINGS_CLK_SI5338_H */
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

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ